效果:

Video_2021-02-08_211721.wmv (380.82KB)

样式:

  1. <h1>Tic Tac Toe</h1>
  2. <div class="container">
  3. <!-- 游戏面板(棋盘) -->
  4. <div id="bord" class="game-board x">
  5. <div class="row">
  6. <div class="cell"></div>
  7. <div class="cell"></div>
  8. <div class="cell"></div>
  9. </div>
  10. <div class="row">
  11. <div class="cell"></div>
  12. <div class="cell"></div>
  13. <div class="cell"></div>
  14. </div>
  15. <div class="row">
  16. <div class="cell"></div>
  17. <div class="cell"></div>
  18. <div class="cell"></div>
  19. </div>
  20. </div>
  21. <!-- 游戏获胜信息面板 -->
  22. <div id="message" class="game-message">
  23. <p id="winner">X 赢了!</p>
  24. <button id="restart">重新开始</button>
  25. </div>
  26. </div>
  1. p {
  2. margin: 0;
  3. }
  4. body {
  5. background-color: #f9f2e7;
  6. }
  7. /* 标题 */
  8. h1 {
  9. text-align: center;
  10. font-size: 60px;
  11. color: #477998;
  12. }
  13. /* 游戏内容容器 */
  14. .container {
  15. position: relative;
  16. width: 471px;
  17. height: 471px;
  18. margin: 0 auto;
  19. }
  20. /* 游戏获胜信息面板 */
  21. .game-message {
  22. display: none;
  23. position: absolute;
  24. top: 0;
  25. left: 0;
  26. right: 0;
  27. bottom: 0;
  28. background-color: rgba(69, 133, 136, 0.4);
  29. text-align: center;
  30. }
  31. /* winner 获胜者 */
  32. .game-message p {
  33. margin: 180px 0 40px 0;
  34. color: #fff;
  35. font-size: 50px;
  36. }
  37. /* 重新开始游戏按钮 */
  38. .game-message button {
  39. color: #517304;
  40. border-color: #517304;
  41. width: 110px;
  42. height: 40px;
  43. font-size: 20px;
  44. cursor: pointer;
  45. }
  46. /* 游戏面板棋盘 */
  47. .game-board {
  48. width: 471px;
  49. height: 471px;
  50. }
  51. .game-board.x .cell:not(.x):not(.o):hover::before {
  52. content: 'X';
  53. color: lightgray;
  54. }
  55. .game-board.o .cell:not(.x):not(.o):hover::before {
  56. content: 'O';
  57. color: lightgray;
  58. }
  59. /* 棋盘 - 行 */
  60. .row {
  61. display: flex;
  62. }
  63. .row:last-child .cell {
  64. border-bottom: 0;
  65. }
  66. /* 棋盘 - 单元格 */
  67. .cell {
  68. flex: 1;
  69. box-sizing: border-box;
  70. width: 157px;
  71. height: 157px;
  72. line-height: 157px;
  73. border-right: 6px solid #546363;
  74. border-bottom: 6px solid #546363;
  75. text-align: center;
  76. cursor: pointer;
  77. font-size: 88px;
  78. font-family: 'Gill Sans', 'Gill Sans MT', Calibri, sans-serif;
  79. }
  80. .cell:last-child {
  81. border-right: 0;
  82. }
  83. /* x 玩家 */
  84. .cell.x::before {
  85. content: 'X';
  86. color: #01a8c6;
  87. }
  88. /* o 玩家 */
  89. .cell.o::before {
  90. content: 'O';
  91. color: #8fbe01;
  92. }

第一步 单元格点击

1、获取所有的单元格列表。
2、遍历单元格列表,给每一个单元格添加点击事件。
3、给当前被点击的单元格添加类名 x 或 o。

  1. var cells = document.getElementsByClassName("cell");
  2. var arr = [...cells]
  3. arr.forEach(item=>{
  4. item.onclick = function(){
  5. this.classList.add("x")
  6. this.onclick = null; //优化使点击事件只执行一次
  7. }
  8. })

第二步 切换玩家

1、创建一个存储当前玩家的变量(currentPlayer),默认值为:x。
2、将添加给单元格时写死的类名x,替换为变量(currentPlayer)的值。
3、切换到另一个玩家:在添加类名(下棋完成一步)后,根据当前玩家,得到另外一个玩家。
4、处理下一步提示:移除游戏面板中的x和o类名,添加下一个玩家对应的类名。

  1. var gameBord = document.getElementById("bord");
  2. var cells = document.getElementsByClassName("cell");
  3. var arr = [...cells]
  4. var currentPlayer = 'x'
  5. arr.forEach(item=>{
  6. item.onclick = function(){
  7. this.classList.add(currentPlayer)
  8. // 根据当前玩家,得到另外一个玩家
  9. currentPlayer = currentPlayer == 'x' ? 'o' : 'x';
  10. // 处理下一步提示
  11. gameBord.classList.remove('x','o');
  12. gameBord.classList.add(currentPlayer);
  13. // 事件执行一次
  14. this.onclick = null;
  15. }
  16. })

