如何在 JavaScript 游戏中通过函数嵌套与状态管理实现回合制得分追踪

2次阅读

如何在 JavaScript 游戏中通过函数嵌套与状态管理实现回合制得分追踪

本文详解如何重构“石头剪刀布”游戏,解决因重复调用 `playround()` 导致的逻辑错乱与得分丢失问题,通过参数化设计、单次执行原则和局部变量合理作用域,实现清晰、可维护的回合得分统计机制。

javaScript 初学者开发回合制游戏(如石头剪刀布)时,一个典型误区是:在同一个逻辑上下文中多次调用同一函数,却未保存其返回值。你原始代码中 game() 函数内三次调用 playRound()(一次 console.log,两次用于条件判断),每次调用都会重新执行 getPlayerChoice() 和 getComputerChoice() —— 这不仅造成用户被反复提示输入、电脑选择被重生成,更导致前后不一致的比对结果,使得分判定完全失效。

根本原因在于:playRound() 原本是“自包含型”函数(内部获取输入并返回结果),但 game() 又自行获取了 playerSelection 和 computerSelection,却未将它们传入 playRound(),反而让 playRound() 重复执行——形成逻辑割裂与资源浪费。

✅ 正确解法是践行 “单一职责 + 显式传参 + 单次执行” 原则:

  1. playRound() 应专注裁决,不负责输入获取 → 改为接收 playerSelection 和 computerSelection 作为参数;
  2. game() 负责流程控制 → 每轮只调用一次 getPlayerChoice() 和 getComputerChoice(),并将结果传给 playRound();
  3. 得分变量保留在 game() 作用域内(或提升为模块级变量),避免全局污染,同时确保跨轮累积有效。

以下是优化后的核心实现(已修复拼写错误 scrissors → scissors,并增强健壮性):

立即学习Java免费学习笔记(深入)”;

// ✅ 修正拼写:Scrissors → Scissors(全代码统一) function getPlayerChoice() {   let playerInput = prompt("Choose rock, paper or scissors.");   // ? 安全处理:用户点击「取消」时 prompt 返回 null   if (playerInput === null) {     alert("Game cancelled. Refresh to restart.");     return null; // 中断后续流程   }   let playerChoice = playerInput.trim().toLowerCase();   if (playerChoice === "rock") return "Rock";   if (playerChoice === "paper") return "Paper";   if (playerChoice === "scissors") return "Scissors";   alert("Invalid input! Please enter 'rock', 'paper', or 'scissors'.");   return getPlayerChoice(); // 递归重试(仅限合法输入) }  function getComputerChoice() {   const choices = ["Rock", "Paper", "Scissors"];   return choices[Math.floor(Math.random() * choices.length)]; }  // ✅ playRound 纯裁决函数:只依赖输入参数,返回结构化结果 function playRound(player, computer) {   if (player === computer) return { result: "tie", message: "It's a tie!" };    const winConditions = [     ["Rock", "Scissors"],     ["Paper", "Rock"],     ["Scissors", "Paper"]   ];    if (winConditions.some(([p, c]) => p === player && c === computer)) {     return { result: "win", message: `You win! ${player} beats ${computer}.` };   } else {     return { result: "lose", message: `You lose. ${computer} beats ${player}.` };   } }  // ✅ game:主流程,本地维护 score,每轮只执行一次完整逻辑链 function game() {   let playerScore = 0;   let computerScore = 0;    console.log("? Starting 5-round Rock-Paper-Scissors game...n");    for (let round = 1; round <= 5; round++) {     console.log(`--- Round ${round} ---`);      const playerSelection = getPlayerChoice();     if (playerSelection === null) return; // 用户取消,提前退出      const computerSelection = getComputerChoice();     const roundResult = playRound(playerSelection, computerSelection);      console.log(`You chose: ${playerSelection}`);     console.log(`Computer chose: ${computerSelection}`);     console.log(roundResult.message);      // ✅ 基于结构化返回值更新分数(更可靠,避免字符串硬编码匹配)     if (roundResult.result === "win") {       playerScore++;       console.log(`→ Player score: ${playerScore}`);     } else if (roundResult.result === "lose") {       computerScore++;       console.log(`→ Computer score: ${computerScore}`);     } else {       console.log(`→ Tie! No points awarded.`);     }     console.log("");   }    // ? 终局判定   console.log("? Final Score:");   console.log(`Player: ${playerScore} | Computer: ${computerScore}`);   if (playerScore > computerScore) {     console.log("? You won the game!");   } else if (playerScore < computerScore) {     console.log("? You lost the game.");   } else {     console.log("? Game ended in a draw!");   }   console.log("? Game Over."); }  // 启动游戏 game();

⚠️ 关键注意事项与进阶建议

  • 避免字符串硬匹配:原方案用 response === "You win..." 判定胜负极易出错(空格、大小写、标点微小差异即失败)。改用对象返回 { result: "win" } 是更健壮、可扩展的设计。
  • 输入容错增强prompt() 返回 NULL 时必须显式处理,否则 .toLowerCase() 会抛出 TypeError: Cannot read Property 'toLowerCase' of null —— 这正是你遇到的报错根源。
  • 不要滥用递归重试:getPlayerChoice() 中的递归虽能实现重试,但深层调用可能引发栈溢出。生产环境推荐用 while 循环替代。
  • 为 HTML 集成预留接口:若后续迁移到网页界面,可将 prompt/alert 替换为 dom 操作(如 + button),而 playRound() 和得分逻辑完全无需修改 —— 这正是良好函数拆分的价值。

? 总结:函数不是“黑盒”,而是契约。明确每个函数的输入、输出与副作用,是写出可预测、可调试、可复用代码的第一步。得分不是凭空产生,而是由每一次确定的输入 → 确定的裁决 → 确定的状态变更所累积而成。

text=ZqhQzanResources