-
Notifications
You must be signed in to change notification settings - Fork 1
/
PROJECT.txt
311 lines (267 loc) · 11.1 KB
/
PROJECT.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
LIGHT-DARK or USER-HOST or CLIENT-SERVER
single-session
layout of variant is separate from the rules/script; the layout of piles is of no concern to DARK
DARK knows nothing of ebiten or gg
LIGHT is really stupid. It's passed an entire baize of cards, and lerps the difference between old and new.
Cannot have WASM version because scripts/*.lua need to be loaded
TODO
====
Game timer/project hourglass
----------------------------
Statistics, average time?
[ ] fail better when no scripts directory
[ ] embed scripts/*.lua in executable
https://pkg.go.dev/embed
//go:embed static
var embededFiles embed.FS
...
fsys, err := fs.Sub(embededFiles, "static")
"when you include a directory, it won’t include files that start with . or _, but if you use a wildcard, like dir/*, it will include all files that match, even if they start with . or _. "
"using //go:embed dir/* is almost always a mistake. Use //go:embed dir or //go:embed dir/*.ext as needed instead. For security reasons, Go also won’t follow symbolic links or go up a directory when embedding."
[x] do not draw [x] piles
[x] DefaultTailAppendError()
[ ] Load a Lua library after creating a new state and registering moon functions
scripts/~library.lua
[x] Safe collect triggered when card colors = 4? (Australian)
[x] Walk scripts directory tree
scripts/Canfields/Duchess.lua
add/verify "> Canfields" to variantGroups
add "Duchess" to that group
add "Duchess" to variants
[x] BUG Undo isn't restoring stock recycle glyph
[ ] the only pile we ever tap is the stock pile
then we either recycle or recall/redeal (Usk, Cruel, Perseverence)
[x] b.cardCount is len(b.cardMap)
[ ] append or accept := build?
[ ] separate layout struct for each variant
[x] pile.appendFrom
[x] move and append, two checks each going through 3 layers
pile, vtable, script
pile.canMoveTail
..should hand off to
script.TailMoveError
pile.vtable.canAcceptTail
...hands off to script.TailAppendError
[ ] Abandon Hapgood (games are shite)
instead do https://en.wikipedia.org/wiki/List_of_patience_games
[x] PileSlot Deg becomes an int
[x] transition rotated cards when going to or from a rotated pile
[ ] robot to stop cyclic moves
[x] grep case *Discard / empty
post Tail being dragged, the flow is:
pile.canMoveTail // check for prone and move type
script.TailMoveError // check if tail needs to be conformant
dst-pile.canAcceptTail // filter out by pile type
// check power moves (now we know dst)
// tableau/foundation check script.TailAppendError
TailAppendError // empty check before passing to TwoCards
[x] why not give each pile a two-card compare func when it is created?
[x] fractional pile slots (currently using image.Point{int,int})
type PileSlot struct {
X, Y, Deg float32
}
...it's the pile slot that has rotation angle,
which is transferred to all cards in the pile.
[x] Pile.boundaryPile from lsol
[ ] id robot moves available, show FAB
[x] Stock tapped cards are not always flipping
seems okay with -noload/-nosave
cause by new deal not setting prone flag when reforming Stock
[ ] get rid of the scrunch loop
Q. How do we do scrunch in gosol?
A. if the card positions are dirty, scrunch is called on each pile
scrunch works out the maximum pile size
then, starting from the max/default fan factor
the fan factor is reduced, the cards refanned, until the cards fit in the max pile size
Q. How do we do scrunch in lsol?
A.
[ ] Stock and pile tap in Robot()?
certainly in Solver
[ ] Do all the Hapgood variants
requires rotating cards
which only gets tricky when fanned
and looks jaggy
[ ] go Solve() when weights are 1
Robot() when there are weights > 1
[ ] https://garden.bradwoods.io/notes/design/juice
fly-away % complete, that animates upward from the card dst?
[ ] Robot()
only do one round in case of circulars
36x36 white robot/wizard/assist icon
[x] why save >1 tap target for each card? just makes the solver more complicated.
[x] packs, suits in ScriptBase AND Stock!?
[x] only calc Baize crc when it's needed, only use Baize.crc in solver
[ ] WASM
[ ] Mirror Baize
[x] dark makeTail failing after loading a game
[x] recenter variant in toolbar after changing it
CHAMELEON
=========
Chameleon solitaire is a one-deck patience game that is played with four foundations and three tableau piles. The object of the game is to build all of the cards in the foundations, in ascending order by suit. The cards of the starting rank must be played first.
To set up the game, deal three cards face up to each tableau pile. Deal one card face up to the first foundation. The remaining cards form the stock. The waste pile is beside the stock.
To play, you can build the cards in the following ways:
You can build the cards in the foundations up in suit, from Ace to King.
You can build the cards in the tableau piles down in any order, regardless of suit.
You can move cards from one tableau pile to another, as long as the move follows the rules above.
You can fill empty spaces in the tableau piles with cards from the reserve pile or from the stock.
If you can no longer make any moves, the game is over. If you are able to build all of the cards in the foundations, you win the game.
Here are some additional tips for playing chameleon solitaire:
Pay attention to the ranks of the cards in the tableau piles. If you can identify a sequence of cards that can be moved to the foundations, do so as soon as possible.
Use the reserve pile to your advantage. If you have a card that you can't use in the tableau piles, but that can be used on the foundations, move it to the reserve pile.
Don't give up too easily. Even if it seems like you're stuck, there may be a way to win the game. Keep trying different moves until you find a solution.
Chameleon solitaire is a challenging but rewarding game. With a little practice, you'll be able to win more and more games.
PROFILING
=========
https://go.dev/blog/pprof
copy __debug_bin to gosold
./gosold -debug -cpuprofile=gsold.prof
quit using x NOT menu
go tool pprof gosold gosold.prof
top30
JOKERS
======
joker bit set in CardID
create card with suit=0, ord=0 to create a joker
set suit, ord when joker is 'resolved'
...so the compare functions still work
jokers display faded suit, ord, and maybe the word 'JOKER' across card
discard calculation (or anything relying on len(b.cardMap)) will be wrong
maybe use len(cardMap) - numberOfJokers
the jokers don't figure in 'complete' calculation, they are spare at the end of the game
(where do they go? specific pile type for jokers?)
an unresolved joker will match with anything, and afterwards will resolve to a match
SOLVER
======
[ ] Score each baize
percent complete
number of free/used cells
foundation percent complete
13 x 4 = 52
18 cards
[x] doing a Q makes autoCollect not work
put tapTargets in a map?
Baize.tapTargets = map[cardid.CardID] (dst?, weight, *savedBaize)
can't because Card will have >1 tapTarget
could use the undo stack to probe tap targets
and use another struct tree to record progress?
struct would have children, savableBaize, percentComplete ...
can move from current position to somewhere in the tree (auto move)
card has []tapTarget
tapTarget has *saveableBaize
try using the undo stack
foreach tap target
push current baize
do move
find tap targets, moves on Baize0
foreach tap target
clone Baize0 -> Baize1
translate tap targets from Baize0 -> Baize1
make move
for each tapDestination
create/clone a child baize (Baize1) with depth+1, (and undoStack, fnNotify == nil)
make a note of the card to be tapped
make the tap move on Baize1
if the Baize1 crc already exists, disregard
?(record tapped card in Baize1)
add Baize1 to taDestination
if percentComplete == 100%, stop
find highest percentComplete in leaf node of tapTargets
follow parent links, extra-highlighting tappedCard in each parent
Q&A
===
Q. Is the Baize transport object going to be *Baize or Baize?
A. *Baize
Q. Pile.Placeholder()
A. In LIGHT, this needs recreating everytime Pile.Label/Baize.Recycles is updated
Stock is a special case (uses rune based on Baize.Recycles)
Cell, Reserve are special cases (they draw nothing)
LIGHT is going to need a switch Pile.Category
Q. Is Statistics LIGHT or DARK?
Dark needs it for sorting "All by Played"
Light needs it to display to user
Dark knows when a game is complete, so it can record to json
A. DARK
Q. Are preferences LIGHT or DARK?
A. They are LIGHT
But DARK must be told of PowerMoves
SafeCollect, AutoCollect, can be API calls from Light to DARK
Q. MirrorBaize?
A. Entirely up to LIGHT, as the relative positions of piles don't change, only slot positions and fan type
...implies need for LIGHT to hold copies of DARK Slot() and FanType()
... may require help from DARK to do the undo push, restart, undo pop
Q. Start a new deal?
A.
Q. Change variant?
A.
Q. AutoCollect?
A. LIGHT can call Baize.Collect(safe bool) (replaces sol.Baize.AfterAfterUserMove)
BUT
Collect() should only be done while idle
because Collect() is like the user doing a few card taps
and so will invoke DARK AfterUserMove and LIGHT AfterMove
Q. DARK settings?
A. Maybe have a darkSettings struct
containing PowerMoves, SafeCollect, AutoCollect?
HAPGOOD
=======
"Solitaire and Patience" by George Hapgood, Esq (c) 1908
"Seventy games to test the card player's skill and make a lonely hour pass quickly"
Uncle Sam
The Rainbow
Tit-Tat-To
Light and Shadow
The Square
The Great Pyramid
X Square
Nestor
Mary Ann
Congress
Demon Patience
On Honor
Squaring the Circle
Last Chance
Forget Me Not
The Shifting Eight
Leap Frog
The Royal Assemblage
Filling the Well
...
get corners of rotated rectangle
Center point = (center.x, center.y)
Angle = angle
Height = height
Width = width
TOP RIGHT VERTEX:
Top_Right.x = center.x + ((width / 2) * cos(angle)) - ((height / 2) * sin(angle))
Top_Right.y = center.y + ((width / 2) * sin(angle)) + ((height / 2) * cos(angle))
TOP LEFT VERTEX:
Top_Left.x = center.x - ((width / 2) * cos(angle)) - ((height / 2) * sin(angle))
Top_Left.y = center.y - ((width / 2) * sin(angle)) + ((height / 2) * cos(angle))
BOTTOM LEFT VERTEX:
Bot_Left.x = center.x - ((width / 2) * cos(angle)) + ((height / 2) * sin(angle))
Bot_Left.y = center.y - ((width / 2) * sin(angle)) - ((height / 2) * cos(angle))
BOTTOM RIGHT VERTEX:
Bot_Right.x = center.x + ((width / 2) * cos(angle)) + ((height / 2) * sin(angle))
Bot_Right.y = center.y + ((width / 2) * sin(angle)) - ((height / 2) * cos(angle))
// calculate the top and left points, then just flips them for the opposite corners.
rotatedRect(float x, float y, float halfWidth, float halfHeight, float angle)
{
float c = cos(angle);
float s = sin(angle);
float r1x = -halfWidth * c - halfHeight * s;
float r1y = -halfWidth * s + halfHeight * c;
float r2x = halfWidth * c - halfHeight * s;
float r2y = halfWidth * s + halfHeight * c;
// Returns four points in clockwise order starting from the top left.
return
(x + r1x, y + r1y),
(x + r2x, y + r2y),
(x - r1x, y - r1y),
(x - r2x, y - r2y);
}
func pointInRotatedRect() bool {}
https://math.stackexchange.com/search?tab=Relevance&pagesize=50&q=rotated%20rectangle&searchOn=3
card rotation angle (degrees for human comprehension)
type SlotPosition struct {
X, Y float32
}