(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) (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) (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 (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)) (set save.data.storedAngles (new Map)) (set save.data.storedOrigins (new Map)) (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 save.data.storedAngles) (dictGet indexMap s) s.angle) (dictSet (the Map 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 (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) // TODO add other windows and add them to the windowIsShown and tempWindowIsShown lists (method windowIsShown [] (doFor window [entryWindow] (when (and window (window.isShown)) (return true))) false) (method tempWindowIsShown [] (doFor window [] (when (and window (window.isShown)) (return true))) false) (prop &mut :FlxTypedGroup logTexts (new FlxTypedGroup)) (prop &mut :FlxKeyShortcutHandler 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 rewardSprites null) (prop &mut :Map matchingPiecesLeft (new Map)) (prop &mut :Map matchingPiecesRight (new Map)) (prop &mut :Map matchingPiecesUp (new Map)) (prop &mut :Map matchingPiecesDown (new Map)) (prop &mut :Map pieceData (new Map)) (prop &mut :Map> connectedPieces (new Map)) (prop &mut :Map indexMap (new Map)) (prop &mut :Map 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 minRewardFile null) (prop &mut :Null 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) (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)) (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 (- 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.GRAY) (_makeText "{space} Cycle background color" 0 ->_ (defAndCall method toggleBackgroundColor (set save.data.backgroundIndex #{(save.data.backgroundIndex + 1) % backgroundOptions.length;}#) (save.flush) (refreshModel))) (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 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))) (unless save.data.storedAngles (set save.data.storedAngles (new Map))) (unless save.data.storedOrigins (set save.data.storedOrigins (new Map))) (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 startingPoints [] :Array 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 save.data.storedPositions) i)] point (.addPoint (nth startingPoints i) camera.scroll)) angle (ifLet [angle (dictGet (the Map 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 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 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 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) }) // Uncomment to debug piece IDs **(#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)) (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 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 recursivelyConnectedPieces [s &opt :Array 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))