mirror of
https://github.com/tiennm99/try-claudekit.git
synced 2026-04-17 15:21:21 +00:00
fix: NEXT panel clipping, add labels to previews, improve fruit colors
Derive canvas width from container + panel dimensions so the NEXT panel fits with consistent padding. Extract drawFruitCircle helper so all fruit rendering (in-game, cursor preview, NEXT panel) shows the name label consistently. Update color palette for maximum visual distinction between similar fruits (cherry/strawberry/apple, dekopon/persimmon). Center score text over the container instead of the canvas.
This commit is contained in:
@@ -1,13 +1,21 @@
|
|||||||
// Game dimensions
|
// Game dimensions
|
||||||
export const CANVAS_WIDTH = 500;
|
export const WALL_THICKNESS = 10;
|
||||||
export const CANVAS_HEIGHT = 700;
|
|
||||||
|
|
||||||
export const CONTAINER_WIDTH = 400;
|
export const CONTAINER_WIDTH = 400;
|
||||||
export const CONTAINER_HEIGHT = 600;
|
export const CONTAINER_HEIGHT = 600;
|
||||||
export const CONTAINER_X = (CANVAS_WIDTH - CONTAINER_WIDTH) / 2;
|
|
||||||
export const CONTAINER_Y = CANVAS_HEIGHT - CONTAINER_HEIGHT - 20;
|
|
||||||
|
|
||||||
export const WALL_THICKNESS = 10;
|
// NEXT fruit panel sits to the right of the container
|
||||||
|
export const PANEL_WIDTH = 60;
|
||||||
|
export const PANEL_GAP = 12;
|
||||||
|
|
||||||
|
// Canvas sized to fit: padding + container + walls + gap + panel + padding
|
||||||
|
const PADDING = 20;
|
||||||
|
export const CANVAS_WIDTH = PADDING + WALL_THICKNESS + CONTAINER_WIDTH + WALL_THICKNESS + PANEL_GAP + PANEL_WIDTH + PADDING;
|
||||||
|
export const CANVAS_HEIGHT = 700;
|
||||||
|
|
||||||
|
// Container is positioned so the panel fits to its right
|
||||||
|
export const CONTAINER_X = PADDING + WALL_THICKNESS;
|
||||||
|
export const CONTAINER_Y = CANVAS_HEIGHT - CONTAINER_HEIGHT - 20;
|
||||||
|
|
||||||
// Danger line: ~50px below the top of the container walls
|
// Danger line: ~50px below the top of the container walls
|
||||||
export const DANGER_LINE_Y = CONTAINER_Y + 50;
|
export const DANGER_LINE_Y = CONTAINER_Y + 50;
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
// Community-standard fruit stats (TomboFry/moonfloof clones, Suika Game Wiki).
|
// Community-standard fruit stats (TomboFry/moonfloof clones, Suika Game Wiki).
|
||||||
// Radii scaled from the standard (24..192) by 0.52 to fit our 400px container.
|
// Radii scaled from the standard (24..192) by 0.52 to fit our 400px container.
|
||||||
// Points match the Nintendo Switch scoring: 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 66.
|
// Points match the Nintendo Switch scoring: 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 66.
|
||||||
|
// Colors chosen for maximum visual distinction between similar fruits:
|
||||||
|
// Cherry/Strawberry/Apple use crimson/pink/vivid-red; Dekopon/Persimmon use orange/red-orange.
|
||||||
export const FRUITS = [
|
export const FRUITS = [
|
||||||
{ tier: 0, name: 'Cherry', radius: 12, color: '#E8373B', points: 1 },
|
{ tier: 0, name: 'Cherry', radius: 12, color: '#CC0022', points: 1 },
|
||||||
{ tier: 1, name: 'Strawberry', radius: 17, color: '#FF6B6B', points: 3 },
|
{ tier: 1, name: 'Strawberry', radius: 17, color: '#FF3B5C', points: 3 },
|
||||||
{ tier: 2, name: 'Grape', radius: 21, color: '#7B52AB', points: 6 },
|
{ tier: 2, name: 'Grape', radius: 21, color: '#7B2D8B', points: 6 },
|
||||||
{ tier: 3, name: 'Dekopon', radius: 29, color: '#FF8C00', points: 10 },
|
{ tier: 3, name: 'Dekopon', radius: 29, color: '#FF7A00', points: 10 },
|
||||||
{ tier: 4, name: 'Persimmon', radius: 33, color: '#E2611C', points: 15 },
|
{ tier: 4, name: 'Persimmon', radius: 33, color: '#E84A00', points: 15 },
|
||||||
{ tier: 5, name: 'Apple', radius: 37, color: '#C0392B', points: 21 },
|
{ tier: 5, name: 'Apple', radius: 37, color: '#E8003D', points: 21 },
|
||||||
{ tier: 6, name: 'Pear', radius: 44, color: '#C8D44E', points: 28 },
|
{ tier: 6, name: 'Pear', radius: 44, color: '#C8D400', points: 28 },
|
||||||
{ tier: 7, name: 'Peach', radius: 50, color: '#FFBF8C', points: 36 },
|
{ tier: 7, name: 'Peach', radius: 50, color: '#FFBF80', points: 36 },
|
||||||
{ tier: 8, name: 'Pineapple', radius: 67, color: '#F5C518', points: 45 },
|
{ tier: 8, name: 'Pineapple', radius: 67, color: '#FFD700', points: 45 },
|
||||||
{ tier: 9, name: 'Melon', radius: 83, color: '#90C040', points: 55 },
|
{ tier: 9, name: 'Melon', radius: 83, color: '#5CB85C', points: 55 },
|
||||||
{ tier: 10, name: 'Watermelon', radius: 100, color: '#3A7D44', points: 66 },
|
{ tier: 10, name: 'Watermelon', radius: 100, color: '#1A7A2E', points: 66 },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Only the 5 smallest fruits can be randomly selected for dropping
|
// Only the 5 smallest fruits can be randomly selected for dropping
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import {
|
|||||||
CONTAINER_HEIGHT,
|
CONTAINER_HEIGHT,
|
||||||
WALL_THICKNESS,
|
WALL_THICKNESS,
|
||||||
DANGER_LINE_Y,
|
DANGER_LINE_Y,
|
||||||
|
PANEL_WIDTH,
|
||||||
|
PANEL_GAP,
|
||||||
} from './constants.js';
|
} from './constants.js';
|
||||||
|
|
||||||
export function createCanvas() {
|
export function createCanvas() {
|
||||||
@@ -81,30 +83,30 @@ function drawDangerLine(ctx) {
|
|||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function drawFruitCircle(ctx, fruit, x, y, radius) {
|
||||||
|
const r = radius ?? fruit.radius;
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(x, y, r, 0, Math.PI * 2);
|
||||||
|
ctx.fillStyle = fruit.color;
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
ctx.strokeStyle = darkenColor(fruit.color, 0.2);
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
ctx.fillStyle = '#FFFFFF';
|
||||||
|
ctx.font = `bold ${Math.max(10, r * 0.6)}px sans-serif`;
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.textBaseline = 'middle';
|
||||||
|
ctx.fillText(fruit.name.slice(0, 2), x, y);
|
||||||
|
}
|
||||||
|
|
||||||
function drawFruits(ctx, bodies) {
|
function drawFruits(ctx, bodies) {
|
||||||
for (const body of bodies) {
|
for (const body of bodies) {
|
||||||
if (body.fruitTier === undefined || body.removing) continue;
|
if (body.fruitTier === undefined || body.removing) continue;
|
||||||
|
|
||||||
const fruit = FRUITS[body.fruitTier];
|
const fruit = FRUITS[body.fruitTier];
|
||||||
const { x, y } = body.position;
|
drawFruitCircle(ctx, fruit, body.position.x, body.position.y);
|
||||||
|
|
||||||
// Circle
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.arc(x, y, fruit.radius, 0, Math.PI * 2);
|
|
||||||
ctx.fillStyle = fruit.color;
|
|
||||||
ctx.fill();
|
|
||||||
|
|
||||||
// Darker border
|
|
||||||
ctx.strokeStyle = darkenColor(fruit.color, 0.2);
|
|
||||||
ctx.lineWidth = 2;
|
|
||||||
ctx.stroke();
|
|
||||||
|
|
||||||
// Label
|
|
||||||
ctx.fillStyle = '#FFFFFF';
|
|
||||||
ctx.font = `bold ${Math.max(10, fruit.radius * 0.6)}px sans-serif`;
|
|
||||||
ctx.textAlign = 'center';
|
|
||||||
ctx.textBaseline = 'middle';
|
|
||||||
ctx.fillText(fruit.name.slice(0, 2), x, y);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,13 +120,7 @@ function drawNextFruitPreview(ctx, state) {
|
|||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.globalAlpha = state.isDropCooldown ? 0.3 : 0.7;
|
ctx.globalAlpha = state.isDropCooldown ? 0.3 : 0.7;
|
||||||
|
|
||||||
ctx.beginPath();
|
drawFruitCircle(ctx, fruit, x, y);
|
||||||
ctx.arc(x, y, fruit.radius, 0, Math.PI * 2);
|
|
||||||
ctx.fillStyle = fruit.color;
|
|
||||||
ctx.fill();
|
|
||||||
ctx.strokeStyle = darkenColor(fruit.color, 0.2);
|
|
||||||
ctx.lineWidth = 2;
|
|
||||||
ctx.stroke();
|
|
||||||
|
|
||||||
// Drop guide line
|
// Drop guide line
|
||||||
if (!state.isDropCooldown) {
|
if (!state.isDropCooldown) {
|
||||||
@@ -143,9 +139,9 @@ function drawNextFruitPreview(ctx, state) {
|
|||||||
function drawNextFruitPanel(ctx, state) {
|
function drawNextFruitPanel(ctx, state) {
|
||||||
if (state.isGameOver) return;
|
if (state.isGameOver) return;
|
||||||
|
|
||||||
const panelX = CONTAINER_X + CONTAINER_WIDTH + WALL_THICKNESS + 10;
|
const panelX = CONTAINER_X + CONTAINER_WIDTH + WALL_THICKNESS + PANEL_GAP;
|
||||||
const panelY = CONTAINER_Y;
|
const panelY = CONTAINER_Y;
|
||||||
const panelSize = 60;
|
const panelSize = PANEL_WIDTH;
|
||||||
|
|
||||||
ctx.fillStyle = '#FFFDF5';
|
ctx.fillStyle = '#FFFDF5';
|
||||||
ctx.strokeStyle = '#8B7355';
|
ctx.strokeStyle = '#8B7355';
|
||||||
@@ -166,13 +162,7 @@ function drawNextFruitPanel(ctx, state) {
|
|||||||
const cx = panelX + panelSize / 2;
|
const cx = panelX + panelSize / 2;
|
||||||
const cy = panelY + 46;
|
const cy = panelY + 46;
|
||||||
|
|
||||||
ctx.beginPath();
|
drawFruitCircle(ctx, fruit, cx, cy, previewRadius);
|
||||||
ctx.arc(cx, cy, previewRadius, 0, Math.PI * 2);
|
|
||||||
ctx.fillStyle = fruit.color;
|
|
||||||
ctx.fill();
|
|
||||||
ctx.strokeStyle = darkenColor(fruit.color, 0.2);
|
|
||||||
ctx.lineWidth = 2;
|
|
||||||
ctx.stroke();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawScore(ctx, score) {
|
function drawScore(ctx, score) {
|
||||||
@@ -180,7 +170,7 @@ function drawScore(ctx, score) {
|
|||||||
ctx.font = 'bold 28px sans-serif';
|
ctx.font = 'bold 28px sans-serif';
|
||||||
ctx.textAlign = 'center';
|
ctx.textAlign = 'center';
|
||||||
ctx.textBaseline = 'top';
|
ctx.textBaseline = 'top';
|
||||||
ctx.fillText(`Score: ${score}`, CANVAS_WIDTH / 2, 10);
|
ctx.fillText(`Score: ${score}`, CONTAINER_X + CONTAINER_WIDTH / 2, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawGameOverOverlay(ctx, score) {
|
function drawGameOverOverlay(ctx, score) {
|
||||||
|
|||||||
Reference in New Issue
Block a user