让电脑对手自动前进,更新阵列,并更换玩家以回合用户



上下文:我正在用JavaScript和HTML设计一个井字游戏。我需要有两种游戏模式,玩家对玩家和玩家对电脑。如果用户选择一种或另一种游戏类型,我决定调用两个单独的函数。

要求:您的程序应允许用户选择两个单选按钮之一:a.玩家对玩家b.播放器与电脑

  1. 如果游戏是"玩家对电脑",玩家总是得到X,电脑总是得到O符号,但谁先移动取决于随机化,就像玩家对玩家一样

  2. 人工智能并没有那么聪明。计算机只是在板上寻找一个空位随机选择一个(再次使用Math.random。)再次,该点必须是开放的-未被占用-您如果某个符号已经存在于某个特定位置,则不得重写该符号。

我已经对玩家对玩家的功能集进行了编码,游戏运行良好。我怀疑是否会根据玩家对电脑的要求修改它们。我不知道从哪里开始。

我必须在游戏板的单元格中使用onClick()。不允许使用jQuery。

为了解决这个问题,我有两个功能。一种是手动播放器对播放器组的功能。另一个是播放器v电脑的功能组。对于玩家v电脑,我真的很纠结如何在轮到电脑的时候自动更新阵列,然后切换到用户那里移动,然后再切换回来?直到找到赢家。电脑总是得到x的分配,但谁开始游戏是随机的。抱歉代码太乱了,还在学习!谢谢你看。

我尝试了什么

  • 我设置了控制台消息来跟踪计算机是x还是o
  • 我设置了控制台消息来跟踪数组[compBox]的值,并确保它没有未定义
  • 我尝试了一些innerHTMLDOM脚本来用x或o更新数组索引
  • 我设置了一个测试,看看起始玩家是否是x,然后代码应该用x更新数组
  • 如果首发球员是o,那么我可以点击游戏板并放置o
  • 然后玩家应该换回电脑,又名x

'use-strict';
//declare variables here
const boxs=document.querySelectorAll('.box');
const statusTxt=document.querySelector('#status');
const btnRestart=document.querySelector('#restart');
const btnStart=document.querySelector('#start');
const btnNewGame=document.querySelector('#newGame');
let x = "x";
let o = "o";
options=["","","","","","","","",""];
let running=false;
let game = null;
let player;
//combo of wins possible that game checks for after each turn
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]
];
/////////////////////////////////////////////////////////////
//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);
}
);
//i have a hoisted variable problem
//game = "no game radio button";
//console.log("test game type at page load = "+game);
//for the start of the game
//what about when someone changes their game type?
function checkGameType() {
//btnPVP = document.getElementById("pvp");
//btnPVC = document.getElementById("pvc");
radioPVPchecked = document.getElementById("pvp").checked;
radioPVCchecked = document.getElementById("pvc").checked;
if (radioPVPchecked && !radioPVCchecked){
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);
if (game==="pvp") {
newGame();
pvp();
};
} else if (radioPVCchecked && !radioPVPchecked){
document.getElementById("messageGame").innerHTML = "Player v Computer Game!";
console.log("player v computer");
game="pvc";
//console.log("test game type at page load = "+game);
//clear last game settings here! pvp settings are carrying over
clearCount();
count = 0;
options=["","","","","","","","",""];
if (game==="pvc") {
newGame();
pvc();
};
} else if (!radioPVPchecked && !radioPVCchecked) {
document.getElementById("messageGame").innerHTML = "No Game Selected!";
game="none";
console.log("test game type at page load = "+game);
newGame();
}
}//end checkGameType
/*!! decided not to use this code
function clearGameType() {
radioPVPunchecked = (document.getElementById("pvp").checked = false);
radioPVCunchecked = (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
*/
//these functions are for external to the player game play
function newGame() {
options=["","","","","","","","",""];
//radioPVPunchecked = (document.getElementById("pvp").checked = false);
//radioPVCunchecked = (document.getElementById("pvc").checked = false);
running=true;
boxs.forEach(box=>{
box.innerHTML="";
box.classList.remove('win');
});
counter = 0;
stopCount();
}
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
let counter = 0;
let timeout;
let 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;
}
/////////////////////////////////////
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;
console.log(game+" test in func setRandomPlayer");
}//end of random player func

