前端五子棋第二版

試玩鏈接:五子棋
效果圖如下:
在這裏插入圖片描述
與上個版本相比,主要實現了兩個功能:
一個是處理的音效消失的bug,另一個是新增了人機模式。
先說說背景音效的問題,其實解決方案很簡單,就是把中文的MP3音樂名改成英文名字即可。
直接上代碼:
HTML代碼:

<!DOCTYPE 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="css/gobang.css">
</head>

<body>
    <a href="javascript:void(0)" id="computerPlay">人機對戰</a>
    <a href="" id="newGame">重開一局</a>
    <a href="javascript:void(0)" id="personPlay">雙人對戰</a>
    <canvas id="canvas" width="480" height="480">
    </canvas>
    <audio class="audio">
        <source src="cbgm.mp3" type="audio/mp3" />
    </audio>
    <audio class="music">
        <source src="bgm.mp3" type="audio/mp3" />
    </audio>
    <script src="js/game.js"></script>
    <script src="js/ai.js"></script>
    <script src="js/type.js"></script>
</body>

</html>

由於代碼量變得更多,所以這個版本我採取的外部文件引用的方式,也就是將css代碼以及js代碼都分離開來,這樣也更符合代碼格式標準。
CSS代碼:

body {
    margin: 0;
    background-color: #ccc;
}

#canvas {
    display: block;
    position: relative;
    margin: 135px auto;
    background-color: rgb(221, 168, 21);
}
a{
    position: absolute;
    width: 100px;
    height: 25px;
    font-family: Arial;
    color: white;
    border-radius: 10px;
    text-decoration: none;
    text-align: center;
}
#computerPlay {
    background-color: #f14343;
    top: 90px;
    left: 36%;
}
#computerPlay:hover {
    background: #c90707;
}
#personPlay {
    background-color: rgb(57, 190, 243);
    top: 90px;
    left: 57.5%;
}
#personPlay:hover{
    background: rgb(8, 168, 231);
}
#newGame{
    background-color: rgb(221, 224, 26);
    top: 90px;
    left: 47%;
}
#newGame:hover{
    background: rgb(198, 201, 40);
}

遊戲開始以及雙人對戰的js代碼:

var canvas = document.querySelector("canvas");
var computerPlay = document.getElementById("computerPlay");
var personPlay = document.getElementById("personPlay");
var chessColor = ['black', 'white'];
var musicStart = false;
var step = 0;
var mapChess = [];
var mode = [
    [1, 0],
    [0, 1],
    [1, 1],
    [1, -1]
]
for (var i = 0; i < 15; i++) {
    mapChess[i] = [];
    for (var j = 0; j < 15; j++) {
        mapChess[i][j] = '';
    }
}
var ctx = canvas.getContext("2d");/* 獲取繪製環境 */
for (var i = 1; i < 16; i++) {
    ctx.moveTo(30 * i, 30);
    ctx.lineTo(30 * i, 450);/* 描述繪製路徑 */
    ctx.moveTo(30, 30 * i);
    ctx.lineTo(450, 30 * i);
}
ctx.stroke();/* 將之前所有的路徑全部繪製一次 */
drawPoint(120, 120);
drawPoint(120, 360);
drawPoint(360, 120);
drawPoint(360, 360);
drawPoint(240, 240);
computerPlay.addEventListener('click', computerStart, false);
personPlay.addEventListener('click', personStart, false);

