首页
/ JavaScript游戏开发实战:从零构建互动记忆灯光游戏

JavaScript游戏开发实战:从零构建互动记忆灯光游戏

2026-03-10 04:05:48作者:劳婵绚Shirley

在前端游戏开发领域,互动编程实践是提升技能的有效途径。本文将带你使用现代Web技术栈实现经典的记忆灯光游戏,通过构建这一项目,你将掌握状态管理、事件处理和动画效果等核心前端开发技能,同时深入理解游戏交互逻辑的设计与实现。

游戏设计理念:从经典到现代

记忆灯光游戏(Simon Game)作为一款经典的电子记忆游戏,其核心魅力在于简单规则与复杂挑战的完美结合。现代Web技术的发展为这款经典游戏带来了新的生命力,通过JavaScript、CSS和HTML的组合,我们可以构建出视觉效果出色、交互体验流畅的网页版记忆灯光游戏。

团队协作开发场景

核心游戏机制解析

记忆灯光游戏的核心机制基于以下几个关键元素:

  • 序列记忆挑战:玩家需要记住并重复由系统随机生成的灯光序列
  • 渐进式难度:随着游戏进行,序列长度逐渐增加,难度不断提升
  • 多感官反馈:结合视觉和听觉反馈增强游戏体验
  • 模式选择:提供普通模式和严格模式两种游戏体验

核心技术拆解:构建游戏的四大支柱

1. 游戏状态管理系统

游戏状态管理是任何游戏的核心,我们采用TypeScript实现一个健壮的状态管理模块,确保游戏逻辑的清晰与可维护性。

// 定义游戏状态接口
interface GameState {
  sequence: number[];        // 生成的随机序列
  playerSequence: number[];  // 玩家输入的序列
  round: number;             // 当前回合数
  isPlaying: boolean;        // 游戏是否进行中
  isStrict: boolean;         // 是否启用严格模式
  isShowingSequence: boolean; // 是否正在展示序列
}

// 初始化游戏状态
const initialState: GameState = {
  sequence: [],
  playerSequence: [],
  round: 0,
  isPlaying: false,
  isStrict: false,
  isShowingSequence: false
};

class GameManager {
  private state: GameState;
  
  constructor() {
    this.state = { ...initialState };
  }
  
  // 获取当前游戏状态(只读)
  getState(): Readonly<GameState> {
    return { ...this.state };
  }
  
  // 启动新游戏
  startNewGame(): void {
    this.state = {
      ...initialState,
      isPlaying: true,
      round: 1
    };
    this.generateSequence();
  }
  
  // 生成新的序列
  private generateSequence(): void {
    // 生成1-4之间的随机数,添加到序列中
    const newNumber = Math.floor(Math.random() * 4) + 1;
    this.state.sequence.push(newNumber);
  }
  
  // 记录玩家输入
  addPlayerInput(value: number): boolean {
    // 如果正在展示序列,不接受玩家输入
    if (this.state.isShowingSequence) return false;
    
    this.state.playerSequence.push(value);
    
    // 检查输入是否正确
    const currentIndex = this.state.playerSequence.length - 1;
    const isCorrect = this.state.sequence[currentIndex] === value;
    
    if (!isCorrect) {
      this.handleError();
      return false;
    }
    
    // 检查是否完成当前回合
    if (this.state.playerSequence.length === this.state.sequence.length) {
      this.nextRound();
    }
    
    return true;
  }
  
  // 处理错误输入
  private handleError(): void {
    if (this.state.isStrict) {
      // 严格模式下,直接游戏结束
      this.gameOver();
    } else {
      // 非严格模式下,重新展示序列
      this.state.playerSequence = [];
      this.showSequence();
    }
  }
  
  // 进入下一轮
  private nextRound(): void {
    this.state.round++;
    this.state.playerSequence = [];
    this.generateSequence();
    this.showSequence();
  }
  
  // 游戏结束
  gameOver(): void {
    this.state.isPlaying = false;
    // 可以在这里添加游戏结束的逻辑,如记录最高分等
  }
  
  // 展示序列(需要配合UI实现)
  async showSequence(): Promise<void> {
    this.state.isShowingSequence = true;
    // 这里将实现序列展示逻辑,实际项目中需要配合UI组件
    await new Promise(resolve => setTimeout(resolve, 1000));
    this.state.isShowingSequence = false;
  }
}

2. 交互式UI组件设计

游戏的用户界面是玩家与游戏交互的桥梁,我们需要设计直观且响应式的UI组件。

UI组件结构流程图

