前言

就是想写一个五子棋练练手

HTML+CSS+JS全手写五子棋_判定胜负

思路

布局

一共分这么几层

  • 外部容器
  • 棋盘层,设置背景色,使用grid布局
  • 棋盘单元格,背景白色,能趁出棋盘层的背景色,当做棋盘线
  • 落子层,比棋盘层大一个单元格,使用grid布局
  • 落子单元格,用来存放棋子元素

逻辑

  • 棋盘层样式设置
  • 棋盘单元格生成插入
  • 落子层样式设置
  • 落子点二维数组生成
  • 根据落子点数组生成落子单元格插入落子层
  • 点击落子单元格,进行修改二维数组并重新绘制落子单元格
  • 每次落子后判定输赢
  • 以最新落子坐标为中心,获取横向,纵向,左上到右下向,右上到左下向的路径数组
  • 根据当前黑子还是白子 遍历数组 如果连续次数等于5,即获胜


代码

HTML


<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>五子棋游戏</title>
    <link rel="stylesheet" href="style.css">
</head>

<body>
    <!-- 棋盘容器 -->
    <div class="box">
        <!-- 棋盘层 -->
        <div class="board"></div>
        <!-- 落子层 -->
        <div class="gobang-area"></div>
    </div>
    <!-- 弹框信息 -->
    <div class="msgbox">
        <div></div>
        <button onclick="reset()">OK</button>
    </div>
    <script src="app.js"></script>
</body>

</html>

CSS

:root{
    --gobang-cell: 60%;
    --gobang-shadow: 0 0 1px gray, 2px 2px 2px #ddd;
}
body{
    display: flex;
    justify-content: center;
    padding: 50px;
}
.box{
    position: relative;
}
.board{
    display: grid;
    box-sizing: border-box;
}
.gobang-area{
    position: absolute;
    left: 0;
    top: 0;

    display: grid;
}
.gobang-area div{
    display: flex;
    justify-content: center;
    align-items: center;
}
.gobang-area div:not(.black):not(.white):hover::before{
    content: '';
    width: var(--gobang-cell);
    height: var(--gobang-cell);
    border-radius: 50%;
    box-shadow: var(--gobang-shadow);
}
.gobang-area .white::after{
    content: '';
    width: var(--gobang-cell);
    height: var(--gobang-cell);
    border-radius: 50%;
    background-color: white;
    box-shadow: var(--gobang-shadow);
}
.gobang-area .black::after{
    content: '';
    width: var(--gobang-cell);
    height: var(--gobang-cell);
    border-radius: 50%;
    background-color: black;
    box-shadow: var(--gobang-shadow);
}
.cell{
    background-color: white;
}

.msgbox{
    width: 40%;
    height: 30%;
    border: 1px solid gray;
    background-color: white;
    box-shadow: 0 0 30px #ddd;
    display: none;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    position: absolute;
    left: 30%;
    top: 35%;
}
.msgbox div{
    text-align: center;
    font-size: 30px;
    width: 90%;
    height: 60%;
}
.msgbox button{
    border: none;
    margin: 0;
    padding: 10px 20px;
    background-color: pink;
    color:white;
}

新用到了not伪类,比较有用

grid这次也是正式使用

JS代码

// 横线 竖线的数量
let rowCount = 10;
let columnCount = 10;

// 棋盘尺寸
let checkerboardWidth = 350;
let checkerboardHeight = 350;
// 棋盘颜色
let checkerboardColor = 'gray';
// 棋盘线宽度
let checkerboardLineWidth = 2;

// 落子点二维数组
let gobangArr = [];

// 棋盘层dom
let board;
// 落子层
let gobangArea;

// 黑子白子 true白子,false黑子  默认黑旗先行
let wb = false;


init();

// 初始化
function init(){
    // 获取棋盘层
    board = document.querySelector('.board');
    // 获取落子层
    gobangArea = document.querySelector('.gobang-area');

    setCheckerboard();
    setGobangArea();
}
// 棋盘层--------------------------------------------------------
// 设置棋盘层样式
function setCheckerboard(){
    // 设置棋盘样式
    board.style.width = checkerboardWidth + 'px';
    board.style.height = checkerboardHeight + 'px';
    board.style.backgroundColor = checkerboardColor;
    // board.style.border = `${checkerboardLineWidth}px solid ${checkerboardColor}`;
    board.style.padding = `${checkerboardLineWidth}px`;
    board.style.gridTemplateColumns = `repeat(${columnCount-1}, 1fr)`;
    board.style.gridTemplateRows = `repeat(${rowCount-1}, 1fr)`;
    board.style.gap = `${checkerboardLineWidth}px`;

    createCheckerboardCell();
}

// 生成棋盘层单元格
function createCheckerboardCell(){
    // 线的数量-1就是单元格的数量,横竖都是这个道理
    for(let i=0;i<columnCount-1;i++){
        for(let j=0;j<rowCount-1;j++){
            let cell = document.createElement('div');
            cell.className = 'cell';
            board.appendChild(cell);
        }
    }
}
// 落子层--------------------------------------------------------
// 设置落子层样式
function setGobangArea(){
    // 获取棋盘层单元格尺寸
    let cell = document.querySelector('.board').firstChild;
    let cellWidth = cell.offsetWidth, cellHeight = cell.offsetHeight;

    // 计算落子层尺寸
    gobangArea.style.width = (board.offsetWidth + cellWidth) + 'px';
    gobangArea.style.height = (board.offsetHeight + cellHeight) + 'px';

    // 设置落子层偏移位置
    gobangArea.style.left = `-${cellWidth/2}px`;
    gobangArea.style.top = `-${cellHeight/2}px`;

    // 设置grid布局
    gobangArea.style.gridTemplateColumns = `repeat(${columnCount}, 1fr)`;
    gobangArea.style.gridTemplateRows = `repeat(${rowCount}, 1fr)`;


    createArr();
}