第三步 使用枚举修改当前玩家

1、创建字符串枚举(Player),提供X和O两个成员。
2、将成员X的值设置为:’x’(类名);将成员O的值设置为:’o’(类名)。
3、将变量(currentPlayer)的类型设置为Player枚举类型,默认值为Player.X。
4、将所有用到x和o的地方全部使用枚举成员代替。

  1. ## enum.ts
  2. enum Player {
  3. X = 'x',
  4. O = 'o'
  5. }
  1. //枚举部分
  2. var Player;
  3. (function (Player) {
  4. Player["X"] = "x";
  5. Player["O"] = "o";
  6. })(Player || (Player = {}));
  7. var gameBord = document.getElementById("bord");
  8. var cells = document.getElementsByClassName("cell");
  9. var arr = [...cells]
  10. // 当前玩家
  11. var currentPlayer = Player.X
  12. arr.forEach(item=>{
  13. item.onclick = function(){
  14. this.classList.add(currentPlayer)
  15. // 根据当前玩家,得到另外一个玩家
  16. currentPlayer = currentPlayer == Player.X ? Player.O : Player.X;
  17. // 处理下一步提示
  18. gameBord.classList.remove(Player.X,Player.O);
  19. gameBord.classList.add(currentPlayer);
  20. // 事件执行一次
  21. this.onclick = null;
  22. }
  23. })

第四步 实现判赢函数

1、使用some方法遍历数组,并将some方法的返回值作为判赢函数的返回结果。
2、在some方法的回调函数中,获取到每种获胜情况对应的3个单元格。
3、判断这3个单元格元素是否同时包含当前玩家的类名。
4、如果包含(玩家获胜),就在回调函数中返回true停止循环;否则,返回false,继续下一次循环。

  1. var winArr = [
  2. [0,1,2],[3,4,5],[6,7,8], //横
  3. [0,3,6],[1,4,7],[2,5,8], //竖
  4. [0,4,8],[2,4,6] //斜
  5. ]
  6. var gameBord = document.getElementById("bord");
  7. var cells = document.getElementsByClassName("cell");
  8. var arr = [...cells]
  9. // 当前玩家
  10. var currentPlayer = 'x'
  11. arr.forEach(item=>{
  12. item.onclick = function(){
  13. this.classList.add(currentPlayer)
  14. // 调用判赢函数判断是否获胜
  15. let isWin = checkWin(currentPlayer)
  16. if(isWin){
  17. console.log("当前玩家获胜了",currentPlayer)
  18. }
  19. // 根据当前玩家,得到另外一个玩家
  20. currentPlayer = currentPlayer == 'x' ? 'o' : 'x';
  21. // 处理下一步提示
  22. gameBord.classList.remove('x','o');
  23. gameBord.classList.add(currentPlayer);
  24. // 事件执行一次
  25. this.onclick = null;
  26. }
  27. })
  28. // 封装判赢函数
  29. function checkWin(player){
  30. return winArr.some(item=>{
  31. let cellIndex1 = item[0];
  32. let cellIndex2 = item[1];
  33. let cellIndex3 = item[2];
  34. if(
  35. hasClass(arr[cellIndex1],player) &&
  36. hasClass(arr[cellIndex2],player) &&
  37. hasClass(arr[cellIndex3],player)
  38. ){
  39. return true
  40. }
  41. return false
  42. })
  43. }
  44. // 封装hasClass函数
  45. function hasClass(el,name){
  46. return el.classList.contains(name);
  47. }

第五步 判断平局

1、创建变量(steps),默认值为0。
2、在玩家下棋后,让steps加1。
3、在判赢的代码后面,判断steps是否等于9。
4、如果等于9说明是平局,游戏结束,就直接return,不再执行后续代码。

  1. var winArr = [
  2. [0,1,2],[3,4,5],[6,7,8], //横
  3. [0,3,6],[1,4,7],[2,5,8], //竖
  4. [0,4,8],[2,4,6] //斜
  5. ]
  6. var gameBord = document.getElementById("bord");
  7. var cells = document.getElementsByClassName("cell");
  8. var arr = [...cells]
  9. // 当前玩家
  10. var currentPlayer = 'x';
  11. // 记录已下棋的次数
  12. var steps = 0;
  13. arr.forEach(item=>{
  14. item.onclick = function(){
  15. this.classList.add(currentPlayer)
  16. steps++;
  17. // 调用判赢函数判断是否获胜
  18. let isWin = checkWin(currentPlayer)
  19. if(isWin){
  20. console.log("当前玩家获胜了",currentPlayer)
  21. return
  22. }
  23. // 判断平局
  24. if(steps == 9){
  25. return
  26. }
  27. // 根据当前玩家,得到另外一个玩家
  28. currentPlayer = currentPlayer == 'x' ? 'o' : 'x';
  29. // 处理下一步提示
  30. gameBord.classList.remove('x','o');
  31. gameBord.classList.add(currentPlayer);
  32. // 事件执行一次
  33. this.onclick = null;
  34. }
  35. })

