我正在制作井字游戏,我的问题是游戏功能不会等待用户选择他想移动的位置,它只是在我之后立即运行(gameOver)功能按下开始游戏按钮。
谁能告诉我我的代码有什么问题并帮助我解决它?
```
const start = document.getElementById('start');
const table = document.getElementById('table');
places = ["one", "two", "three", "four", "five", "six", "seven", 'eight', "nine"];
let move = 0;
start.addEventListener('click', function(){
startGame();
});
function startGame(){
console.log("Game started");
user();
}
function gameOver(){
console.log("Game Over");
}
function computer(){
let index = places[Math.floor(Math.random() * places.length)];
let pos = document.getElementById(index);
if (/[1-9]/.test(pos.innerHTML)){
pos.innerHTML = "O";
move += 1;
places.splice(places.indexOf(pos), 1 );
}
if (move > 8){
gameOver();
} else {
user();
}
}
function user(){
table.addEventListener('click', function(event){
let pos = event.target;
let Id = event.target.id;
if (/[1-9]/.test(pos.innerHTML)){
pos.innerHTML = "X";
move += 1;
places.splice(places.indexOf(Id), 1 );
}
if (move > 8){
gameOver();
} else {
computer();
}
});
}
```
<div class="col text-center">
<table class="table text-center">
<tbody id="table">
<tr>
<td id="one">1</td>
<td id="two">2</td>
<td id="three">3</td>
</tr>
<tr>
<td id="four">4</td>
<td id="five">5</td>
<td id="six">6</td>
</tr>
<tr>
<td id="seven">7</td>
<td id="eight">8</td>
<td id="nine">9</td>
</tr>
</tbody>
</table>
<br />
<button class="btn btn-primary" type="button" id="start">Start Game</button>
</div>
答案 0 :(得分:1)
主要问题是您做了一个while循环来运行游戏,并且有一个“在玩家单击时”事件,该事件是异步的并且不会暂停游戏。
首选方法是使游戏完全异步,而无需使用while循环,并在每次计算机或玩家移动时检查移动计数器。
1)在开始时,将移动计数设置为0
2)在玩家身上单击以增加移动次数并最终运行计算机移动,或者如果move_count> 8
则运行“游戏结束”功能注意1:记住在计算机移动时增加移动次数并检查结束位置。
注意2:使用此解决方案,播放器将始终优先移动。
答案 1 :(得分:1)
让我们分析您的user()函数:
const table = document.getElementById('table');
...
function user(){
table.addEventListener('click', function(event){
let pos = event.target;
if (/[1-9]/.test(pos.innerHTML)){
pos.innerHTML = "X";
player = true;
}
});
}
这里的要点是JavaScript是一种高度异步的语言。 addEventListener函数在执行时会添加事件侦听器,然后返回,这表示user()函数已完成。然后,此监听器将在每次单击时触发相应的功能,它不会停止代码并等待单击输入。然后,由于您的代码在一段时间内并且用户功能已完全执行(请记住,只有一个语句为addEventListener),因此代码会很快完成。
要解决此问题,请在start函数的开头调用addEventListener,然后将相应的逻辑放入相应的click函数中。这样,您的代码将在用户单击时仅执行,您可以从此处调用计算机移动或gameOver功能。
答案 2 :(得分:1)
另一种方法是将游戏创建为迭代器,可以将其暂停并等待用户输入。
var game; //variable to reference the currently running game
function* newGame() { //note the '*' which creates an iterator
while (i <= 9){
if (player === true) {
computer();
} else {
user();
yield i; //pause the method (must return a value)
}
i++;
}
gameOver();
}
function startGame(){
game = newGame(); //start the game by creating new iterator
game.next(); //perform first step
}
function user(){
table.addEventListener('click', function(event){
let pos = event.target;
if (/[1-9]/.test(pos.innerHTML)){
pos.innerHTML = "X";
player = true;
game.next(); //continue with the game...
}
});
}
请注意,现在编写方式将为您分配4个不同的点击处理程序。
您还应该调用removeEventListener()
,因为在调用监听器后,它不会自动清除!
但是一旦游戏开始运行,您就会发现;)。
答案 3 :(得分:0)
感谢@Keith的指导,我正在编辑此答案。
浏览器的用户输入本质上是异步的。也许有一天,我们将有一个等待事件的API,但在那之前,我们只剩下用Promises修补猴子了。
在多个项目中有用的模式是在其范围之外解决Promise。在这种模式下,解析器类似于延迟器。这种模式允许将解析程序运送到Promise构造程序以外的工作单元中。这是您可以异步等待DOM事件的两种方式的比较:
(async () => {
const button1 = document.createElement("button")
button1.innerText = "Resolve it out of scope"
document.body.appendChild(button1)
const button2 = document.createElement("button")
button2.innerText = "Resolve it in scope"
document.body.appendChild(button2)
const remove = button => button.parentNode.removeChild(button);
const asyncResolveOutScope = async () => {
let resolver
button1.onclick = () => {
remove(button1)
resolver()
}
return new Promise(resolve => resolver = resolve)
}
const asyncResolveInScope = async () => {
return new Promise(resolve => {
button2.onclick = () => {
remove(button2)
resolve()
}
})
}
console.log("Click the buttons, I'm waiting")
await Promise.all([asyncResolveOutScope(), asyncResolveInScope()])
console.log("You did it")
})()
我不确定这对您有多大帮助,但这是一个使用tic-tac-toe-toe-toe-toe-toe-toe的游戏示例,该游戏使用出厂的(解决范围外)Promise模式:
class game {
constructor(name, order=["Computer", "User"]) {
this.userTurnResolver
this.name = name
this.players = [
{name: "Computer", turn: async() => {
const cells = this.unusedCells
return cells[Math.floor(Math.random() * cells.length)]
}},
{name: "User", turn: async() => new Promise(resolve => this.userTurnResolver = resolve)}
].sort((a, b) => order.indexOf(a.name) - order.indexOf(b.name))
this.players[0].symbol = "X"
this.players[1].symbol = "O"
}
log(...args) {
console.log(`${this.name}: `, ...args)
}
get cells() {
return Array.from(this.table.querySelectorAll("td")).map(td => td.textContent)
}
get unusedCells() {
return Array.from(this.table.querySelectorAll("td")).filter(td => !isNaN(td.textContent)).map(td => td.textContent)
}
userClick(e) {
const cell = isNaN(e.target.textContent) ? false : parseInt(e.target.textContent)
if (cell && this.userTurnResolver) this.userTurnResolver(cell)
}
render() {
//This would usually be done with HyperHTML. No need for manual DOM manipulation or event bindings.
const container = document.createElement("div")
container.textContent = this.name
document.body.appendChild(container)
this.table = document.createElement("table")
this.table.innerHTML = "<tbody><tr><td>1</td><td>2</td><td>3</td></tr><tr><td>4</td><td>5</td><td>6</td></tr><tr><td>7</td><td>8</td><td>9</td></tr></tbody>"
this.table.onclick = e => this.userClick(e)
container.appendChild(this.table)
}
async start() {
this.render()
this.log("Game has started")
const wins = [
{desc: "First Row", cells: [0, 1, 2]}, {desc: "Second Row", cells: [3, 4, 5]},
{desc: "Third Row", cells: [6, 7, 8]}, {desc: "Diagonal", cells: [0, 4, 8]},
{desc: "Diagonal", cells: [2, 4, 6]}, {desc: "First Column", cells: [0, 3, 6]},
{desc: "Second Column", cells: [1, 4, 7]}, {desc: "Third Column", cells: [2, 5, 8]}
]
const checkWin = symbol => wins.find(win => win.cells.every(i => this.cells[i] === symbol))
const takeTurn = async ({name, turn, symbol}, round, i) => {
this.log(`It is ${name}'s turn (round ${round}, turn ${i})`)
const choice = await turn()
this.log(`${name} had their turn and chose ${choice}`)
this.table.querySelectorAll("td")[choice-1].textContent = symbol
return checkWin(symbol)
}
let win, round = 0, i = 0
while (!win && i < 9) {
round++
for (let player of this.players) {
i++
win = await takeTurn(player, round, i)
if (win) {
this.log(`${player.name} is the winner with ${win.desc}`)
break
}
if (i===9) {
this.log(`We have a stalemate`)
break
}
}
}
this.log("Game over")
}
}
(new game("Game 1", ["User", "Computer"])).start();
(new game("Game 2")).start();
(new game("Game 3")).start();
答案 4 :(得分:0)
Javascript是单线程运行时,因此您所做的许多事情都被称为pd.cut
,例如获得鼠标单击,执行ajax / fetch请求之类的事情。所有这些都不会阻塞Javascript线程,而是使用回调等。
幸运的是,现代JS引擎具有一个称为asynchronous
的功能,它的基本作用是使异步代码看起来是同步的。
下面是对您的代码进行了稍微修改的地方,主要的变化是使您的async / await
函数变成了Promise,以便可以成为user
。
顺便说一句。这样做还有其他错误,例如单击已使用的单元格,但是我将其保留在此处,因此人们很容易看到我为使脚本运行而进行了哪些更改。
awaited
const start = document.getElementById('start');
const table = document.getElementById('table');
places = ["one", "two", "three", "four", "five", "six", "seven", 'eight', "nine"];
let i = 1;
let player = true;
start.addEventListener('click', function(){
startGame();
});
async function startGame(){
while (i <= 9){
if (player === true) {
computer();
} else {
await user();
}
i++;
}
gameOver();
}
function gameOver(){
console.log("Game Over");
}
function computer(){
let index = places[Math.floor(Math.random() * places.length)];
let pos = document.getElementById(index);
if (/[1-9]/.test(pos.innerHTML)){
pos.innerHTML = "O";
player = false;
}
}
function user(){
return new Promise((resolve) => {
function evClick(event) {
table.removeEventListener('click', evClick);
let pos = event.target;
if (/[1-9]/.test(pos.innerHTML)){
pos.innerHTML = "X";
player = true;
}
resolve();
}
table.addEventListener('click', evClick)
});
}
td {
border: 1px solid black;
margin: 15px;
padding: 15px;
}