function personStart() {
    canvas.addEventListener('click', start, false);
    computerPlay.removeEventListener('click', computerStart, false);
}
function computerStart() {
    if (step == 0) {
        document.querySelector('.audio').play();
        document.querySelector('.music').play();
        drawChess(240, 240, 'black');
        mapChess[7][7] = 'black';
        step++;
    }
    canvas.addEventListener('click', aiStart, false);
    personPlay.removeEventListener('click', personStart, false);
}
function drawChess(x, y, color) {
    ctx.fillStyle = color;
    ctx.beginPath();/* 提筆 */
    ctx.arc(x, y, 13, Math.PI * 2, false);
    ctx.fill();
    ctx.stroke();
}
function drawPoint(x, y) {
    ctx.fillStyle = 'black';
    ctx.beginPath();/* 提筆 */
    ctx.arc(x, y, 2, Math.PI * 2, false);
    ctx.fill();
    ctx.stroke();
}
function start(e) {
    var audio = document.querySelector('.audio');
    var music = document.querySelector('.music');
    var color = chessColor[step % 2];
    var dx = Math.floor((e.offsetX + 15) / 30) - 1;
    var dy = Math.floor((e.offsetY + 15) / 30) - 1;
    if (dx < 0 || dx > 14 || dy < 0 || dy > 14) {
        return;
    }
    if (mapChess[dx][dy] == '') {
        var audioPromise = document.querySelector('.audio').play();
        document.querySelector('.music').play();
        music.muted = false;
        drawChess((dx + 1) * 30, (dy + 1) * 30, color);
        mapChess[dx][dy] = color;
        if (judge(dx, dy, color, mode[0], 5) ||
            judge(dx, dy, color, mode[1], 5) ||
            judge(dx, dy, color, mode[2], 5) ||
            judge(dx, dy, color, mode[3], 5)
        ) {
            music.muted = true;
            step % 2 == 0 ? alert("黑棋獲勝") : alert("白棋獲勝");
            canvas.removeEventListener('click', start, false);
            personPlay.removeEventListener('click', personStart, false);
            return;
        }
        if (audioPromise !== undefined) {
            audioPromise.then(_ => {
                audio.paused = true;
            })
                .catch(error => {

                });
        }
        step++;
    }
}
function judge(x, y, color, mode, number) {
    var count = 1;
    for (var i = 1; i < number; i++) {
        if (mapChess[x + i * mode[0]]) {
            if (mapChess[x + i * mode[0]][y + i * mode[1]] == color) {
                count++;
            } else {
                break;
            }
        }
    }
    for (var i = 1; i < number; i++) {
        if (mapChess[x - i * mode[0]]) {
            if (mapChess[x - i * mode[0]][y - i * mode[1]] == color) {
                count++;
            } else {
                break;
            }
        }
    }
    return count >= number ? true : false;
}

以上這些代碼除了新添增三個按鈕以及綁定其點擊事件外(這部分看代碼應該很容易理解),其餘部分的代碼在上個版本以及講解過,如果看不明白的夥伴可以參看以下鏈接:前端五子棋第一版
然後就是實現新增的五子棋的ai功能,不考慮ai,其大體實現思路與雙人對戰的模式幾乎一樣,只不過是在玩家下完一步後電腦緊接着再下一步。
電腦下棋的js代碼:

var backX = 0;
var backY = 0;

function aiStart(e) {
    var audio = document.querySelector('.audio');
    var music = document.querySelector('.music');
    var color = chessColor[step % 2];
    var dx = Math.floor((e.offsetX + 15) / 30) - 1;
    var dy = Math.floor((e.offsetY + 15) / 30) - 1;
    if (dx < 0 || dx > 14 || dy < 0 || dy > 14) {
        return;
    }
    if (mapChess[dx][dy] == '') {
        var audioPromise = document.querySelector('.audio').play();
        document.querySelector('.music').play();
        music.muted = false;
        //玩家落子
        drawChess((dx + 1) * 30, (dy + 1) * 30, color);
        mapChess[dx][dy] = color;
        if (judge(dx, dy, color, mode[0], 5) ||
            judge(dx, dy, color, mode[1], 5) ||
            judge(dx, dy, color, mode[2], 5) ||
            judge(dx, dy, color, mode[3], 5)
        ) {
            music.muted = true;
            canvas.removeEventListener('click', aiStart, false);
            computerPlay.removeEventListener('click', computerStart, false);
            step % 2 == 0 ? alert("黑棋獲勝") : alert("白棋獲勝");
            return;
        }
        step++;
        //電腦落子
        AI();
        dx = backX;
        dy = backY;
        drawChess((dx + 1) * 30, (dy + 1) * 30, 'black');
        mapChess[dx][dy] = 'black';
        if (judge(dx, dy, 'black', mode[0], 5) ||
            judge(dx, dy, 'black', mode[1], 5) ||
            judge(dx, dy, 'black', mode[2], 5) ||
            judge(dx, dy, 'black', mode[3], 5)
        ) {
            music.muted = true;
            canvas.removeEventListener('click', aiStart, false);
            computerPlay.removeEventListener('click', computerStart, false);
            step % 2 == 0 ? alert("黑棋獲勝") : alert("白棋獲勝");
            return;
        }
        if (audioPromise !== undefined) {
            audioPromise.then(_ => {
                audio.paused = true;
            })
                .catch(error => {

                });
        }
        step++;
    }
}