// 生成落子点二维数组,0空位,10白子,20黑子
function createArr(){
    for(let i=0;i<columnCount;i++){
        let rowArr = [];
        for(let j=0;j<rowCount;j++){
            rowArr.push(0);
        }
        gobangArr.push(rowArr);
    }
    createGobangAreaCell();
}

// 根据落子点数组 生成棋子单元格
function createGobangAreaCell(){
    // 清空落子层的单元格
    gobangArea.innerHTML = '';
    let rowindex = 0;
    for (const row of gobangArr) {
        let columnindex = 0;
        for (const cell of row) {
            let cellDiv = document.createElement('div');
            cellDiv.dataset.rowindex = rowindex;
            cellDiv.dataset.columnindex = columnindex;
            // 绑定点击事件
            cellDiv.addEventListener('click',myClick);

            switch(cell){
                case 10:
                    cellDiv.className = 'white';
                    break;
                case 20:
                    cellDiv.className = 'black';
                    break;
            }
            gobangArea.appendChild(cellDiv);
            columnindex++;
        }
        rowindex++;
    }
}

// 落子事件
function myClick(e){
    // 获取落子点对应落子数组的位置
    let rowindex = e.target.dataset.rowindex;
    let columnindex = e.target.dataset.columnindex;
    
    let cellValue = gobangArr[rowindex][columnindex];

    // 如果为空白格,就进行落子,否则不作为
    if(cellValue === 0){
        // 更改对应落子数组项的值
        gobangArr[rowindex][columnindex] = wb ? 10 : 20;
        // 重绘落子层单元格
        createGobangAreaCell();
        // 判定输赢
        if(checkWin(Number(rowindex), Number(columnindex))){
            showMsg((wb ? '白棋' : '黑棋') + "赢啦!");
        }
        // 更改黑白棋
        wb = !wb;
    }
}

// 整体判定输赢
function checkWin(rowindex, columnindex){
    return checkWinRow(rowindex) ||
    checkWinColumn(columnindex) ||
    checkWinLT2RB(rowindex, columnindex) ||
    checkWinRT2LB(rowindex, columnindex);
}

// 判断不同方向的输赢--------------------------
// 判定横向输赢
function checkWinRow(rowindex){
    // 获取横向数组
    let arr = gobangArr[rowindex];
    return checkWinCell(arr);
}

// 判定纵向输赢
function checkWinColumn(columnindex){
    // 获取纵向数组
    let arr = [];
    // 获得棋子路径数组
    for (const row of gobangArr) {
        arr.push(row[columnindex]);
    }
    return checkWinCell(arr);
}

// 判定捺向输赢-从左上到右下
function checkWinLT2RB(rowindex, columnindex){
    // 获取纵向数组
    let arr = [];
    // 获得起始坐标
    let [startX,startY] = getFirstXY_LTRB(rowindex, columnindex);
    // 获得棋子路径数组
    for(let i=startX; i<gobangArr.length; i++){
        let rowArr = gobangArr[i];
        if(rowArr[startY]===undefined){break;}
        arr.push(rowArr[startY]);
        startY++;
    }
    return checkWinCell(arr);
}

// 判定撇向输赢-从右上到左下
function checkWinRT2LB(rowindex, columnindex){
    // 获取纵向数组
    let arr = [];
    // 获得起始坐标
    let [startX, startY] = getFirstXY_RTLB(rowindex, columnindex);
    // 获得棋子路径数组
    for(let i=startX; i<gobangArr.length; i++){
        let rowArr = gobangArr[i];
        if(rowArr[startY]===undefined){break;}
        arr.push(rowArr[startY]);
        startY--;
    }
    console.log(arr);

    return checkWinCell(arr);
}

// 判定输赢-基础方法
function checkWinCell(arr){
    let value = wb ? 10 : 20;
    let cellCount = 0;
    for (const item of arr) {
        if(item == value){
            cellCount++;
        }else{
            cellCount = 0;
        }
        if(cellCount>=5){
            return true;
        }
    }
    return false;
}

// 根据坐标获取左上到右下方向的第一个落子点的坐标,返回[行下标,列下标]
function getFirstXY_LTRB(rowindex, columnindex){
    let cha = rowindex - columnindex;
    return cha > 0 ? [cha, 0] : [0, Math.abs(cha)];
}

//  根据坐标获取右上到左下方向的第一个落子点的坐标,返回[行下标,列下标]
function getFirstXY_RTLB(rowindex, columnindex){
    let he = rowindex + columnindex;
    if(he === (gobangArr.length-1)){
        return [0, (gobangArr.length-1)]
    }else if(he < (gobangArr.length-1)){
        return [0, (rowindex + columnindex)];
    }else{
        return [(rowindex - (gobangArr.length - 1 - columnindex)), (gobangArr.length-1)];
    }

}

// 显示消息
function showMsg(content){
    document.querySelector('.msgbox').style.display = 'flex';
    document.querySelector('.msgbox div').innerText = content;
}
// 重置棋盘
function reset(){
    document.querySelector('.msgbox').style.display = 'none';
    document.querySelector('.msgbox div').innerText = '';
    for(let i=0;i<gobangArr.length;i++){
        for(let j=0; j<gobangArr[i].length;j++){
            gobangArr[i][j] = 0;
        }
    }
    wb = false;
    // 重绘
    createGobangAreaCell();
}

js中比较复杂的就是上述说道的获取以落子坐标为中心的四条路径数组

代码中已经详细进行了注释说明

总结

上述的代码直接拷贝即可使用,里面有一些稍微复杂的逻辑,可能写的有点乱,请大家多多指教