feat(settings/options/2048): display score in top bar, and implement intro and outro animation

This commit is contained in:
2026-02-06 15:37:13 +01:00
parent ff02a032db
commit 7baf63f30a
2 changed files with 80 additions and 29 deletions

View File

@@ -12,8 +12,11 @@ const handleActivateB = () => {
confirmationModal.open({ confirmationModal.open({
text: $t("settings.options.2048.quitConfirmation"), text: $t("settings.options.2048.quitConfirmation"),
onConfirm: () => {}, onConfirm: () => {},
onClosed: (choice) => { onClosed: async (choice) => {
if (choice === "confirm") store.closeSubMenu(); if (choice === "confirm") {
await animateOutro();
store.closeSubMenu();
}
}, },
}); });
}; };
@@ -62,9 +65,17 @@ const BOARD_X = 64;
const BOARD_Y = 32; const BOARD_Y = 32;
const BOARD_SIZE = 4; const BOARD_SIZE = 4;
const SCORE_X = 195; const SLIDE_OFFSET = 96;
const SCORE_Y = 36; const SLIDE_DURATION = 0.25;
const HIGH_SCORE_Y = 68; const SCORE_OFFSET = -20;
const SCORE_DURATION = 0.15;
const intro = reactive({
frameOffsetY: SLIDE_OFFSET,
frameOpacity: 0,
scoreOffsetY: SCORE_OFFSET,
tilesVisible: false,
});
const emptyBoard = () => const emptyBoard = () =>
Array.from({ length: BOARD_SIZE }, () => Array.from({ length: BOARD_SIZE }, () =>
@@ -125,12 +136,55 @@ const animateSpawnAll = () => {
} }
}; };
const animateIntro = async () => {
buildTilesFromBoard();
await gsap
.timeline()
.to(intro, { frameOffsetY: 0, duration: SLIDE_DURATION, ease: "none" }, 0)
.to(intro, { frameOpacity: 1, duration: SLIDE_DURATION, ease: "none" }, 0)
.call(
() => {
intro.tilesVisible = true;
animateSpawnAll();
},
[],
SLIDE_DURATION,
)
.to(
intro,
{ scoreOffsetY: 0, duration: SCORE_DURATION, ease: "none" },
SLIDE_DURATION,
);
};
const animateOutro = async () => {
await gsap
.timeline()
.to(
intro,
{ frameOffsetY: SLIDE_OFFSET, duration: SLIDE_DURATION, ease: "none" },
0,
)
.to(intro, { frameOpacity: 0, duration: SLIDE_DURATION, ease: "none" }, 0)
.to(
intro,
{ scoreOffsetY: SCORE_OFFSET, duration: SCORE_DURATION, ease: "none" },
0,
);
};
onMounted(() => {
animateIntro();
});
onRender((ctx) => { onRender((ctx) => {
assets.images.home.topScreen.background.draw(ctx, 0, 0); assets.images.home.topScreen.background.draw(ctx, 0, 0);
ctx.textBaseline = "top"; ctx.textBaseline = "top";
ctx.save(); ctx.save();
ctx.translate(BOARD_X, BOARD_Y); ctx.globalAlpha = intro.frameOpacity;
ctx.translate(BOARD_X, BOARD_Y + intro.frameOffsetY);
assets.images.settings.bottomScreen.options._2048.frame.draw(ctx, -3, -3); assets.images.settings.bottomScreen.options._2048.frame.draw(ctx, -3, -3);
@@ -149,6 +203,11 @@ onRender((ctx) => {
} }
} }
if (!intro.tilesVisible) {
ctx.restore();
return;
}
for (const tile of tiles) { for (const tile of tiles) {
const color = TILE_COLORS[tile.value] ?? LAST_TILE_COLOR; const color = TILE_COLORS[tile.value] ?? LAST_TILE_COLOR;
@@ -174,27 +233,20 @@ onRender((ctx) => {
ctx.restore(); ctx.restore();
} }
ctx.restore(); ctx.restore();
ctx.font = "7px NDS7";
ctx.fillStyle = "#010101";
// score
ctx.fillText(`${$t("settings.options.2048.score")}:`, SCORE_X, SCORE_Y);
ctx.fillText(score.toString(), SCORE_X, SCORE_Y + 16);
// high score
ctx.fillText(
`${$t("settings.options.2048.highScore")}:`,
SCORE_X,
HIGH_SCORE_Y,
);
ctx.fillText(
savedState.value.highScore.toString(),
SCORE_X,
HIGH_SCORE_Y + 16,
);
}); });
onRender((ctx) => {
ctx.translate(0, intro.scoreOffsetY);
drawButton(ctx, `${$t("settings.options.2048.score")}: ${score}`, 10, 2, 118);
drawButton(
ctx,
`${$t("settings.options.2048.highScore")}: ${savedState.value.highScore}`,
138,
2,
108,
);
}, 110);
const getCell = (row: number, col: number) => board[row]![col]!; const getCell = (row: number, col: number) => board[row]![col]!;
const setCell = (row: number, col: number, val: number) => { const setCell = (row: number, col: number, val: number) => {
board[row]![col] = val; board[row]![col] = val;
@@ -297,10 +349,9 @@ const isDead = () => {
}; };
if (board.every((r) => r.every((c) => c === 0))) { if (board.every((r) => r.every((c) => c === 0))) {
resetBoard(); spawnTile();
} else { spawnTile();
buildTilesFromBoard(); saveState();
animateSpawnAll();
} }
const slide = (rowDir: number, colDir: number) => { const slide = (rowDir: number, colDir: number) => {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 B

After

Width:  |  Height:  |  Size: 134 B