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

736 lines
35 KiB
Plaintext

(loadFrom "kiss-tools" "src/kiss_tools/RefactorUtil.kiss")
(prop &mut :Jigsawx jigsaw)
(prop &mut :FlxCamera pieceCamera)
(prop &mut :FlxCamera uiCamera)
(defAlias &ident textSize SimpleWindow.textSize)
(method &override :Void create []
(add logTexts)
(set Prelude.printStr log)
(defAndCall method newPieceCamera
(if pieceCamera
{
(FlxG.cameras.remove pieceCamera true)
(set pieceCamera (new FlxCamera))
(FlxG.cameras.add pieceCamera)
}
(set pieceCamera FlxG.camera))
(set FlxG.camera pieceCamera)
(when debugLayer
(set debugLayer.cameras [pieceCamera]))
(when uiCamera
(FlxG.cameras.remove uiCamera false)
(FlxG.cameras.add uiCamera)))
(set uiCamera (new FlxCamera))
(set uiCamera.bgColor FlxColor.TRANSPARENT)
(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 []
(fHalf (max rewardSprite.pixels.width rewardSprite.pixels.height)))
(prop &mut :EntryType typeAdding Todo)
(prop &mut :FlxInputText entryNameText)
(prop &mut :DebugLayer debugLayer null)
(prop &mut t 1)
(prop :FlxRect disableMouse (new FlxRect 0 0 0 0))
(method &override :Void update [:Float elapsed]
(super.update elapsed)
(if (windowIsShown)
{
(set FlxMouseControl.mouseZone disableMouse)
(when FlxMouseControl.dragTarget
(FlxMouseControl.dragTarget.stopDrag)
(set FlxMouseControl.dragTarget null))
}
(set FlxMouseControl.mouseZone null))
(#when debug
(debugLayer.clear)
(when (and model.rewardFiles rewardSprites.alive FlxG.debugger.visible)
(doFor s rewardSprites
(let [i (dictGet indexMap s)
jig (dictGet pieceData i)]
(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))
(let [matchZones [(matchZoneLeft s) (matchZoneRight s)(matchZoneUp s)(matchZoneDown s)]]
(doFor z matchZones
(unless z.isEmpty
(debugLayer.drawFlxRect z FlxColor.RED)))))))
(when model.rewardFiles
(unless (or bar (windowIsShown))
(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 (and entryWindow !(tempWindowIsShown))
(when FlxG.keys.justPressed.ESCAPE
(if (entryWindow.isShown)
(entryWindow.hide)
(entryWindow.show))))
(#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
{
(unless (windowIsShown)
(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))
(when entryWindow
(set entryWindow.keyboardEnabled false))
(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)
(when entryWindow
(set entryWindow.keyboardEnabled true)))
(true
(startAdding Todo))))
(when FlxG.keys.justPressed.UP
(startAdding Bonus))
}
// Left and right arrow keys can switch between unlocked puzzles
(unless (or entryNameText (tempWindowIsShown))
(when FlxG.keys.justPressed.LEFT
(defAndCall method clearBar
(when bar
(remove bar)
(remove asyncLoop)))
(unless (= rewardFileIndex minRewardFile)
--rewardFileIndex
(while .skipped (nth model.rewardFiles rewardFileIndex)
--rewardFileIndex)
(refreshModel)))
(when FlxG.keys.justPressed.RIGHT
(clearBar)
(unless (= rewardFileIndex maxRewardFile)
++rewardFileIndex
(while .skipped (nth model.rewardFiles rewardFileIndex)
++rewardFileIndex)
(refreshModel)))))
(prop &mut :FlxSave save null)
(prop &mut :SimpleWindow entryWindow null)
(prop &mut :SimpleWindow puzzlePackChoiceWindow null)
(prop &mut :SimpleWindow entryDeletionWindow null)
(method windowIsShown []
(or (tempWindowIsShown) (and entryWindow (entryWindow.isShown))))
(method tempWindowIsShown []
(doFor window [puzzlePackChoiceWindow entryDeletionWindow]
(when (and window (window.isShown))
(return true)))
false)
(prop &mut :FlxTypedGroup<FlxText> logTexts (new FlxTypedGroup))
(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 :Null<Int> minRewardFile null)
(prop &mut :Null<Int> maxRewardFile null)
(defAlias &ident SCROLL_BOUND_MARGIN (scrollBoundMargin))
(method scrollBoundMargin []
(max rewardSprite.pixels.width rewardSprite.pixels.height))
(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)
(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))
(cond
((and m.rewardFiles !.skipped (last m.rewardFiles))
(while (> p .startingPoints (nth m.rewardFiles i))
(set rewardFileIndex i)
(set currentRewardFile (nth m.rewardFiles i))
(unless minRewardFile
(unless .skipped (nth m.rewardFiles i)
(set minRewardFile i)))
(unless .skipped (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)))))
((and m.rewardFiles .skipped (last m.rewardFiles))
(set puzzleUnlocked (max 0 (- p 1))))
(true
(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.fromRGBFloat 0.2 0.2 0.2))
(_makeText "{space} Cycle background color" 0
->_
(defAndCall method toggleBackgroundColor
(set save.data.backgroundIndex #{(save.data.backgroundIndex + 1) % backgroundOptions.length;}#)
(save.flush)
(refreshModel)))
(set entryWindow.textColor FlxColor.RED)
(_makeText "Delete a habit or task" 0
->_
(defAndCall method deleteHabitOrTask
(entryWindow.hide)
(let [delWindow (SimpleWindow.promptForChoice "Delete which habit/task? (You will keep all your points)"
(model.allUndeletedEntries)
->:Void [:Entry e] {
(model.deleteEntry e)
(refreshModel)
(entryWindow.show)
}
null null FlxColor.WHITE 0.9 0.9 true)]
(set entryDeletionWindow delWindow)
(set delWindow.cameras [uiCamera]))))
(when (= rewardFileIndex (- m.rewardFiles.length 1))
(_makeText "Abandon this puzzle" 0
->_
(defAndCall method skipPuzzle
(model.skipRewardFile)
(setModel model)))))
(set entryWindow.textColor FlxColor.ORANGE)
(map (m.activeDailyEntries) makeText)
(set entryWindow.textColor FlxColor.GREEN)
(map (m.activeMonthlyEntries) makeText)
(set entryWindow.textColor FlxColor.CYAN)
(map (m.activeIntervalEntries) makeText)
(set entryWindow.textColor FlxColor.WHITE)
(map (m.activeBonusEntries) makeText)
(set entryWindow.textColor FlxColor.YELLOW)
(map (m.activeTodoEntries) makeText)
(when windowWasShown
(entryWindow.show))
(when !(= puzzleUnlocked -1)
(startPuzzlePackChoice puzzleUnlocked)))
(when debugLayer
(remove debugLayer))
(unless debugLayer
(set debugLayer (new DebugLayer))
(set debugLayer.cameras [pieceCamera]))
(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
])
(function nameForSave [:String name]
(doFor forbiddenChar (.split #"~%&\;:"',<>?# "# "")
(set name (name.replace forbiddenChar "")))
name)
(method makeRewardSprites [m p currentRewardFile]
(set save (new FlxSave))
(assert (save.bind (nameForSave currentRewardFile.path)) "failed to bind save data")
(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))
(unless (and (= lastRewardFileIndex rewardFileIndex) (= lastTotalPoints (m.totalPoints)))
// When the current puzzle has changed:
(unless (= lastRewardFileIndex rewardFileIndex)
// Make a new camera so scroll from the last puzzle doesn't start the camera out of boundS
(newPieceCamera)
(set pieceCamera.bgColor (nth backgroundOptions save.data.backgroundIndex))
(set rewardSprite
(new FlxSprite 0 0
(BitmapData.fromFile
currentRewardFile.path)))
(set matchingPiecesLeft (new Map))
(set matchingPiecesRight (new Map))
(set matchingPiecesUp (new Map))
(set matchingPiecesDown (new Map))
(set pieceData (new Map))
(set connectedPieces (new Map))
(doFor i (range TOTAL_PIECES) (dictSet connectedPieces i []))
(set indexMap (new Map))
(set spriteMap (new Map))
(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))
// TODO the rewardSprite can be the "box image" for player reference
(rewardSprite.scale.set 1 1)
(when rewardSprites
#{
var plugin = FlxG.plugins.get(DragToSelectPlugin);
plugin.clearEnabledSprites();
}#
(rewardSprites.destroy)
(remove rewardSprites)
(set rewardSprites null)))
(unless rewardSprites
(set rewardSprites (new FlxTypedGroup))
// add rewardSprites group before enabling drag-to-select on instances, but kill it so pieces aren't rendered until they are all loaded
(add rewardSprites)
(rewardSprites.kill))
(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)))
(localVar piecesUnlocked (min TOTAL_PIECES (* currentRewardFile.piecesPerPoint (- p currentRewardFile.startingPoints))))
(localVar piecesAlreadyMade rewardSprites.length)
(localVar newPieces (- piecesUnlocked piecesAlreadyMade))
(localVar makeJig -+>count []
(let [i (+ piecesAlreadyMade count -1)
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))
&mut s (dictGet spriteMap i)
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))]
(unless s
(set s (new KissExtendedSprite pos.x pos.y))
(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.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 4)
(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)
(localVar unhighlightedS (new FlxSprite))
(FlxSpriteUtil.alphaMask unhighlightedS source.pixels mask.pixels)
(localVar highlightedS (new FlxSprite))
(s.loadGraphic unhighlightedS.pixels)
(highlightedS.loadGraphic unhighlightedS.pixels false 0 0 true)
(drawPieceShape highlightedS jig ros FlxColor.TRANSPARENT FlxColor.LIME)
(localFunction loadRotatedGraphic [:FlxSprite _s]
(s.loadRotatedGraphic _s.pixels 4 -1))
(loadRotatedGraphic unhighlightedS)
(s.enableDragToSelect
->:Void {
(loadRotatedGraphic highlightedS)
}
->:Void {
(loadRotatedGraphic unhighlightedS)
})
(set s.cameras [pieceCamera])
(rewardSprites.add s))))
(prop &mut :FlxBar bar null)
(prop &mut :FlxAsyncLoop asyncLoop null)
(set bar (new FlxBar 0 0 LEFT_TO_RIGHT (iThird FlxG.width) SimpleWindow.textSize rewardSprites "length" 0 piecesUnlocked true))
(set bar.cameras [uiCamera])
(set asyncLoop (new FlxAsyncLoop newPieces makeJig 1))
(bar.createColoredEmptyBar (FlxColor.LIME.getDarkened) true FlxColor.LIME)
(bar.createColoredFilledBar FlxColor.LIME false)
(bar.screenCenter)
(set bar.filledCallback ->:Void {
(remove bar)
(remove asyncLoop)
(rewardSprites.revive)
(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))
(pieceCamera.calculateScrollBounds rewardSprites uiCamera SCROLL_BOUND_MARGIN)
(when save.data.zoom
(set pieceCamera.zoom save.data.zoom)
(set pieceCamera.scroll save.data.scroll))
(set bar null)
(set asyncLoop null)
})
(add bar)
(add asyncLoop)
(asyncLoop.start)))
(set lastRewardFileIndex rewardFileIndex)
(prop &mut lastTotalPoints -1)
(set lastTotalPoints (m.totalPoints))
(set pieceCamera.bgColor (nth backgroundOptions save.data.backgroundIndex))
(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 :Void log [:String message]
(when (> message.length 100) (log (message.substr 0 100)) (return))
(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]
(assertLet [id (dictGet indexMap s)
jig (dictGet pieceData id)]
(let [bubblePoints (dictGet jig.bubblePoints compass)]
(unless bubblePoints
(return (new FlxRect 0 0 0 0)))
(printLocalNulls)
(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 :Void startPuzzlePackChoice [nextStartingPoints]
(set puzzlePackChoiceWindow (SimpleWindow.promptForChoice "Choose a puzzle pack:"
(PuzzlePack.availablePacks model)
->[:PuzzlePack pack] (ifLet [(Some np) pack.nextPuzzle]
(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)))
(startPuzzlePackChoice nextStartingPoints))
null null FlxColor.LIME null 0.9))
(set puzzlePackChoiceWindow.cameras [uiCamera]))
(var MIN_PUZZLE_SIZE 5)
(var MAX_PUZZLE_SIZE 32)
(var PUZZLE_SIZE_OPTIONS (collect (range MIN_PUZZLE_SIZE MAX_PUZZLE_SIZE 2)))
(method :Void startPuzzleSizeChoice [:StartPuzzleFunc startPuzzle]
(set puzzlePackChoiceWindow (SimpleWindow.promptForChoice "Approx. # of Pieces:"
// TODO also limit puzzle size by rewardSprite dimensions (which are available in bmd in startPuzzlePackChoice())
(for size PUZZLE_SIZE_OPTIONS (* size size))
->:Void [:Int size] (startPiecesPerPointChoice (Std.int (Math.sqrt size)) startPuzzle)
null null FlxColor.LIME null 0.9))
(set puzzlePackChoiceWindow.cameras [uiCamera]))
(method :Void startPiecesPerPointChoice [size :StartPuzzleFunc startPuzzle]
(let [maxPPP (Math.round (/ (* size size) (* MIN_PUZZLE_SIZE MIN_PUZZLE_SIZE)))]
(when (= maxPPP 1)
(startPuzzle size 1)
(return))
(set puzzlePackChoiceWindow (SimpleWindow.promptForChoice "# of pieces to unlock per habit point:"
(collect (range 1 maxPPP))
->:Void [:Int points] (startPuzzle size points)
null null FlxColor.LIME null 0.9)))
(set puzzlePackChoiceWindow.cameras [uiCamera]))
(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))