feat(settings/touchScreen/tapTap): implement
This commit is contained in:
@@ -15,6 +15,7 @@ import ClockAchievements from "./Clock/Achievements.vue";
|
|||||||
import ClockDate from "./Clock/Date.vue";
|
import ClockDate from "./Clock/Date.vue";
|
||||||
import ClockTime from "./Clock/Time.vue";
|
import ClockTime from "./Clock/Time.vue";
|
||||||
import TouchScreenMenu from "./TouchScreen/Menu.vue";
|
import TouchScreenMenu from "./TouchScreen/Menu.vue";
|
||||||
|
import TouchScreenTapTap from "./TouchScreen/TapTap.vue";
|
||||||
import Selector from "~/components/Common/ButtonSelector.vue";
|
import Selector from "~/components/Common/ButtonSelector.vue";
|
||||||
|
|
||||||
const app = useAppStore();
|
const app = useAppStore();
|
||||||
@@ -60,7 +61,8 @@ const { select, selected, selectorPosition } = useButtonNavigation({
|
|||||||
if (buttonName === "options") select("optionsLanguage");
|
if (buttonName === "options") select("optionsLanguage");
|
||||||
if (buttonName === "clock") select("clockAchievements");
|
if (buttonName === "clock") select("clockAchievements");
|
||||||
if (buttonName === "user") select("userUserName");
|
if (buttonName === "user") select("userUserName");
|
||||||
if (buttonName === "touchScreen") throw new Error("Not implemented");
|
if (buttonName === "touchScreen")
|
||||||
|
settingsStore.openSubMenu("touchScreenTapTap");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
navigation: {
|
navigation: {
|
||||||
@@ -190,6 +192,8 @@ const viewComponents: Record<string, Component> = {
|
|||||||
userBirthday: UserBirthday,
|
userBirthday: UserBirthday,
|
||||||
userUserName: UserUserName,
|
userUserName: UserUserName,
|
||||||
userSnake: UserSnake,
|
userSnake: UserSnake,
|
||||||
|
|
||||||
|
touchScreenTapTap: TouchScreenTapTap,
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,349 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useLocalStorage } from "@vueuse/core";
|
||||||
|
|
||||||
|
const store = useSettingsStore();
|
||||||
|
const achievements = useAchievementsStore();
|
||||||
|
const confirmationModal = useConfirmationModal();
|
||||||
|
|
||||||
|
const { onRender, onClick } = useScreen();
|
||||||
|
const { assets } = useAssets();
|
||||||
|
|
||||||
|
const BAR_HEIGHT = 24;
|
||||||
|
const MAX_RADIUS = 27;
|
||||||
|
const MIN_RADIUS = 3;
|
||||||
|
const BASE_SHRINK_SPEED = 0.09;
|
||||||
|
const MAX_SHRINK_SPEED = 0.14;
|
||||||
|
const BASE_SPAWN_INTERVAL = 90;
|
||||||
|
const MIN_SPAWN_INTERVAL = 45;
|
||||||
|
const DIFFICULTY_SCORE_CAP = 100;
|
||||||
|
const RING_STROKE_WIDTH = 5;
|
||||||
|
const RING_EXPAND_SPEED = 0.3;
|
||||||
|
const RING_FADE_SPEED = 0.05;
|
||||||
|
const MAX_LIVES = 3;
|
||||||
|
const CROSSHAIR_MOVE_SPEED = 5;
|
||||||
|
|
||||||
|
const getDifficulty = () => {
|
||||||
|
const progress = Math.min(score / DIFFICULTY_SCORE_CAP, 1);
|
||||||
|
return {
|
||||||
|
shrinkSpeed:
|
||||||
|
BASE_SHRINK_SPEED + (MAX_SHRINK_SPEED - BASE_SHRINK_SPEED) * progress,
|
||||||
|
spawnInterval: Math.floor(
|
||||||
|
BASE_SPAWN_INTERVAL -
|
||||||
|
(BASE_SPAWN_INTERVAL - MIN_SPAWN_INTERVAL) * progress,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type Circle = {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
radius: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Ring = {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
radius: number;
|
||||||
|
alpha: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = ref<"waiting" | "playing" | "paused" | "ended">("waiting");
|
||||||
|
let lives = MAX_LIVES;
|
||||||
|
|
||||||
|
let circles: Circle[] = [];
|
||||||
|
let rings: Ring[] = [];
|
||||||
|
let spawnTimer = 0;
|
||||||
|
|
||||||
|
// crosshair
|
||||||
|
let x = 0;
|
||||||
|
let y = LOGICAL_HEIGHT * 2 - 20;
|
||||||
|
let targetX = 31;
|
||||||
|
let targetY = 31;
|
||||||
|
let horizontalFirst = false;
|
||||||
|
|
||||||
|
const highScore = useLocalStorage("taptap_high_score", 0);
|
||||||
|
let score = 0;
|
||||||
|
let isNewBest = false;
|
||||||
|
|
||||||
|
const handleActivateB = () => {
|
||||||
|
if (state.value === "playing") {
|
||||||
|
state.value = "paused";
|
||||||
|
confirmationModal.open({
|
||||||
|
text: $t("settings.touchScreen.tapTap.quitConfirmation"),
|
||||||
|
bLabel: $t("common.no"),
|
||||||
|
aLabel: $t("common.yes"),
|
||||||
|
onClosed: (choice) => {
|
||||||
|
if (choice === "confirm") {
|
||||||
|
store.closeSubMenu();
|
||||||
|
} else {
|
||||||
|
state.value = "playing";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
store.closeSubMenu();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleActivateA = () => {
|
||||||
|
if (state.value === "playing") {
|
||||||
|
state.value = "paused";
|
||||||
|
confirmationModal.open({
|
||||||
|
text: $t("settings.touchScreen.tapTap.restartConfirmation"),
|
||||||
|
bLabel: $t("common.no"),
|
||||||
|
aLabel: $t("common.yes"),
|
||||||
|
onClosed: (choice) => {
|
||||||
|
if (choice === "confirm") {
|
||||||
|
resetGame();
|
||||||
|
} else {
|
||||||
|
state.value = "playing";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
resetGame();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const moveTowards = (current: number, target: number) => {
|
||||||
|
if (current === target) return current;
|
||||||
|
const direction = Math.sign(target - current);
|
||||||
|
return (
|
||||||
|
current +
|
||||||
|
direction * Math.min(CROSSHAIR_MOVE_SPEED, Math.abs(target - current))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const spawnCircle = () => {
|
||||||
|
const PADDING_X = MAX_RADIUS;
|
||||||
|
const PADDING_TOP = MAX_RADIUS + BAR_HEIGHT;
|
||||||
|
const PADDING_BOT = MAX_RADIUS + BAR_HEIGHT;
|
||||||
|
const MAX_ATTEMPTS = 25;
|
||||||
|
|
||||||
|
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
||||||
|
const newX = Math.floor(
|
||||||
|
PADDING_X + Math.random() * (LOGICAL_WIDTH - PADDING_X * 2),
|
||||||
|
);
|
||||||
|
const newY = Math.floor(
|
||||||
|
PADDING_TOP +
|
||||||
|
Math.random() * (LOGICAL_HEIGHT - PADDING_TOP - PADDING_BOT),
|
||||||
|
);
|
||||||
|
|
||||||
|
const overlaps = circles.some((circle) => {
|
||||||
|
const dx = newX - circle.x;
|
||||||
|
const dy = newY - circle.y;
|
||||||
|
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
return distance < MAX_RADIUS + circle.radius;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!overlaps) {
|
||||||
|
circles.push({ x: newX, y: newY, radius: MAX_RADIUS });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetGame = () => {
|
||||||
|
state.value = "playing";
|
||||||
|
circles = [];
|
||||||
|
rings = [];
|
||||||
|
spawnTimer = 0;
|
||||||
|
score = 0;
|
||||||
|
lives = MAX_LIVES;
|
||||||
|
isNewBest = false;
|
||||||
|
achievements.unlock("taptap_play");
|
||||||
|
};
|
||||||
|
|
||||||
|
const showDeathScreen = () => {
|
||||||
|
const title = isNewBest
|
||||||
|
? $t("settings.touchScreen.tapTap.newRecord")
|
||||||
|
: $t("settings.touchScreen.tapTap.gameOver");
|
||||||
|
const text = `${title}\n${$t("settings.touchScreen.tapTap.finalScore", { score })}`;
|
||||||
|
|
||||||
|
confirmationModal.open({
|
||||||
|
text,
|
||||||
|
bLabel: $t("common.quit"),
|
||||||
|
aLabel: $t("common.restart"),
|
||||||
|
onClosed: (choice) => {
|
||||||
|
if (choice === "confirm") {
|
||||||
|
resetGame();
|
||||||
|
} else {
|
||||||
|
store.closeSubMenu();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onClick((mx, my) => {
|
||||||
|
if (state.value !== "playing") return;
|
||||||
|
if (my <= BAR_HEIGHT || my >= LOGICAL_HEIGHT - BAR_HEIGHT - 1) return;
|
||||||
|
|
||||||
|
targetX = mx;
|
||||||
|
targetY = my;
|
||||||
|
horizontalFirst = Math.abs(targetX - x) > Math.abs(targetY - y);
|
||||||
|
|
||||||
|
for (let i = circles.length - 1; i >= 0; i--) {
|
||||||
|
const circle = circles[i]!;
|
||||||
|
const dx = mx - circle.x;
|
||||||
|
const dy = my - circle.y;
|
||||||
|
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
|
if (distance <= circle.radius) {
|
||||||
|
rings.push({
|
||||||
|
x: circle.x,
|
||||||
|
y: circle.y,
|
||||||
|
radius: circle.radius,
|
||||||
|
alpha: 1,
|
||||||
|
});
|
||||||
|
circles.splice(i, 1);
|
||||||
|
score++;
|
||||||
|
if (score > highScore.value) {
|
||||||
|
highScore.value = score;
|
||||||
|
isNewBest = true;
|
||||||
|
}
|
||||||
|
if (score === 10) achievements.unlock("taptap_score_10");
|
||||||
|
if (score === 20) achievements.unlock("taptap_score_20");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onRender((ctx) => {
|
||||||
|
assets.images.home.topScreen.background.draw(ctx, 0, 0);
|
||||||
|
|
||||||
|
// update crosshair position in all modes except paused
|
||||||
|
if (state.value !== "paused") {
|
||||||
|
if (horizontalFirst) {
|
||||||
|
if (x !== targetX) x = moveTowards(x, targetX);
|
||||||
|
else y = moveTowards(y, targetY);
|
||||||
|
} else {
|
||||||
|
if (y !== targetY) y = moveTowards(y, targetY);
|
||||||
|
else x = moveTowards(x, targetX);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// game logic
|
||||||
|
if (state.value === "playing") {
|
||||||
|
const { shrinkSpeed, spawnInterval } = getDifficulty();
|
||||||
|
|
||||||
|
// spawn circles
|
||||||
|
spawnTimer++;
|
||||||
|
if (spawnTimer >= spawnInterval) {
|
||||||
|
spawnCircle();
|
||||||
|
spawnTimer = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update circles and rings
|
||||||
|
circles = circles.filter((circle) => {
|
||||||
|
circle.radius -= shrinkSpeed;
|
||||||
|
if (circle.radius < MIN_RADIUS) {
|
||||||
|
lives--;
|
||||||
|
if (lives <= 0) {
|
||||||
|
state.value = "ended";
|
||||||
|
showDeathScreen();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
rings = rings.filter((ring) => {
|
||||||
|
ring.radius += RING_EXPAND_SPEED;
|
||||||
|
ring.alpha -= RING_FADE_SPEED;
|
||||||
|
return ring.alpha > 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw start instructions
|
||||||
|
if (state.value === "waiting") {
|
||||||
|
ctx.fillStyle = "#fbfbfb";
|
||||||
|
ctx.textBaseline = "top";
|
||||||
|
ctx.fillRect(32, 112, 191, 31);
|
||||||
|
|
||||||
|
ctx.fillStyle = "#797979";
|
||||||
|
ctx.font = "10px NDS10";
|
||||||
|
fillTextHCentered(
|
||||||
|
ctx,
|
||||||
|
$t("settings.touchScreen.tapTap.startPrompt"),
|
||||||
|
32,
|
||||||
|
123,
|
||||||
|
191,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.value !== "waiting") {
|
||||||
|
// draw circles and rings
|
||||||
|
for (const circle of circles) {
|
||||||
|
ctx.fillStyle = "#fb0000";
|
||||||
|
fillCirclePixelated(ctx, circle.x, circle.y, circle.radius);
|
||||||
|
|
||||||
|
ctx.fillStyle = "#fbfafa";
|
||||||
|
fillCirclePixelated(ctx, circle.x, circle.y, circle.radius * 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const ring of rings) {
|
||||||
|
ctx.globalAlpha = ring.alpha;
|
||||||
|
ctx.fillStyle = "#fb0000";
|
||||||
|
strokeCirclePixelated(
|
||||||
|
ctx,
|
||||||
|
ring.x,
|
||||||
|
ring.y,
|
||||||
|
ring.radius,
|
||||||
|
RING_STROKE_WIDTH,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ctx.globalAlpha = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw crosshair
|
||||||
|
ctx.fillStyle = "#494949";
|
||||||
|
ctx.fillRect(x, 0, 1, LOGICAL_HEIGHT);
|
||||||
|
ctx.fillRect(0, y, LOGICAL_WIDTH, 1);
|
||||||
|
|
||||||
|
ctx.fillStyle = "#fb0000";
|
||||||
|
ctx.fillRect(x - 4, y - 4, 9, 9);
|
||||||
|
|
||||||
|
ctx.fillStyle = "#fbfafa";
|
||||||
|
ctx.fillRect(x - 2, y - 2, 5, 5);
|
||||||
|
|
||||||
|
ctx.fillStyle = "#fb0000";
|
||||||
|
ctx.fillRect(x, y - 1, 1, 3);
|
||||||
|
ctx.fillRect(x - 1, y, 3, 1);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
onRender((ctx) => {
|
||||||
|
drawButton(
|
||||||
|
ctx,
|
||||||
|
$t("settings.touchScreen.tapTap.score", { score }),
|
||||||
|
10,
|
||||||
|
3,
|
||||||
|
88,
|
||||||
|
);
|
||||||
|
drawButton(
|
||||||
|
ctx,
|
||||||
|
$t("settings.touchScreen.tapTap.best", { best: highScore.value }),
|
||||||
|
108,
|
||||||
|
3,
|
||||||
|
88,
|
||||||
|
);
|
||||||
|
drawButton(
|
||||||
|
ctx,
|
||||||
|
lives > 0 ? ICONS.HEART.repeat(lives) : ICONS.SAD,
|
||||||
|
206,
|
||||||
|
3,
|
||||||
|
40,
|
||||||
|
);
|
||||||
|
}, 110);
|
||||||
|
|
||||||
|
defineOptions({ render: () => null });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CommonButtons
|
||||||
|
:y-offset="confirmationModal.buttonsYOffset"
|
||||||
|
:b-label="$t('common.quit')"
|
||||||
|
:a-label="state === 'waiting' ? $t('common.start') : $t('common.restart')"
|
||||||
|
@activate-b="handleActivateB"
|
||||||
|
@activate-a="handleActivateA"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
@@ -49,6 +49,7 @@ const IMAGES_MAP: Record<string, AtlasImage> = {
|
|||||||
userColor: assets.images.settings.topScreen.user.color,
|
userColor: assets.images.settings.topScreen.user.color,
|
||||||
|
|
||||||
touchScreen: assets.images.settings.topScreen.touchScreen.touchScreen,
|
touchScreen: assets.images.settings.topScreen.touchScreen.touchScreen,
|
||||||
|
touchScreenTapTap: assets.images.settings.topScreen.touchScreen.touchScreen,
|
||||||
};
|
};
|
||||||
|
|
||||||
const menuNotification = computed(() => {
|
const menuNotification = computed(() => {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export const SETTINGS_SUB_MENUS = [
|
|||||||
"userUserName",
|
"userUserName",
|
||||||
"userSnake",
|
"userSnake",
|
||||||
"userColor",
|
"userColor",
|
||||||
|
"touchScreenTapTap",
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type SettingsMenu = (typeof SETTINGS_MENUS)[number];
|
export type SettingsMenu = (typeof SETTINGS_MENUS)[number];
|
||||||
|
|||||||
@@ -78,6 +78,41 @@ export const drawCheckbox = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const fillCirclePixelated = (
|
||||||
|
ctx: CanvasRenderingContext2D,
|
||||||
|
cx: number,
|
||||||
|
cy: number,
|
||||||
|
radius: number,
|
||||||
|
) => {
|
||||||
|
const r = Math.floor(radius);
|
||||||
|
for (let dy = -r; dy <= r; dy++) {
|
||||||
|
for (let dx = -r; dx <= r; dx++) {
|
||||||
|
if (dx * dx + dy * dy <= r * r) {
|
||||||
|
ctx.fillRect(Math.floor(cx) + dx, Math.floor(cy) + dy, 1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const strokeCirclePixelated = (
|
||||||
|
ctx: CanvasRenderingContext2D,
|
||||||
|
cx: number,
|
||||||
|
cy: number,
|
||||||
|
radius: number,
|
||||||
|
thickness: number = 2,
|
||||||
|
) => {
|
||||||
|
const outerR = Math.floor(radius);
|
||||||
|
const innerR = Math.floor(radius - thickness);
|
||||||
|
for (let dy = -outerR; dy <= outerR; dy++) {
|
||||||
|
for (let dx = -outerR; dx <= outerR; dx++) {
|
||||||
|
const distSq = dx * dx + dy * dy;
|
||||||
|
if (distSq <= outerR * outerR && distSq > innerR * innerR) {
|
||||||
|
ctx.fillRect(Math.floor(cx) + dx, Math.floor(cy) + dy, 1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const fillTextWordWrapped = (
|
export const fillTextWordWrapped = (
|
||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
text: string,
|
text: string,
|
||||||
@@ -120,3 +155,39 @@ export const fillTextWordWrapped = (
|
|||||||
|
|
||||||
return lineCount * height;
|
return lineCount * height;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const drawButton = (
|
||||||
|
ctx: CanvasRenderingContext2D,
|
||||||
|
text: string,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
width: number,
|
||||||
|
) => {
|
||||||
|
const { assets } = useAssets((a) => a.images.common);
|
||||||
|
|
||||||
|
const LEFT_WIDTH = assets.buttonLeft.rect.width;
|
||||||
|
const RIGHT_WIDTH = assets.buttonRight.rect.width;
|
||||||
|
const BODY_WIDTH = assets.buttonBody.rect.width;
|
||||||
|
|
||||||
|
const bodySpace = width - LEFT_WIDTH - RIGHT_WIDTH;
|
||||||
|
const bodyCount = Math.ceil(bodySpace / BODY_WIDTH);
|
||||||
|
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(x, y);
|
||||||
|
|
||||||
|
assets.buttonLeft.draw(ctx, 0, 0);
|
||||||
|
|
||||||
|
for (let i = 0; i < bodyCount; i++) {
|
||||||
|
assets.buttonBody.draw(ctx, LEFT_WIDTH + i * BODY_WIDTH, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
assets.buttonRight.draw(ctx, width - RIGHT_WIDTH, 0);
|
||||||
|
|
||||||
|
ctx.textBaseline = "top";
|
||||||
|
ctx.font = "10px NDS10";
|
||||||
|
ctx.fillStyle = "#494118";
|
||||||
|
|
||||||
|
fillTextHCentered(ctx, text, 1, 4, width);
|
||||||
|
|
||||||
|
ctx.restore();
|
||||||
|
};
|
||||||
|
|||||||
@@ -7,7 +7,9 @@
|
|||||||
"restart": "Restart",
|
"restart": "Restart",
|
||||||
"reset": "Reset",
|
"reset": "Reset",
|
||||||
"select": "Select",
|
"select": "Select",
|
||||||
"goBack": "Go back"
|
"goBack": "Go back",
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No"
|
||||||
},
|
},
|
||||||
"achievementsScreen": {
|
"achievementsScreen": {
|
||||||
"title": "Achievements"
|
"title": "Achievements"
|
||||||
@@ -23,6 +25,11 @@
|
|||||||
"settings_color_change": "Change the system\ncolor",
|
"settings_color_change": "Change the system\ncolor",
|
||||||
"snake_play": "Play Snake",
|
"snake_play": "Play Snake",
|
||||||
"snake_score_40": "Score 40 points\nin Snake",
|
"snake_score_40": "Score 40 points\nin Snake",
|
||||||
|
"2048_play": "Play 2048",
|
||||||
|
"2048_score_512": "Reach the 512 tile\nin 2048",
|
||||||
|
"taptap_play": "Play TapTap",
|
||||||
|
"taptap_score_10": "Score 10 points\nin TapTap",
|
||||||
|
"taptap_score_20": "Score 20 points\nin TapTap",
|
||||||
"settings_color_try_all": "Try all colors",
|
"settings_color_try_all": "Try all colors",
|
||||||
"settings_language_try_all": "Try all languages",
|
"settings_language_try_all": "Try all languages",
|
||||||
"settings_visit_all": "Visit all settings\nsubmenus",
|
"settings_visit_all": "Visit all settings\nsubmenus",
|
||||||
@@ -130,14 +137,26 @@
|
|||||||
"description": "Play a quick game of Snake!",
|
"description": "Play a quick game of Snake!",
|
||||||
"score": "Score: {score}",
|
"score": "Score: {score}",
|
||||||
"best": "Best: {best}",
|
"best": "Best: {best}",
|
||||||
"startPrompt": "[A] Start",
|
"startPrompt": "Press icon_a to start.",
|
||||||
"quitConfirmation": "Quit the game?",
|
"quitConfirmation": "Quit the game?",
|
||||||
"restartConfirmation": "Restart the game?"
|
"restartConfirmation": "Restart the game?"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"touchScreen": {
|
"touchScreen": {
|
||||||
"title": "Touch Screen",
|
"title": "Touch Screen",
|
||||||
"description": "TODO"
|
"description": "Touch screen calibration and games.",
|
||||||
|
"tapTap": {
|
||||||
|
"title": "TapTap",
|
||||||
|
"description": "Tap the circles before they disappear!",
|
||||||
|
"startPrompt": "Press icon_a to start.",
|
||||||
|
"score": "Score: {score}",
|
||||||
|
"best": "Best: {best}",
|
||||||
|
"gameOver": "Game Over!",
|
||||||
|
"finalScore": "Final Score: {score}",
|
||||||
|
"newRecord": "New Record!",
|
||||||
|
"quitConfirmation": "Quit the game?",
|
||||||
|
"restartConfirmation": "Restart the game?"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"contact": {
|
"contact": {
|
||||||
|
|||||||
BIN
public/nds/images/common/button-body.webp
Normal file
BIN
public/nds/images/common/button-body.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 86 B |
BIN
public/nds/images/common/button-left.webp
Normal file
BIN
public/nds/images/common/button-left.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 88 B |
BIN
public/nds/images/common/button-right.webp
Normal file
BIN
public/nds/images/common/button-right.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 88 B |
Reference in New Issue
Block a user