Files
kiss-vscode/projects/flixel-desktop-habit-puzzle-game/source/HabitState.kiss

618 lines
28 KiB
Plaintext

(loadFrom "kiss-tools" "src/kiss_tools/RefactorUtil.kiss")
(prop &mut :Jigsawx jigsaw)
(prop &mut :FlxCamera pieceCamera)
(prop &mut :FlxCamera uiCamera)
(load "PuzzlePacks.kiss")
(defAlias &ident textSize SimpleWindow.textSize)
(method &override :Void create []
(add logTexts)
(set Prelude.printStr log)
(set pieceCamera FlxG.camera)
(set uiCamera (new FlxCamera))
(set uiCamera.bgColor FlxColor.TRANSPARENT)
(pieceCamera.copyFrom FlxG.camera)
(set FlxG.camera pieceCamera)
(FlxG.cameras.add uiCamera)
(FlxG.plugins.add (new FlxMouseControl))
(set FlxMouseControl.sortIndex "priorityID")
(set bgColor FlxColor.TRANSPARENT)
(super.create))
(defAlias &ident KEYBOARD_SCROLL_SPEED (keyboardScrollSpeed))
(method keyboardScrollSpeed []
(/ 800 rewardSprite.scale.x))
(prop &mut :EntryType typeAdding Todo)
(prop &mut :FlxInputText entryNameText)
(prop &mut :DebugLayer debugLayer null)
(prop &mut t 1)
(method &override :Void update [:Float elapsed]
(super.update elapsed)
(#when debug
(debugLayer.clear)
(when model.rewardFiles
(doFor s rewardSprites
null
// Uncomment for debugging piece rotation:
//(debugLayer.drawCircle s.x s.y 1 FlxColor.RED)
//(debugLayer.drawCircle (+ s.x s.origin.x) (+ s.y s.origin.y) 1 FlxColor.LIME)
// Uncomment for debugging match zones:
**(let [matchZones [(matchZoneLeft s) (matchZoneRight s)(matchZoneUp s)(matchZoneDown s)]]
(doFor z matchZones
(unless z.isEmpty
(debugLayer.drawFlxRect z FlxColor.RED)))))))
(when model.rewardFiles
(let [zoom pieceCamera.zoom
scroll (pieceCamera.scroll.copyTo)]
(pieceCamera.updateScrollWheelZoom elapsed 5)
(pieceCamera.updateMouseBorderControl elapsed KEYBOARD_SCROLL_SPEED 0.002 uiCamera)
(when (or !(= zoom pieceCamera.zoom) !(scroll.equals pieceCamera.scroll))
(set save.data.zoom pieceCamera.zoom)
(set save.data.scroll pieceCamera.scroll)
(save.flush)))
(when entryWindow
(when FlxG.keys.justPressed.ESCAPE
(if (entryWindow.isShown)
(entryWindow.hide)
(entryWindow.show))))
(when (and FlxG.keys.justPressed.SPACE !entryNameText)
(defAndCall method toggleBackgroundColor
(set save.data.backgroundIndex #{(save.data.backgroundIndex + 1) % backgroundOptions.length;}#)
(save.flush)
// setModel so the entry text gets remade in inverted/lightened colors when necessary
(refreshModel)))
(#when debug
(when FlxG.keys.justPressed.SEMICOLON
(set pieceCamera.zoom 1))
**(when FlxG.keys.justPressed.CONTROL
(set save.data.storedPositions (new Map<Int,FlxPoint>))
(set save.data.storedAngles (new Map<Int,Float>))
(set save.data.storedOrigins (new Map<Int,FlxPoint>))
(save.flush))))
(when FlxG.keys.justPressed.DELETE
(Sys.exit 0))
// TODO provide a saner/configurable set of bindings to trigger these ui action functions
{
(when FlxG.mouse.justPressedRight
(when draggingSprite
(draggingSprite.rotate 90)
(doFor s (recursivelyConnectedPieces draggingSprite)
(dictSet (the Map<Int,Float> save.data.storedAngles) (dictGet indexMap s) s.angle)
(dictSet (the Map<Int,FlxPoint> save.data.storedOrigins) (dictGet indexMap s) s.origin))
(save.flush)))
(method startAdding [:EntryType type]
(set typeAdding type)
(set entryNameText (new FlxInputText 0 0 FlxG.width "" textSize true))
(set entryNameText.cameras [uiCamera])
(set entryNameText.hasFocus true)
(add entryNameText))
(when FlxG.keys.justPressed.ENTER
(cond
(entryNameText
// addEntry() calls save()
(model.addEntry typeAdding [entryNameText.text])
(refreshModel)
(entryNameText.kill)
(set entryNameText null))
(true
(startAdding Todo))))
(when FlxG.keys.justPressed.UP
(startAdding Bonus))
}
// Left and right arrow keys can switch between unlocked puzzles
(unless entryNameText
(when FlxG.keys.justPressed.LEFT
(unless (= rewardFileIndex 0)
(-= rewardFileIndex 1)
(refreshModel)))
(when FlxG.keys.justPressed.RIGHT
(unless (= rewardFileIndex maxRewardFile)
(+= rewardFileIndex 1)
(refreshModel)))))
(prop &mut :FlxSave save null)
(prop &mut :SimpleWindow entryWindow null)
(prop &mut :FlxTypedGroup<FlxText> logTexts (new FlxTypedGroup))
(prop &mut :FlxKeyShortcutHandler<Entry> shortcutHandler null)
(prop &mut :HabitModel model null)
(method smallerDimension [] (min rewardSprite.pixels.width rewardSprite.pixels.height))
// TODO these variables don't do exactly what I think they do when scaled, like at all:
(defAlias &ident EDGE_LEEWAY 25)
(defAlias &ident BUBBLE_SIZE 15)
(defAlias &ident PUZZLE_WIDTH .puzzleWidth (nth model.rewardFiles rewardFileIndex))
(defAlias &ident PUZZLE_HEIGHT .puzzleHeight (nth model.rewardFiles rewardFileIndex))
(method roughOptimalScale []
(* (/ (max PUZZLE_WIDTH PUZZLE_HEIGHT) MIN_PUZZLE_SIZE) (/ 367 (smallerDimension))))
(defAlias &ident TOTAL_PIECES (* PUZZLE_WIDTH PUZZLE_HEIGHT))
(prop &mut :FlxSprite rewardSprite null)
(prop &mut :FlxTypedGroup<KissExtendedSprite> rewardSprites null)
(prop &mut :Map<Int,KissExtendedSprite> matchingPiecesLeft (new Map))
(prop &mut :Map<Int,KissExtendedSprite> matchingPiecesRight (new Map))
(prop &mut :Map<Int,KissExtendedSprite> matchingPiecesUp (new Map))
(prop &mut :Map<Int,KissExtendedSprite> matchingPiecesDown (new Map))
(prop &mut :Map<Int,JigsawPiece> pieceData (new Map))
(prop &mut :Map<Int,Array<KissExtendedSprite>> connectedPieces (new Map))
(prop &mut :Map<KissExtendedSprite,Int> indexMap (new Map))
(prop &mut :Map<Int,KissExtendedSprite> spriteMap (new Map)) // Because rewardSprites will be re-ordered in depth handling, this is required
(prop &mut lastRewardFileIndex -1)
(prop &mut rewardFileIndex 0)
(prop &mut maxRewardFile 0)
(defAlias &ident SCROLL_BOUND_MARGIN (scrollBoundMargin))
(method scrollBoundMargin []
(/ (max FlxG.width FlxG.height) rewardSprite.scale.x))
(prop &mut :KissExtendedSprite draggingSprite null)
(prop &mut :FlxPoint draggingLastPos null)
// Main.hx sets off 99% of the app's logic by parsing the model file and calling setModel on startup and on a 30s loop:
(method :Void setModel [m &opt :RewardFile currentRewardFile]
(set model m)
(set shortcutHandler (new FlxKeyShortcutHandler))
(let [p (m.totalPoints)
&mut i 0
&mut puzzleUnlocked -1]
// Find, load, and add the current reward image as big as possible:
(unless currentRewardFile
(set currentRewardFile (nth m.rewardFiles 0))
(if m.rewardFiles
(while (> p .startingPoints (nth m.rewardFiles i))
(set rewardFileIndex i)
(set currentRewardFile (nth m.rewardFiles i))
(set maxRewardFile i)
(when (>= ++i m.rewardFiles.length)
--i
(let [lastStartingPoints .startingPoints (nth m.rewardFiles i)
piecesPerPoint .piecesPerPoint (nth m.rewardFiles i)
nextStartingPoints (+ lastStartingPoints (Math.ceil (/ TOTAL_PIECES piecesPerPoint)))]
(when (> p nextStartingPoints)
(set puzzleUnlocked nextStartingPoints))
(break))))
(set puzzleUnlocked 0)))
(when m.rewardFiles
(makeRewardSprites m p currentRewardFile))
(localVar &mut windowWasShown true)
(when entryWindow
(set windowWasShown (entryWindow.isShown))
(entryWindow.hide))
(set entryWindow (new SimpleWindow "" (FlxColor.fromRGBFloat 0 0 0 0.5) FlxColor.WHITE 0.9 0.9))
(set entryWindow.cameras [uiCamera])
(set entryWindow.textColor FlxColor.LIME)
(when m.rewardFiles
(_makeText "Puzzle #$(+ 1 rewardFileIndex) / ${model.rewardFiles.length}" (max 0 (- TOTAL_PIECES (* currentRewardFile.piecesPerPoint (- p currentRewardFile.startingPoints))))))
(set entryWindow.textColor FlxColor.ORANGE)
(map (m.activeDailyEntries) makeText)
(set entryWindow.textColor FlxColor.GREEN)
(map (m.activeMonthlyEntries) makeText)
(set entryWindow.textColor FlxColor.BLUE)
(map (m.activeIntervalEntries) makeText)
(set entryWindow.textColor FlxColor.WHITE)
(map (m.activeBonusEntries) makeText)
(set entryWindow.textColor FlxColor.YELLOW)
(map (m.activeTodoEntries) makeText)
(set entryWindow.textColor FlxColor.GRAY)
(when m.rewardFiles
(_makeText "[SPACE] Cycle background color" 0))
(when windowWasShown
(entryWindow.show))
(when !(= puzzleUnlocked -1)
(startPuzzlePackChoice puzzleUnlocked)))
(unless debugLayer
(set debugLayer (new DebugLayer))
(set debugLayer.cameras [pieceCamera]))
(remove debugLayer)
(add debugLayer))
(method refreshModel [&opt m]
(let [m (or m model)]
(setModel m (nth m.rewardFiles rewardFileIndex))))
(prop &mut textY 0)
(prop :Array<FlxColor> backgroundOptions [
FlxColor.BLACK
FlxColor.WHITE
FlxColor.GRAY
])
(method makeRewardSprites [m p currentRewardFile]
(set save (new FlxSave))
(save.bind currentRewardFile.path)
(unless save.data.storedPositions
(set save.data.storedPositions (new Map<Int,FlxPoint>)))
(unless save.data.storedAngles
(set save.data.storedAngles (new Map<Int,Float>)))
(unless save.data.storedOrigins
(set save.data.storedOrigins (new Map<Int,FlxPoint>)))
(unless save.data.backgroundIndex
(set save.data.backgroundIndex 0))
(set pieceCamera.bgColor (nth backgroundOptions save.data.backgroundIndex))
(when save.data.zoom
(set pieceCamera.zoom save.data.zoom)
(set pieceCamera.scroll save.data.scroll))
(unless (and (= lastRewardFileIndex rewardFileIndex) (= lastTotalPoints (m.totalPoints)))
(set rewardSprite
(new FlxSprite 0 0
(BitmapData.fromFile
currentRewardFile.path)))
(when rewardSprites
(remove rewardSprites))
(rewardSprite.setGraphicSize FlxG.width 0)
(rewardSprite.updateHitbox)
(when (> rewardSprite.height FlxG.height)
(rewardSprite.setGraphicSize 0 FlxG.height))
(rewardSprite.updateHitbox)
(rewardSprite.screenCenter)
(unless save.data.zoom
(set pieceCamera.zoom rewardSprite.scale.x))
(set rewardSprites (new FlxTypedGroup))
// add rewardSprites group before enabling drag-to-select on instances
(add rewardSprites)
(doFor map [matchingPiecesLeft matchingPiecesRight matchingPiecesUp matchingPiecesDown]
(map.clear))
(connectedPieces.clear)
(doFor i (range TOTAL_PIECES) (dictSet connectedPieces i []))
(indexMap.clear)
(let [r (new FlxRandom (Strings.hashCode currentRewardFile.path))
ros (roughOptimalScale)
graphicWidth (* ros rewardSprite.pixels.width)
graphicHeight (* ros rewardSprite.pixels.height)
pieceAssetWidth (/ (- graphicWidth (* EDGE_LEEWAY 2)) PUZZLE_WIDTH)
pieceAssetHeight (/ (- graphicHeight (* EDGE_LEEWAY 2)) PUZZLE_HEIGHT)
j (new Jigsawx pieceAssetWidth pieceAssetHeight graphicWidth graphicHeight EDGE_LEEWAY BUBBLE_SIZE PUZZLE_HEIGHT PUZZLE_WIDTH r)
PIECE_WIDTH
(/ rewardSprite.width PUZZLE_WIDTH)
PIECE_HEIGHT
(/ rewardSprite.height PUZZLE_HEIGHT)
:Array<FlxPoint> startingPoints []
:Array<Float> startingAngles []]
(let [&mut i 0]
(doFor y (range PUZZLE_HEIGHT)
(doFor x (range PUZZLE_WIDTH)
(startingAngles.push (* 90 (r.int 0 3)))
(startingPoints.push
(new FlxPoint (+ rewardSprite.x (* x PIECE_WIDTH)) (+ rewardSprite.y (* y PIECE_HEIGHT))))
(+= i 1))))
(r.shuffle startingPoints)
(set jigsaw j)
(r.shuffle jigsaw.jigs)
(localVar spriteGrid (for y (range PUZZLE_HEIGHT) (for x (range PUZZLE_WIDTH) null)))
(localVar indexGrid (for y (range PUZZLE_HEIGHT) (for x (range PUZZLE_WIDTH) -1)))
(doFor i (range (min TOTAL_PIECES (* currentRewardFile.piecesPerPoint (- p currentRewardFile.startingPoints))))
(let [jig (nth jigsaw.jigs i)
pos (ifLet [point (dictGet (the Map<Int,FlxPoint> save.data.storedPositions) i)]
point
(.addPoint (nth startingPoints i) camera.scroll))
angle (ifLet [angle (dictGet (the Map<Int,Float> save.data.storedAngles) i)]
angle
(nth startingAngles i))
s (new KissExtendedSprite pos.x pos.y)
source (new FlxSprite)
mask (new FlxSprite)
sourceRect (new Rectangle (/ jig.xy.x ros) (/ jig.xy.y ros) (/ jig.wh.x ros) (/ jig.wh.y ros))]
(set s.angle angle)
(set s.priorityID i)
(dictSet (the Map<Int,FlxPoint> save.data.storedPositions) i pos)
(setNth spriteGrid jig.row jig.col s)
(setNth indexGrid jig.row jig.col i)
(dictSet pieceData i jig)
(dictSet indexMap s i)
(dictSet spriteMap i s)
(set s.draggable true)
(s.enableDragToSelect)
(s.enableMouseDrag false true)
(set s.mouseStartDragCallback
->:Void [s x y]
(let [s (cast s KissExtendedSprite)]
(set s.priorityID (+ 1 .priorityID (last (the kiss.List<KissExtendedSprite> rewardSprites.members))))
(let [connectedPieces (recursivelyConnectedPieces s)]
// Bring currently held pieces to the front:
(rewardSprites.bringAllToFront connectedPieces))
(set draggingSprite s)
(set draggingLastPos (new FlxPoint s.x s.y))))
(set s.mouseStopDragCallback
->:Void [s x y]
(let [s (cast s KissExtendedSprite)]
(set draggingSprite null)
(let [connectedPieces (.concat (s.connectedAndSelectedSprites) [s])]
(doFor connected connectedPieces
(checkMatches (dictGet indexMap connected)))
(doFor connected connectedPieces
(dictSet (the Map<Int,FlxPoint> save.data.storedPositions) (dictGet indexMap connected) (new FlxPoint connected.x connected.y))))
(pieceCamera.calculateScrollBounds rewardSprites uiCamera SCROLL_BOUND_MARGIN)
(save.flush)))
(var ROT_PADDING 2)
(localVar fWidth (+ (Std.int sourceRect.width) (* 2 ROT_PADDING)))
(localVar fHeight (+ (Std.int sourceRect.height) (* 2 ROT_PADDING)))
(source.makeGraphic fWidth fHeight FlxColor.TRANSPARENT true)
(source.pixels.copyPixels rewardSprite.pixels sourceRect (new Point ROT_PADDING ROT_PADDING))
(mask.makeGraphic fWidth fHeight FlxColor.TRANSPARENT true)
(drawPieceShape mask jig ros FlxColor.BLACK)
(FlxSpriteUtil.alphaMask s source.pixels mask.pixels)
// Uncomment to debug piece ids and row/columns
**(#when debug
(kiss_flixel.SpriteTools.writeOnSprite "$i" 32 s (object x (Percent 0.5) y (Percent 0.5)) FlxColor.RED)
(kiss_flixel.SpriteTools.writeOnSprite "(${jig.col},${jig.row})" 32 s (object x (Percent 0.5) y (Percent 0.7)) FlxColor.RED))
(s.loadRotatedGraphic s.pixels 4 -1 /*false true*/)
(set s.cameras [pieceCamera])
(rewardSprites.add s)))
(doFor row (range PUZZLE_HEIGHT)
(doFor col (range PUZZLE_WIDTH)
(let [id (nth indexGrid row col)]
(when (= id -1) (continue))
(when (>= (- col 1) 0)
(let [toLeft (nth spriteGrid row (- col 1))]
(dictSet matchingPiecesLeft id toLeft)))
(when (< (+ col 1) PUZZLE_WIDTH)
(let [toRight (nth spriteGrid row (+ col 1))]
(dictSet matchingPiecesRight id toRight)))
(when (>= (- row 1) 0)
(let [toUp (nth spriteGrid (- row 1) col)]
(dictSet matchingPiecesUp id toUp)))
(when (< (+ row 1) PUZZLE_HEIGHT)
(let [toDown (nth spriteGrid (+ row 1) col)]
(dictSet matchingPiecesDown id toDown))))))
(doFor i (range TOTAL_PIECES)
(checkMatches i))))
(set lastRewardFileIndex rewardFileIndex)
(prop &mut lastTotalPoints -1)
(set lastTotalPoints (m.totalPoints))
(pieceCamera.calculateScrollBounds rewardSprites uiCamera SCROLL_BOUND_MARGIN)
(save.flush))
(method makeText [:Entry e]
(let [label (HabitModel.activeLabel e)]
(_makeText label.label label.points ->:Void text {
// TODO move all of this logic other than setModel into HabitModel logic
(let [label (HabitModel.activeLabel e)]
(+= label.points 1)
(whenLet [(Daily days lastDayDone) e.type]
(set e.type (Daily days (HabitModel.todayString))))
(whenLet [(Monthly days lastDayDone) e.type]
(set e.type (Monthly days (.toString (DateTime.now)))))
(whenLet [(Interval days lastDayDone) e.type]
(set e.type (Interval days (.toString (DateTime.now)))))
(model.save)
(setModel model))
})))
// TODO configurable text size
(method _makeText [:String s :Int points &opt :Action action]
(entryWindow.makeText (+ s (pointsStr points)) action))
(method log [:String message]
(trace message)
(prop &mut logTextY 0)
(#when debug
(when (> logTextY FlxG.height)
(logTexts.clear)
(set logTextY 0))
(let [text (new FlxText FlxG.width logTextY 0 message textSize)]
(set text.color FlxColor.LIME)
(set text.cameras [uiCamera])
(+= logTextY text.height)
(-= text.x text.width)
(logTexts.add text))))
(method :FlxRect matchZone [:KissExtendedSprite s compass]
(let [bubblePoints (dictGet .bubblePoints (dictGet pieceData (dictGet indexMap s)) compass)]
(unless bubblePoints
(return (new FlxRect 0 0 0 0)))
(let [ros (roughOptimalScale)
pointsX (for point bubblePoints point.x)
pointsY (for point bubblePoints point.y)
minX (/ (apply min pointsX) ros)
minY (/ (apply min pointsY) ros)
maxX (/ (apply max pointsX) ros)
maxY (/ (apply max pointsY) ros)
tlc (.add (new FlxPoint minX minY) ROT_PADDING ROT_PADDING)
brc (.add (new FlxPoint maxX maxY) ROT_PADDING ROT_PADDING)
rotationPadding (s.getRotationPadding)
rect (.fromTwoPoints (new FlxRect) (tlc.addPoint rotationPadding) (brc.addPoint rotationPadding))
originOffset (new FlxPoint (- s.origin.x rect.x) (- s.origin.y rect.y))
rotated (rect.getRotatedBounds s.angle originOffset)]
(+= rotated.x s.x)
(+= rotated.y s.y)
rotated)))
(method :FlxRect matchZoneLeft [:KissExtendedSprite s]
(matchZone s WEST))
(method :FlxRect matchZoneRight [:KissExtendedSprite s]
(matchZone s EAST))
(method :FlxRect matchZoneUp [:KissExtendedSprite s]
(matchZone s NORTH))
(method :FlxRect matchZoneDown [:KissExtendedSprite s]
(matchZone s SOUTH))
(prop &mut c 0)
(method :Bool connectPiece [id self toSprite selfMatchZone toSpriteMatchZone]
(let [thisConnectedPieces (dictGet connectedPieces id)
toConnectedPieces (dictGet connectedPieces (dictGet indexMap toSprite))]
// Don't add duplicates or snap for pieces alread connected
(when (contains thisConnectedPieces toSprite)
(return false))
(+= c 1)
// Snap the pieces together
(let [offsetX (- toSpriteMatchZone.x selfMatchZone.x)
offsetY (- toSpriteMatchZone.y selfMatchZone.y)
selfAndAttached (recursivelyConnectedPieces self)
indices (for s selfAndAttached (dictGet indexMap s))
otherAndAttached (recursivelyConnectedPieces toSprite)
otherIndices (for s otherAndAttached (dictGet indexMap s))]
//(print "attaching $indices to $otherIndices")
(doFor piece selfAndAttached
(+= piece.x offsetX)
(+= piece.y offsetY))
// TODO check for matches created by snapping all the pieces?
// Or is it fine not to?
)
(thisConnectedPieces.push toSprite)
(toConnectedPieces.push self)
(let [selfAndAttached (recursivelyConnectedPieces self)]
(doFor s selfAndAttached
(set s.connectedSprites selfAndAttached)))
true))
(defMacro _checkMatch [side otherSide]
(let [sideStr (symbolNameValue side)
otherSideStr (symbolNameValue otherSide)
to (symbol "to$sideStr")
mp (symbol "matchingPieces$sideStr")
mz1 (symbol "matchZone$sideStr")
mz2 (symbol "matchZone$otherSideStr")]
`(whenLet [,to (dictGet ,mp id)
mz1 (,mz1 s)
mz2 (,mz2 ,to)]
(unless (or !(= s.angle .angle ,to) .isEmpty (mz1.intersection mz2))
(connectPiece id s ,to mz1 mz2)))))
(method :Bool checkMatches [id]
(when !(pieceData.exists id) (return false))
(let [s (dictGet spriteMap id)
jig (dictGet pieceData id)
row jig.row
col jig.col]
(_checkMatch Left Right)
(_checkMatch Right Left)
(_checkMatch Up Down)
(_checkMatch Down Up))
false)
(method :Array<KissExtendedSprite> recursivelyConnectedPieces [s &opt :Array<KissExtendedSprite> collected]
(unless collected (set collected [s]))
(whenLet [directlyConnected (dictGet connectedPieces (dictGet indexMap s))]
(doFor piece directlyConnected
(unless (contains collected piece)
(collected.push piece)
(recursivelyConnectedPieces piece collected))))
collected)
(prop &mut :FlxGroup nextPuzzleChoiceGroup null)
(method startPuzzlePackChoice [nextStartingPoints]
(unless nextPuzzleChoiceGroup
(set nextPuzzleChoiceGroup (new FlxGroup))
(set nextPuzzleChoiceGroup.cameras [uiCamera])
// TODO position these aesthetically with a partly transparent background behind them
// like the habit ui window should also have
(let [x 0 &mut y 0]
(doFor pack (availablePacks model)
(let [text (new FlxText x y 0 "$(haxe.io.Path.withoutDirectory pack.path): ${pack.puzzlesDone}/${pack.puzzlesTotal}" textSize)]
// TODO not that color though
(set text.color FlxColor.LIME)
(nextPuzzleChoiceGroup.add text)
(whenLet [(Some np) pack.nextPuzzle]
(nextPuzzleChoiceGroup.add
(new FlxButton (+ x text.width) y "CHOOSE" ->:Void {
(remove nextPuzzleChoiceGroup)
(set nextPuzzleChoiceGroup null)
(startPuzzleSizeChoice
->[chosenSize pointsPerPiece]
(let [bmd (BitmapData.fromFile np.path)
aspectRatioX (/ bmd.width bmd.height)
aspectRatioY (/ bmd.height bmd.width)
w (max 1 (Math.round (* aspectRatioX chosenSize)))
h (max 1 (Math.round (* aspectRatioY chosenSize)))]
(model.addRewardFile np.path nextStartingPoints w h pointsPerPiece)
(setModel model)))
}))))
(+= y textSize)))
(add nextPuzzleChoiceGroup)))
(var MIN_PUZZLE_SIZE 5)
(var MAX_PUZZLE_SIZE 32)
(var PUZZLE_SIZE_OPTIONS (collect (range MIN_PUZZLE_SIZE MAX_PUZZLE_SIZE 2)))
(method startPuzzleSizeChoice [:StartPuzzleFunc startPuzzle]
(set nextPuzzleChoiceGroup (new FlxGroup))
(set nextPuzzleChoiceGroup.cameras [uiCamera])
(add nextPuzzleChoiceGroup)
(let [x 0 &mut y 0 &mut :FlxButton b null]
// TODO also limit puzzle size by rewardSprite dimensions (which are available in bmd in startPuzzlePackChoice())
(doFor size PUZZLE_SIZE_OPTIONS
(set b (new FlxButton x y "$(* size size)" ->:Void {
(remove nextPuzzleChoiceGroup)
(startPiecesPerPointChoice size startPuzzle)
}))
(nextPuzzleChoiceGroup.add b)
(+= y b.height))))
(method startPiecesPerPointChoice [size :StartPuzzleFunc startPuzzle]
(set nextPuzzleChoiceGroup (new FlxGroup))
(set nextPuzzleChoiceGroup.cameras [uiCamera])
(add nextPuzzleChoiceGroup)
(let [x 0 &mut y 0 maxPPP (Math.round (/ (* size size) (* MIN_PUZZLE_SIZE MIN_PUZZLE_SIZE))) &mut :FlxButton b null]
(when (= maxPPP 1)
(startPuzzle size 1)
(return))
(doFor points (range 1 maxPPP)
(set b (new FlxButton x y "$points" ->:Void {
(remove nextPuzzleChoiceGroup)
(startPuzzle size points)
}))
(nextPuzzleChoiceGroup.add b)
(+= y b.height))))
(function pointsStr [points]
(let [&mut str "" tallyUnit 5 symbols ["+" "*" "\$"]]
(doFor i (reverse (collect (range symbols.length)))
(let [scaledTallyUnit (^ tallyUnit i)
tallies (Math.floor (/ points scaledTallyUnit))]
(+= str (* (nth symbols i) tallies))
(-= points (* tallies scaledTallyUnit))))
str))