feat(settings/options/2048): allow faster movements and show restart modal when trying to move on an lost game

This commit is contained in:
2026-01-30 00:25:02 +01:00
parent 7017d3c2fe
commit 2a396dc4f5

View File

@@ -28,33 +28,31 @@ const handleActivateA = () => {
resetBoard(); resetBoard();
} }
}, },
onCancel: () => {
confirmationModal.close();
},
}); });
}; };
// TODO: one color scheme per app color
const TILE_COLORS: Record<number, { bg: string; fg: string }> = { const TILE_COLORS: Record<number, { bg: string; fg: string }> = {
[0]: { bg: "#ccc0b4", fg: "#776e65" }, [0]: { bg: "#f7f7f7", fg: "#776e65" },
[2]: { bg: "#eee4db", fg: "#776e65" }, [2]: { bg: "#ebebf3", fg: "#292929" },
[4]: { bg: "#eee0cb", fg: "#776e65" }, [4]: { bg: "#d3dbe3", fg: "#292929" },
[8]: { bg: "#f3b27a", fg: "#f9f6f2" }, [8]: { bg: "#bacbd3", fg: "#292929" },
[16]: { bg: "#f69664", fg: "#f9f6f2" }, [16]: { bg: "#a2bac3", fg: "#f9f6f2" },
[32]: { bg: "#f67c5f", fg: "#f9f6f2" }, [32]: { bg: "#8aa2b2", fg: "#f9f6f2" },
[64]: { bg: "#f7603c", fg: "#f9f6f2" }, [64]: { bg: "#7192a2", fg: "#f9f6f2" },
[128]: { bg: "#ecd072", fg: "#f9f6f2" }, [128]: { bg: "#698aa2", fg: "#f9f6f2" },
[256]: { bg: "#eecc62", fg: "#f9f6f2" }, [256]: { bg: "#61829a", fg: "#f9f6f2" },
[512]: { bg: "#eec950", fg: "#f9f6f2" }, [512]: { bg: "#5c7b92", fg: "#f9f6f2" },
[1024]: { bg: "#edc53f", fg: "#f9f6f2" }, [1024]: { bg: "#57758a", fg: "#f9f6f2" }, // -2.5L, -0.8C
[2048]: { bg: "#edc12e", fg: "#f9f6f2" }, [2048]: { bg: "#476277", fg: "#f9f6f2" },
[4046]: { bg: "#3c3a33", fg: "#f9f6f2" }, [4046]: { bg: "#173446", fg: "#f9f6f2" },
}; };
const LAST_TILE_COLOR = const LAST_TILE_COLOR =
Object.values(TILE_COLORS)[Object.values(TILE_COLORS).length - 1]!; Object.values(TILE_COLORS)[Object.values(TILE_COLORS).length - 1]!;
const TILE_SIZE = 28; const TILE_SIZE = 28;
const ANIM_DURATION = 0.1; const ANIM_DURATION = 0.1;
const BORDER_COLOR = "#bbada0"; const BORDER_COLOR = "#d7d7d7";
const BORDER_SIZE = 3; const BORDER_SIZE = 3;
const BOARD_X = 64; const BOARD_X = 64;
@@ -81,6 +79,21 @@ const cellY = (row: number) => BORDER_SIZE + row * (TILE_SIZE + BORDER_SIZE);
let tiles: VisualTile[] = []; let tiles: VisualTile[] = [];
let animating = false; let animating = false;
const showRestartModal = () => {
let confirmed = false;
confirmationModal.open({
text: "Game Over!\nRestart?",
onConfirm: () => {
confirmed = true;
},
onClosed: () => {
if (confirmed) {
resetBoard();
}
},
});
};
const buildTilesFromBoard = () => { const buildTilesFromBoard = () => {
tiles = []; tiles = [];
for (let row = 0; row < BOARD_SIZE; row += 1) { for (let row = 0; row < BOARD_SIZE; row += 1) {
@@ -104,6 +117,8 @@ onRender((ctx) => {
ctx.textBaseline = "top"; ctx.textBaseline = "top";
ctx.translate(BOARD_X, BOARD_Y); ctx.translate(BOARD_X, BOARD_Y);
assets.images.settings.bottomScreen.options._2048.frame.draw(ctx, -3, -3);
ctx.fillStyle = BORDER_COLOR; ctx.fillStyle = BORDER_COLOR;
ctx.fillRect( ctx.fillRect(
0, 0,
@@ -251,7 +266,16 @@ if (board.every((r) => r.every((c) => c === 0))) {
} }
const slide = (rowDir: number, colDir: number) => { const slide = (rowDir: number, colDir: number) => {
if (animating) return; if (isDead() && !confirmationModal.isOpen) {
showRestartModal();
return;
}
if (animating) {
gsap.globalTimeline.clear();
animating = false;
buildTilesFromBoard();
}
const beforeTiles: { row: number; col: number; value: number }[] = []; const beforeTiles: { row: number; col: number; value: number }[] = [];
for (let row = 0; row < BOARD_SIZE; row += 1) { for (let row = 0; row < BOARD_SIZE; row += 1) {
@@ -358,6 +382,12 @@ const slide = (rowDir: number, colDir: number) => {
const spawned = spawnTile(); const spawned = spawnTile();
saveBoard(); saveBoard();
if (isDead()) {
buildTilesFromBoard();
showRestartModal();
return;
}
tiles = animTiles; tiles = animTiles;
animating = true; animating = true;
@@ -365,27 +395,11 @@ const slide = (rowDir: number, colDir: number) => {
onComplete: () => { onComplete: () => {
animating = false; animating = false;
buildTilesFromBoard(); buildTilesFromBoard();
if (isDead()) {
confirmationModal.open({
text: "Game Over!\nRestart?",
onConfirm: () => {
resetBoard();
confirmationModal.close();
},
onCancel: () => {
confirmationModal.close();
},
});
}
}, },
}); });
for (const { tile, toX, toY } of tweens) { for (const { tile, toX, toY } of tweens) {
tl.to( tl.to(tile, { x: toX, y: toY, duration: ANIM_DURATION, ease: "none" }, 0);
tile,
{ x: toX, y: toY, duration: ANIM_DURATION, ease: "power1.out" },
0,
);
} }
if (mergePairs.length > 0) { if (mergePairs.length > 0) {
@@ -399,7 +413,7 @@ const slide = (rowDir: number, colDir: number) => {
tl.fromTo( tl.fromTo(
keep, keep,
{ scale: 1.1 }, { scale: 1.1 },
{ scale: 1, duration: ANIM_DURATION, ease: "power1.out" }, { scale: 1, duration: ANIM_DURATION, ease: "none" },
); );
} }
} }
@@ -424,6 +438,15 @@ const slide = (rowDir: number, colDir: number) => {
useKeyDown((key) => { useKeyDown((key) => {
switch (key) { switch (key) {
// TODO: remove this, testing only
case "n":
savedBoard.value = [
[0, 0, 2, 4],
[8, 16, 32, 64],
[128, 256, 512, 1024],
[2048, 4096, 8192, 16384],
];
break;
case "NDS_UP": case "NDS_UP":
slide(-1, 0); slide(-1, 0);
break; break;