(prop &mut :Jigsawx jigsaw) (prop &mut :FlxCamera pieceCamera) (prop &mut :FlxCamera uiCamera) (method &override :Void create [] (set FlxG.cameras.bgColor FlxColor.TRANSPARENT) (set pieceCamera FlxG.camera) (set uiCamera (new FlxCamera)) (pieceCamera.copyFrom FlxG.camera) (set FlxG.camera pieceCamera) (FlxG.cameras.add uiCamera) (FlxG.plugins.add (new FlxMouseControl)) (set bgColor FlxColor.TRANSPARENT) (super.create)) (var KEYBOARD_SCROLL_SPEED 200) (method &override :Void update [:Float elapsed] (super.update elapsed) (pieceCamera.updateScrollWheelZoom elapsed 1) (pieceCamera.updateMouseBorderControl elapsed KEYBOARD_SCROLL_SPEED 0.15) // Hold left-click to hide the habit text and see the image clearly: (when entryTexts (if FlxG.mouse.pressed (remove entryTexts) (add entryTexts))) (when FlxG.keys.justPressed.ESCAPE (Sys.exit 0)) // drag along connected pieces (when draggingSprite (let [dx (- draggingSprite.x draggingLastPos.x) dy (- draggingSprite.y draggingLastPos.y)] (set draggingLastPos (new FlxPoint draggingSprite.x draggingSprite.y)) (doFor s (recursivelyConnectedPieces draggingSprite) (+= s.x dx) (+= s.y dy)))) // Left and right arrow keys can switch between unlocked puzzles (when FlxG.keys.justPressed.LEFT (unless (= rewardFileIndex 0) (-= rewardFileIndex 1) (setModel model (nth model.rewardFiles rewardFileIndex)))) (when FlxG.keys.justPressed.RIGHT (unless (= rewardFileIndex maxRewardFile) (+= rewardFileIndex 1) (setModel model (nth model.rewardFiles rewardFileIndex)))) // Handle keyboard input: (when shortcutHandler (shortcutHandler.update))) (prop &mut :FlxSave save null) (prop &mut :FlxTypedGroup entryTexts null) (prop &mut :FlxKeyShortcutHandler shortcutHandler null) (prop &mut :HabitModel model null) (prop EDGE_LEEWAY 25) (prop BUBBLE_SIZE 15) (var PUZZLE_WIDTH 6) (var PUZZLE_HEIGHT 5) (var TOTAL_PIECES (* PUZZLE_WIDTH PUZZLE_HEIGHT)) (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 rewardFileIndex 0) (prop &mut maxRewardFile 0) (var SCROLL_BOUND_MARGIN 200) (prop &mut :FlxExtendedSprite draggingSprite null) (prop &mut :FlxPoint draggingLastPos null) (method setModel [m &opt :RewardFile currentRewardFile] (set model m) (set shortcutHandler (new FlxKeyShortcutHandler)) (let [p (m.totalPoints) &mut i 0 ] // Find, load, and add the current reward image as big as possible: (unless currentRewardFile (set currentRewardFile (nth m.rewardFiles 0)) (while (> p .startingPoints (nth m.rewardFiles i)) (set rewardFileIndex i) (set currentRewardFile (nth m.rewardFiles i)) (set maxRewardFile i) (if (>= ++i m.rewardFiles.length) (break)))) (set save (new FlxSave)) (save.bind currentRewardFile.path) (unless save.data.storedPositions (set save.data.storedPositions (new Map))) (let [rewardSprite (new FlxSprite 0 0 (BitmapData.fromFile (joinPath (Path.directory m.textFile) 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) (set pieceCamera.zoom rewardSprite.scale.x) (set rewardSprites (new FlxTypedGroup)) (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)) graphicWidth rewardSprite.pixels.width graphicHeight rewardSprite.pixels.height pieceAssetWidth (Std.int (/ graphicWidth PUZZLE_WIDTH)) pieceAssetHeight (Std.int (/ graphicHeight PUZZLE_HEIGHT)) j (new Jigsawx pieceAssetWidth pieceAssetHeight EDGE_LEEWAY BUBBLE_SIZE PUZZLE_HEIGHT PUZZLE_WIDTH r) PIECE_WIDTH (/ rewardSprite.width PUZZLE_WIDTH) PIECE_HEIGHT (/ rewardSprite.height PUZZLE_HEIGHT) :Array startingPoints []] (let [&mut i 0] (doFor y (range PUZZLE_HEIGHT) (doFor x (range PUZZLE_WIDTH) (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) 0))) (doFor i (range (min TOTAL_PIECES (- p currentRewardFile.startingPoints))) (let [jig (nth jigsaw.jigs i) pos (ifLet [point (dictGet (the Map save.data.storedPositions) i)] point (nth startingPoints i)) s (new FlxExtendedSprite pos.x pos.y) source (new FlxSprite) mask (new FlxSprite) sourceRect (new Rectangle jig.xy.x jig.xy.y jig.wh.x jig.wh.y)] (setNth spriteGrid jig.row jig.col s) (setNth indexGrid jig.row jig.col i) (dictSet pieceData i jig) (dictSet indexMap s i) (set s.draggable true) (s.enableMouseDrag false true) (set s.mouseStartDragCallback ->:Void [s x y] { (set draggingSprite s) (set draggingLastPos (new FlxPoint s.x s.y)) }) (set s.mouseStopDragCallback ->:Void [s x y] { (set draggingSprite null) (checkMatches i) (dictSet (the Map save.data.storedPositions) i (new FlxPoint s.x s.y)) (doFor connected (recursivelyConnectedPieces s) (checkMatches (dictGet indexMap connected)) (dictSet (the Map save.data.storedPositions) (dictGet indexMap connected) (new FlxPoint connected.x connected.y))) (pieceCamera.calculateScrollBounds rewardSprites SCROLL_BOUND_MARGIN) (save.flush) }) (source.makeGraphic (Std.int sourceRect.width) (Std.int sourceRect.height) FlxColor.TRANSPARENT true) (source.pixels.copyPixels rewardSprite.pixels sourceRect (new Point 0 0)) (mask.makeGraphic (Std.int sourceRect.width) (Std.int sourceRect.height) FlxColor.TRANSPARENT true) (drawPieceShape mask jig FlxColor.BLACK) (FlxSpriteUtil.alphaMask s source.pixels mask.pixels) (set s.cameras [pieceCamera]) (rewardSprites.add s))) (doFor row (range PUZZLE_HEIGHT) (doFor col (range PUZZLE_WIDTH) (let [id (nth indexGrid row col)] // combination of try/whenLet should cover target languages // where out-of-bounds nth throws an error AND languages // where it returns null (try (whenLet [toLeft (nth spriteGrid row (- col 1))] (dictSet matchingPiecesLeft id toLeft)) (catch [e] null)) (try (whenLet [toRight (nth spriteGrid row (+ col 1))] (dictSet matchingPiecesRight id toRight)) (catch [e] null)) (try (whenLet [toUp (nth spriteGrid (- row 1) col)] (dictSet matchingPiecesUp id toUp)) (catch [e] null)) (try (whenLet [toDown (nth spriteGrid (+ row 1) col)] (dictSet matchingPiecesDown id toDown)) (catch [e] null))))) (add rewardSprites) (doFor i (range TOTAL_PIECES) (checkMatches i)))) (pieceCamera.calculateScrollBounds rewardSprites SCROLL_BOUND_MARGIN) (when entryTexts (remove entryTexts)) (set entryTexts (new FlxTypedGroup)) (set textY 0) (set color FlxColor.LIME) (_makeText "Puzzle #$(+ 1 rewardFileIndex) / ${model.rewardFiles.length}" (- TOTAL_PIECES (- p currentRewardFile.startingPoints))) (set color FlxColor.ORANGE) (map (m.activeDailyEntries) makeText) (set color FlxColor.GREEN) (map (m.activeMonthlyEntries) makeText) (set color FlxColor.BLUE) (map (m.activeIntervalEntries) makeText) (set color FlxColor.WHITE) (map (m.activeBonusEntries) makeText) (set color FlxColor.YELLOW) (map (m.activeTodoEntries) makeText) (add entryTexts)) (doFor e (m.allEntries) (when (HabitModel.isActive e) (let [label (HabitModel.activeLabel e)] (shortcutHandler.registerItem label.label e)))) (set shortcutHandler.onBadKey ->:Void [_ _] {}) (set shortcutHandler.onSelectItem ->:Void [:Entry e] (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))))) (m.save) (setModel m) (shortcutHandler.start))) (shortcutHandler.start)) (prop &mut textY 0) (prop &mut :FlxColor color FlxColor.BLACK) (method makeText [:Entry e] (let [label (HabitModel.activeLabel e)] (_makeText label.label label.points))) (method _makeText [:String s :Int points] (let [text (new FlxText 0 textY 0 (+ s (* points "+")) 16)] (set text.color color) (set text.cameras [uiCamera]) (+= textY text.height) (entryTexts.add text))) (method :FlxRect matchZoneLeft [:FlxExtendedSprite s] (new FlxRect s.x (+ s.y s.origin.y) EDGE_LEEWAY EDGE_LEEWAY)) (method :FlxRect matchZoneRight [:FlxExtendedSprite s] (new FlxRect (- (+ s.x s.width) EDGE_LEEWAY) (+ s.y s.origin.y) EDGE_LEEWAY EDGE_LEEWAY)) (method :FlxRect matchZoneUp [:FlxExtendedSprite s] (new FlxRect (+ s.x s.origin.x) s.y EDGE_LEEWAY EDGE_LEEWAY)) (method :FlxRect matchZoneDown [:FlxExtendedSprite s] (new FlxRect (+ s.x s.origin.x) (- (+ s.y s.height) EDGE_LEEWAY) EDGE_LEEWAY EDGE_LEEWAY)) (prop &mut c 0) (method :Void connectPiece [id self toSprite] (let [thisConnectedPieces (dictGet connectedPieces id) toConnectedPieces (dictGet connectedPieces (dictGet indexMap toSprite))] (+= c 1) // Don't add duplicates (thisConnectedPieces.remove toSprite) (thisConnectedPieces.push toSprite) (toConnectedPieces.remove self) (toConnectedPieces.push self))) (method :Void checkMatches [id] (when !(pieceData.exists id) (return)) (let [s (nth rewardSprites.members id) jig (dictGet pieceData id) row jig.row col jig.col] /* // TODO tune the match zones (let [l (matchZoneLeft s) r (matchZoneRight s)] (s.drawRect (- l.x s.x) (- l.y s.y) l.width l.height) (s.drawRect (- r.x s.x) (- r.y s.y) r.width r.height)) */ (whenLet [toLeft (dictGet matchingPiecesLeft id) mzl (matchZoneLeft s) mzr (matchZoneRight toLeft)] (unless .isEmpty (mzl.intersection mzr) (connectPiece id s toLeft))) (whenLet [toRight (dictGet matchingPiecesRight id) mzr (matchZoneRight s) mzl (matchZoneLeft toRight)] (unless .isEmpty (mzl.intersection mzr) (connectPiece id s toRight))) (whenLet [toUp (dictGet matchingPiecesUp id) mzu (matchZoneUp s) mzd (matchZoneDown toUp)] (unless .isEmpty (mzu.intersection mzd) (connectPiece id s toUp))) (whenLet [toDown (dictGet matchingPiecesDown id) mzd (matchZoneDown s) mzu (matchZoneUp toDown)] (unless .isEmpty (mzu.intersection mzd) (connectPiece id s toDown))))) (method :Array recursivelyConnectedPieces [s &opt :Array collected] (unless collected (set collected [])) (let [directlyConnected (dictGet connectedPieces (dictGet indexMap s))] (doFor piece directlyConnected (unless (contains collected piece) (collected.push piece) (recursivelyConnectedPieces piece collected)))) collected)