Commit 08f2679a authored by 邱旭's avatar 邱旭

联机初版,代码未整理

parent 6c9809d8
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>
\ No newline at end of file
# 00.前言 - 井子棋对战版
#### 2021.06.16
## 井子棋介绍
井子棋游戏的棋盘为三横三竖的格子,黑白玩家轮流下棋
在横或竖或斜着的方向,三颗同色棋子连成一线则游戏结束且该玩家胜利
如果棋盘上九个格子都被占,且没有完成上述条件则为和棋
## 前言
完成井子棋对战版,实现对战,观战,上下坐
不要求完美的容错
![00_01.png](img/00_01.png)
## 预备知识
- Html
- JavaScript
- 了解NodeJs
- 了解WebSocket
## 功能分解
主要分为C端和S端,先完成单机功能,后完成联机部分
![00_02.png](img/00_02.png)
# 01.单机功能 - 井子棋对战版
## 绘制棋盘
井子棋游戏的棋盘为三横三竖的格子
使用二维3×3或一维度1×9数组来表示棋盘
如下代码所示
本案例选择二维数组的表示方法,更易于大家理解
```javascript
const map = [
[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
];
const map = [
0, 0, 0,
0, 0, 0,
0, 0, 0,
];
```
遍历棋盘,创建棋盘,并设置相关样式
```javascript
// 创建一个div作为棋盘的容器
const mapDiv = document.createElement("div");
// 遍历棋盘
for (let row = 0; row < 3; row++) {
for (let col = 0; col < 3; col++) {
const grid = document.createElement("div"); // 创建一个div作为格子
grid.className = "grid"; // 给格子一个样式
mapDiv.appendChild(grid); // 加到地图容器里
}
mapDiv.appendChild(document.createElement("br")); // 换行
}
document.body.appendChild(mapDiv); // 将该容器插入到html中完成渲染
```
```css
html, body {
margin: 0;
padding: 0;
background-color: #222222; /* 给个背景,方便视觉观察 */
width: 100%;
height: 100%;
display: flex; /* 居个中 */
align-items: center;
justify-content: center;
overflow: hidden;
}
.grid {
width: 100px; /* size */
height: 100px;
display: inline-block; /* 让div不要单独占一行 */
background-color: darkgreen; /* 给个背景看得出是格子 */
vertical-align: bottom; /* 可以去除行与行之间的空白 */
border: 2px solid whitesmoke; /* 加个边框区分格子 */
}
```
完成效果如下
![img.png](img/01_01.png)
## 实现点击格子添加棋子
- 将格子都保存起来,方便之后都操作
- 添加点击事件
- 设置一个flag区分当前是白棋还是黑棋
- 根据点击格子的行列在这个格子上添加棋子,并更新棋盘信息
- 点击之后切换flag
可以直接将格子保存在map数组里
并将行列信息,当前格子的棋子信息保存在格子里
同时添加点击事件 修改渲染地图的代码如下,并写出点击函数
```javascript
/**
* 当前颜色,0 啥也没有,1 白棋,2 黑棋
*/
let color = 1; // 初始化为1表示白棋先下
for (let row = 0; row < 3; row++) {
for (let col = 0; col < 3; col++) {
const grid = document.createElement("div"); // 创建一个div作为格子
grid.className = "grid"; // 给格子一个样式
map[row][col] = grid; // 直接保存在map里,方便维护
grid.row = row; // 保存行列信息
grid.col = col;
grid.color = 0; // 表示为当前格子棋子的颜色
grid.addEventListener("click", clickGrid); // 添加点击事件
mapDiv.appendChild(grid); // 加到地图容器里
}
mapDiv.appendChild(document.createElement("br")); // 换行
}
document.body.appendChild(mapDiv); // 将该容器插入到html中完成渲染
/**
* 点击格子
* @param {MouseEvent} e
*/
function clickGrid(e) {
const grid = e.target; // 拿到点击的格子
if (grid.color === 0) { // 如果是空白的则干点事情
// 给格子添加一个css样式,用于渲染棋子
grid.className = `grid ${color === 1 ? "white" : "black"}`;
grid.color = color; // 更新棋盘信息
color = 3 - color; // 交换棋权
}
}
```
完成棋子的css部分
利用伪元素绘制棋子
```css
.grid::before {
content: "";
width: 80px;
height: 80px;
display: block;
border-radius: 50%;
margin: 10px auto 0;
box-sizing: border-box;
}
.grid.white::before {
background-color: whitesmoke;
border: 2px solid black;
}
.grid.black::before {
background-color: black;
border: 2px solid whitesmoke;
}
```
效果如下
![img.png](img/01_02.png)
## 实现局面判断
在横或竖或斜着的方向,三颗同色棋子连成一线则游戏结束且该玩家胜利
如果棋盘上九个格子都被占,且没有完成上述条件则为和棋
- 写一个judge函数,在函数内实现局面判断
- 每下一步棋,调用一次judge
#### 分析如何判断输赢
> 棋子的flag为0或1或2
>
> 三数中一个数与其他任意两个数进行`&`运算 得到结果为0,如 `0 & 1 = 0` `1 & 2 = 0`
>
> 相同的数进行`&`运算的到其本身,如 `1 & 1 = 1` `2 & 2 = 2`
>
> 可以将每行没列三个和两个斜线的三个数进行`&`运算,结果为0则没有同色棋子连成一线,结果为1则白色棋子连成一线,为2则是黑色
#### 分析如何判断和棋
> 棋子的flag为0或1或2
> 0乘以任何数都为0
>
> 可以将棋盘中9个格子都相乘,得出结果为0则表示没下完,如不为0且没有同色棋子连为一线则判断为和棋
```javascript
/**
* 判断输赢和
*/
function judge() {
let draw = map[0][0].color; // 是否和棋
for (let i = 0; i < 3; i++) {
let rowColor = map[i][0].color; // 计算横竖输赢
let colColor = map[0][i].color;
draw *= map[i][0].color * map[0][i].color; // 计算和棋
for (let j = 1; j < 3; j++) {
rowColor &= map[i][j].color; // 计算横竖输赢
colColor &= map[j][i].color;
draw *= map[i][j].color * map[j][i].color; // 计算和棋
}
if (rowColor || colColor) {
return gameOver(rowColor || colColor);
}
}
// 计算斜线输赢
const x1 = map[0][0].color & map[1][1].color & map[2][2].color;
const x2 = map[2][0].color & map[1][1].color & map[0][2].color;
if (x1 || x2) {
return gameOver(x1 || x2);
}
// 判断是否和棋
draw && gameOver(0);
}
```
#### 游戏结束弹出弹窗提示
- 在html中预制一个弹窗,并完成弹窗样式,初始化为隐藏
- 写一个gameOver函数,传入局面情况,在结束时调用
- 在gameOver函数里将弹窗显示,并修改提示文字
```html
<div id="overPanel">
<div id="overTip"></div>
<div id="resetBtn">重新开始</div>
</div>
```
```css
#overPanel {
position: fixed;
width: 330px;
height: 330px;
background-color: #aaaaaa;
color: black;
display: none;
flex-direction: column;
justify-content: center;
align-items: center;
}
#overTip {
flex: 0.6;
font-size: 40px;
display: flex;
align-items: center;
align-content: center;
}
#resetBtn {
width: 100px;
height: 45px;
background-color: #4d4d4d;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
}
```
```javascript
// 取出需要改变样式的dom元素
const overPanel = document.getElementById("overPanel");
const overTip = document.getElementById("overTip");
const resetBtn = document.getElementById("resetBtn");
/**
* 游戏结束
* @param {number} flag 结束标志 0 和棋, 1 白棋赢, 2 黑棋赢
*/
function gameOver(flag) {
overPanel.style.display = "flex";
switch (flag) {
case 0:
overTip.innerText = "和棋";
overTip.style.color = "#00ff00";
resetBtn.style.color = "#00ff00";
break;
case 1:
overTip.innerText = "白棋胜";
overTip.style.color = "white";
resetBtn.style.color = "white";
break;
case 2:
overTip.innerText = "黑棋胜";
overTip.style.color = "black";
resetBtn.style.color = "black";
break
}
}
```
效果如下
![img.png](img/01_03.png)
## 重设棋局
在上一步中已经写了一个重新开始按钮
这一步要实现一个`reset`函数来重设棋局,并绑定给这个按钮
```html
<div id="overPanel">
<div id="overTip"></div>
<div id="resetBtn" onclick="reset()">重新开始</div>
</div>
```
```javascript
/**
* 重设棋盘
*/
function reset() {
// 把所有格子都恢复成初始状态
for (let row = 0; row < 3; row++) {
for (let col = 0; col < 3; col++) {
map[row][col].className = "grid";
map[row][col].color = 0;
}
}
color = 1; // 重设下棋flag为白棋
overPanel.style.display = "none"; // 隐藏结束弹窗
}
```
到此单机版本的井子棋就已经完成了,下一节将带大家完成联机功能
# 02.联机功能 - 井子棋对战版
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment