game-dev
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGame Dev Skill
游戏开发技能
This skill enables rapid creation of terminal-based games for .
@hypersocial/cli-games本技能可用于为快速创建基于终端的游戏。
@hypersocial/cli-gamesQuick Start
快速开始
- Get game concept and name from user
- Create game file at
src/games/{name}/index.ts - Register in
src/games/index.ts - Follow patterns below for consistent look and feel
- 从用户处获取游戏概念和名称
- 在路径下创建游戏文件
src/games/{name}/index.ts - 在中注册游戏
src/games/index.ts - 遵循下方的模式保证统一的外观和使用体验
Architecture Overview
架构总览
All games share these characteristics:
- GameController interface: method and
stop()getterisRunning - State machine: ,
gameStarted,gameOver,pausedflagswon - Shared pause menu: Import from
../shared/menu - Shared effects: Import from
../shared/effects - Theme awareness: Use from
getCurrentThemeColor()../utils - Terminal rendering: ANSI escape codes, alternate buffer mode
所有游戏都具备以下特性:
- GameController 接口:包含方法和
stop()getterisRunning - 状态机:包含、
gameStarted、gameOver、paused标志位won - 共享暂停菜单:从导入
../shared/menu - 共享效果:从导入
../shared/effects - 主题感知:使用提供的
../utils方法getCurrentThemeColor() - 终端渲染:使用ANSI转义码、备用缓冲区模式
File Structure
文件结构
src/games/
├── {gamename}/
│ ├── index.ts # Main game file (required)
│ └── effects.ts # Complex games: separate effects
├── shared/
│ ├── menu.ts # Shared pause menu system
│ ├── effects.ts # Shared particle, popup, shake, flash utilities
│ └── index.ts # Re-exports
├── gameTransitions.ts # Quit/switch game helpers
├── utils.ts # Theme colors, utilities
└── index.ts # Game registrysrc/games/
├── {gamename}/
│ ├── index.ts # 主游戏文件(必填)
│ └── effects.ts # 复杂游戏:单独存放效果逻辑
├── shared/
│ ├── menu.ts # 共享暂停菜单系统
│ ├── effects.ts # 共享粒子、弹窗、震动、闪屏工具
│ └── index.ts # 重新导出
├── gameTransitions.ts # 退出/切换游戏辅助工具
├── utils.ts # 主题颜色、工具函数
└── index.ts # 游戏注册表Required Imports
必须导入的模块
typescript
import type { Terminal } from '@xterm/xterm';
import { getCurrentThemeColor } from '../utils';
import { dispatchGameQuit, dispatchGameSwitch, dispatchGamesMenu } from '../gameTransitions';
import { PAUSE_MENU_ITEMS, renderSimpleMenu, navigateMenu } from '../shared/menu';For effects, import from the shared module:
typescript
import {
type Particle,
type ScorePopup,
spawnParticles,
updateParticles,
addScorePopup,
updatePopups,
triggerShake,
applyShake,
createShakeState,
MAX_PARTICLES,
PARTICLE_CHARS,
} from '../shared/effects';typescript
import type { Terminal } from '@xterm/xterm';
import { getCurrentThemeColor } from '../utils';
import { dispatchGameQuit, dispatchGameSwitch, dispatchGamesMenu } from '../gameTransitions';
import { PAUSE_MENU_ITEMS, renderSimpleMenu, navigateMenu } from '../shared/menu';如果需要使用效果,从共享模块导入:
typescript
import {
type Particle,
type ScorePopup,
spawnParticles,
updateParticles,
addScorePopup,
updatePopups,
triggerShake,
applyShake,
createShakeState,
MAX_PARTICLES,
PARTICLE_CHARS,
} from '../shared/effects';GameController Interface
GameController 接口
Every game exports a controller with this shape:
typescript
export interface {Name}Controller {
stop: () => void;
isRunning: boolean;
}每个游戏都要导出符合以下结构的控制器:
typescript
export interface {Name}Controller {
stop: () => void;
isRunning: boolean;
}State Machine Pattern
状态机模式
typescript
let running = true;
let gameStarted = false;
let gameOver = false;
let paused = false;
let pauseMenuSelection = 0;
let won = false;State transitions:
- Start: -> any key ->
!gameStarted && !pausedgameStarted = true - Pause: ESC toggles , reset
pausedpauseMenuSelection = 0 - Game Over: collision/loss -> , optionally
gameOver = truewon = true - Quit: Q key or menu selection -> +
controller.stop()dispatchGameQuit()
typescript
let running = true;
let gameStarted = false;
let gameOver = false;
let paused = false;
let pauseMenuSelection = 0;
let won = false;状态流转:
- 启动:-> 按下任意键 ->
!gameStarted && !pausedgameStarted = true - 暂停:ESC键切换状态,重置
pausedpauseMenuSelection = 0 - 游戏结束:碰撞/失败 -> ,胜利时可额外设置
gameOver = truewon = true - 退出:按下Q键或选择对应菜单选项 -> +
controller.stop()dispatchGameQuit()
Pause Menu Integration
暂停菜单集成
CRITICAL: Always use the shared menu system:
typescript
import { PAUSE_MENU_ITEMS, renderSimpleMenu, navigateMenu } from '../shared/menu';
// In render():
if (paused) {
output += renderSimpleMenu(PAUSE_MENU_ITEMS, pauseMenuSelection, {
centerX: Math.floor(cols / 2),
startY: pauseY + 2,
showShortcuts: false,
});
}
// In key handler:
if (paused) {
const { newSelection, confirmed } = navigateMenu(
pauseMenuSelection,
PAUSE_MENU_ITEMS.length,
key,
domEvent
);
if (newSelection !== pauseMenuSelection) {
pauseMenuSelection = newSelection;
return;
}
if (confirmed) {
switch (pauseMenuSelection) {
case 0: paused = false; break; // Resume
case 1: initGame(); gameStarted = true; paused = false; break; // Restart
case 2: controller.stop(); dispatchGameQuit(terminal); break; // Quit
case 3: dispatchGamesMenu(terminal); break; // List Games
case 4: dispatchGameSwitch(terminal); break; // Next Game
}
}
}重要提示:必须使用共享菜单系统:
typescript
import { PAUSE_MENU_ITEMS, renderSimpleMenu, navigateMenu } from '../shared/menu';
// 在render()方法中:
if (paused) {
output += renderSimpleMenu(PAUSE_MENU_ITEMS, pauseMenuSelection, {
centerX: Math.floor(cols / 2),
startY: pauseY + 2,
showShortcuts: false,
});
}
// 在按键处理逻辑中:
if (paused) {
const { newSelection, confirmed } = navigateMenu(
pauseMenuSelection,
PAUSE_MENU_ITEMS.length,
key,
domEvent
);
if (newSelection !== pauseMenuSelection) {
pauseMenuSelection = newSelection;
return;
}
if (confirmed) {
switch (pauseMenuSelection) {
case 0: paused = false; break; // 继续游戏
case 1: initGame(); gameStarted = true; paused = false; break; // 重新开始
case 2: controller.stop(); dispatchGameQuit(terminal); break; // 退出
case 3: dispatchGamesMenu(terminal); break; // 游戏列表
case 4: dispatchGameSwitch(terminal); break; // 下一个游戏
}
}
}Terminal Size Handling
终端尺寸处理
Always check minimum size and show helpful resize message:
typescript
const MIN_COLS = 40;
const MIN_ROWS = 20;
// In render():
if (cols < MIN_COLS || rows < MIN_ROWS) {
const msg1 = 'Terminal too small!';
const needWidth = cols < MIN_COLS;
const needHeight = rows < MIN_ROWS;
let hint = needWidth && needHeight ? 'Make pane larger'
: needWidth ? 'Make pane wider ->' : 'Make pane taller';
const msg2 = `Need: ${MIN_COLS}x${MIN_ROWS} Have: ${cols}x${rows}`;
// Center and render messages...
return;
}必须检查最小尺寸并展示友好的调整提示:
typescript
const MIN_COLS = 40;
const MIN_ROWS = 20;
// 在render()方法中:
if (cols < MIN_COLS || rows < MIN_ROWS) {
const msg1 = 'Terminal too small!';
const needWidth = cols < MIN_COLS;
const needHeight = rows < MIN_ROWS;
let hint = needWidth && needHeight ? 'Make pane larger'
: needWidth ? 'Make pane wider ->' : 'Make pane taller';
const msg2 = `Need: ${MIN_COLS}x${MIN_ROWS} Have: ${cols}x${rows}`;
// 居中并渲染提示信息...
return;
}Glitch Title Effect
故障风标题效果
Every game has a glitchy ASCII title:
typescript
const title = [
'{TITLE_LINE_1}',
'{TITLE_LINE_2}',
];
let glitchFrame = 0;
// In render():
glitchFrame = (glitchFrame + 1) % 60;
const glitchOffset = glitchFrame >= 55 ? Math.floor(Math.random() * 3) - 1 : 0;
const titleX = Math.floor((cols - title[0].length) / 2) + glitchOffset;
if (glitchFrame >= 55 && glitchFrame < 58) {
output += `\x1b[1;${titleX}H\x1b[91m${title[0]}\x1b[0m`;
output += `\x1b[2;${titleX + 1}H\x1b[96m${title[1]}\x1b[0m`;
} else {
output += `\x1b[1;${titleX}H${themeColor}\x1b[1m${title[0]}\x1b[0m`;
output += `\x1b[2;${titleX}H${themeColor}\x1b[1m${title[1]}\x1b[0m`;
}每个游戏都需要配备故障风ASCII标题:
typescript
const title = [
'{TITLE_LINE_1}',
'{TITLE_LINE_2}',
];
let glitchFrame = 0;
// 在render()方法中:
glitchFrame = (glitchFrame + 1) % 60;
const glitchOffset = glitchFrame >= 55 ? Math.floor(Math.random() * 3) - 1 : 0;
const titleX = Math.floor((cols - title[0].length) / 2) + glitchOffset;
if (glitchFrame >= 55 && glitchFrame < 58) {
output += `\x1b[1;${titleX}H\x1b[91m${title[0]}\x1b[0m`;
output += `\x1b[2;${titleX + 1}H\x1b[96m${title[1]}\x1b[0m`;
} else {
output += `\x1b[1;${titleX}H${themeColor}\x1b[1m${title[0]}\x1b[0m`;
output += `\x1b[2;${titleX}H${themeColor}\x1b[1m${title[1]}\x1b[0m`;
}Game Loop Setup
游戏循环设置
typescript
setTimeout(() => {
if (!running) return;
// Enter alternate buffer, hide cursor
terminal.write('\x1b[?1049h');
terminal.write('\x1b[?25l');
initGame();
gameStarted = false;
const renderInterval = setInterval(() => {
if (!running) { clearInterval(renderInterval); return; }
render();
}, 50); // 20 FPS
const gameInterval = setInterval(() => {
if (!running) { clearInterval(gameInterval); return; }
update();
}, 50);
const keyListener = terminal.onKey(({ domEvent }) => {
if (!running) { keyListener.dispose(); return; }
domEvent.preventDefault();
domEvent.stopPropagation();
// Handle input...
});
// Override stop to clean up
const originalStop = controller.stop;
controller.stop = () => {
clearInterval(renderInterval);
clearInterval(gameInterval);
keyListener.dispose();
originalStop();
};
}, 50);typescript
setTimeout(() => {
if (!running) return;
// 进入备用缓冲区,隐藏光标
terminal.write('\x1b[?1049h');
terminal.write('\x1b[?25l');
initGame();
gameStarted = false;
const renderInterval = setInterval(() => {
if (!running) { clearInterval(renderInterval); return; }
render();
}, 50); // 20 FPS
const gameInterval = setInterval(() => {
if (!running) { clearInterval(gameInterval); return; }
update();
}, 50);
const keyListener = terminal.onKey(({ domEvent }) => {
if (!running) { keyListener.dispose(); return; }
domEvent.preventDefault();
domEvent.stopPropagation();
// 处理输入...
});
// 重写stop方法用于清理资源
const originalStop = controller.stop;
controller.stop = () => {
clearInterval(renderInterval);
clearInterval(gameInterval);
keyListener.dispose();
originalStop();
};
}, 50);Registering a Game
注册游戏
In , add a direct import and entry:
src/games/index.tstypescript
// 1. Add the import at the top with existing imports:
import { run{Name}Game } from './{name}';
// 2. Add to the games array:
export const games: GameInfo[] = [
// ... existing games
{ id: '{name}', name: '{Name}', description: 'Game description', run: run{Name}Game },
];
// 3. Add to the individual game runner exports:
export {
// ... existing exports
run{Name}Game,
};在中添加直接导入和注册条目:
src/games/index.tstypescript
// 1. 在顶部现有导入区域添加导入语句:
import { run{Name}Game } from './{name}';
// 2. 添加到games数组中:
export const games: GameInfo[] = [
// ... 现有游戏
{ id: '{name}', name: '{Name}', description: 'Game description', run: run{Name}Game },
];
// 3. 添加到单独的游戏运行器导出列表中:
export {
// ... 现有导出
run{Name}Game,
};Effect Patterns
效果模式
See for:
patterns/effects.md- Particle systems (burst, trail, firework)
- Screen shake
- Score popups
- Flash effects
- Kill streaks and combos
Use the shared effects module at for common effect logic.
src/games/shared/effects.ts查看了解以下内容:
patterns/effects.md- 粒子系统(爆发、拖尾、烟花)
- 屏幕震动
- 得分弹窗
- 闪屏效果
- 连杀和连击
使用的共享效果模块实现通用效果逻辑。
src/games/shared/effects.tsInput Patterns
输入模式
See for:
patterns/input-handling.md- Arrow key navigation
- Action keys (space, etc.)
- ESC/Q handling
- Start screen any-key
查看了解以下内容:
patterns/input-handling.md- 方向键导航
- 动作按键(空格等)
- ESC/Q按键处理
- 开始界面任意键触发
Rendering Patterns
渲染模式
See for:
patterns/rendering.md- ANSI escape codes reference
- Border drawing
- Centering content
- Color cycling
- Perspective effects
查看了解以下内容:
patterns/rendering.md- ANSI转义码参考
- 边框绘制
- 内容居中
- 颜色循环
- 透视效果
Game Complexity Guide
游戏复杂度指南
Simple (~300-500 lines): Snake, Hangman
- Single file
- Basic collision
- Minimal physics
Medium (~500-800 lines): Tetris, Pong
- Single file with more systems
- AI or grid mechanics
- More effect polish
Complex (~1000+ lines): Chopper
- Multiple files (index.ts, effects.ts)
- Physics, particles, popups
- Level progression
简单(约300-500行代码): 贪吃蛇、猜词游戏
- 单文件
- 基础碰撞检测
- 极简物理逻辑
中等(约500-800行代码): 俄罗斯方块、乒乓球
- 单文件包含更多系统
- AI或网格机制
- 更丰富的效果打磨
复杂(约1000行以上代码): 直升机游戏
- 多文件(index.ts、effects.ts)
- 物理、粒子、弹窗效果
- 关卡进度系统
Contributing a Game
贡献游戏
- Create your game in
src/games/{name}/index.ts - Register it in
src/games/index.ts - Use shared effects from instead of inlining particle/popup code
../shared/effects - Use the shared menu from for pause/game-over menus
../shared/menu - Run and
npm run buildto verifynpm run typecheck - Submit a PR
- 在中创建你的游戏
src/games/{name}/index.ts - 在中注册游戏
src/games/index.ts - 使用的共享效果,不要内联粒子/弹窗代码
../shared/effects - 暂停/游戏结束菜单使用的共享菜单
../shared/menu - 运行和
npm run build验证代码npm run typecheck - 提交PR
Template
模板
Use the scaffold template at as a starting point.
templates/game-scaffold.ts可以使用的脚手架模板作为开发起点。
templates/game-scaffold.ts