效果:
样式:
<h1>Tic Tac Toe</h1><div class="container"><!-- 游戏面板(棋盘) --><div id="bord" class="game-board x"><div class="row"><div class="cell"></div><div class="cell"></div><div class="cell"></div></div><div class="row"><div class="cell"></div><div class="cell"></div><div class="cell"></div></div><div class="row"><div class="cell"></div><div class="cell"></div><div class="cell"></div></div></div><!-- 游戏获胜信息面板 --><div id="message" class="game-message"><p id="winner">X 赢了!</p><button id="restart">重新开始</button></div></div>
p {margin: 0;}body {background-color: #f9f2e7;}/* 标题 */h1 {text-align: center;font-size: 60px;color: #477998;}/* 游戏内容容器 */.container {position: relative;width: 471px;height: 471px;margin: 0 auto;}/* 游戏获胜信息面板 */.game-message {display: none;position: absolute;top: 0;left: 0;right: 0;bottom: 0;background-color: rgba(69, 133, 136, 0.4);text-align: center;}/* winner 获胜者 */.game-message p {margin: 180px 0 40px 0;color: #fff;font-size: 50px;}/* 重新开始游戏按钮 */.game-message button {color: #517304;border-color: #517304;width: 110px;height: 40px;font-size: 20px;cursor: pointer;}/* 游戏面板棋盘 */.game-board {width: 471px;height: 471px;}.game-board.x .cell:not(.x):not(.o):hover::before {content: 'X';color: lightgray;}.game-board.o .cell:not(.x):not(.o):hover::before {content: 'O';color: lightgray;}/* 棋盘 - 行 */.row {display: flex;}.row:last-child .cell {border-bottom: 0;}/* 棋盘 - 单元格 */.cell {flex: 1;box-sizing: border-box;width: 157px;height: 157px;line-height: 157px;border-right: 6px solid #546363;border-bottom: 6px solid #546363;text-align: center;cursor: pointer;font-size: 88px;font-family: 'Gill Sans', 'Gill Sans MT', Calibri, sans-serif;}.cell:last-child {border-right: 0;}/* x 玩家 */.cell.x::before {content: 'X';color: #01a8c6;}/* o 玩家 */.cell.o::before {content: 'O';color: #8fbe01;}
第一步 单元格点击
1、获取所有的单元格列表。
2、遍历单元格列表,给每一个单元格添加点击事件。
3、给当前被点击的单元格添加类名 x 或 o。
var cells = document.getElementsByClassName("cell");var arr = [...cells]arr.forEach(item=>{item.onclick = function(){this.classList.add("x")this.onclick = null; //优化使点击事件只执行一次}})
第二步 切换玩家
1、创建一个存储当前玩家的变量(currentPlayer),默认值为:x。
2、将添加给单元格时写死的类名x,替换为变量(currentPlayer)的值。
3、切换到另一个玩家:在添加类名(下棋完成一步)后,根据当前玩家,得到另外一个玩家。
4、处理下一步提示:移除游戏面板中的x和o类名,添加下一个玩家对应的类名。
var gameBord = document.getElementById("bord");var cells = document.getElementsByClassName("cell");var arr = [...cells]var currentPlayer = 'x'arr.forEach(item=>{item.onclick = function(){this.classList.add(currentPlayer)// 根据当前玩家,得到另外一个玩家currentPlayer = currentPlayer == 'x' ? 'o' : 'x';// 处理下一步提示gameBord.classList.remove('x','o');gameBord.classList.add(currentPlayer);// 事件执行一次this.onclick = null;}})
第三步 使用枚举修改当前玩家
1、创建字符串枚举(Player),提供X和O两个成员。
2、将成员X的值设置为:’x’(类名);将成员O的值设置为:’o’(类名)。
3、将变量(currentPlayer)的类型设置为Player枚举类型,默认值为Player.X。
4、将所有用到x和o的地方全部使用枚举成员代替。
## enum.tsenum Player {X = 'x',O = 'o'}
//枚举部分var Player;(function (Player) {Player["X"] = "x";Player["O"] = "o";})(Player || (Player = {}));var gameBord = document.getElementById("bord");var cells = document.getElementsByClassName("cell");var arr = [...cells]// 当前玩家var currentPlayer = Player.Xarr.forEach(item=>{item.onclick = function(){this.classList.add(currentPlayer)// 根据当前玩家,得到另外一个玩家currentPlayer = currentPlayer == Player.X ? Player.O : Player.X;// 处理下一步提示gameBord.classList.remove(Player.X,Player.O);gameBord.classList.add(currentPlayer);// 事件执行一次this.onclick = null;}})
第四步 实现判赢函数
1、使用some方法遍历数组,并将some方法的返回值作为判赢函数的返回结果。
2、在some方法的回调函数中,获取到每种获胜情况对应的3个单元格。
3、判断这3个单元格元素是否同时包含当前玩家的类名。
4、如果包含(玩家获胜),就在回调函数中返回true停止循环;否则,返回false,继续下一次循环。
var winArr = [[0,1,2],[3,4,5],[6,7,8], //横[0,3,6],[1,4,7],[2,5,8], //竖[0,4,8],[2,4,6] //斜]var gameBord = document.getElementById("bord");var cells = document.getElementsByClassName("cell");var arr = [...cells]// 当前玩家var currentPlayer = 'x'arr.forEach(item=>{item.onclick = function(){this.classList.add(currentPlayer)// 调用判赢函数判断是否获胜let isWin = checkWin(currentPlayer)if(isWin){console.log("当前玩家获胜了",currentPlayer)}// 根据当前玩家,得到另外一个玩家currentPlayer = currentPlayer == 'x' ? 'o' : 'x';// 处理下一步提示gameBord.classList.remove('x','o');gameBord.classList.add(currentPlayer);// 事件执行一次this.onclick = null;}})// 封装判赢函数function checkWin(player){return winArr.some(item=>{let cellIndex1 = item[0];let cellIndex2 = item[1];let cellIndex3 = item[2];if(hasClass(arr[cellIndex1],player) &&hasClass(arr[cellIndex2],player) &&hasClass(arr[cellIndex3],player)){return true}return false})}// 封装hasClass函数function hasClass(el,name){return el.classList.contains(name);}
第五步 判断平局
1、创建变量(steps),默认值为0。
2、在玩家下棋后,让steps加1。
3、在判赢的代码后面,判断steps是否等于9。
4、如果等于9说明是平局,游戏结束,就直接return,不再执行后续代码。
var winArr = [[0,1,2],[3,4,5],[6,7,8], //横[0,3,6],[1,4,7],[2,5,8], //竖[0,4,8],[2,4,6] //斜]var gameBord = document.getElementById("bord");var cells = document.getElementsByClassName("cell");var arr = [...cells]// 当前玩家var currentPlayer = 'x';// 记录已下棋的次数var steps = 0;arr.forEach(item=>{item.onclick = function(){this.classList.add(currentPlayer)steps++;// 调用判赢函数判断是否获胜let isWin = checkWin(currentPlayer)if(isWin){console.log("当前玩家获胜了",currentPlayer)return}// 判断平局if(steps == 9){return}// 根据当前玩家,得到另外一个玩家currentPlayer = currentPlayer == 'x' ? 'o' : 'x';// 处理下一步提示gameBord.classList.remove('x','o');gameBord.classList.add(currentPlayer);// 事件执行一次this.onclick = null;}})
第六步 展示获胜信息
1、获取到与获胜信息相关的两个DOM元素:1 #message 2 #winner。
2、显示获胜信息面板(通过style属性实现)。
3、展示获胜信息:如果获胜,展示“x赢了!”或“o赢了!”;如果是平局,展示“平局”。
// 获胜信息面板var message = document.getElementById("message");// 获胜者var winner = document.getElementById("winner");// 当前玩家var currentPlayer = 'x';// 记录已下棋的次数var steps = 0;arr.forEach(item=>{item.onclick = function(){this.classList.add(currentPlayer)steps++;// 调用判赢函数判断是否获胜let isWin = checkWin(currentPlayer)if(isWin){message.style.display = 'block';winner.innerText = currentPlayer + ' 赢了!'return}// 判断平局if(steps == 9){message.style.display = 'block';winner.innerHTML = '平局'return}// 根据当前玩家,得到另外一个玩家currentPlayer = currentPlayer == 'x' ? 'o' : 'x';// 处理下一步提示gameBord.classList.remove('x','o');gameBord.classList.add(currentPlayer);// 事件执行一次this.onclick = null;}})
第七步 重新游戏
1、获取到重新开始按钮(#restart),并绑定点击事件。
2、在点击事件中,重置游戏数据。
3、隐藏获胜信息、清空棋盘、移除单元格点击事件、重新给单元格绑定点击事件。
4、重置下棋次数、重置默认玩家为x、重置下棋提示为x。
// 判赢数组var winArr = [[0,1,2],[3,4,5],[6,7,8], //横[0,3,6],[1,4,7],[2,5,8], //竖[0,4,8],[2,4,6] //斜]var gameBord = document.getElementById("bord");var cells = document.getElementsByClassName("cell");var arr = [...cells];// 获胜信息面板var message = document.getElementById("message");// 获胜者var winner = document.getElementById("winner");// 重新开始按钮var restart = document.getElementById("restart")// 当前玩家var currentPlayer = 'x';// 记录已下棋的次数var steps = 0;restart.onclick = function(){message.style.display = 'none';steps = 0;currentPlayer = 'x';gameBord.classList.remove('x','o');gameBord.classList.add('x');arr.forEach(item=>{item.classList.remove('x','o');item.onclick = function(){};clickCell(item);})}arr.forEach(item=>{clickCell(item);})function clickCell(item){item.onclick = function(){this.classList.add(currentPlayer)steps++;// 调用判赢函数判断是否获胜let isWin = checkWin(currentPlayer)if(isWin){message.style.display = 'block';winner.innerText = currentPlayer + ' 赢了!'return}// 判断平局if(steps == 9){message.style.display = 'block';winner.innerHTML = '平局'return}// 根据当前玩家,得到另外一个玩家currentPlayer = currentPlayer == 'x' ? 'o' : 'x';// 处理下一步提示gameBord.classList.remove('x','o');gameBord.classList.add(currentPlayer);// 事件执行一次this.onclick = null;}}// 封装判赢函数function checkWin(player){return winArr.some(item=>{let cellIndex1 = item[0];let cellIndex2 = item[1];let cellIndex3 = item[2];if(hasClass(arr[cellIndex1],player) &&hasClass(arr[cellIndex2],player) &&hasClass(arr[cellIndex3],player)){return true}return false})}// 封装hasClass函数function hasClass(el,name){return el.classList.contains(name);}

