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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -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
@@ -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: [
|
||||
|
||||
Reference in New Issue
Block a user