///////// player v player function //////////////////
///
///
function pvp() {
function init(){
//the system waits for user interaction - a click in the box
boxs.forEach(box=>box.addEventListener('click',boxClick));
btnRestart.addEventListener('click',restartGame);
setRandomPlayer();
statusTxt.textContent=player+"'s turn!";
running=true;
//console.log(game+" test in init");
}//end of init
init();
console.log("if pvp func test---- "+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 +"'s 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 func
///////// player v computer function //////////////////
///
///

function pvc() {
function init(){
//the system waits for user interaction - a click in the box
boxs.forEach(box=>box.addEventListener('click',boxClick2));
btnRestart.addEventListener('click',restartGame);
setRandomPlayer();//who will start the game first
statusTxt.textContent=player+"'s turn - nComputer = x | You = o";
running=true;
//console.log(game+" test in init");
}//end of init
init();
//I DONT KNOW HOW TO DO THIS LOL
console.log("CODE COMPUTER PART OF THIS GAME");
//assign stamps to player and computer per instructions
comp = "x";
you = "o";
//place the stamp on the board
//umm
//figure out a random box for the computer
compBox = Math.floor(Math.random() * options.length);   
compMove = options[compBox]; 
console.log(compBox+" -- is the cell for computer"); //shows the random spaces of the options
console.log(player+" -- is the current player"); //works
console.log(comp+ " -- is computer"); //works
console.log(compBox, compMove+" -- test computer array spot"); //works
//when current turn is computer:
//AI places a stamp
//then it should be user's turn

if (player === comp) {
compMove = player;
boxClick2();
updateBox2();
nextPlayer2();
}
//click somewhere
if (player === you) {
boxClick2();
updateBox2();
nextPlayer2();
}
function boxClick2(){
//compBox=this.dataset.compBox;
if(options[compBox]!=="" || !running){
return;
}
console.log("a move is recorded here");
updateBox2(this,compBox);
//isWinner();
}
//update the box
function updateBox2(box){
compMove=player;
//dynamic html in the doc
//box.innerHTML=player;
console.log(player+" plays their turn");
}
//next players turn
function nextPlayer2(){
player=(player==="x") ? "o" :"x";
currentPlayer=(currentPlayer===x) ? o :x;
statusTxt.textContent=currentPlayer +"'s turn";
console.log("next player's turn "+currentPlayer);
}
}//end of pvc play functions
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'DM Serif Display', sans-serif;
}
body {
background-color: rgb(192, 192, 192);
color: black;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
#timer {
padding-bottom: 20px;
}
#timeClock {
text-align: center;
}
#pickGame {
padding-top: 50px;
padding-bottom: 10px;
}
#messageGame {
padding-top: 10px;
color: blueviolet;
}
.container {
display: grid;
grid-template-columns: repeat(3, auto);
}
.box {
width: 100px;
height: 100px;
border: 1px solid navy;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
font-size: 25px;
}
#status {
padding: 15px;
font-size: 30px;
font-weight: 500;
}
button {
padding-bottom: 10px;
width: 150px;
height: 40px;
font-size: 22px;
color: white;
background-color: lightseagreen;
border: 1px black;
border-style: solid;
border-radius: 10px;
}
button:active {
background-color: salmon;
box-shadow: 0 5px #666;
transform: translateY(4px);
}
.win {
animation: winAnimation ease-in-out 1s infinite;
}
@keyframes winAnimation {
0% {
background-color: #7c74d4;
}
100% {
background-color: #d8d5f5;
}
0% {
background-color: #7c74d4;
}
}
<link rel="stylesheet" href="style.css">
<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="newGame()">New Game</button>-->
<div id="messageGame"></div><br>
<div>----[[Game Clock]]----</div>
<p id="timeClock"> </p>
<div id="status">Pick a game above and hit Start!</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>

很抱歉回答晚了,周末和假期我都没用电脑。

我喜欢你的尝试

我真正喜欢你代码的地方是你写代码的方式。常量是在最上面声明的。您为变量使用了或多或少自我解释的名称,而不是有助于提高可读性的快捷方式。你的契约很干净,评论也很充分。我也认可你的努力,你在这里发帖之前真的试着自己解决了任务!

我不喜欢你的尝试

