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
This commit is contained in:
2025-11-04 17:00:20 +00:00
parent 68078e1ec7
commit 166ccd7783
5 changed files with 46 additions and 25 deletions
+2 -2
View File
@@ -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);
}
}
+13 -8
View File
@@ -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<HTMLDivElement | null>(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
+13 -5
View File
@@ -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);
};
+8 -2
View File
@@ -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<GameMode, number> = {
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
+10 -8
View File
@@ -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: [