单选按钮选择后无法执行嵌套函数?JavaScript游戏开发问题
问题:单选按钮触发对应游戏函数失败,嵌套函数未定义
问题概述
自学JavaScript,目标实现单选按钮选择游戏类型(PVP/PVC)并触发对应函数组的功能。当前代码逻辑混乱,遇到核心问题:
- 选择PVP后运行
pvp()时,提示内部函数未定义; - 移除
pvp()外层包裹后,所有功能可正常运行。
已排查变量作用域、变量提升问题,调整过变量位置,控制台调试无进展,确认script标签在HTML末尾,无需window.onload。
预期操作流程
- 用户通过单选按钮选择PVP或PVC游戏类型
- 点击提交按钮
- 根据选择为
game变量赋值 - 若
game === "pvp"则运行pvp();若game === "pvc"则运行pvc()(PVC函数未编写,因PVP逻辑先失效)
原代码展示
JavaScript代码
'use-strict'; //declare variables here let boxs = document.querySelectorAll('.box'); let statusTxt = document.querySelector('#status'); let btnRestart = document.querySelector('#restart'); let btnStart = document.querySelector('#start'); let btnNewGame = document.querySelector('#newGame'); //a timer for seconds when the game starts //example in the text for the window.onload but I don't want to use that //I need to start this timer when the first token is placed..... //set up x and o stamps //fix the height here because stamps are huge in the game //I need to figure out how to get the players to be randomly started ///////////////////////////////////////////////////////////// //which type of game is being played? test radio buttons... //run functions based on what the value ofgame is... btnStart.addEventListener("click", e => { console.log(e); }); btnNewGame.addEventListener("click", e => { console.log(e); }); var game; //i have a hoisted variable problem game = "no game radio button"; console.log("test game type at page load = " + game); function checkGameType() { btnPVP = document.getElementById("pvp"); btnPVC = document.getElementById("pvc"); btnPVPchecked = document.getElementById("pvp").checked; btnPVCchecked = document.getElementById("pvc").checked; if (document.getElementById("pvp").checked) { document.getElementById("messageGame").innerHTML = "Player V Player Game!"; console.log("player v player"); clearCount(); count = 0; game = "pvp"; console.log("test game type at page load = " + game); } else if (document.getElementById("pvc").checked) { document.getElementById("messageGame").innerHTML = "Player V Computer Game!"; console.log("player v computer"); game = "pvc"; console.log("test game type at page load = " + game); } else if (!btnPVPchecked && !btnPVCchecked) { document.getElementById("messageGame").innerHTML = "No Game Selected!"; console.log("no game type selcted"); game = "none"; console.log("test game type at page load = " + game); } } //end checkGameType function clearGameType() { btnPVPunchecked = (document.getElementById("pvp").checked = false); btnPVCunchecked = !(document.getElementById("pvc").checked = false); console.log("game selection has been cleared"); document.getElementById("messageGame").innerHTML = "Pick a game!"; restartGame(); game = "none"; console.log(game + " ---- New Game var test"); } //end clearGameType function restartGame() { options = ["", "", "", "", "", "", "", "", ""]; setRandomPlayer(); running = true; statusTxt.textContent = 'New Game! ' + player + ' turn!'; boxs.forEach(box => { box.innerHTML = ""; box.classList.remove('win'); }); counter = 0; stopCount(); } //end of restartgame /////////////////////////////// //set up timer here so it can be accessed in funcs //function for game type player v player //game timer that looks ugly but works //i tried making this a nested function and it was a disaster var counter = 0; var timeout; var timer_on = 0; function timedCount() { document.getElementById("timeClock").innerHTML = counter + " seconds"; counter++; timeout = setTimeout(timedCount, 1000); //testing the timer in console //console.log(counter); } function startCount() { if (!timer_on) { timer_on = 1; timedCount(); } //console.log(counter); } function stopCount() { clearTimeout(timeout); timer_on = 0; //counter = 0; document.getElementById("timeClock").innerHTML = counter + " seconds"; } function clearCount() { clearTimeout(timeout); counter = 0; } //let currentPlayer=x; -- this is for if I wanted to start as x stamp every time //initialize empty box options for the game board here //when the game first loads in the browser function init() { //the game waits for user interaction - a click in the box boxs.forEach(box => box.addEventListener('click', boxClick)); btnRestart.addEventListener('click', restartGame); setRandomPlayer(); statusTxt.textContent = 'Current Game - player ' + player + ' ----- turn!'; running = true; console.log(game + " test in init"); //here i see that game is undefined so this is why my pvp func is not working below.... } //end of init init(); function setRandomPlayer() { //wow it actually works, sometimes o starts, sometimes x starts currentPlayer = Math.random() > 0.5 ? x : o; //images are being used not x and o //this needs to be fixed because it doesn't match the random player order //ok fixed the status message matches the current player player = currentPlayer; } //end of random player func function pvp() { var x = "x"; var o = "o"; var options = ["", "", "", "", "", "", "", "", ""]; var running = false; //combo of wins possible that game checks for after each turn var win = [ [0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6] ]; console.log('before game play func ---- ' + game); /////////////////////////////// //start game play here function boxClick() { const index = this.dataset.index; //if if (options[index] !== "" || !running) { return; } console.log("a click is recorded here"); updateBox(this, index); isWinner(); } //use a button to start the game play and attach timer to that //not needed ^; timer works fine onclick, finally function updateBox(box, index) { options[index] = player; //dynamic html in the doc box.innerHTML = currentPlayer; console.log(currentPlayer + " plays their turn"); } function nextPlayer() { player = (player === "x") ? "o" : "x"; currentPlayer = (currentPlayer === x) ? o : x; statusTxt.textContent = player + ' turn'; console.log("next player's turn " + currentPlayer); } //did anyone win function isWinner() { let isWon = false; for (let i = 0; i < win.length; i++) { const condition = win[i]; //[0,1,2] const box1 = options[condition[0]]; const box2 = options[condition[1]]; const box3 = options[condition[2]]; if (box1 === "" || box2 === "" || box3 === "") { continue; } if (box1 === box2 && box2 === box3) { isWon = true; boxs[condition[0]].classList.add('win'); boxs[condition[1]].classList.add('win'); boxs[condition[2]].classList.add('win'); } } if (isWon) { statusTxt.textContent = 'player ' + player + ' wins!!'; running = false; //stop the timer here too stopCount(); } else if (!options.includes("")) { statusTxt.textContent = 'The Game is Tied'; running = false; } else { nextPlayer(); } } //end of check winner // } //end of pvp game //functionality for game type player v computer
HTML代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Title</title> <link rel="stylesheet" href="style.css"> <link rel="shortcut icon" href="#"> </head> <body> <br> <h3>What kind of game do you want to play ?</h3><br> <input type="radio" id="pvp" name="gameType" value="Player v Player">Player v Player <input type="radio" id="pvc" name="gameType" value="Player v Computer" />Player v Computer<br><br> <button id="start" onclick="checkGameType()">START!</button> <button id="newGame" onclick="clearGameType()">New Game</button> <div id="messageGame"></div><br> <div>----[[Game Clock]]----</div> <p id="timeClock"> </p> <div id="status">Place your stamp to start playing!</div> <div class="container" id="board"> <div data-index="0" class="box" onclick="startCount()"></div> <div data-index="1" class="box" onclick="startCount()"></div> <div data-index="2" class="box" onclick="startCount()"></div> <div data-index="3" class="box" onclick="startCount()"></div> <div data-index="4" class="box" onclick="startCount()"></div> <div data-index="5" class="box" onclick="startCount()"></div> <div data-index="6" class="box" onclick="startCount()"></div> <div data-index="7" class="box" onclick="startCount()"></div> <div data-index="8" class="box" onclick="startCount()"></div> </div> <br> <button id="restart" onclick="restartGame()">Restart</button> <script src="pvpgame.js"></script> </body> </html>
问题根源与修复方案
核心问题分析
嵌套函数作用域冲突
boxClick等核心游戏逻辑嵌套在pvp()内部,但全局的init()函数提前给格子绑定了boxClick事件——此时pvp()未执行,嵌套函数还未创建,导致报错“函数未定义”。移除pvp()外层后,函数变为全局作用域,init()能找到,所以功能正常。未触发函数初始化
checkGameType()仅给game变量赋值,未调用pvp()初始化游戏逻辑,也未重新绑定事件。全局变量污染
restartGame()中未声明options、running等变量,自动变为全局变量;而pvp()内部又声明同名局部变量,导致作用域混乱。
修复后的代码示例
'use-strict'; // 全局DOM元素与通用变量 let boxs = document.querySelectorAll('.box'); let statusTxt = document.querySelector('#status'); let btnRestart = document.querySelector('#restart'); let btnStart = document.querySelector('#start'); let btnNewGame = document.querySelector('#newGame'); var counter = 0; var timeout; var timer_on = 0; let game = "no game radio button"; // 通用计时器函数 function timedCount() { document.getElementById("timeClock").textContent = counter + " seconds"; counter++; timeout = setTimeout(timedCount, 1000); } function startCount() { if (!timer_on) { timer_on = 1; timedCount(); } } function stopCount() { clearTimeout(timeout); timer_on = 0; document.getElementById("timeClock").textContent = counter + " seconds"; } function clearCount() { clearTimeout(timeout); counter = 0; } // 移除格子事件绑定,避免冲突 function removeBoxEvents() { boxs.forEach(box => { box.removeEventListener('click', boxClick); box.removeEventListener('click', startCount); }); } // PVP游戏初始化模块 function initPVP() { let x = "x"; let o = "o"; let options = ["", "", "", "", "", "", "", "", ""]; let running = true; let currentPlayer; let player; // 随机选择起始玩家 function setRandomPlayer() { currentPlayer = Math.random() > 0.5 ? x : o; player = currentPlayer; statusTxt.textContent = 'New Game! ' + player + ' turn!'; } // 格子点击逻辑 function boxClick() { const index = this.dataset.index; if (options[index] !== "" || !running) return; updateBox(this, index); isWinner(); } function updateBox(box, index) { options[index] = player; box.textContent = currentPlayer; } function nextPlayer() { player = player === x ? o : x; currentPlayer = currentPlayer === x ? o : x; statusTxt.textContent = player + ' turn'; } function isWinner() { let isWon = false; const win = [ [0,1,2],[3,4,5],[6,7,8], [0,3,6],[1,4,7],[2,5,8], [0,4,8],[2,4,6] ]; for (let i = 0; i < win.length; i++) { const condition = win[i]; const box1 = options[condition[0]]; const box2 = options[condition[1]]; const box3 = options[condition[2]]; if (box1 === "" || box2 === "" || box3 === "") continue; if (box1 === box2 && box2 === box3) { isWon = true; boxs[condition[0]].classList.add('win'); boxs[condition[1]].classList.add('win'); boxs[condition[2]].classList.add('win'); } } if (isWon) { statusTxt.textContent = 'player ' + player + ' wins!!'; running = false; stopCount(); } else if (!options.includes("")) { statusTxt.textContent = 'The Game is Tied'; running = false; } else { nextPlayer(); } } // 初始化PVP游戏 removeBoxEvents(); setRandomPlayer(); boxs.forEach(box => { box.addEventListener('click', boxClick); box.addEventListener('click', startCount); }); clearCount(); counter = 0; } // 检查游戏类型并初始化对应逻辑 function checkGameType() { const isPVP = document.getElementById("pvp").checked; const isPVC = document.getElementById("pvc").checked; if (isPVP) { document.getElementById("messageGame").textContent = "Player V Player Game!"; game = "pvp"; initPVP(); } else if (isPVC) { document.getElementById("messageGame").textContent = "Player V Computer Game!"; game = "pvc"; // 后续添加PVC游戏初始化逻辑 } else { document.getElementById("messageGame").textContent = "No Game Selected!"; game = "none"; } } // 重置游戏选择 function clearGameType() { document.getElementById("pvp").checked = false; document.getElementById("pvc").checked = false; document.getElementById("messageGame").textContent = "Pick a game!"; removeBoxEvents(); statusTxt.textContent = 'Place your stamp to start playing!';
相关产品推荐
相关产品推荐