IMHO你错过了项目的专业规划。你的写作风格不错,但前后矛盾。例如你有变量(实际上,它们是播放器xo的常量。但在代码中,您使用过一次这些变量,但第二次您决定使用字符串而不是变量。在顶部,您决定使用querySelector作为ID,再往下切换到getElementById。您的一些变量或常量名称本可以更准确或更详细。例如,game是一个需要定义的变量游戏模式或类型->它应该这样命名。最后但同样重要的是,您的代码适用于大多数重复代码。虽然这里不再赘述,但这些问题主要是由于规划失误造成的。

规划

任何一个优秀的程序员在开始编程之前都会在计划上付出很大的努力。有一种方法作为指导方针非常有效:SDLC软件开发生命周期的7个步骤。

  1. 需求分析
  2. 定义
  3. 设计
  4. 编码
  5. 测试
  6. 部署
  7. 维护

Standish Group Chaos的报告指出,近2/3的失败项目是由于计划失误而失败的。

收集分析几乎是你的任务,而对你来说更有趣的是定义。在那里,你可以计划如何解决任务,一个很好的工具是Program Flow-ChartsNassi-Shneiderman Diagrams。在编程之前,先在纸上制定一个如何解决问题的计划:

程序逻辑

在大多数情况下,除了人工智能或计算机移动逻辑之外,你已经解决了任务。但正如前面所说,您的代码是重复的,需要重做很多步骤。

  1. 您需要检查已选择的游戏模式。在你的尝试中,你只有两种可能性:PvP和PvC。但还有第三种可能性:没有选择游戏模式。平均值是,你必须检查是否选择了游戏模式,如果没有,则输出错误消息
  2. 如果游戏开始(到目前为止,start gamerestart gamestart new game之间没有差异),则必须初始化棋盘。这意味着它必须重置所有字段、变量和计数。。。无论如何,如果你必须初始化所有这些,为什么要在顶部声明它们呢?为什么要声明初始化在不同的位置重置
  3. PvP和PvC在游戏逻辑、获胜检查逻辑或下一回合逻辑上都没有区别。PvP和PvC之间的唯一区别是,在PvC中,Player O必须进行自动移动。最简单的解决方法是检查游戏模式是否为PvC,玩家O是否必须移动,如果双重条件成立,则随机移动
  4. 你不需要检查每一轮是否有人获胜或是否有平局。一名玩家可能获胜的最早回合是在第5回合。在此之前,没有人进行过3次移动,因此无法连接3个单元。唯一可能的平局是在第9圈结束时

计算机移动逻辑

你的任务要求是有一个愚蠢的人工智能,它不会做出有教育意义的动作,只是对一个可能的细胞进行随机移动。

let unpickedCells = [];

for (let i = 0; i < options.length; i++) {
if (options[i] === '') {
unpickedCells.push(i);
}
}

所以你在每个AI上创建一个数组,在那里你检查options数组中的空单元格,然后你把这些空单元格(分别是它们的索引)推到新的数组中。unpickedCells的数组是所有可能的可用选择的列表。然后,您可以使用Mat.random函数从该数组中选择一个随机元素:

unpickedCells[Math.floor(Math.random() * unpickedCells.length)]

这将返回尚未拾取的单元格的索引,该索引必须在脚本中使用才能运行相同的clickBox函数,而只是替换this.dataset.index函数。

这不是我解决整个任务的方式,而是尽可能多地重复使用您的代码,并将其缩减为必要的部分,而不会像最初那样重复代码:

//declare variables here
const boxs = document.querySelectorAll('.box');
const statusTxt = document.querySelector('#status');
const btnRestart = document.querySelector('#restart');
const btnStart = document.querySelector('#start');
const btnNewGame = document.querySelector('#newGame');
const messageGame = document.querySelector('#messageGame');
const x = 'X';
const o = 'O';
const pvp = 'Player v Player';
const pvc = 'Player v Computer';

let options;
let running = false;
let gameType = null;
let player;
let isWon;
let currentTurn;
// I didnt reused your clock as it had nothing to do with the actual issue. You can readd it of course.
function startCount() {
// empty function to avoid onclick trigger "Uncaught ReferenceError"
}
// What will be the difference between resratGame, ChckGameType, and newGame?
function restartGame() {
checkGameType();
}
// Checks if a gamy mode has been selected and which one
function checkGameType() {
let checkedGameTypeElement = document.querySelector('input[name="gameType"]:checked');
// you missed the possiblity that no mode was selected and as such must include an error message for that case
gameType = (checkedGameTypeElement !== null) ? checkedGameTypeElement.value : null;
messageGame.textContent = (checkedGameTypeElement !== null) ? `${gameType} Game!` : `No Game Type has been selected`;
// don't start directly with playing, initialize the board first!
if (gameType !== null) {
init();
}
}
function init() {
boxs.forEach(box => box.addEventListener('click', boxClick));
running = true;
isWon = false;
// new variable that tracks the current turn. The function nextPlayer will increment it. So start with 0
currentTurn = 0;
player = Math.random() > 0.5 ? x : o;

options = ['', '', '', '', '', '', '', '', ''];
running = true;
boxs.forEach(box => {
box.textContent = '';
box.classList.remove('win');
box.setAttribute('aria-disabled', 'false');
});
counter = 0;
//stopCount();

nextPlayer();
}
function nextPlayer() {
player = (player === x) ? o : x;
statusTxt.textContent = `${player}'s turn!`;
currentTurn++;

// Check if the game mode is PvC and if it is, then run the AI logic to automate Player O 
if (player === o && gameType === pvc) {
boxClick();
}
}
function boxClick() {
// here you get the either the idnex of the clicked cell or a random index of an empty cell in case that the AI made a turn
let clickedCellIndex = (player === o && gameType === pvc) ? AImove() : this.dataset.index;
let clickedElement = document.querySelector(`.box[data-index="${clickedCellIndex}"]`);
clickedElement.textContent = player;
// aria-disabled is an attribtue same as disabled with the exception that it can be used on a non-input element such as a div. See CSS for more
clickedElement.setAttribute('aria-disabled', 'true');
options[clickedCellIndex] = player;

// the earlist point where a palyer could win is at turn 5. No need to run the function before
if (currentTurn >= 5) {
isWinner();
}

// only run the function for the nextPlayer if the game didnt ended in a draw as otherwise the nextMove would update and overwrite the draw message
if (currentTurn < 9 && !isWon) {
nextPlayer();
}
}

function AImove() {
let unpickedCells = [];

for (let i = 0; i < options.length; i++) {
if (options[i] === '') {
unpickedCells.push(i);
}
}

return (unpickedCells[Math.floor(Math.random() * unpickedCells.length)]);
}

function isWinner() {
// even though that this is a constant, it is only relevant for that specific function and as such should be placed here. Later on important if you want to use modules
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 || currentTurn === 9) {
statusTxt.textContent = (isWon) ? `Player ${player} wins!!` : 'The Game is Tied';
running = false;
// disables all othe cells so they can't be when the game has ended.
boxs.forEach(box => box.setAttribute('aria-disabled', 'true'));
//stopCount();
}
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'DM Serif Display', sans-serif;
}
body {
background-color: rgb(192, 192, 192);
color: black;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
#timer {
padding-bottom: 20px;
}
#timeClock {
text-align: center;
}
#pickGame {
padding-top: 50px;
padding-bottom: 10px;
}
#messageGame {
padding-top: 10px;
color: blueviolet;
}
.container {
display: grid;
grid-template-columns: repeat(3, auto);
}
.box {
width: 100px;
height: 100px;
border: 1px solid navy;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
font-size: 25px;
}
#status {
padding: 15px;
font-size: 30px;
font-weight: 500;
}
button {
padding-bottom: 10px;
width: 150px;
height: 40px;
font-size: 22px;
color: white;
background-color: lightseagreen;
border: 1px black;
border-style: solid;
border-radius: 10px;
}
button:active {
background-color: salmon;
box-shadow: 0 5px #666;
transform: translateY(4px);
}
/* this CSS line will disable pointer events preventing from a cell to be clicked if it is disabled. Works with screen readers too */
.box[aria-disabled="true"] {
pointer-events: none;
}
.win {
animation: winAnimation ease-in-out 1s infinite;
}
@keyframes winAnimation {
0% {
background-color: #7c74d4;
}
100% {
background-color: #d8d5f5;
}
0% {
background-color: #7c74d4;
}
}
<link rel="stylesheet" href="style.css">
<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="newGame()">New Game</button>-->
<div id="messageGame"></div><br>
<div>----[[Game Clock]]----</div>
<p id="timeClock"> </p>
<div id="status">Pick a game above and hit Start!</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>

出于性能(修复DOM)和安全原因(XSS),我用textContent替换了innerHTML。如果您仍然对逻辑或代码是如何修复的有疑问,请随时询问!

相关内容

最新更新