From 4a378af820792d9e5002e7f3ccdd822570531967 Mon Sep 17 00:00:00 2001 From: DevAlissu Date: Sun, 19 Apr 2026 00:12:02 -0400 Subject: [PATCH] ajusta overlay de game over e comportamento do combo - remove pills de dificuldade e modo do game over (ja disponiveis no topo) - combo com multiplicador aplica apenas no modo competitivo - move indicador de combo do canvas para o painel lateral - adiciona barra de progresso e fade out visual conforme tempo expira - remove ComboBadge nao mais utilizado --- guidelines/Guidelines.md | 61 +++++++++++++++++++ src/features/snake-game/SnakeGame.tsx | 12 +--- .../snake-game/components/ComboBadge.tsx | 15 ----- .../snake-game/components/GameOverlay.tsx | 56 +++-------------- .../snake-game/components/ScoreDisplay.tsx | 25 +++++++- src/features/snake-game/hooks/useSnakeGame.ts | 2 + src/features/snake-game/store/useGameStore.ts | 5 +- src/styles/theme.css | 25 ++++++++ 8 files changed, 124 insertions(+), 77 deletions(-) create mode 100644 guidelines/Guidelines.md delete mode 100644 src/features/snake-game/components/ComboBadge.tsx diff --git a/guidelines/Guidelines.md b/guidelines/Guidelines.md new file mode 100644 index 0000000..110f117 --- /dev/null +++ b/guidelines/Guidelines.md @@ -0,0 +1,61 @@ +**Add your own guidelines here** + diff --git a/src/features/snake-game/SnakeGame.tsx b/src/features/snake-game/SnakeGame.tsx index 9f93734..efdd855 100644 --- a/src/features/snake-game/SnakeGame.tsx +++ b/src/features/snake-game/SnakeGame.tsx @@ -9,7 +9,6 @@ import { ScoreDisplay } from './components/ScoreDisplay'; import { DecorativeBolt } from './components/DecorativeBolt'; import { GameTabs } from './components/GameTabs'; import { HighScoreBadge } from './components/HighScoreBadge'; -import { ComboBadge } from './components/ComboBadge'; import { Countdown } from './components/Countdown'; import { PauseOverlay } from './components/PauseOverlay'; import { useSwipe } from './hooks/useSwipe'; @@ -29,6 +28,7 @@ export function SnakeGame({ className = '' }: SnakeGameProps) { mode, leaderboard, combo, + lastEatTime, particles, shakeKey, actions, @@ -89,12 +89,9 @@ export function SnakeGame({ className = '' }: SnakeGameProps) { status={status} score={score} mode={mode} - difficulty={difficulty} leaderboard={leaderboard} onStart={handleStartClick} onSaveScore={actions.saveToLeaderboard} - onSetDifficulty={actions.setDifficulty} - onSetMode={actions.setMode} /> {status === 'countdown' && } @@ -121,18 +118,13 @@ export function SnakeGame({ className = '' }: SnakeGameProps) { )} - {status === 'playing' && combo > 1 && ( -
- -
- )}
- +
- ); -} diff --git a/src/features/snake-game/components/GameOverlay.tsx b/src/features/snake-game/components/GameOverlay.tsx index 61ddafd..c37ee97 100644 --- a/src/features/snake-game/components/GameOverlay.tsx +++ b/src/features/snake-game/components/GameOverlay.tsx @@ -1,33 +1,25 @@ import { memo, useState } from 'react'; -import type { GameStatus, GameMode, LeaderboardEntry, Difficulty } from '../types'; -import { DIFFICULTY_LABELS, MODE_LABELS } from '../constants'; +import type { GameStatus, GameMode, LeaderboardEntry } from '../types'; +import { DIFFICULTY_LABELS } from '../constants'; const MEDALS = ['#FFD700', '#C0C0C0', '#CD7F32'] as const; -const DIFFICULTIES: Difficulty[] = ['easy', 'normal', 'hard']; -const MODES: GameMode[] = ['casual', 'competitive']; interface GameOverlayProps { status: GameStatus; score: number; mode: GameMode; - difficulty: Difficulty; leaderboard: LeaderboardEntry[]; onStart: () => void; onSaveScore: (name: string) => void; - onSetDifficulty: (d: Difficulty) => void; - onSetMode: (m: GameMode) => void; } export const GameOverlay = memo(function GameOverlay({ status, score, mode, - difficulty, leaderboard, onStart, onSaveScore, - onSetDifficulty, - onSetMode, }: GameOverlayProps) { const [playerName, setPlayerName] = useState(''); const [saved, setSaved] = useState(false); @@ -144,44 +136,12 @@ export const GameOverlay = memo(function GameOverlay({ )} -
-
- {DIFFICULTIES.map((d) => ( - - ))} - | - {MODES.map((m) => ( - - ))} -
- - -
+ ); diff --git a/src/features/snake-game/components/ScoreDisplay.tsx b/src/features/snake-game/components/ScoreDisplay.tsx index ffa491d..db93433 100644 --- a/src/features/snake-game/components/ScoreDisplay.tsx +++ b/src/features/snake-game/components/ScoreDisplay.tsx @@ -1,14 +1,23 @@ import { memo } from 'react'; -import { FOOD_TO_WIN_CASUAL } from '../constants'; +import { FOOD_TO_WIN_CASUAL, COMBO_WINDOW_MS } from '../constants'; import type { GameMode } from '../types'; interface ScoreDisplayProps { score: number; mode: GameMode; + combo: number; + lastEatTime: number; } -export const ScoreDisplay = memo(function ScoreDisplay({ score, mode }: ScoreDisplayProps) { +export const ScoreDisplay = memo(function ScoreDisplay({ + score, + mode, + combo, + lastEatTime, +}: ScoreDisplayProps) { if (mode === 'competitive') { + const showCombo = combo > 1; + const multiplier = Math.min(combo, 5); return (

@@ -17,6 +26,18 @@ export const ScoreDisplay = memo(function ScoreDisplay({ score, mode }: ScoreDis

{score}

+
+ {showCombo && ( +
+ combo x{multiplier} + +
+ )} +
); } diff --git a/src/features/snake-game/hooks/useSnakeGame.ts b/src/features/snake-game/hooks/useSnakeGame.ts index 29679cf..45b21d1 100644 --- a/src/features/snake-game/hooks/useSnakeGame.ts +++ b/src/features/snake-game/hooks/useSnakeGame.ts @@ -13,6 +13,7 @@ export function useSnakeGame() { const mode = useGameStore((s) => s.mode); const leaderboard = useGameStore((s) => s.leaderboard); const combo = useGameStore((s) => s.combo); + const lastEatTime = useGameStore((s) => s.lastEatTime); const particles = useGameStore((s) => s.particles); const shakeKey = useGameStore((s) => s.shakeKey); const startGame = useGameStore((s) => s.startGame); @@ -90,6 +91,7 @@ export function useSnakeGame() { mode, leaderboard, combo, + lastEatTime, particles, shakeKey, remainingFood, diff --git a/src/features/snake-game/store/useGameStore.ts b/src/features/snake-game/store/useGameStore.ts index 7cc8f7f..557291e 100644 --- a/src/features/snake-game/store/useGameStore.ts +++ b/src/features/snake-game/store/useGameStore.ts @@ -276,9 +276,10 @@ export const useGameStore = create((set, get) => ({ if (food && newHead.x === food.x && newHead.y === food.y) { const { score, highScore } = get(); const foodCfg = FOOD_TYPES[food.type]; + const isCompetitive = mode === 'competitive'; const withinCombo = tickTime - lastEatTime < COMBO_WINDOW_MS && lastEatTime > 0; - const newCombo = withinCombo ? combo + 1 : 1; - const comboMultiplier = Math.min(newCombo, 5); + const newCombo = isCompetitive && withinCombo ? combo + 1 : 1; + const comboMultiplier = isCompetitive ? Math.min(newCombo, 5) : 1; const newScore = score + foodCfg.points * comboMultiplier; const newFood = generateRandomFood(newSnake); const newParticles = [...particles, ...spawnParticles(food.x, food.y, foodCfg.color, 10)]; diff --git a/src/styles/theme.css b/src/styles/theme.css index c7b5f6e..66b7846 100644 --- a/src/styles/theme.css +++ b/src/styles/theme.css @@ -105,6 +105,31 @@ animation: combo-pop 0.25s ease-out; } +@keyframes snake-combo-fade { + 0% { opacity: 1; } + 60% { opacity: 1; } + 100% { opacity: 0; } +} + +.snake-combo { + animation: snake-combo-fade linear forwards; +} + +@keyframes snake-combo-bar { + 0% { transform: scaleX(1); } + 100% { transform: scaleX(0); } +} + +.snake-combo-bar { + display: inline-block; + width: 40px; + height: 3px; + background: #b14eff; + border-radius: 2px; + transform-origin: left center; + animation: snake-combo-bar linear forwards; +} + @keyframes countdown-pop { 0% { transform: scale(0.4); opacity: 0; } 40% { transform: scale(1.1); opacity: 1; }