// 游戏按钮组件
class GameButton {
  private element: HTMLElement;
  private color: string;
  private sound: HTMLAudioElement;
  private onClick: (id: number) => void;
  private id: number;
  
  constructor(id: number, color: string, soundUrl: string, onClick: (id: number) => void) {
    this.id = id;
    this.color = color;
    this.onClick = onClick;
    this.element = this.createButtonElement();
    this.sound = new Audio(soundUrl);
    this.setupEventListeners();
  }
  
  // 创建按钮元素
  private createButtonElement(): HTMLElement {
    const button = document.createElement('div');
    button.className = 'game-button';
    button.style.backgroundColor = this.color;
    button.dataset.id = this.id.toString();
    return button;
  }
  
  // 设置事件监听
  private setupEventListeners(): void {
    // 鼠标点击事件
    this.element.addEventListener('click', () => this.onClick(this.id));
    
    // 触摸事件支持(移动端)
    this.element.addEventListener('touchstart', (e) => {
      e.preventDefault(); // 防止触摸事件的默认行为
      this.onClick(this.id);
    });
  }
  
  // 激活按钮(显示高亮效果)
  activate(): void {
    this.element.classList.add('active');
    this.playSound();
    
    // 一段时间后移除激活状态
    setTimeout(() => {
      this.element.classList.remove('active');
    }, 300);
  }
  
  // 播放按钮声音
  private playSound(): void {
    this.sound.currentTime = 0; // 重置音频播放位置
    this.sound.play().catch(e => {
      console.warn('音频播放失败:', e);
      // 处理浏览器自动播放限制
    });
  }
  
  // 获取按钮元素
  getElement(): HTMLElement {
    return this.element;
  }
}

3. 音频与视觉反馈系统

多感官反馈是提升游戏体验的关键,我们需要为游戏添加丰富的音频和视觉效果。

// 音频管理器
class AudioManager {
  private sounds: Map<number, HTMLAudioElement>;
  private errorSound: HTMLAudioElement;
  private successSound: HTMLAudioElement;
  
  constructor() {
    this.sounds = new Map();
    this.errorSound = new Audio('sounds/error.mp3');
    this.successSound = new Audio('sounds/success.mp3');
    
    // 预加载声音
    this.preloadSounds();
  }
  
  // 预加载声音资源
  private preloadSounds(): void {
    const soundUrls = {
      1: 'sounds/button-1.mp3',
      2: 'sounds/button-2.mp3',
      3: 'sounds/button-3.mp3',
      4: 'sounds/button-4.mp3'
    };
    
    for (const [id, url] of Object.entries(soundUrls)) {
      const audio = new Audio(url);
      audio.load();
      this.sounds.set(parseInt(id), audio);
    }
  }
  
  // 播放按钮声音
  playButtonSound(buttonId: number): void {
    const sound = this.sounds.get(buttonId);
    if (sound) {
      sound.currentTime = 0;
      sound.play().catch(e => console.warn('无法播放声音:', e));
    }
  }
  
  // 播放错误声音
  playErrorSound(): void {
    this.errorSound.currentTime = 0;
    this.errorSound.play();
  }
  
  // 播放成功声音
  playSuccessSound(): void {
    this.successSound.currentTime = 0;
    this.successSound.play();
  }
}

// CSS 视觉效果
/* 按钮样式 */
.game-button {
  width: 150px;
  height: 150px;
  border: none;
  border-radius: 10px;
  margin: 10px;
  cursor: pointer;
  transition: all 0.2s ease;
  box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}