function AI() {
    var computerScore = [];
    var personScore = [];
    var maxScore = 0;
    for (var i = 0; i < 15; i++) {
        personScore[i] = [];
        computerScore[i] = [];
        for (var j = 0; j < 15; j++) {
            personScore[i][j] = 0;
            computerScore[i][j] = 0;
        }
    }
    for (var i = 0; i < mapChess.length; i++) {
        for (var j = 0; j < mapChess.length; j++) {
            if (mapChess[i][j] == '') {
                var sum = 0;
                //一步得分
                for (var k = 0; k <= 3; k++) {
                    var perArr = getType(i, j, 'white', mode[k]);
                    personScore[i][j] += 4 * Math.pow(10, perArr[1] - perArr[0]);
                    var comArr = getType(i, j, 'black', mode[k]);
                    computerScore[i][j] += 6 * Math.pow(10, comArr[1] - comArr[0]);
                }
                sum = computerScore[i][j] + personScore[i][j];
                if (sum > maxScore) {
                    maxScore = sum;
                    xMax = i;
                    yMax = j;
                }
            }
        }
    }
    backX = xMax;
    backY = yMax;
}

在aiStart這個函數裏說明兩點:

  1. 由於電腦下棋是無需觸發點擊事件,所以我直接將其綁定在玩家下棋的後面。
  2. 所謂ai下棋,無非就是通過一系列複雜的邏輯計算,得到最終位置的座標,所以我使用全解變量backX ,backY 來接收最終的電腦下棋的位置。

在AI這個函數裏說明兩點:

  1. 首先需要用computerScore 和personScore 來分別保存棋盤中每個位置的計算得分,所以它們也是和棋盤一樣大的數組。又由於每次下棋後部分位置的得分會改變,所以它們也是局部變量。(正是由於只有部分位置的得分會改變,所以這裏也是算棋功能的優化點)
  2. 通過getType函數來獲取某個點的一個方向的得分(只有四個方向),這裏我把電腦的得分系數設置爲6而玩家的得分系數設置爲4,因爲下棋的目的是獲勝,所以己方的優勢更重要一些,但是同時敵之要低,我方也需要爭取,這也是爲什麼最後我設置的得分是兩者之間的求和而不是作差。

最後,該亮出最神奇的getType函數:

function getType(x, y, color, mode) {
    var countBlock = 0;
    var count = 1;
    var i = 1;
    while ((x + i * mode[0]) < 15 && (y + i * mode[1]) < 15 && mapChess[x + i * mode[0]][y + i * mode[1]] != '') {
        if (mapChess[x + i * mode[0]][y + i * mode[1]] == color) {
            count++;
        } else {
            countBlock++;
            break;
        }
        i++;
    }
    if ((x + i * mode[0]) < 15 || (y + i * mode[1]) < 15) {
        countBlock = 4;
    }
    i = 1;
    while ((x - i * mode[0]) >= 0 && (y - i * mode[1]) >= 0 && mapChess[x - i * mode[0]][y - i * mode[1]] != '') {
        if (mapChess[x - i * mode[0]][y - i * mode[1]] == color) {
            count++;
        } else {
            countBlock++;
            break;
        }
        i++;
    }
    if ((x - i * mode[0]) >= 0 || (y - i * mode[1]) >= 0) {
        countBlock = 4;
    }
    return [countBlock, count];
}

在getType這個函數裏說明三點:

  1. 首先爲什麼說它神奇呢,因爲這個函數包括了五子棋裏面的眠一,活一,死一,眠二,活二,死二眠三,活三,死三,眠四,死四,活四,成五這麼多種單個位置的局面(這裏的死是表示兩端都已被對手的棋子封住),注意這裏並不包含跳眠二,跳活二等跳棋局面的考慮。(但是後面版本我會加上)
  2. 這裏面還做出了countBlock = 4的邊界考慮,就是爲了避免電腦下無用棋,但是我這裏的設置還是存在問題
  3. 最終的結果是由count數值減去countBlock數值作爲指數,其實這並不是一個最好的算法,但是是一個很有效率的算法。

總之,此電腦ai依然存在不少bug,但是就單步下棋水平而言,我個人感覺已經馬馬虎虎了。後續的重點將是給與電腦算棋甚至是算殺的功能。這部分算法的難度很大,我目前暫時還沒有這個能力,所以還得更加持續學習,我目前有個思路就是回溯剪枝算法。這裏我很歡迎大佬們評論區留言賜教!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章