第六步 展示获胜信息

1、获取到与获胜信息相关的两个DOM元素:1 #message 2 #winner。
2、显示获胜信息面板(通过style属性实现)。
3、展示获胜信息:如果获胜,展示“x赢了!”或“o赢了!”;如果是平局,展示“平局”。

  1. // 获胜信息面板
  2. var message = document.getElementById("message");
  3. // 获胜者
  4. var winner = document.getElementById("winner");
  5. // 当前玩家
  6. var currentPlayer = 'x';
  7. // 记录已下棋的次数
  8. var steps = 0;
  9. arr.forEach(item=>{
  10. item.onclick = function(){
  11. this.classList.add(currentPlayer)
  12. steps++;
  13. // 调用判赢函数判断是否获胜
  14. let isWin = checkWin(currentPlayer)
  15. if(isWin){
  16. message.style.display = 'block';
  17. winner.innerText = currentPlayer + ' 赢了!'
  18. return
  19. }
  20. // 判断平局
  21. if(steps == 9){
  22. message.style.display = 'block';
  23. winner.innerHTML = '平局'
  24. return
  25. }
  26. // 根据当前玩家,得到另外一个玩家
  27. currentPlayer = currentPlayer == 'x' ? 'o' : 'x';
  28. // 处理下一步提示
  29. gameBord.classList.remove('x','o');
  30. gameBord.classList.add(currentPlayer);
  31. // 事件执行一次
  32. this.onclick = null;
  33. }
  34. })

第七步 重新游戏

1、获取到重新开始按钮(#restart),并绑定点击事件。
2、在点击事件中,重置游戏数据。
3、隐藏获胜信息、清空棋盘、移除单元格点击事件、重新给单元格绑定点击事件。
4、重置下棋次数、重置默认玩家为x、重置下棋提示为x。

  1. // 判赢数组
  2. var winArr = [
  3. [0,1,2],[3,4,5],[6,7,8], //横
  4. [0,3,6],[1,4,7],[2,5,8], //竖
  5. [0,4,8],[2,4,6] //斜
  6. ]
  7. var gameBord = document.getElementById("bord");
  8. var cells = document.getElementsByClassName("cell");
  9. var arr = [...cells];
  10. // 获胜信息面板
  11. var message = document.getElementById("message");
  12. // 获胜者
  13. var winner = document.getElementById("winner");
  14. // 重新开始按钮
  15. var restart = document.getElementById("restart")
  16. // 当前玩家
  17. var currentPlayer = 'x';
  18. // 记录已下棋的次数
  19. var steps = 0;
  20. restart.onclick = function(){
  21. message.style.display = 'none';
  22. steps = 0;
  23. currentPlayer = 'x';
  24. gameBord.classList.remove('x','o');
  25. gameBord.classList.add('x');
  26. arr.forEach(item=>{
  27. item.classList.remove('x','o');
  28. item.onclick = function(){};
  29. clickCell(item);
  30. })
  31. }
  32. arr.forEach(item=>{
  33. clickCell(item);
  34. })
  35. function clickCell(item){
  36. item.onclick = function(){
  37. this.classList.add(currentPlayer)
  38. steps++;
  39. // 调用判赢函数判断是否获胜
  40. let isWin = checkWin(currentPlayer)
  41. if(isWin){
  42. message.style.display = 'block';
  43. winner.innerText = currentPlayer + ' 赢了!'
  44. return
  45. }
  46. // 判断平局
  47. if(steps == 9){
  48. message.style.display = 'block';
  49. winner.innerHTML = '平局'
  50. return
  51. }
  52. // 根据当前玩家,得到另外一个玩家
  53. currentPlayer = currentPlayer == 'x' ? 'o' : 'x';
  54. // 处理下一步提示
  55. gameBord.classList.remove('x','o');
  56. gameBord.classList.add(currentPlayer);
  57. // 事件执行一次
  58. this.onclick = null;
  59. }
  60. }
  61. // 封装判赢函数
  62. function checkWin(player){
  63. return winArr.some(item=>{
  64. let cellIndex1 = item[0];
  65. let cellIndex2 = item[1];
  66. let cellIndex3 = item[2];
  67. if(
  68. hasClass(arr[cellIndex1],player) &&
  69. hasClass(arr[cellIndex2],player) &&
  70. hasClass(arr[cellIndex3],player)
  71. ){
  72. return true
  73. }
  74. return false
  75. })
  76. }
  77. // 封装hasClass函数
  78. function hasClass(el,name){
  79. return el.classList.contains(name);
  80. }