使用了原生的html+css+js 不含框架 实现该小游戏 兼容pc与移动端 该例子比较适合刚学前端的朋友巩固知识
tools 重置与棋盘大小设置工具栏
board 棋盘 items 棋盘格子 dots 棋盘黑点 triggers 用于点击响应的对象 chess_list 存放棋子的dom
棋盘大小:
当前棋权:黑方
根据棋盘图片我们可以知道 棋盘是一个正方形格子布局为N*N 这时候我们就想到了grid布局
并且 宽=高 为了在手机上也能正常使用 我们使用vh作为单位 并设置最大宽度和最小宽度 为了方便调试 定义一组css变量
:root {
--w : 80vh;
--w_min: 320px;
--w_max: 640px;
--num : 15;
}
* {
margin : 0;
padding : 0;
box-sizing: border-box;
}
body {
width : 100%;
height : 100vh;
background-color: #fdf6e3;
display : flex;
flex-direction : column;
justify-content : center;
align-items : center;
}
.board {
position : relative;
width : var(--w);
height : var(--w);
min-width : var(--w_min);
min-height : var(--w_min);
max-width : var(--w_max);
max-height : var(--w_max);
border : 1px solid #000;
border-right : 0;
border-bottom: 0;
}
.items {
width : 100%;
height : 100%;
display : grid;
grid-template-columns: repeat(var(--num), calc(100% / var(--num)));
grid-template-rows : repeat(var(--num), calc(100% / var(--num)));
}
.item {
border-right : 1px solid #000;
border-bottom: 1px solid #000;
}
.dot {
position : absolute;
border-radius : 50%;
background-color: #000;
}
.chess {
position : absolute;
border-radius: 50%;
border : 1px solid #000;
z-index : 2;
transform : translate(-40%, -40%);
}
.trigger {
position : absolute;
width : 8%;
height : 8%;
background-color: transparent;
transform : translate(-40%, -40%);
}
.white {
background-color: #fff;
}
.black {
background-color: #000;
}
.highlight {
border: 3px solid red;
}
.tools {
margin-bottom: 20px;
font-size : 2vh;
}
.tools .current_drop {
margin-top: 10px;
}
代码量不多包含了许多注释 看注释即可 都是js的基础知识
// 棋子类型
const CHESS_TYPE = {
BLACK: 1,
WHITE: 2,
};
// 方向类型
const DIRECTION = {
TOP: "top",
BOTTOM: "bottom",
LEFT: "left",
RIGHT: "right",
TOP_LEFT: "top_left",
TOP_RIGHT: "top_right",
BOTTOM_LEFT: "bottom_left",
BOTTOM_RIGHT: "bottom_right",
};
// 提前获取需要修改的dom
const board_dom = document.querySelector(".board");
const items = document.querySelector(".items");
const dots = document.querySelector(".dots");
const triggers = document.querySelector(".triggers");
const chess_list = document.querySelector(".chess_list");
const num_input = document.getElementById("num_input");
const text_node = document.querySelector(".current_drop");
// 行列数
let num = 10;
// 存储的棋盘数据
let board = [];
// 当前棋权
let current = CHESS_TYPE.BLACK;
// 是否结束
let is_end = false;
// 一个格子的大小
let grid_size = 0;
// 初始化input的事件与数据
num_input.value = num;
num_input.onchange = (event) => {
num = event.target.value;
if (num < 8) {
num = 8;
} else if (num > 15) {
num = 15;
}
num_input.value = num;
};
// 设置当前的棋权
function setCurrent(type) {
current = type;
text_node.innerText = `当前棋权:${type == CHESS_TYPE.BLACK ? "黑方" : "白方"}`;
}
// 棋盘初始化
function initBoard() {
// 设置css变量数据
document.body.style.setProperty("--num", num);
// 结束标志位置为false
is_end = false;
setCurrent(CHESS_TYPE.BLACK);
// 清空子节点
for (let father of [items, dots, triggers, chess_list]) {
father.innerHTML = "";
}
board = [];
// 计算黑点的相对位置 相对边缘距离为3
const diff = num - 3;
// 棋盘黑点所在位置 取格子的右上角放置
const dotsPosition = [
{ column: 3, row: 3 },
{ column: diff, row: 3 },
{ column: Math.ceil(num / 2), row: Math.ceil(num / 2) },
{ column: 3, row: diff },
{ column: diff, row: num - 3 },
];
// 初始化棋盘格子
for (let i = 0; i < num; i++) {
for (let j = 0; j < num; j++) {
let item = document.createElement("div");
item.classList.add("item");
item.setAttribute("grid-column", j + 1);
item.setAttribute("grid-row", num - i);
items.append(item);
grid_size = item.clientHeight;
}
}
// 初始化棋盘黑点
for (let item of dotsPosition) {
let target = document.querySelector(`div[grid-column="${item.column}"][grid-row="${item.row}"]`);
let dom = document.createElement("div");
let size = board_dom.clientHeight * 0.02;
let r = size / 2;
dom.classList.add("dot");
// 将黑点布置到点位的右上角
dom.style.top = target.offsetTop - r + "px";
dom.style.left = target.offsetLeft - r + target.clientHeight + "px";
dom.style.width = size + "px";
dom.style.height = size + "px";
dots.append(dom);
}
// 初始化棋盘点击区域 这里比较关键的就是 <=num 而不是 {
alert("游戏结束");
}, 100);
return;
}
// 交换棋权
setCurrent(current == CHESS_TYPE.BLACK ? CHESS_TYPE.WHITE : CHESS_TYPE.BLACK);
}
// 创建棋子
function createChess(target) {
let column = target.getAttribute("data-column");
let row = target.getAttribute("data-row");
// 判断该位置是否落子
if (board[row][column]) return null;
board[row][column] = current;
// 创建棋子
let chess = document.createElement("div");
chess.classList.add("chess", current === CHESS_TYPE.BLACK ? "black" : "white");
let chess_size = target.clientHeight;
chess.style.top = row * grid_size + "px";
chess.style.left = column * grid_size + "px";
chess.style.width = chess_size + "px";
chess.style.height = chess_size + "px";
chess.setAttribute("chess-column", column);
chess.setAttribute("chess-row", row);
chess_list.append(chess);
return { column, row };
}
// 判断胜负
function judge(row, column) {
// 当前点位为中心点 若任意方向叠加获取到的同色棋子数据为5 就为赢家
// 获取各方向上相同棋子数据
let direction_map = {};
for (let direction of Object.values(DIRECTION)) {
direction_map[direction] = getDirectionChess(row, column, direction);
}
// 获取各方向后 判断每个方向之和是否达到五个
for (let direction of [DIRECTION.TOP, DIRECTION.LEFT, DIRECTION.TOP_LEFT, DIRECTION.TOP_RIGHT]) {
if (checkDirection(row, column, direction_map, direction)) {
return true;
}
}
return false;
}
// 获取方向的向量增加数据
function getDirectionSum(directions) {
// 因为有斜向的存在 所以我们要分开定义两个位置的对应数据
const ROW_DIRECTION = {
top: -1,
bottom: 1,
};
const COLUMN_DRECITION = {
left: -1,
right: 1,
};
// 会有top_left的存在 所以要拆解他 使用.split
// 会出现['top']['left]['top','left]
// 0索引有可能是垂直方向也有可能是水平方向
// 1索引一定是水平方向的位置
// 所以当出现left 位置时 根据我们定义的ROW_DIRECTION拿不到里面的存在 就赋值为0 就处理了两个方向的数据
directions = directions.split("_");
const column_sum = COLUMN_DRECITION[directions[0]] || (directions[1] && COLUMN_DRECITION[directions[1]]) || 0;
const row_sum = ROW_DIRECTION[directions[0]] || 0;
return { column_sum, row_sum };
}
// 获取方向上对应类型数量的棋子
function getDirectionChess(row, column, directions) {
let count = 0;
// 获取对应方向的索引 应该加还是减
let { column_sum, row_sum } = getDirectionSum(directions);
let nextColumn = column + column_sum;
let nextRow = row + row_sum;
// 当遇到不存在的边界或者下一个索引不是相同类型的棋子就中断
while (board[nextRow] && board[nextRow][nextColumn]) {
let same = board[nextRow][nextColumn] == current;
if (same) {
count++;
nextColumn += column_sum;
nextRow += row_sum;
} else break;
}
return count;
}
// 检查是否连成五个并高亮
function checkDirection(row, column, direction_map, direction) {
const handler = (from, to) => {
// 1 代表我们落子的位置 由落子从form到to的数量达到五个及以上 就完成了五子 代表有人胜出了
if (direction_map[from] + direction_map[to] + 1 >= 5) {
// 拿到往from方向的索引 开始位置=中心点位置+相距棋子数量*向量
let { column_sum, row_sum } = getDirectionSum(from);
// 计算出 计算起始点的位置(x,y)
let start_row = row + row_sum * direction_map[from];
let start_column = column + column_sum * direction_map[from];
// 获取从FROM到TO的向量数据 不用担心开始位置索引不对的问题 如果对应的map[from]不存在 拿到的都是0 起始点就是(row,column)
let { column_sum: next_column_sum, row_sum: next_row_sum } = getDirectionSum(to);
let dom_list = [];
// 后记高亮点位上的棋子
while (dom_list.length < 5) {
let dom = document.querySelector(`div[chess-column="${start_column}"][chess-row="${start_row}"]`);
if (!dom) break;
dom_list.push(dom);
start_row += next_row_sum;
start_column += next_column_sum;
}
for (let dom of dom_list) {
dom.classList.add("highlight");
}
return true;
}
return false;
};
// 统一处理 上到下 左到右 左斜到右下 右斜到左下的方向棋子数量判断
const map = {
[DIRECTION.TOP]: handler.bind(this, DIRECTION.TOP, DIRECTION.BOTTOM),
[DIRECTION.LEFT]: handler.bind(this, DIRECTION.LEFT, DIRECTION.RIGHT),
[DIRECTION.TOP_LEFT]: handler.bind(this, DIRECTION.TOP_LEFT, DIRECTION.BOTTOM_RIGHT),
[DIRECTION.TOP_RIGHT]: handler.bind(this, DIRECTION.TOP_RIGHT, DIRECTION.BOTTOM_LEFT),
};
return map[direction]();
}
// 初始化棋盘
initBoard();
// 初始化棋盘点击事件
// 为什么设置棋盘的点击事件 而不是触发器的点击事件
// 是因为js的事件冒泡机制 https://blog.csdn.net/lph159/article/details/142134303
board_dom.onclick = drop;
window.onresize = initBoard;