From 166ccd7783e8794619537defac403a813e22e228 Mon Sep 17 00:00:00 2001 From: Andre Padez Date: Tue, 4 Nov 2025 17:00:20 +0000 Subject: [PATCH] feat: implement sentences mode with centered text and slower speed - Set sentence fall speed to 25 pixels/second - Replace sentences with longer, realistic ones including commas and punctuation - Sentences always render centered on screen (80% max width constraint) - Ship stays centered during sentence mode (no horizontal movement) - Add punctuation support (. , ! ?) to keyboard input - Disable auto-tracking for sentence/paragraph modes --- example/src/useKeyBindings.ts | 4 ++-- src/useBoard/useBoard.ts | 21 +++++++++++++-------- src/useGame/useGame.ts | 18 +++++++++++++----- src/useGameStatus/useGameStatus.ts | 10 ++++++++-- src/wordLists.ts | 18 ++++++++++-------- 5 files changed, 46 insertions(+), 25 deletions(-) diff --git a/example/src/useKeyBindings.ts b/example/src/useKeyBindings.ts index 4654873..848b077 100755 --- a/example/src/useKeyBindings.ts +++ b/example/src/useKeyBindings.ts @@ -30,9 +30,9 @@ export const useKeyBindings = (gameManager: GameManager) => { return; } - // handle letter typing during game + // handle letter and punctuation typing during game if (gameManager.gameStatus === "started") { - if (ev.key.length === 1 && /[a-zA-Z]/.test(ev.key)) { + if (ev.key.length === 1 && /[a-zA-Z.,!?]/.test(ev.key)) { gameManager.handleTypedLetter(ev.key); } } diff --git a/src/useBoard/useBoard.ts b/src/useBoard/useBoard.ts index fe4753d..4635f4f 100644 --- a/src/useBoard/useBoard.ts +++ b/src/useBoard/useBoard.ts @@ -1,9 +1,10 @@ import { useRef, useEffect } from "react"; -import type { PlayerPosition, ShipState } from "../types"; +import type { PlayerPosition, ShipState, GameMode } from "../types"; const CANVAS_WIDTH = 800; const CANVAS_HEIGHT = 600; const SPACESHIP_Y = CANVAS_HEIGHT - 40; +const MAX_TEXT_WIDTH = CANVAS_WIDTH * 0.8; // 80% of canvas width for sentences/paragraphs export function useBoard() { const containerRef = useRef(null); @@ -27,7 +28,7 @@ export function useBoard() { canvasRef.current = canvas; }, []); - const renderBoard = (playerPosition: PlayerPosition, letters: any = [], shipState: ShipState = "normal", shipExplosionTime?: number) => { + const renderBoard = (playerPosition: PlayerPosition, letters: any = [], shipState: ShipState = "normal", shipExplosionTime?: number, gameMode: GameMode = 'letters') => { if (shipExplosionTime !== undefined) { shipExplosionTimeRef.current = shipExplosionTime; } @@ -173,6 +174,10 @@ export function useBoard() { ctx.textBaseline = "middle"; letters.forEach((letter: any) => { + // For sentences/paragraphs, always center horizontally + const isSentenceMode = gameMode === 'sentences' || gameMode === 'paragraphs'; + const letterX = isSentenceMode ? CANVAS_WIDTH / 2 : letter.x; + if (letter.state === 'wrong') { // Red glow flash ctx.shadowBlur = 20; @@ -181,8 +186,8 @@ export function useBoard() { // Draw full text or just current letter const displayText = letter.fullText || letter.letter; - const textWidth = ctx.measureText(displayText).width; - ctx.fillText(displayText, letter.x - textWidth / 2, letter.y); + const textWidth = Math.min(ctx.measureText(displayText).width, MAX_TEXT_WIDTH); + ctx.fillText(displayText, letterX - textWidth / 2, letter.y); ctx.shadowBlur = 0; } else if (letter.state === 'exploding') { // Explosion effect @@ -192,7 +197,7 @@ export function useBoard() { const opacity = 1 - progress; ctx.save(); - ctx.translate(letter.x, letter.y); + ctx.translate(letterX, letter.y); ctx.scale(scale, scale); ctx.globalAlpha = opacity; @@ -212,7 +217,7 @@ export function useBoard() { // Full text fading ctx.fillStyle = "#ffffff"; const displayText = letter.fullText || letter.letter; - const textWidth = ctx.measureText(displayText).width; + const textWidth = Math.min(ctx.measureText(displayText).width, MAX_TEXT_WIDTH); ctx.fillText(displayText, -textWidth / 2, 0); ctx.restore(); @@ -222,8 +227,8 @@ export function useBoard() { const charIndex = letter.charIndex ?? 0; // Calculate total width to center all text - const totalWidth = ctx.measureText(fullText).width; - const startX = letter.x - totalWidth / 2; + const totalWidth = Math.min(ctx.measureText(fullText).width, MAX_TEXT_WIDTH); + const startX = letterX - totalWidth / 2; let currentX = startX; // Draw completed characters in green diff --git a/src/useGame/useGame.ts b/src/useGame/useGame.ts index 30d2584..70c003e 100755 --- a/src/useGame/useGame.ts +++ b/src/useGame/useGame.ts @@ -18,7 +18,7 @@ export function useGame( // Wrap renderBoard to include player position const renderBoardWrapped = (letters: any = []) => { - boardManager.renderBoard(cursorManager.position(), letters, gameStatusManager.shipState, gameStatusManager.shipExplosionTime); + boardManager.renderBoard(cursorManager.position(), letters, gameStatusManager.shipState, gameStatusManager.shipExplosionTime, gameMode); }; // Game loop tick handler @@ -26,12 +26,20 @@ export function useGame( const playerPos = cursorManager.position(); gameStatusManager.updatePlayerX(playerPos.x); - if (letters.length > 0) { - const letter = letters[0]; - cursorManager.setTargetX(letter.x); + // For words/letters, auto-track to falling letters + // For sentences/paragraphs, keep ship centered + if (gameMode !== 'sentences' && gameMode !== 'paragraphs') { + if (letters.length > 0) { + const letter = letters[0]; + cursorManager.setTargetX(letter.x); + } + + cursorManager.moveTowardTarget(deltaTime); + } else { + // Keep ship centered for sentence/paragraph mode + cursorManager.setTargetX(400); // Canvas center } - cursorManager.moveTowardTarget(deltaTime); renderBoardWrapped(letters); }; diff --git a/src/useGameStatus/useGameStatus.ts b/src/useGameStatus/useGameStatus.ts index 45e7f73..436fcdd 100755 --- a/src/useGameStatus/useGameStatus.ts +++ b/src/useGameStatus/useGameStatus.ts @@ -2,7 +2,12 @@ import { useState, useRef } from "react"; import type { GameStatus, FallingLetter, ShipState, GameMode } from "../types"; import { getRandomWord } from "../wordLists"; -const LETTER_SPEED = 100; // pixels per second +const LETTER_SPEEDS: Record = { + letters: 100, + words: 50, + sentences: 25, + paragraphs: 50, +}; const SPAWN_INTERVAL = 1000; // ms between letter spawns const MAX_ACTIVE_LETTERS = 1; // Only one letter on screen at a time const MARGIN_LEFT = 50; // Safe margin from left edge @@ -53,8 +58,9 @@ export function useGameStatus(onGameLoopTick?: (deltaTime: number, letters: Fall } // Update letter positions + const speed = LETTER_SPEEDS[gameModeRef.current]; lettersRef.current.forEach(letter => { - letter.y += (LETTER_SPEED * deltaTime) / 1000; + letter.y += (speed * deltaTime) / 1000; }); // Check for collisions with ship diff --git a/src/wordLists.ts b/src/wordLists.ts index cdea56d..9184e1e 100644 --- a/src/wordLists.ts +++ b/src/wordLists.ts @@ -16,14 +16,16 @@ export const wordLists = { ] as const, sentences: [ - 'the quick brown fox', - 'jump over lazy dog', - 'coding is fun today', - 'practice makes perfect', - 'type faster every day', - 'aim for high scores', - 'stay focused always', - 'keep calm and type', + 'The quick brown fox jumps over the lazy dog.', + 'Typing fast, with accuracy, is the best skill.', + 'How are you doing today?', + 'Practice makes perfect, so keep trying!', + 'Type faster every day, and improve your skills.', + 'Stay focused, avoid distractions, and type well.', + 'Keep calm, breathe deeply, and type with purpose!', + 'Coding is fun, challenging, and very rewarding.', + 'What are you typing about today?', + 'Speed, accuracy, and consistency matter the most!', ] as const, paragraphs: [