前端小案例——网页井字棋
前言:我们在学习完了HTML、CSS和JavaScript之后,就会想着使用这三个东西去做一些小案例,不过又没有什么好的案例让我们去练手,本篇文章就提供里一个案例——网页井字棋。
✨✨✨这里是秋刀鱼不做梦的BLOG
✨✨✨想要了解更多内容可以访问我的主页秋刀鱼不做梦-CSDN博客
目录
写在前面
——该案例的全部代码已经放在文章末尾,有兴趣的读者可以到最后将全部代码复制到自己的编译器上运行,感受一下井字棋案例的最终效果!!!
——首先先让我们了解一下我们需要了解的前置知识:
1.HTML骨架
2.CSS装饰
1. 引入字体和全局样式
2.设置 body 样式
3 设置 .wrapper 样式
4.设置 .current-status 和其中的元素样式
5.设置 board 和 .cell 样式
6.鼠标悬浮时的图片效果
7.设置 game-end-overlay 样式
8 设置 .winning-message 样式
9. 重启按钮样式
3.JavaScript的交互
1. 获取页面元素
2. 初始化游戏状态
3. 所有获胜组合
4. 设置鼠标悬停时的样式
5. 在格子上放置图片
6. 切换回合
7. 更新当前状态
8. 检查是否获胜
9. 判断是否平局
10. 开始游戏
11. 结束游戏
12. 处理格子点击事件
13. 重置游戏
14. 启动游戏
——所有代码:
写在前面
——该案例的全部代码已经放在文章末尾,有兴趣的读者可以到最后将全部代码复制到自己的编译器上运行,感受一下井字棋案例的最终效果!!!
——首先先让我们了解一下我们需要了解的前置知识:
HTML内容
HTML基础标签的使用
CSS内容
通用样式重置 (
* { margin: 0; padding: 0; box-sizing: inherit; })Flexbox 布局 (
display: flex,justify-content,align-items)CSS Grid 布局 (
grid-template-columns,grid-template-rows,grid-gap)背景和图像处理 (
background-size,background-image)伪元素 (
::before)伪类
:hover动画与过渡效果 (
transition,transform)使用 CSS 类控制显示与隐藏
JavaScript内容
DOM 操作 (
document.getElementById(),document.querySelector())类操作 (
element.classList.add(),element.classList.remove())事件监听与处理 (
addEventListener(),removeEventListener())回调函数与事件处理
轮换玩家逻辑 (
unicornTurn变量)胜利判断与平局判断 (
winningCombinations,checkWin(),isDraw())函数设计与重用性
函数封装与模块化设计
重用性和功能拆分(如
startGame(),placeBeastImg())条件判断与数组方法
Array.prototype.some()和Array.prototype.every()
——了解完了实现这个小案例所需掌握的基本知识之后,让我们看一下该案例的最终结果:

——案例中所需的资源(读者可以右键进行下载):