/* 按钮激活状态 */
.game-button.active {
  transform: scale(0.95);
  opacity: 0.7;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

/* 不同颜色按钮 */
.game-button[data-id="1"] { background-color: #3498db; }
.game-button[data-id="2"] { background-color: #2ecc71; }
.game-button[data-id="3"] { background-color: #e74c3c; }
.game-button[data-id="4"] { background-color: #f39c12; }

4. 游戏流程控制逻辑

游戏流程控制负责协调整个游戏的运行,包括序列生成、展示、玩家输入验证等核心逻辑。

游戏流程图

class GameController {
  private gameManager: GameManager;
  private buttons: GameButton[];
  private audioManager: AudioManager;
  private statusDisplay: HTMLElement;
  
  constructor() {
    this.gameManager = new GameManager();
    this.audioManager = new AudioManager();
    this.buttons = this.createButtons();
    this.statusDisplay = document.getElementById('game-status')!;
    
    this.setupEventListeners();
  }
  
  // 创建游戏按钮
  private createButtons(): GameButton[] {
    const buttonConfig = [
      { id: 1, color: '#3498db', sound: 'sounds/button-1.mp3' },
      { id: 2, color: '#2ecc71', sound: 'sounds/button-2.mp3' },
      { id: 3, color: '#e74c3c', sound: 'sounds/button-3.mp3' },
      { id: 4, color: '#f39c12', sound: 'sounds/button-4.mp3' }
    ];
    
    return buttonConfig.map(config => 
      new GameButton(
        config.id, 
        config.color, 
        config.sound, 
        (id) => this.handleButtonClick(id)
      )
    );
  }
  
  // 设置事件监听
  private setupEventListeners(): void {
    // 开始按钮
    document.getElementById('start-button')?.addEventListener('click', () => {
      this.startGame();
    });
    
    // 严格模式切换
    document.getElementById('strict-mode')?.addEventListener('change', (e) => {
      const isChecked = (e.target as HTMLInputElement).checked;
      this.gameManager.getState().isStrict = isChecked;
    });
  }
  
  // 开始游戏
  startGame(): void {
    this.gameManager.startNewGame();
    this.updateStatus(`第 ${this.gameManager.getState().round} 回合`);
    this.showSequence();
  }
  
  // 处理按钮点击
  private handleButtonClick(buttonId: number): void {
    const state = this.gameManager.getState();
    
    // 如果游戏未开始或正在展示序列,不处理点击
    if (!state.isPlaying || state.isShowingSequence) return;
    
    // 激活按钮
    this.buttons.find(btn => btn.getId() === buttonId)?.activate();
    
    // 记录玩家输入
    const isCorrect = this.gameManager.addPlayerInput(buttonId);
    
    if (!isCorrect) {
      this.handleError();
    } else if (state.playerSequence.length === state.sequence.length) {
      this.updateStatus(`回合 ${state.round} 完成! 准备下一轮...`);
      // 短暂延迟后进入下一轮
      setTimeout(() => {
        this.updateStatus(`第 ${state.round + 1} 回合`);
        this.showSequence();
      }, 1000);
    }
  }
  
  // 展示序列
  private async showSequence(): Promise<void> {
    const state = this.gameManager.getState();
    this.updateStatus('记住这个序列...');
    
    // 按顺序激活每个按钮
    for (const buttonId of state.sequence) {
      await new Promise(resolve => setTimeout(resolve, 600));
      this.buttons.find(btn => btn.getId() === buttonId)?.activate();
      await new Promise(resolve => setTimeout(resolve, 400));
    }
    
    this.updateStatus('现在轮到你了!');
  }
  
  // 处理错误
  private handleError(): void {
    this.audioManager.playErrorSound();
    this.updateStatus('错误! 重试...');
    
    const state = this.gameManager.getState();
    if (state.isStrict) {
      this.updateStatus('严格模式: 游戏结束!');
      setTimeout(() => this.startGame(), 2000);
    } else {
      // 非严格模式下重新展示序列
      setTimeout(() => {
        this.updateStatus('重新展示序列...');
        this.showSequence();
      }, 1000);
    }
  }
  
  // 更新状态显示
  private updateStatus(message: string): void {
    this.statusDisplay.textContent = message;
  }
}

// 初始化游戏
document.addEventListener('DOMContentLoaded', () => {
  new GameController();
});

游戏开发设计模式分析

状态模式在游戏状态管理中的应用

状态模式是游戏开发中的一种常用设计模式,特别适合处理具有多种状态且状态间转换复杂的场景。在记忆灯光游戏中,我们可以将游戏的不同阶段视为不同的状态:

// 状态接口
interface GameState {
  enter(): void;
  handleInput(buttonId: number): void;
  update(deltaTime: number): void;
}

// 准备状态
class ReadyState implements GameState {
  private game: Game;
  
  constructor(game: Game) {
    this.game = game;
  }
  
  enter(): void {
    this.game.updateStatus('点击开始按钮开始游戏');
  }
  
  handleInput(buttonId: number): void {
    // 准备状态下不处理按钮输入
  }
  
  update(deltaTime: number): void {
    // 准备状态下无更新逻辑
  }
}

// 展示序列状态
class ShowingSequenceState implements GameState {
  private game: Game;
  private sequenceIndex: number = 0;
  private timer: number = 0;
  
  constructor(game: Game) {
    this.game = game;
  }
  
  enter(): void {
    this.game.updateStatus('记住序列...');
    this.sequenceIndex = 0;
    this.timer = 0;
  }
  
  handleInput(buttonId: number): void {
    // 展示序列时不处理用户输入
  }
  
  update(deltaTime: number): void {
    this.timer += deltaTime;
    
    // 每隔一定时间展示下一个按钮
    if (this.timer > 1000 && this.sequenceIndex < this.game.getSequence().length) {
      const buttonId = this.game.getSequence()[this.sequenceIndex];
      this.game.activateButton(buttonId);
      this.sequenceIndex++;
      this.timer = 0;
    }
    
    // 序列展示完毕,切换到等待玩家输入状态
    if (this.sequenceIndex >= this.game.getSequence().length) {
      this.game.changeState(new WaitingInputState(this.game));
    }
  }
}

// 等待玩家输入状态
class WaitingInputState implements GameState {
  private game: Game;
  
  constructor(game: Game) {
    this.game = game;
  }
  
  enter(): void {
    this.game.updateStatus('请重复序列');
  }
  
  handleInput(buttonId: number): void {
    // 处理玩家输入逻辑
    const isCorrect = this.game.checkPlayerInput(buttonId);
    
    if (!isCorrect) {
      this.game.changeState(new ErrorState(this.game));
      return;
    }
    
    // 检查是否完成当前回合
    if (this.game.isPlayerSequenceComplete()) {
      this.game.changeState(new RoundCompleteState(this.game));
    }
  }
  
  update(deltaTime: number): void {
    // 等待输入状态下无更新逻辑
  }
}

// 游戏类,使用状态模式管理游戏状态
class Game {
  private currentState: GameState;
  private states: {
    ready: ReadyState;
    showingSequence: ShowingSequenceState;
    waitingInput: WaitingInputState;
    error: ErrorState;
    roundComplete: RoundCompleteState;
  };
  
  constructor() {
    // 初始化所有状态
    this.states = {
      ready: new ReadyState(this),
      showingSequence: new ShowingSequenceState(this),
      waitingInput: new WaitingInputState(this),
      error: new ErrorState(this),
      roundComplete: new RoundCompleteState(this)
    };
    
    // 设置初始状态
    this.currentState = this.states.ready;
    this.currentState.enter();
  }
  
  // 改变状态
  changeState(state: GameState): void {
    this.currentState = state;
    this.currentState.enter();
  }
  
  // 处理输入
  handleInput(buttonId: number): void {
    this.currentState.handleInput(buttonId);
  }
  
  // 更新游戏
  update(deltaTime: number): void {
    this.currentState.update(deltaTime);
  }
  
  // 其他游戏方法...
}

观察者模式实现游戏事件系统

观察者模式允许对象订阅特定事件,并在事件发生时收到通知。在游戏开发中,这一模式可以用于解耦游戏逻辑和UI展示。

// 事件发射器
class EventEmitter {
  private listeners: Map<string, Array<(data?: any) => void>>;
  
  constructor() {
    this.listeners = new Map();
  }
  
  // 订阅事件
  on(event: string, callback: (data?: any) => void): void {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, []);
    }
    this.listeners.get(event)!.push(callback);
  }
  
  // 触发事件
  emit(event: string, data?: any): void {
    if (this.listeners.has(event)) {
      this.listeners.get(event)!.forEach(callback => callback(data));
    }
  }
  
  // 取消订阅
  off(event: string, callback?: (data?: any) => void): void {
    if (!this.listeners.has(event)) return;
    
    if (callback) {
      this.listeners.set(event, this.listeners.get(event)!.filter(cb => cb !== callback));
    } else {
      this.listeners.delete(event);
    }
  }
}

// 游戏模型,继承事件发射器
class GameModel extends EventEmitter {
  private sequence: number[];
  private playerSequence: number[];
  private round: number;
  
  constructor() {
    super();
    this.sequence = [];
    this.playerSequence = [];
    this.round = 0;
  }
  
  // 开始新游戏
  startGame(): void {
    this.sequence = [];
    this.playerSequence = [];
    this.round = 1;
    this.generateSequence();
    this.emit('gameStart', { round: this.round });
    this.emit('sequenceGenerated', { sequence: this.sequence });
  }
  
  // 生成序列
  private generateSequence(): void {
    const newNumber = Math.floor(Math.random() * 4) + 1;
    this.sequence.push(newNumber);
  }
  
  // 添加玩家输入
  addPlayerInput(value: number): void {
    this.playerSequence.push(value);
    this.emit('playerInput', { value, sequence: this.playerSequence });
    
    const currentIndex = this.playerSequence.length - 1;
    if (this.playerSequence[currentIndex] !== this.sequence[currentIndex]) {
      this.emit('inputError', { expected: this.sequence[currentIndex], actual: value });
      return;
    }
    
    if (this.playerSequence.length === this.sequence.length) {
      this.roundComplete();
    }
  }
  
  // 回合完成
  private roundComplete(): void {
    this.emit('roundComplete', { round: this.round });
    this.round++;
    this.playerSequence = [];
    this.generateSequence();
    this.emit('sequenceGenerated', { sequence: this.sequence, round: this.round });
  }
}

// UI视图组件
class GameView {
  private model: GameModel;
  private buttons: GameButton[];
  private statusDisplay: HTMLElement;
  
  constructor(model: GameModel) {
    this.model = model;
    this.buttons = this.createButtons();
    this.statusDisplay = document.getElementById('status')!;
    
    // 订阅模型事件
    this.model.on('gameStart', (data) => this.onGameStart(data));
    this.model.on('sequenceGenerated', (data) => this.onSequenceGenerated(data));
    this.model.on('playerInput', (data) => this.onPlayerInput(data));
    this.model.on('inputError', (data) => this.onInputError(data));
    this.model.on('roundComplete', (data) => this.onRoundComplete(data));
  }
  
  // 事件处理方法...
  private onGameStart(data: { round: number }): void {
    this.statusDisplay.textContent = `第 ${data.round} 回合`;
  }
  
  private onSequenceGenerated(data: { sequence: number[], round?: number }): void {
    if (data.round) {
      this.statusDisplay.textContent = `第 ${data.round} 回合 - 记住序列`;
    }
    this.showSequence(data.sequence);
  }
  
  // 其他事件处理方法...
}

实战开发指南:从零开始构建游戏

环境搭建与项目结构

首先,我们需要搭建一个基础的项目结构,用于组织游戏代码:

memory-light-game/
├── index.html         # 游戏页面
├── css/
│   └── game.css       # 游戏样式
├── js/
│   ├── game-manager.ts # 游戏状态管理
│   ├── ui-components.ts # UI组件
│   ├── audio-manager.ts # 音频管理
│   └── main.ts        # 游戏入口
└── sounds/            # 音频资源
    ├── button-1.mp3
    ├── button-2.mp3
    ├── button-3.mp3
    ├── button-4.mp3
    ├── error.mp3
    └── success.mp3

核心功能实现指南

1. 基础HTML结构

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>记忆灯光游戏 - JavaScript游戏开发</title>
  <link rel="stylesheet" href="css/game.css">
</head>
<body>
  <div class="game-container">
    <h1>记忆灯光游戏</h1>
    
    <div class="game-status" id="game-status">点击开始按钮开始游戏</div>
    
    <div class="game-board" id="game-board">
      <!-- 游戏按钮将通过JavaScript动态创建 -->
    </div>
    
    <div class="game-controls">
      <button id="start-button" class="control-button">开始游戏</button>
      <label class="strict-mode">
        <input type="checkbox" id="strict-mode"> 严格模式
      </label>
      <div class="round-counter">回合: <span id="round-number">0</span></div>
    </div>
  </div>

  <script src="js/main.js"></script>
</body>
</html>

2. 基础样式实现

/* 基础样式设置 */
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  font-family: 'Arial', sans-serif;
  background-color: #f0f0f0;
  display: flex;
  flex-direction: column;
  align-items: center;
  min-height: 100vh;
  padding: 20px;
}

.game-container {
  background-color: #fff;
  border-radius: 15px;
  box-shadow: 0 0 20px rgba(0,0,0,0.1);
  padding: 30px;
  max-width: 600px;
  width: 100%;
}

h1 {
  text-align: center;
  color: #333;
  margin-bottom: 20px;
}

/* 游戏状态显示 */
.game-status {
  text-align: center;
  font-size: 1.2em;
  margin: 20px 0;
  padding: 10px;
  background-color: #f8f8f8;
  border-radius: 5px;
  min-height: 40px;
}

/* 游戏面板 */
.game-board {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 15px;
  margin: 30px auto;
  max-width: 400px;
}

/* 游戏按钮 */
.game-button {
  width: 100%;
  height: 150px;
  border: none;
  border-radius: 10px;
  cursor: pointer;
  transition: all 0.2s ease;
  box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}

.game-button.active {
  transform: scale(0.95);
  opacity: 0.7;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

/* 控制区域 */
.game-controls {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 20px;
}

.control-button {
  padding: 10px 20px;
  background-color: #3498db;
  color: white;
  border: none;
  border-radius: 5px;
  cursor: pointer;
  font-size: 1em;
  transition: background-color 0.2s;
}

.control-button:hover {
  background-color: #2980b9;
}

.strict-mode {
  display: flex;
  align-items: center;
  color: #555;
}

.strict-mode input {
  margin-right: 5px;
}

.round-counter {
  font-size: 1.1em;
  color: #333;
}

3. 游戏主逻辑实现

// main.ts - 游戏入口文件
import { GameManager } from './game-manager';
import { GameButton } from './ui-components';
import { AudioManager } from './audio-manager';

// 等待DOM加载完成
document.addEventListener('DOMContentLoaded', () => {
  // 创建游戏管理器
  const gameManager = new GameManager();
  
  // 创建音频管理器
  const audioManager = new AudioManager();
  
  // 创建游戏按钮
  const buttonContainer = document.getElementById('game-board')!;
  const buttons: GameButton[] = [];
  
  // 按钮配置
  const buttonConfig = [
    { id: 1, color: '#3498db', sound: 'sounds/button-1.mp3' },
    { id: 2, color: '#2ecc71', sound: 'sounds/button-2.mp3' },
    { id: 3, color: '#e74c3c', sound: 'sounds/button-3.mp3' },
    { id: 4, color: '#f39c12', sound: 'sounds/button-4.mp3' }
  ];
  
  // 创建按钮并添加到容器
  buttonConfig.forEach(config => {
    const button = new GameButton(
      config.id,
      config.color,
      config.sound,
      (id) => handleButtonClick(id)
    );
    buttonContainer.appendChild(button.getElement());
    buttons.push(button);
  });
  
  // 按钮点击处理函数
  function handleButtonClick(buttonId: number): void {
    if (!gameManager.getState().isPlaying || gameManager.getState().isShowingSequence) {
      return;
    }
    
    // 激活按钮
    buttons.find(btn => btn.getId() === buttonId)?.activate();
    audioManager.playButtonSound(buttonId);
    
    // 记录玩家输入
    const isCorrect = gameManager.addPlayerInput(buttonId);
    
    // 更新UI
    updateUI();
    
    if (!isCorrect) {
      handleError();
    } else if (gameManager.getState().playerSequence.length === gameManager.getState().sequence.length) {
      // 回合完成
      setTimeout(() => {
        nextRound();
      }, 1000);
    }
  }
  
  // 处理错误
  function handleError(): void {
    audioManager.playErrorSound();
    updateStatus('错误! 重试...');
    
    const state = gameManager.getState();
    if (state.isStrict) {
      updateStatus('游戏结束! 点击开始按钮重新开始');
      gameManager.gameOver();
    } else {
      // 重新展示序列
      setTimeout(() => {
        showSequence();
      }, 1000);
    }
  }
  
  // 下一轮
  function nextRound(): void {
    audioManager.playSuccessSound();
    gameManager.nextRound();
    updateStatus(`第 ${gameManager.getState().round} 回合 - 记住序列`);
    showSequence();
  }
  
  // 展示序列
  async function showSequence(): Promise<void> {
    const state = gameManager.getState();
    gameManager.setState({ ...state, isShowingSequence: true });
    
    for (const buttonId of state.sequence) {
      await new Promise(resolve => setTimeout(resolve, 600));
      buttons.find(btn => btn.getId() === buttonId)?.activate();
      audioManager.playButtonSound(buttonId);
      await new Promise(resolve => setTimeout(resolve, 400));
    }
    
    gameManager.setState({ ...state, isShowingSequence: false });
    updateStatus('现在轮到你了!');
  }
  
  // 更新UI
  function updateUI(): void {
    const state = gameManager.getState();
    document.getElementById('round-number')!.textContent = state.round.toString();
  }
  
  // 更新状态显示
  function updateStatus(message: string): void {
    document.getElementById('game-status')!.textContent = message;
  }
  
  // 开始按钮事件监听
  document.getElementById('start-button')?.addEventListener('click', () => {
    const strictMode = (document.getElementById('strict-mode') as HTMLInputElement).checked;
    gameManager.startNewGame(strictMode);
    updateUI();
    updateStatus(`第 ${gameManager.getState().round} 回合 - 记住序列`);
    showSequence();
  });
});

技术难点解析

1. 音频播放问题

问题现象:在某些浏览器中,音频可能无法自动播放,或者播放延迟。

解决方案

  • 预加载所有音频资源
  • 使用用户交互触发首次音频播放
  • 重置音频.currentTime属性确保可以连续播放
// 改进的音频播放方法
playSound(sound: HTMLAudioElement): void {
  // 重置播放位置
  sound.currentTime = 0;
  
  // 尝试播放音频
  sound.play().catch(e => {
    console.warn('音频播放失败,尝试用户交互后再播放:', e);
    
    // 创建用户交互监听器,确保后续可以播放
    const unlockAudio = () => {
      sound.play().then(() => {
        document.removeEventListener('click', unlockAudio);
        document.removeEventListener('touchstart', unlockAudio);
      });
    };
    
    document.addEventListener('click', unlockAudio);
    document.addEventListener('touchstart', unlockAudio);
  });
}

优化思路:使用Web Audio API替代HTML5 Audio元素,可以获得更精确的音频控制和更低的延迟。

2. 序列展示与用户输入同步

问题现象:在序列展示过程中,用户可能会点击按钮,干扰游戏流程。

解决方案

  • 使用状态变量标记序列展示状态
  • 在展示序列时忽略用户输入
  • 添加视觉反馈指示当前游戏状态
// 改进的按钮点击处理
handleButtonClick(buttonId: number): void {
  // 如果正在展示序列,不处理点击
  if (this.state.isShowingSequence) {
    // 提供视觉反馈,表示当前不能点击
    this.showTemporaryMessage("请等待序列展示完成");
    return;
  }
  
  // 正常处理点击逻辑...
}

// 临时消息显示
showTemporaryMessage(message: string, duration: number = 1000): void {
  const messageElement = document.createElement('div');
  messageElement.className = 'temp-message';
  messageElement.textContent = message;
  document.body.appendChild(messageElement);
  
  setTimeout(() => {
    messageElement.remove();
  }, duration);
}

优化思路:实现一个完整的状态机,清晰管理游戏的各个状态(准备、展示序列、等待输入、错误等)。

创新拓展方向

难度系统实现指南

为游戏添加难度系统可以增加游戏的可玩性和挑战性。我们可以通过调整序列展示速度和序列长度增长方式来实现不同难度级别。

// 难度设置接口
interface DifficultySettings {
  initialSpeed: number; // 初始序列展示速度(ms)
  speedDecrement: number; // 每回合减少的速度(ms)
  minSpeed: number; // 最小展示速度(ms)
  initialLength: number; // 初始序列长度
  lengthIncrement: number; // 每回合增加的长度
}

// 难度级别配置
const difficultyLevels: Record<string, DifficultySettings> = {
  easy: {
    initialSpeed: 1000,
    speedDecrement: 50,
    minSpeed: 500,
    initialLength: 1,
    lengthIncrement: 1
  },
  medium: {
    initialSpeed: 800,
    speedDecrement: 75,
    minSpeed: 300,
    initialLength: 2,
    lengthIncrement: 1
  },
  hard: {
    initialSpeed: 600,
    speedDecrement: 100,
    minSpeed: 200,
    initialLength: 3,
    lengthIncrement: 2
  }
};

// 在GameManager中集成难度系统
class AdvancedGameManager extends GameManager {
  private difficulty: keyof typeof difficultyLevels;
  private settings: DifficultySettings;
  
  constructor(difficulty: keyof typeof difficultyLevels = 'medium') {
    super();
    this.difficulty = difficulty;
    this.settings = difficultyLevels[difficulty];
  }
  
  // 重写生成序列方法
  generateSequence(): void {
    const currentLength = this.state.sequence.length;
    const targetLength = this.settings.initialLength + 
      (this.state.round - 1) * this.settings.lengthIncrement;
    
    // 根据难度设置生成相应长度的序列
    while (this.state.sequence.length < targetLength) {
      const newNumber = Math.floor(Math.random() * 4) + 1;
      this.state.sequence.push(newNumber);
    }
  }
  
  // 重写展示序列方法
  async showSequence(): Promise<void> {
    this.state.isShowingSequence = true;
    
    // 根据当前回合计算展示速度
    const speed = Math.max(
      this.settings.initialSpeed - (this.state.round - 1) * this.settings.speedDecrement,
      this.settings.minSpeed
    );
    
    for (const buttonId of this.state.sequence) {
      await new Promise(resolve => setTimeout(resolve, speed));
      this.activateButton(buttonId);
      await new Promise(resolve => setTimeout(resolve, speed * 0.5));
    }
    
    this.state.isShowingSequence = false;
  }
  
  // 切换难度
  setDifficulty(difficulty: keyof typeof difficultyLevels): void {
    this.difficulty = difficulty;
    this.settings = difficultyLevels[difficulty];
  }
}

跨框架实现对比

React实现思路

React版本的记忆灯光游戏可以利用组件化思想和Hooks管理游戏状态:

// React组件示例
function GameButton({ color, onClick, isActive }) {
  return (
    <div 
      className={`game-button ${isActive ? 'active' : ''}`}
      style={{ backgroundColor: color }}
      onClick={onClick}
      data-testid="game-button"
    />
  );
}

function MemoryGame() {
  const [gameState, setGameState] = useState(initialState);
  const audioManager = useRef(new AudioManager());
  
  // 处理游戏逻辑的自定义Hook
  const gameLogic = useGameLogic(gameState, setGameState, audioManager.current);
  
  return (
    <div className="game-container">
      <h1>记忆灯光游戏</h1>
      <div className="status">{gameState.status}</div>
      
      <div className="button-grid">
        {[1, 2, 3, 4].map(id => (
          <GameButton
            key={id}
            color={getColorForId(id)}
            onClick={() => gameLogic.handleButtonClick(id)}
            isActive={gameState.activeButton === id}
          />
        ))}
      </div>
      
      <div className="controls">
        <button onClick={gameLogic.startGame}>开始</button>
        <label>
          <input 
            type="checkbox" 
            checked={gameState.strictMode}
            onChange={gameLogic.toggleStrictMode}
          />
          严格模式
        </label>
      </div>
    </div>
  );
}

Vue实现思路

Vue版本可以利用Vue的响应式系统和组合式API:

<template>
  <div class="game-container">
    <h1>记忆灯光游戏</h1>
    <div class="status">{{ status }}</div>
    
    <div class="button-grid">
      <GameButton
        v-for="id in [1, 2, 3, 4]"
        :key="id"
        :color="getColorForId(id)"
        :is-active="activeButton === id"
        @click="handleButtonClick(id)"
      />
    </div>
    
    <div class="controls">
      <button @click="startGame">开始</button>
      <label>
        <input 
          type="checkbox" 
          v-model="strictMode"
          @change="toggleStrictMode"
        />
        严格模式
      </label>
    </div>
  </div>
</template>

<script setup>
import { ref, reactive, computed } from 'vue';
import GameButton from './components/GameButton.vue';
import { useGameLogic } from './composables/useGameLogic';

// 响应式状态
const gameState = reactive({
  sequence: [],
  playerSequence: [],
  round: 0,
  isPlaying: false,
  strictMode: false,
  status: '点击开始按钮开始游戏'
});

// 使用组合式API封装游戏逻辑
const { 
  startGame, 
  handleButtonClick, 
  toggleStrictMode,
  activeButton
} = useGameLogic(gameState);

// 计算属性和辅助函数
const getColorForId = (id) => {
  const colors = ['#3498db', '#2ecc71', '#e74c3c', '#f39c12'];
  return colors[id - 1];
};
</script>

React和Vue实现对比:

  • 状态管理:React使用useState/useReducer,Vue利用响应式系统
  • 组件通信:React主要通过props和context,Vue除了props外还可以使用provide/inject
  • 模板系统:React使用JSX,Vue使用HTML模板
  • 生命周期:React使用useEffect,Vue使用onMounted等生命周期钩子

两种框架都能很好地实现记忆灯光游戏,选择哪种取决于个人偏好和项目需求。React更灵活,适合复杂逻辑;Vue更简洁,上手门槛较低。

总结

通过本指南,我们详细介绍了如何使用JavaScript和TypeScript构建一个功能完整的记忆灯光游戏。从游戏状态管理到UI组件设计,从音频视觉反馈到游戏流程控制,我们覆盖了游戏开发的各个方面。同时,我们还探讨了游戏开发中的设计模式应用,以及如何解决常见的技术难点。

这个项目不仅是对前端技能的综合练习,也是对游戏开发基本原理的深入理解。通过实现这个经典游戏,你可以掌握状态管理、事件处理、动画效果和音频控制等核心前端技术。希望这篇指南能够激发你对前端游戏开发的兴趣,继续探索更多有趣的互动编程实践!

记忆灯光游戏虽然简单,但包含了现代Web应用开发的许多核心概念。掌握这些概念后,你可以尝试构建更复杂的游戏和交互应用,如贪吃蛇、俄罗斯方块等经典游戏,或者开发自己原创的游戏作品。

最后,记住编程学习最好的方式是实践。动手尝试修改和扩展这个游戏,添加自己的创意功能,这将帮助你更深入地理解前端开发和游戏设计的精髓。🛠️🎮

登录后查看全文
热门项目推荐
相关项目推荐