feat: implement game board rendering and spaceship player control
- Create 10x20 game board grid with retro styling - Render spaceship emoji at player position (bottom center) - Implement cursor movement with hjkl (left/right only) - Add moveToStart (0) and moveToEnd ($) anchor motions - Re-render board when cursor position changes - Board displays dark grid with highlighted player row
This commit is contained in:
+8
-3
@@ -3,19 +3,24 @@ import { useGame } from "@vimazing/typing-chud";
|
||||
import "@vimazing/typing-chud/game.css";
|
||||
import type { GameManager } from "@vimazing/typing-chud";
|
||||
import { useKeyBindings } from "./useKeyBindings";
|
||||
import { useMounted } from "./useMounted";
|
||||
|
||||
function App() {
|
||||
const gameManager = useGame({}, useKeyBindings);
|
||||
const { containerRef, gameStatus, scoreManager, renderBoard } = gameManager;
|
||||
const { containerRef, gameStatus, scoreManager, renderBoard, cursor } = gameManager;
|
||||
const gameManagerRef = useRef<GameManager>(gameManager);
|
||||
const [mounted] = useMounted();
|
||||
|
||||
useEffect(() => {
|
||||
gameManagerRef.current = gameManager;
|
||||
}, [gameManager]);
|
||||
|
||||
// Re-render board when cursor position changes
|
||||
useEffect(() => {
|
||||
renderBoard();
|
||||
}, [renderBoard]);
|
||||
if (mounted) {
|
||||
renderBoard();
|
||||
}
|
||||
}, [cursor.position(), renderBoard, mounted]);
|
||||
|
||||
const formatTime = (ms: number) => {
|
||||
const seconds = Math.floor(ms / 1000);
|
||||
|
||||
+9
-5
@@ -15,10 +15,16 @@ export const getGamePhase = (status: GameStatus): GamePhase =>
|
||||
status === 'waiting' ? 'idle' : 'playing';
|
||||
|
||||
// ============================================================================
|
||||
// Game-Specific Types (to be defined)
|
||||
// Game-Specific Types
|
||||
// ============================================================================
|
||||
|
||||
// TODO: Define game-specific types here
|
||||
export type PlayerPosition = { x: number }; // 0-9 for 10 columns
|
||||
export type Word = {
|
||||
id: string;
|
||||
text: string;
|
||||
x: number; // 0-9 column position
|
||||
y: number; // vertical position (0 = top, increases downward)
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Input & Key Tracking
|
||||
@@ -45,10 +51,8 @@ export type BoardManager = {
|
||||
|
||||
export type CursorMode = 'normal';
|
||||
|
||||
export type Coord = { row: number; col: number };
|
||||
|
||||
export type CursorManager = {
|
||||
position: () => Coord;
|
||||
position: () => PlayerPosition;
|
||||
mode: () => CursorMode;
|
||||
|
||||
// VIM-style motions (required by Unified API)
|
||||
|
||||
@@ -1,13 +1,64 @@
|
||||
import { useRef } from "react";
|
||||
import type { PlayerPosition } from "../types";
|
||||
|
||||
const BOARD_WIDTH = 10;
|
||||
const BOARD_HEIGHT = 20;
|
||||
const CELL_SIZE = 40; // pixels
|
||||
|
||||
export function useBoard() {
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const renderBoard = () => {
|
||||
const renderBoard = (playerPosition: PlayerPosition) => {
|
||||
const container = containerRef.current;
|
||||
if (!container) return;
|
||||
|
||||
// TODO: Implement board rendering
|
||||
container.innerHTML = "";
|
||||
|
||||
// Create board grid
|
||||
const board = document.createElement("div");
|
||||
board.className = "typing-board";
|
||||
board.style.display = "grid";
|
||||
board.style.gridTemplateColumns = `repeat(${BOARD_WIDTH}, ${CELL_SIZE}px)`;
|
||||
board.style.gap = "1px";
|
||||
board.style.backgroundColor = "#000";
|
||||
board.style.padding = "8px";
|
||||
board.style.borderRadius = "4px";
|
||||
board.style.width = "fit-content";
|
||||
|
||||
// Create cells
|
||||
for (let row = 0; row < BOARD_HEIGHT; row++) {
|
||||
for (let col = 0; col < BOARD_WIDTH; col++) {
|
||||
const cell = document.createElement("div");
|
||||
cell.className = "typing-cell";
|
||||
cell.setAttribute("data-row", String(row));
|
||||
cell.setAttribute("data-col", String(col));
|
||||
cell.style.width = `${CELL_SIZE}px`;
|
||||
cell.style.height = `${CELL_SIZE}px`;
|
||||
cell.style.backgroundColor = "#1a1a2e";
|
||||
cell.style.border = "1px solid #0f3460";
|
||||
cell.style.display = "flex";
|
||||
cell.style.alignItems = "center";
|
||||
cell.style.justifyContent = "center";
|
||||
cell.style.fontSize = "12px";
|
||||
cell.style.fontFamily = "monospace";
|
||||
cell.style.color = "#00ff00";
|
||||
|
||||
// Highlight player row (bottom)
|
||||
if (row === BOARD_HEIGHT - 1) {
|
||||
cell.style.backgroundColor = "#0f3460";
|
||||
|
||||
// Render spaceship in player column
|
||||
if (col === playerPosition.x) {
|
||||
cell.innerHTML = "🚀";
|
||||
cell.style.fontSize = "24px";
|
||||
}
|
||||
}
|
||||
|
||||
board.appendChild(cell);
|
||||
}
|
||||
}
|
||||
|
||||
container.appendChild(board);
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
+19
-15
@@ -1,43 +1,47 @@
|
||||
import { useState } from "react";
|
||||
import type { Coord } from "../types";
|
||||
import type { PlayerPosition } from "../types";
|
||||
|
||||
const BOARD_WIDTH = 10; // 10 columns
|
||||
const START_POSITION = 4; // Start at center (column 4-5)
|
||||
|
||||
export function useCursor() {
|
||||
const [position] = useState<Coord>({ row: 0, col: 0 });
|
||||
const [position, setPosition] = useState<PlayerPosition>({ x: START_POSITION });
|
||||
|
||||
const moveLeft = (_count?: number) => {
|
||||
// TODO: Implement movement
|
||||
const moveLeft = (count: number = 1) => {
|
||||
setPosition(p => ({ x: Math.max(0, p.x - count) }));
|
||||
};
|
||||
|
||||
const moveRight = (_count?: number) => {
|
||||
// TODO: Implement movement
|
||||
const moveRight = (count: number = 1) => {
|
||||
setPosition(p => ({ x: Math.min(BOARD_WIDTH - 1, p.x + count) }));
|
||||
};
|
||||
|
||||
const moveUp = (_count?: number) => {
|
||||
// TODO: Implement movement
|
||||
// Typing game: hjkl only moves left/right, not up/down
|
||||
const moveUp = () => {
|
||||
// No vertical movement in typing game
|
||||
};
|
||||
|
||||
const moveDown = (_count?: number) => {
|
||||
// TODO: Implement movement
|
||||
const moveDown = () => {
|
||||
// No vertical movement in typing game
|
||||
};
|
||||
|
||||
const moveToStart = () => {
|
||||
// TODO: Implement anchor motion
|
||||
setPosition({ x: 0 });
|
||||
};
|
||||
|
||||
const moveToEnd = () => {
|
||||
// TODO: Implement anchor motion
|
||||
setPosition({ x: BOARD_WIDTH - 1 });
|
||||
};
|
||||
|
||||
const moveToTop = () => {
|
||||
// TODO: Implement anchor motion
|
||||
// No vertical movement in typing game
|
||||
};
|
||||
|
||||
const moveToBottom = () => {
|
||||
// TODO: Implement anchor motion
|
||||
// No vertical movement in typing game
|
||||
};
|
||||
|
||||
const repeatLastMotion = () => {
|
||||
// TODO: Implement repeat
|
||||
// TODO: Implement repeat for typing game
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -16,9 +16,14 @@ export function useGame(
|
||||
const gameStatusManager = useGameStatus();
|
||||
const scoreManager = useScore(gameStatusManager);
|
||||
|
||||
// Wrap renderBoard to include player position
|
||||
const renderBoardWrapped = () => {
|
||||
boardManager.renderBoard(cursorManager.position());
|
||||
};
|
||||
|
||||
const gameManager: GameManager = {
|
||||
containerRef: boardManager.containerRef,
|
||||
renderBoard: boardManager.renderBoard,
|
||||
renderBoard: renderBoardWrapped,
|
||||
gameStatus: gameStatusManager.gameStatus,
|
||||
setGameStatus: gameStatusManager.setGameStatus,
|
||||
startGame: gameStatusManager.startGame,
|
||||
|
||||
Reference in New Issue
Block a user