——那么接下来就让我们正式进行案例讲解吧!!!
1.HTML骨架
——首先先让我们搭建一下HTML骨架,由于内容比较简单,所以我们就不进行详细讲解了,以下为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><!-- 引入外部样式表 --><link rel="stylesheet" href="./index.css"><!-- 延迟加载外部 JavaScript 文件 --><script defer src="./index.js"></script>
</head><body><!-- 游戏主要容器 --><div class="wrapper"><!-- 当前状态显示区域 --><div class="current-status" id="currentStatus"><!-- 当前游戏角色的图片 --><img src="./1.gif" id="currentBeastImg" alt=""><!-- 当前轮到哪方玩家 --><p> 's turn</p></div><!-- 游戏棋盘 --><div class="board" id="board"><!-- 游戏棋盘的每个格子,标记为 data-cell --><div class="cell" data-cell></div><div class="cell" data-cell></div><div class="cell" data-cell></div><div class="cell" data-cell></div><div class="cell" data-cell></div><div class="cell" data-cell></div><div class="cell" data-cell></div><div class="cell" data-cell></div><div class="cell" data-cell></div></div><!-- 游戏结束时显示的覆盖层 --><div class="game-end-overlay" id="gameEndOverlay"><!-- 游戏胜利信息显示区域 --><div class="winning-message" data-winning-message><p></p></div><!-- 重启游戏的按钮容器 --><div class="btn-container"><!-- 重启按钮 --><button class="reset-button" id="resetButton">play again</button></div></div></div>
</body></html>
在上面的代码中,我们对代码进行了讲解,读者可以根据注释对代码进行理解!!!
让我们看一下上述代码的最终结果:
嗯......看起来不尽人意,其实有很多的结构由于其内部没有内容,所以其高度为0,所以看起来和没有一样,不过接下来就让我们开始对其进行修饰吧!
2.CSS装饰
——在编写完了HTML代码之后,在让我们为其进行"乔装打扮"一下吧!
1. 引入字体和全局样式
/* 引入自定义字体 Bungee Inline */
@import url("https://fonts.googleapis.com/css2?family=Bungee+Inline&display=swap");/* 全局样式 */
* {padding: 0; /* 清除所有元素的内边距 */margin: 0; /* 清除所有元素的外边距 */box-sizing: inherit; /* 继承父元素的盒子模型 */
}
这部分的代码做了两件事,首先,它引入了一种叫 Bungee Inline 的自定义字体,这样我们就可以在页面中使用这个特别的字体来呈现文本了,接着,* 选择器把所有元素的默认 padding(内边距)和 margin(外边距)清除了,因为不同浏览器可能会有不同的默认值,这样做是为了让布局更加一致。
另外,我们使用 box-sizing: inherit; 确保所有元素的盒模型计算方式一致,避免意外的布局问题。
2.设置 body 样式
body {margin: 0;padding: 0;display: flex; /* 使用 Flexbox 布局 */justify-content: center; /* 内容水平居中 */align-items: center; /* 内容垂直居中 */height: 100vh; /* 高度为视口的 100% */text-align: center; /* 文字居中 */font-family: "Bungee Inline", cursive; /* 设置字体 */color: #f5f5f5; /* 文字颜色 */overflow: hidden; /* 防止页面出现滚动条 */background-image: linear-gradient(to top, #a8edea 0%, #ffc7d9 100%); /* 背景渐变色 */
}
这里我们设置了页面的基础样式,display: flex; 和 justify-content: center;、align-items: center; 这三行代码让页面的内容居中显示,既水平居中又垂直居中,然后,height: 100vh; 让页面高度占满整个屏幕,这样就不会出现页面高度不够的情况,背景设置了渐变色,从蓝色渐变到粉色,overflow: hidden; 让页面没有滚动条。
3 设置 .wrapper 样式
.wrapper {background-color: #55acee53; /* 背景颜色带透明度 */padding: 50px; /* 内边距 */
}
.wrapper 类是用来包裹整个游戏内容的容器。在这里,我们给它设置了一个带透明度的蓝色背景,通过 padding: 50px; 我们给容器添加了 50px 的内边距,避免内容紧贴容器的边缘。
4.设置 .current-status 和其中的元素样式
.current-status {display: flex; /* 使用 Flexbox 布局 */justify-content: center; /* 内容水平居中 */align-items: center; /* 内容垂直居中 */margin-bottom: 25px; /* 底部间距 */
}.current-status p {margin: 0 5px 0 0; /* 给 p 元素的右侧添加间距 */font-size: 24px; /* 设置字体大小 */
}.current-status img {width: auto; /* 自动设置图片宽度 */height: 32px; /* 设置图片高度 */
}
这个部分是用来显示当前游戏状态的,比如谁的回合,.current-status 使用了 Flexbox 布局,把文字和图片都居中对齐,然后通过 margin-bottom: 25px; 我们为这个部分和下面的内容增加了一点间距,p 标签控制了文字的大小和间距,而 img 标签则设置了图片的高度为 32px,确保它在界面上看起来合适。
5.设置 board 和 .cell 样式
.board {display: grid; /* 使用 CSS Grid 布局 */grid-template-columns: repeat(3, minmax(90px, 1fr)); /* 设置 3 列,每列最小宽度为 90px */grid-template-rows: repeat(3, minmax(90px, 1fr)); /* 设置 3 行,每行最小高度为 90px */grid-gap: 12px; /* 设置单元格之间的间距 */width: 100%; /* 宽度为 100% */height: 100%; /* 高度为 100% */max-width: 495px; /* 最大宽度为 495px */margin: 0 auto 15px; /* 居中对齐,并设置底部间距 */
}.cell {cursor: pointer; /* 鼠标悬浮时显示手形光标 */position: relative; /* 使单元格可相对定位 */background-color: #f5f5f5; /* 单元格背景色 */width: 90px; /* 宽度为 90px */height: 90px; /* 高度为 90px */opacity: 0.5; /* 初始透明度为 0.5 */transition: opacity 0.2s ease-in-out; /* 设置透明度变化的过渡效果 */
}.cell:hover {opacity: 1; /* 鼠标悬浮时完全不透明 */
}
在这里,我们首先为 .board 设置了一个 3x3 的网格布局,这样游戏棋盘就能整齐地排列起来,然后通过 grid-template-columns 和 grid-template-rows 我们定义了每一列和每一行的最小尺寸,grid-gap: 12px; 则让每个格子之间有了 12px 的间隙,使得棋盘看起来不那么拥挤。
每个单元格 .cell 都有固定的宽度和高度,并且设置了初始透明度为 0.5,cursor: pointer; 让鼠标悬浮时变成手形,表示用户可以点击,当鼠标悬停时,透明度会变为 1,单元格变得完全不透明,从而提供交互反馈。
6.鼠标悬浮时的图片效果
/* 鼠标悬浮时显示图片 */
.board.unicorn .cell:not(.dragon):not(.unicorn):hover::before,
.board.dragon .cell:not(.dragon):not(.unicorn):hover::before {content: "";width: 70%;height: 70%;display: block;position: absolute;background-repeat: no-repeat;top: 50%;left: 50%;transform: translate3d(-50%, -50%, 0);background-size: contain;opacity: 50%;
}.board.unicorn .cell:not(.dragon):hover::before {background-image: url("./1.gif"); /* 显示独角兽图片 */
}.board.dragon .cell:not(.unicorn):hover::before {background-image: url("./2.gif"); /* 显示龙的图片 */
}
这一段是让棋盘格子在鼠标悬停时显示图片效果,根据不同的游戏状态,我们通过 ::before 在单元格上显示一个小的动画图片,当玩家将鼠标悬停在一个空白格子上时,会看到一个对应的动画图像显示出来。
7.设置 game-end-overlay 样式
.game-end-overlay {display: none; /* 默认隐藏 */position: fixed; /* 固定定位 */top: 0;left: 0;right: 0;bottom: 0;background-color: #0d1021; /* 半透明背景 */
}.game-end-overlay.show {display: flex; /* 游戏结束时显示 */flex-direction: column;justify-content: center;align-items: center;
}
这部分控制了游戏结束时弹窗的显示,.game-end-overlay 默认是隐藏的,只有游戏结束时才会显示,当 show 类被添加时,弹窗会用 flex 布局显示出来,并且通过 justify-content 和 align-items 保证内容居中显示。
8 设置 .winning-message 样式
.winning-message {margin: -50px 0 20px; /* 设置外边距 */
}.winning-message img {width: 100px; /* 设置图片宽度 */
}.winning-message p {font-size: 48px; /* 设置字体大小 */margin: 0; /* 去除外边距 */
}
这里的 .winning-message 是用来显示获胜信息的部分,我们给它设置了一些外边距,确保它和其他内容不会紧挨在一起,图片的宽度被固定为 100px,文字的大小则是 48px。
9. 重启按钮样式
.reset-button {color: #f5f5f5;font-family: "Bungee Inline", cursive;font-size: 30px;white-space: nowrap;border: none;padding: 10px 20px;background-color: #a186be;box-shadow: 5px 5px 0 #55acee;cursor: pointer;transition: transform 0.1s ease-in-out;position: relative;
}.reset-button:hover {transform: scale(1.2); /* 鼠标悬浮时按钮变大 */
}.reset-button:active {top: 6px;left: 6px;box-shadow: none;background-color: #9475b5;
}
这部分代码定义了“重新开始”按钮的样式,在 hover 状态下,按钮会稍微放大而在 active 状态下(即按钮被点击时),按钮会下沉并去除阴影,背景色也会变化。
让我们看一下上述代码的最终结果:

嗯……好看很多了,至此,我们就了解了井字棋小游戏的CSS代码如何编写了!!!
3.JavaScript的交互
在编写完了HTML骨架和CSS样式之后,最后在让我们为其添加一些交互效果,来完成双人井字棋。
1. 获取页面元素
// 获取页面元素
const board = document.getElementById('board');
const cells = document.querySelectorAll('[data-cell]');
const currentStatus = document.getElementById('currentStatus');
const resetButton = document.getElementById('resetButton');
const gameEndOverlay = document.getElementById('gameEndOverlay');
const currentBeastStatusImg = document.getElementById('currentBeastImg');
const winningMessage = document.querySelector('[data-winning-message]');
const winningMessageText = document.querySelector('[data-winning-message] p');
const winningMessageImg = document.createElement('img');
——这段代码是用来获取 HTML 中的各种元素,主要是通过它们来操作游戏的显示和互动。例如:
board是游戏棋盘的容器,通过它我们可以控制整个棋盘的样式和逻辑。
cells用来获取所有的格子(格子上有data-cell属性),每个格子都是用户点击的地方。
currentStatus是用来显示当前回合玩家的地方,告诉你是哪一方的回合。
resetButton是“重新开始”按钮,点击后会重新初始化游戏。
gameEndOverlay是一个覆盖层,当游戏结束时,会显示在屏幕上,告诉玩家结果。
currentBeastStatusImg显示的是当前回合玩家的图标。
winningMessage负责显示游戏的获胜消息或平局消息。
winningMessageText和winningMessageImg主要用来动态更新显示的信息和获胜的图标。
2. 初始化游戏状态
// 初始化游戏状态
let gameIsLive = true;
let unicornTurn = true;
let winner = null; // 没有赢家
在这部分,我们设置了一些变量来控制游戏的状态:
gameIsLive是一个标志,表示游戏是否还在进行。如果为false,游戏就结束了。
unicornTurn表示当前回合是谁。
winner用来存储游戏的获胜者,开始时没有赢家,所以是null。
3. 所有获胜组合
// 所有获胜组合
const winningCombinations = [[0, 1, 2],[3, 4, 5],[6, 7, 8],[0, 3, 6],[1, 4, 7],[2, 5, 8],[0, 4, 8],[2, 4, 6]
];
这个数组 winningCombinations 包含了所有可能的获胜组合。它是一个二维数组,每个子数组里列出的是三格连成一条线的格子的索引。
例如,
[0, 1, 2]就表示第一行的三个格子。游戏的胜负就是看这些组合中的每一行格子是否全部被同一个玩家占领。
4. 设置鼠标悬停时的样式
// 设置鼠标悬停时的样式
const setBoardHoverClass = () => {board.classList.remove('unicorn');board.classList.remove('dragon');if (unicornTurn) {board.classList.add('unicorn');} else {board.classList.add('dragon');}
}
这个函数是用来设置鼠标悬停在棋盘格子上时的样式的。具体来说:
每次回合切换时,都会根据是哪个玩家的回合来修改棋盘的样式。
5. 在格子上放置图片
// 在格子上放置图片
const placeBeastImg = (cell, currentBeast) => {cell.classList.add(currentBeast);
}
这个函数的作用是将图标放到点击的格子里。它的逻辑很简单:
cell是被点击的格子。
currentBeast是当前玩家的标识。我们通过
classList.add把对应的玩家的类名添加到格子中,这样就能在格子中显示相应的图标。
6. 切换回合
// 切换回合
const swapTurns = () => {unicornTurn = !unicornTurn; // 切换回合
}
——当玩家点击了一个格子后,我们需要切换到另一个玩家的回合。这个函数通过反转 unicornTurn 来实现回合切换:
7. 更新当前状态
// 更新当前状态
const updateCurrentStatus = () => {if (unicornTurn) {currentBeastStatusImg.src = './1.gif';currentBeastStatusImg.alt = 'unicorn';} else {currentBeastStatusImg.src = './2.gif';currentBeastStatusImg.alt = 'dragon';}
}
——这部分代码的作用是更新页面上显示的当前回合玩家的图标:
8. 检查是否获胜
// 检查是否获胜
const checkWin = (currentBeast) => {return winningCombinations.some(combination => {return combination.every(i => {return cells[i].classList.contains(currentBeast); // 判断当前组合的所有格子是否都包含当前玩家的类})});
}
这里的 checkWin 函数是用来检查当前玩家是否获胜的:
winningCombinations包含了所有可能的获胜组合。通过
some和every方法,我们遍历这些组合,看看每一组的格子是否都被当前玩家占领。如果某一组格子都被占领了,就说明当前玩家获胜。
9. 判断是否平局
// 判断是否平局
const isDraw = () => {return [...cells].every(cell => {return cell.classList.contains('unicorn') || cell.classList.contains('dragon');})
}
isDraw 函数用来检查是否发生了平局:
如果所有的格子都被填满了,并且没有赢家,游戏就会是平局。
10. 开始游戏
// 开始游戏
const startGame = () => {cells.forEach(cell => {winningMessageImg.remove(); // 移除任何先前的胜利图片cell.classList.remove('unicorn', 'dragon'); // 移除所有格子的类cell.removeEventListener('click', handleCellClick); // 移除之前的点击事件cell.addEventListener('click', handleCellClick, { once: true }); // 给每个格子添加点击事件});setBoardHoverClass(); // 更新棋盘的悬停样式gameEndOverlay.classList.remove('show'); // 隐藏游戏结束的遮罩层
}
这部分是用来初始化或者重置游戏的:
每个格子都被清空,移除所有的
unicorn和dragon类。同时,移除先前的点击事件(防止重复绑定),然后重新给每个格子绑定点击事件。
更新棋盘的悬浮样式,确保玩家能看到当前回合的样式。
如果游戏已经结束,隐藏游戏结束的遮罩层。
11. 结束游戏
// 结束游戏
const endGame = (draw) => {if (draw) {winningMessageText.innerText = `draw!`; // 平局时的消息} else {winningMessageImg.src = unicornTurn ? './1.gif' : './2.gif'; // 根据回合显示获胜者的图片winningMessageImg.alt = unicornTurn ? 'unicorn' : 'dragon';winningMessage.insertBefore(winningMessageImg, winningMessageText); // 显示获胜者的图片winningMessageText.innerText = `wins!!!`; // 显示获胜者的文字}gameEndOverlay.classList.add('show'); // 显示游戏结束的遮罩层
}
这段代码在游戏结束时调用:
如果是平局,显示
draw!。如果有赢家,显示获胜玩家的图像和“wins!!!”。
最后,显示游戏结束的覆盖层,表示游戏已结束。
12. 处理格子点击事件
// 处理格子点击事件
const handleCellClick = (e) => {const cell = e.target;const currentBeast = unicornTurn ? 'unicorn' : 'dragon'; // 获取当前玩家placeBeastImg(cell, currentBeast); // 在格子中放置当前玩家的标识if (checkWin(currentBeast)) {endGame(false); // 如果当前玩家获胜,结束游戏} else if (isDraw()) {endGame(true); // 如果是平局,结束游戏} else {swapTurns(); // 切换回合updateCurrentStatus(); // 更新当前状态setBoardHoverClass(); // 更新棋盘的悬停样式}
}
这段代码处理每次玩家点击格子时的逻辑:
获取当前玩家的标识。
在点击的格子中放置当前玩家的图标。
检查是否有玩家获胜或是否是平局。如果有获胜者或平局,调用
endGame结束游戏。如果游戏没有结束,切换到下一个回合,更新游戏状态,并重新设置棋盘的悬浮效果。
13. 重置游戏
// 重置游戏
resetButton.addEventListener('click', startGame);
——这个事件监听器让玩家可以通过点击“重新开始”按钮来重置游戏,游戏会回到初始状态。
14. 启动游戏
// 开始游戏
startGame();
——最后,我们调用 startGame 来初始化并启动游戏。这是游戏启动时最先调用的函数,确保游戏界面和逻辑都设置好。
至此,我们就讲解完了JavaScript与用户交互方面的代码了!!!
让我们看一下最终的效果:

——所有代码:
<!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><style>@import url("https://fonts.googleapis.com/css2?family=Bungee+Inline&display=swap");* {padding: 0;margin: 0;box-sizing: inherit;}body {margin: 0;padding: 0;display: flex;justify-content: center;align-items: center;height: 100vh;text-align: center;font-family: "Bungee Inline", cursive;color: #f5f5f5;overflow: hidden;background-image: linear-gradient(to top, #a8edea 0%, #ffc7d9 100%);}.wrapper {background-color: #55acee53;padding: 50px;}.current-status {display: flex;justify-content: center;align-items: center;margin-bottom: 25px;}.current-status p {margin: 0 5px 0 0;font-size: 24px;}.current-status img {width: auto;height: 32px;}.board {display: grid;grid-template-columns: repeat(3, minmax(90px, 1fr));grid-template-rows: repeat(3, minmax(90px, 1fr));grid-gap: 12px;width: 100%;height: 100%;max-width: 495px;margin: 0 auto 15px;}/* 鼠标悬浮的时候显示图片 */.board.unicorn .cell:not(.dragon):not(.unicorn):hover::before,.board.dragon .cell:not(.dragon):not(.unicorn):hover::before {content: "";width: 70%;height: 70%;display: block;position: absolute;background-repeat: no-repeat;top: 50%;left: 50%;transform: translate3d(-50%, -50%, 0);background-size: contain;opacity: 50%;}.board.unicorn .cell:not(.dragon):hover::before {background-image: url("./1.gif");}.board.dragon .cell:not(.unicorn):hover::before {background-image: url("./2.gif");}.cell {cursor: pointer;position: relative;background-color: #f5f5f5;width: 90px;height: 90px;opacity: 0.5;transition: opacity 0.2s ease-in-out;}.cell:hover {opacity: 1;}.cell.dragon,.cell.unicorn {opacity: 1;position: relative;cursor: not-allowed;}.cell.dragon::before,.cell.unicorn::before {content: "";width: 70%;height: 70%;display: block;position: absolute;background-repeat: no-repeat;top: 50%;left: 50%;transform: translate3d(-50%, -50%, 0);background-size: contain;}.cell.dragon::before {background-image: url("./2.gif");}.cell.unicorn::before {background-image: url("./1.gif");}.game-end-overlay {display: none;position: fixed;top: 0;left: 0;right: 0;bottom: 0;background-color: #0d1021;}.game-end-overlay.show {display: flex;flex-direction: column;justify-content: center;align-items: center;}.winning-message {margin: -50px 0 20px;}.winning-message img {width: 100px;}.winning-message p {font-size: 48px;margin: 0;}.btn-container {position: relative;}.reset-button {color: #f5f5f5;font-family: "Bungee Inline", cursive;font-size: 30px;white-space: nowrap;border: none;padding: 10px 20px;background-color: #a186be;box-shadow: 5px 5px 0 #55acee;cursor: pointer;transition: transform 0.1s ease-in-out;position: relative;}.reset-button:hover {transform: scale(1.2);}.reset-button:active {top: 6px;left: 6px;box-shadow: none;background-color: #9475b5;}</style>
</head><body><div class="wrapper"><div class="current-status" id="currentStatus"><img src="./1.gif" id="currentBeastImg" alt=""><p> 's turn</p></div><div class="board" id="board"><div class="cell" data-cell></div><div class="cell" data-cell></div><div class="cell" data-cell></div><div class="cell" data-cell></div><div class="cell" data-cell></div><div class="cell" data-cell></div><div class="cell" data-cell></div><div class="cell" data-cell></div><div class="cell" data-cell></div></div><div class="game-end-overlay" id="gameEndOverlay"><div class="winning-message" data-winning-message><p></p></div><div class="btn-container"><button class="reset-button" id="resetButton">play again</button></div></div></div><script>// 获取页面元素const board = document.getElementById('board');const cells = document.querySelectorAll('[data-cell]');const currentStatus = document.getElementById('currentStatus');const resetButton = document.getElementById('resetButton');const gameEndOverlay = document.getElementById('gameEndOverlay');const currentBeastStatusImg = document.getElementById('currentBeastImg');const winningMessage = document.querySelector('[data-winning-message]');const winningMessageText = document.querySelector('[data-winning-message] p');const winningMessageImg = document.createElement('img');// 初始化游戏状态let gameIsLive = true;let unicornTurn = true;let winner = null;// 所有获胜组合const winningCombinations = [[0, 1, 2],[3, 4, 5],[6, 7, 8],[0, 3, 6],[1, 4, 7],[2, 5, 8],[0, 4, 8],[2, 4, 6]];// 设置鼠标悬停时的样式const setBoardHoverClass = () => {board.classList.remove('unicorn');board.classList.remove('dragon');if (unicornTurn) {board.classList.add('unicorn');} else {board.classList.add('dragon');}}// 在格子上放置图片const placeBeastImg = (cell, currentBeast) => {cell.classList.add(currentBeast);}// 切换回合const swapTurns = () => {unicornTurn = !unicornTurn;}// 更新当前状态const updateCurrentStatus = () => {if (unicornTurn) {currentBeastStatusImg.src = './1.gif';currentBeastStatusImg.alt = 'unicorn';} else {currentBeastStatusImg.src = './2.gif';currentBeastStatusImg.alt = 'dragon';}}// 检查是否获胜const checkWin = (currentBeast) => {return winningCombinations.some(combination => {return combination.every(i => {return cells[i].classList.contains(currentBeast);})});}// 判断是否平局const isDraw = () => {return [...cells].every(cell => {return cell.classList.contains('unicorn') || cell.classList.contains('dragon');})}// 开始游戏const startGame = () => {cells.forEach(cell => {winningMessageImg.remove();cell.classList.remove('unicorn');cell.classList.remove('dragon');cell.removeEventListener('click', handleCellClick);cell.addEventListener('click', handleCellClick, { once: true });});setBoardHoverClass();gameEndOverlay.classList.remove('show');}// 结束游戏const endGame = (draw) => {if (draw) {winningMessageText.innerText = `draw!`;} else {winningMessageImg.src = unicornTurn ? './1.gif' : './2.gif';winningMessageImg.alt = unicornTurn ? 'unicorn' : 'dragon';winningMessage.insertBefore(winningMessageImg, winningMessageText);winningMessageText.innerText = `wins!!!`}gameEndOverlay.classList.add('show');}// 处理格子点击事件const handleCellClick = (e) => {const cell = e.target;const currentBeast = unicornTurn ? 'unicorn' : 'dragon';placeBeastImg(cell, currentBeast);if (checkWin(currentBeast)) {endGame(false);} else if (isDraw()) {endGame(true);} else {swapTurns();updateCurrentStatus();setBoardHoverClass();}}// 重置游戏resetButton.addEventListener('click', startGame);// 开始游戏startGame();</script>
</body></html>
以上就是本篇文章全部内容了
相关文章:
前端小案例——网页井字棋
前言:我们在学习完了HTML、CSS和JavaScript之后,就会想着使用这三个东西去做一些小案例,不过又没有什么好的案例让我们去练手,本篇文章就提供里一个案例——网页井字棋。 ✨✨✨这里是秋刀鱼不做梦的BLOG ✨✨✨想要了解更多内容可…...
ComfyUI-PromptOptimizer:文生图提示优化节点
ComfyUI-PromptOptimizer 是 ComfyUI 的一个自定义节点,旨在优化文本转图像模型的提示。它将用户输入的提示转换为更详细、更多样化、更生动的描述,使其更适合生成高质量的图像。无需本地模型。 1、功能 提示优化:优化用户输入的提示以生成…...
AudioGPT全新的 音频内容理解与生成系统
AudioGPT全新的 音频内容理解与生成系统 ChatGPT、GPT-4等大型语言模型 (LLM) 在语言理解、生成、交互和推理方面表现出的非凡能力,引起了学界和业界的极大关注,也让人们看到了LLM在构建通用人工智能 (AGI) 系统方面的潜力。 现有的GPT模型具有极高的语言生成能力,是目前最…...
thinkphp6 + redis实现大数据导出excel超时或内存溢出问题解决方案
redis下载安装(window版本) 参考地址:https://blog.csdn.net/Ci1693840306/article/details/144214215 php安装redis扩展 参考链接:https://blog.csdn.net/jianchenn/article/details/106144313 解决思路:࿰…...
Hexo + NexT + Github搭建个人博客
文章目录 一、 安装二、配置相关项NexT config更新主题主题样式本地实时预览常用命令 三、主题设置1.侧边栏2.页脚3.帖子发布字数统计 4.自定义自定义页面Hexo 的默认页面自定义 404 页自定义样式 5.杂项搜索服务 四、第三方插件NexT 自带插件评论系统阅读和访问人数统计 五、部…...
使用Sum计算Loss和解决梯度累积(Gradient Accumulation)的Bug
使用Sum计算Loss和解决梯度累积的Bug 学习 https://unsloth.ai/blog/gradient:Bugs in LLM Training - Gradient Accumulation Fix 这篇文章的记录。 在深度学习训练过程中,尤其是在大批量(large batch)训练中,如何高…...
基于本地消息表实现分布式事务
假设我们有一个电商系统,包含订单服务和库存服务。当用户下单时,需要在订单服务中创建订单,同时在库存服务中扣减库存。这是一个典型的分布式事务场景,我们需要保证这两个操作要么都成功,要么都失败,以保证数据的最终一致性。 项目结构: 订单服务(Order Service)库存服务(Inv…...
Web3与加密技术的结合:增强个人隐私保护的未来趋势
随着互联网的快速发展,个人隐私和数据安全问题越来越受到关注。Web3作为新一代互联网架构,凭借其去中心化的特性,为个人隐私保护提供了全新的解决方案。而加密技术则是Web3的重要组成部分,进一步增强了隐私保护的能力。本文将探讨…...
广播网络实验
1 实验内容 1、构建星性拓扑下的广播网络,实现hub各端口的数据广播,验证网络的连通性并测试网络效率 2、构建环形拓扑网络,验证该拓扑下结点广播会产生数据包环路 2 实验流程与结果分析 2.1 实验环境 ubuntu、mininet、xterm、wireshark、iperf 2.2 实验方案与结果分析…...
Vscode——SSH连接不上的一种解决办法
一、完整报错: > @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ > IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY! > Someone could be eavesdropping on you right now (man-in-the...
ChatGPT大模型极简应用开发-目录
引言 要理解 ChatGPT,了解其背后的 Transformer 架构和 GPT 技术一路的演进则变得非常必要。 ChatGPT 背后的 LLM 技术使普通人能够通过自然语言完成过去只能由程序员通过编程语言实现的任务,这是一场巨大的变革。然而,人类通常容易高估技术…...
EI Scopus双检索 | 2025年第四届信息与通信工程国际会议(JCICE 2025)
会议简介 Brief Introduction 2025年第四届信息与通信工程国际会议(JCICE 2025) 会议时间:2025年7月25日-27日 召开地点:中国哈尔滨 大会官网:www.jcice.org 由黑龙江大学和成都信息工程大学主办,江苏科技大学协办的2025年第四届信…...
重学SpringBoot3-Spring Retry实践
更多SpringBoot3内容请关注我的专栏:《SpringBoot3》 期待您的点赞??收藏评论 重学SpringBoot3-Spring Retry实践 1. 简介2. 环境准备3. 使用方式 3.1 注解方式 基础使用自定义重试策略失败恢复机制重试和失败恢复效果注意事项 3.2 编程式使用3.3 监听重试过程 监…...
TiDB 和 MySQL 的关系:这两者到底有什么不同和联系?
TiDB 和 MySQL 的关系:这两者到底有什么不同和联系? 在了解 TiDB 和 MySQL 之间的关系时,很多人可能会有疑问:这两个数据库到底有什么区别和联系?是不是 TiDB 就是 MySQL 的升级版?或者 TiDB 是一种“替代…...
【Java】JDK17的下载安装(与JDK1.8相互切换)
本文以参考以下链接为主:JDK17 如果上述操作不生效,请看以下操作: 添加以下变量并移动到最上面即可...
CSS3 3D 转换介绍
CSS3 中的 3D 转换提供了一种在二维屏幕上呈现三维效果的方式,主要包括translate3d、rotate3d、scale3d等转换函数,下面来详细介绍: 1. 3D 转换的基本概念 坐标系 在 CSS3 的 3D 空间中,使用的是右手坐标系。X 轴是水平方向&…...
Vue3 Element-Plus el-tree 右键菜单组件
参考代码:实现Vue3Element-Plus(tree、table)右键菜单组件 这篇文章的代码确实能用,但是存在错误,修正后的代码: <template><div style"text-align: right"><el-icon size"12" color"#…...
鸿蒙学习构建视图的基本语法(二)
一、层叠布局 // 图片 本地图片和在线图片 Image(https://developer.huawei.com/allianceCmsResource/resource/HUAWEI_Developer_VUE/images/080662.png) Entry Component//自适应伸缩 设置layoutWeight属性的子元素与兄弟元素 会按照权重进行分配主轴的空间// Position s…...
python-leetcode-存在重复元素 II
219. 存在重复元素 II - 力扣(LeetCode) class Solution:def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:seen set()for i, num in enumerate(nums):if num in seen:return Trueseen.add(num)if len(seen) > k:seen.remove…...
P6周:VGG-16算法-Pytorch实现人脸识别
🍨 本文为🔗365天深度学习训练营中的学习记录博客🍖 原作者:K同学啊 我的环境 语言环境:Python 3.8.12 编译器:jupyter notebook 深度学习环境:torch 1.12.0cu113 一、前期准备 1.设置GPU im…...
UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...
React 第五十五节 Router 中 useAsyncError的使用详解
前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...
从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...
《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...
Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...
大模型多显卡多服务器并行计算方法与实践指南
一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...
大数据学习(132)-HIve数据分析
🍋🍋大数据学习🍋🍋 🔥系列专栏: 👑哲学语录: 用力所能及,改变世界。 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言Ǵ…...
面向无人机海岸带生态系统监测的语义分割基准数据集
描述:海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而,目前该领域仍面临一个挑战,即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...
uniapp 字符包含的相关方法
在uniapp中,如果你想检查一个字符串是否包含另一个子字符串,你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的,但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...

