html+css+JavaScript實現愛恩斯坦棋遊戲

Wind發表於2022-01-02

title: "html+css+JavaScript實現愛恩斯坦棋遊戲"
author: Sun-Wind
date: December 30, 2021

背景:本貼將基於前端的語言實現愛恩斯坦棋遊戲的實現。
小聲BB:查了一下,這應該是全網第一個基於前端語言實現愛恩斯坦棋的貼,應該是開端了。

效果圖

愛恩斯坦棋類
愛恩斯坦棋2
考慮到大家可能還不瞭解愛恩斯坦棋(其實點進來或多或少應該已經有了解過了吧)
理論上還是應該要介紹一下才對
規則如下:

  • 棋盤為5×5的方格形棋盤,方格為棋位,左上角為紅方出發區;右下角為藍方出發區;
  • 紅藍方各有6枚方塊形棋子,分別標有數字1—6。開局時雙方棋子在出發區的棋位可以隨意擺放;
  • 雙方輪流擲骰子,然後走動與骰子顯示數字相對應的棋子。如果相對應的棋子已從棋盤上移出,便可走動大於或小於此數字的並與此數字最接近的棋子;
  • 紅方棋子走動方向為向右、向下、向右下,每次走動一格;藍方棋子走動方向為向左、向上、向左上,每次走動一格;
  • 如果在棋子走動的目標棋位上有棋子,則要將該棋子從棋盤上移出(吃掉)。有時吃掉本方棋子也是一種策略,因為可以增加其它棋子走動的機會與靈活性;
  • 率先到達對方出發區角點或將對方棋子全部吃掉的一方獲勝;
  • 對弈結果只有勝負,沒有和棋。
  • 每盤每方用時3分鐘,超時判負;每輪雙方對陣最多7盤,輪流先手(甲方一四五盤先手,乙方二三六七盤先手),兩盤中間不休息,先勝4盤為勝方。

需要提前掌握的知識

  • css選擇器
  • 絕對位置
  • css盒子模型
  • 動畫效果
  • js基本語法
  • js中事件

css選擇器

一般用於選擇元素定義其樣式
選擇器表

絕對位置

position 的四個值:static、relative、absolute、fixed
由於只會用到絕對位置,所以我們主要介紹一下absolute屬性
absolute:元素會脫離文件流,如果設定偏移量,會影響其他元素的位置定位
absolute定位原理剖析:
1.在父元素沒有設定相對定位或絕對定位的情況下,元素相對於根元素定位(即html元素)(是父元素沒有)。
2.父元素設定了相對定位或絕對定位,元素會相對於離自己最近的設定了相對或絕對定位的父元素進行定位(或者說離自己最近的不是static的父元素進行定位,因為元素預設是static)。
可以把整個螢幕看成一個座標系,px是單位長度
比如下面這段程式碼

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"> 
<style>
h2
{
	position:absolute;
	left:100px;
	top:150px;
}
</style>
</head>

<body>
<h2>這是一個絕對定位了的標題</h2>
<p>用絕對定位,一個元素可以放在頁面上的任何位置。標題下面放置距離左邊的頁面100 px和距離頁面的頂部150 px的元素。.</p>
</body>

</html>

效果圖如下所示
效果圖

css盒子模型

所有HTML元素可以看作盒子,在CSS中,"box model"這一術語是用來設計和佈局時使用。
CSS盒模型本質上是一個盒子,封裝周圍的HTML元素,它包括:邊距,邊框,填充,和實際內容。
盒模型允許我們在其它元素和周圍元素邊框之間的空間放置元素。
下面的圖片說明了盒子模型(Box Model):
盒子模型

  • Margin(外邊距) - 清除邊框外的區域,外邊距是透明的。
  • Border(邊框) - 圍繞在內邊距和內容外的邊框。
  • Padding(內邊距) - 清除內容周圍的區域,內邊距是透明的。
  • Content(內容) - 盒子的內容,顯示文字和影像。

總元素的寬度=寬度+左填充+右填充+左邊框+右邊框+左邊距+右邊距
總元素的高度=高度+頂部填充+底部填充+上邊框+下邊框+上邊距+下邊距
比如下面這個塊

div {
    width: 300px;
    border: 25px solid green;
    padding: 25px;
    margin: 25px;
}

實際上300px (寬)+ 50px (左 + 右填充)+ 50px (左 + 右邊框)+ 50px (左 + 右邊距)= 450px

動畫效果

要建立 CSS3 動畫,你需要了解 @keyframes 規則。
@keyframes 規則是建立動畫。
@keyframes 規則內指定一個 CSS 樣式和動畫將逐步從目前的樣式更改為新的樣式。

@keyframes myfirst
{
    from {background: red;}
    to {background: yellow;}
}

這個動畫的意思顯而易見是把背景色從紅色變成黃色
現在可以給塊加上動畫了

div
{
    animation: myfirst 5s;
}

必須定義動畫的名稱和動畫的持續時間。如果省略的持續時間,動畫將無法執行,因為預設值是0。
用百分比來規定變化發生的時間,或用關鍵詞 "from" 和 "to",等同於 0% 和 100%。
0% 是動畫的開始,100% 是動畫的完成。
為了得到最佳的瀏覽器支援,我們應該始終定義 0% 和 100% 選擇器。

js基本語法

這裡只列舉本帖子需要用到的一些語法

JavaScript 顯示資料

JavaScript 可以通過不同的方式來輸出資料:

  • 使用 window.alert() 彈出警告框。
  • 使用 document.write() 方法將內容寫到 HTML 文件中。
  • 使用 innerHTML 寫入到 HTML 元素。
  • 使用 console.log() 寫入到瀏覽器的控制檯。

js變數

在 JavaScript 中建立變數通常稱為"宣告"變數。
我們使用 var 關鍵詞來宣告變數:
var carname;
變數宣告之後,該變數是空的(它沒有值)。
如果需要向變數賦值,可以使用等號,也可以在申明的時候給它賦值
JavaScript 擁有動態型別。這意味著相同的變數可用作不同的型別:

var x;               // x 為 undefined
var x = 5;           // 現在 x 為數字
var x = "John";      // 現在 x 為字串

JavaScript 只有一種數字型別。數字可以帶小數點,也可以不帶:

var x1=34.00;      //使用小數點來寫
var x2=34;         //不使用小數點來寫

注意在js中的除法是按照小數除法,這一點和c++有區別
語句方面,c++和js幾乎一樣

js陣列

陣列下標是基於零的,所以第一個專案是 [0],第二個是 [1],以此類推。
建立陣列

var cars=new Array();
cars[0]="Saab";
cars[1]="Volvo";
cars[2]="BMW";

js函式

函式就是包裹在花括號中的程式碼塊,前面使用了關鍵詞 function:

function functionname()
{
    // 執行程式碼
}

當呼叫該函式時,會執行函式內的程式碼。
可以在某事件發生時直接呼叫函式(比如當使用者點選按鈕時),並且可由 JavaScript 在任何位置進行呼叫。
可以傳送任意多的引數,由逗號 (,) 分隔:

js事件

HTML 事件是發生在 HTML 元素上的事情。
當在 HTML 頁面中使用 JavaScript 時, JavaScript 可以觸發這些事件。
以下是 HTML 事件的例項:

  • HTML 頁面完成載入
  • HTML input 欄位改變時
  • HTML 按鈕被點選


事件可以用於處理表單驗證,使用者輸入,使用者行為及瀏覽器動作:

  • 頁面載入時觸發事件
  • 頁面關閉時觸發事件
  • 使用者點選按鈕執行動作
  • 驗證使用者輸入內容的合法性
    等等 ...

設計思路

愛恩斯坦棋的介面設計相對比較簡單,主要運用了JavaScript加入div塊和規定絕對位置來進行實現,在初始化時對每個塊加上id標記,指向這個塊的絕對編號(即本身所具有的固有屬性,不會隨著後面玩家進行佈局錯亂棋子的位置影響。
同時也為後續進行編號和塊號的對映進行了鋪墊,在加入div塊的同時對這個塊設定它的樣式(比如絕對位置等等),在外面格外設定一個ans全域性變數,對塊的編號進行計數。
由於玩家剛開始遊戲的時候可以隨意地佈局,所以我們採用了用點選事件來對各個塊編上玩家想要的號碼。同樣在最外層放置一個計數器cnt,在點選的同時進行計數,同時用querySelector選中這個塊,對這個塊的樣式進行修改(如顏色等等)。
同理對玩家2也是同樣的修改。
同樣我們也需要讓原來的加在每個塊上的click事件移除,防止影響到對後續的棋子的移動。(這個移除操作封裝到“開始遊戲”按鈕)。
設定隨機數按鈕,定義全域性變數play,值為1時表示玩家1,值為2時表示玩家2。
分別對對應玩家1和玩家2。每次開始隨機的時候,用Math庫內建的函式產生一個隨機數,然後加入一個div塊,把隨機數放到這個塊上,更改塊的樣式,動畫等。
使用者開始遊戲後,每次行棋前都要點選隨機數,重新申明兩個個Array陣列player1和player2。
這個陣列表示每個編號的棋子是否存活然後。
再利用一個Array陣列,這個陣列的效果是對應於每個編號所對映的塊的絕對位置(塊號)。
這樣我們能知道所點選塊的塊號,利用點選來進行移動,對產生的隨機數進行搜尋,如果離它最近的棋子存活,可以通過mapp陣列找到它對映的塊號,進而對這個元素新增點選事件,同時搜素它的三種走法,如果這三種走法都是合法的,分別對對應的塊新增點選事件。
方便使用者的移動,同時可以改變play的值。
在第二次點選事件後,增加一個判斷勝利的條件。如果對應塊的位置是對面的顏色,或者player陣列中沒有棋子存活,那麼把此作為勝利的條件。

行棋流程圖

程式碼實現

<!DOCTYPE html>
<html lang='en'>

<head>
    <meta charset="UTF-8">
    <title>愛恩斯坦棋</title>
    <style>
        [class~=big] {
            position: absolute;
            left: 300px;
            top: 200px;
            border: 10px black solid;
            width: 600px;
            height: 600px;
            
        }

        [class~=an] {
            position: absolute;
            left: 1200px;
            top: 600px;
            width: 100px;
            height: 100px;
            border-radius: 50%;
        }
        [class~=bn]{
            position: absolute;
            left: 1200px;
            top: 800px;
            width: 200px;
            height: 50px;
        }
        [class~=tip]{
            position:absolute;
            left:350px;
            top:100px;
            width:500px;
            height:100px;
            border:1px black solid;
            box-shadow:0 9px 14px 0 rgb(240, 192, 35);
            text-align: center;
            font-size:50px;
            font-weight: normal;
        }
        [class~=tips]{
            position:absolute;
            border:1px black solid;
            left:1000px;
            top:0px;
            width:500px;
            height:600px;
            font-size: 20px;
        }
        [class~=saizi]{
            position:absolute;
            border:1px black solid;
            width: 100px;
            height:100px;
            left:1100px;
            top:600px;
            text-align: center;
            background:white;
            font-size:60px;
            border-radius: 20%;
        }
        @keyframes button1 {
            0%{background-color:rgb(117, 114, 106);box-shadow: 0 0 9px rgb(6, 34, 33);}
            100%{background-color: rgb(230, 167, 31);box-shadow: 0 0 9px rgb(6, 34, 33);}
        }
        @keyframes white_show{
            0%{background-color:gray;box-shadow: 0 0 9px rgb(6, 34, 33);}
            100%{background-color: white;box-shadow: 0 0 9px rgb(247, 198, 36);}
        } 
        @keyframes blue_show {
            0%{background-color:gray;box-shadow: 0 0 9px rgb(6, 34, 33);}
            100%{background-color: skyblue;box-shadow: 0 0 9px rgb(235, 175, 48);}
        }
        /* @keyframes white_none {
            0%{background-color:white;}
            100%{background-color: gray;}
        }
        @keyframes blue_none {
            0%{background-color: skyblue;}
            100%{background-color: gray;}
        } */ 
    </style>
</head>

<body bgcolor = "gray">
    <div class = "tips">
        愛恩斯坦棋:
        <p>1.棋盤為5×5的方格形棋盤,方格為棋位,左上角為白方出發區;右下角為藍方出發區;</p>
        <p>2.紅藍方各有6枚方塊形棋子,分別標有數字1—6。開局時雙方棋子在出發區的棋位可以隨意擺放;</p>
        <p>3.雙方輪流擲骰子,然後走動與骰子顯示數字相對應的棋子。如果相對應的棋子已從棋盤上移出,便可走動大於或小於此數字的並與此數字最接近的棋子;</p>
        <p>4.白方方棋子走動方向為向右、向下、向右下,每次走動一格;藍方棋子走動方向為向左、向上、向左上,每次走動一格;</p>
        <p>5.如果在棋子走動的目標棋位上有棋子,則要將該棋子從棋盤上移出(吃掉)。</p>
        <p>6.率先到達對方出發區角點或將對方棋子全部吃掉的一方獲勝;</p>
    </div>
    <div class = "tip">
    </div>
    <div class="big">
    </div>
    <button class="an">隨機數</button>
    <div class = "saizi"></div>
    <button class="bn">開始遊戲</button>
    <script>
        var cnt = 1;//計數器
        var ans = 1;//方格數量
        var click2 = 0;//表示第一個還沒有放完
        var ram = 0;//儲存隨機數
        var val;//儲存權值
        var play = 1;//表示該第幾個玩家,
        var mapp1 = new Array(7);//由數字對映到編號
        var mapp2 = new Array(7);
        var player1 = new Array(7);//場上的棋子是否存活
        var player2 = new Array(7);
        var flag = 0;//標識是否隨機完成
        var tc, tn; //儲存上一個方塊的顏色和數字
        var sel;//儲存上一個次選中的元素
        var big = document.querySelector(".big");
        var an = document.querySelector(".an");
        var bn = document.querySelector(".bn");
        var tip = document.querySelector(".tip");
        var saizi = document.querySelector(".saizi");
        bn.addEventListener("click", clear);
        an.addEventListener("click", star);
        for (var i = 1; i <= 6; ++i) {
            player1[i] = 1;
            player2[i] = 1;
        }
        for (var i = 0; i < 5; ++i)
            for (var j = 0; j < 5; ++j) {
                div = document.createElement("div");
                div.setAttribute("class", "Block" + ans);
                div.setAttribute("id", ans);
                div.style.border = "2px black solid";
                div.style.width = "98px";
                div.style.height = "98px";
                div.style.position = "absolute";
                div.style.textAlign = "center";
                div.style.left = 50 + j * 100 + "px";
                div.style.top = 50 + i * 100 + "px";
                div.style.fontSize = "50px";
                big.appendChild(div);
                ans++;
            }
        for (ans = 1; ans <= 11; ++ans) {
            var temp = document.querySelector(".Block" + ans);
            temp.addEventListener("click", clickblock1);
        }

        for (cnt = 15; cnt <= 25; ++cnt) {
            var temp = document.querySelector(".Block" + cnt);
            // console.log(temp);
            temp.addEventListener("click", clickblock2);
        }
        ans = 1;
        cnt = 1;//重置方便後續的計數

        function clear(){
            for (var i = 1; i <= 11; ++i) {
            var temp = document.querySelector(".Block" + i);
            temp.removeEventListener("click", clickblock1);
            }

        for (var j = 15; j <= 25; ++j) {
            var temp = document.querySelector(".Block" + j);
            // console.log(temp);
            temp.removeEventListener("click", clickblock2);
            }
        }

        function clickblock1(e) {
            e.target.textContent = ans;
            mapp1[ans] = e.target.id;
            console.log(e.target.id);
            ans++;
            e.target.style.backgroundColor = "white";
        }
        function clickblock2(e) {
            e.target.textContent = cnt;
            mapp2[cnt] = e.target.id;
            cnt++;
            e.target.style.backgroundColor = "skyblue";
        }
        function star() {
            ram = Math.floor(Math.random() * 6 + 1);
            console.log(ram);
            // console.log(mapp1);
            // for(var i = 1; i <= 25; ++i){
            //     var id = document.getElementById(i);
            //     id.addEventListener("click", clickmove);
            // }
            saizi.textContent = ram;
            saizi.style.animation = "button1 2s infinite";
            var div = document.createElement("div");
            div.style.backgroundColor = "gray";
            div.style.width = "170px";
            div.style.height = "100px";
            div.style.borderRadius = "50px";
            div.style.textAlign = "center";
            div.textContent = "你只能移動和" + ram + "最接近的數";
            div.style.color = "red";
            div.style.position = "absolute";
            div.style.top = "100px";
            div.style.left = "-30px";
            an.appendChild(div);
            if(play == 1){
                tip.textContent = "輪到白方走棋";
                tip.style.animation = "white_show 5s infinite";
            }
            else{
                tip.textContent = "輪到藍方走棋";
                tip.style.animation = "blue_show 5s infinite";
            }
            if (play == 1) {
                for (var i = 0; i < 6; ++i) {
                    if (ram - i >= 1 && player1[ram - i] == 1) {
                        var id = document.getElementById(mapp1[ram - i]);
                        id.addEventListener("click", clickmove);
                        var exp1 = mapp1[ram - i] - 1;
                        var y = exp1 % 5;
                        var x = (exp1 - y)/ 5;
                        console.log("座標" + x + "" + y);
                        if (y + 1 < 5) {
                            exp1 = x * 5 + y + 2;
                            console.log(exp1);
                            var temp = document.getElementById(exp1);
                            temp.addEventListener("click", clickmove);
                        }
                        if (x + 1 < 5) {
                            exp1 = (x + 1) * 5 + y + 1;
                            console.log(exp1);
                            var temp = document.getElementById(exp1);
                            temp.addEventListener("click", clickmove);
                        }
                        if (y + 1 < 5 && x + 1 < 5) {
                            exp1 = (x + 1) * 5 + y + 2;
                            console.log(exp1);
                            var temp = document.getElementById(exp1);
                            temp.addEventListener("click", clickmove);
                        }
                        flag = 1;
                    }
                    if (ram + i <= 6 && player1[ram + i] == 1 && i != 0) {
                        var id = document.getElementById(mapp1[ram + i]);
                        id.addEventListener("click", clickmove);
                        var exp1 = mapp1[ram + i] - 1;
                        var y = exp1 % 5;
                        var x = (exp1 - y)/ 5;
                        console.log("座標" + x + "" + y);
                        if (y + 1 < 5) {
                            exp1 = x * 5 + y + 2;
                            console.log(exp1);
                            var temp = document.getElementById(exp1);
                            temp.addEventListener("click", clickmove);
                        }
                        if (x + 1 < 5) {
                            exp1 = (x + 1) * 5 + y + 1;
                            console.log(exp1);
                            var temp = document.getElementById(exp1);
                            temp.addEventListener("click", clickmove);
                        }
                        if (y + 1 < 5 && x + 1 < 5) {
                            exp1 = (x + 1) * 5 + y + 2;
                            console.log(exp1);
                            var temp = document.getElementById(exp1);
                            temp.addEventListener("click", clickmove);
                        }
                        flag = 1;
                    }
                    if (flag) {flag = 0;break;}
                }
                play = 2;
            }
            else {
                    for (var i = 0; i < 6; ++i) {
                        if (ram - i >= 1 && player2[ram - i] == 1) {
                            var id = document.getElementById(mapp2[ram - i]);
                            id.addEventListener("click", clickmove);
                            var exp1 = mapp2[ram - i] - 1;
                            var y = exp1 % 5;
                            var x = (exp1 - y)/ 5;
                            console.log("座標" + x + "" + y);
                            console.log(exp1);
                            if (y - 1 >= 0) {
                                exp1 = x * 5 + y;
                                console.log(exp1);
                                var temp = document.getElementById(exp1);
                                temp.addEventListener("click", clickmove);
                            }
                            if (x - 1 >= 0) {
                                exp1 = (x - 1) * 5 + y + 1;
                                console.log(exp1);
                                var temp = document.getElementById(exp1);
                                temp.addEventListener("click", clickmove);
                            }
                            if (y - 1 >= 0 && x - 1 >= 0) {
                                exp1 = (x - 1) * 5 + y;
                                console.log(exp1);
                                var temp = document.getElementById(exp1);
                                temp.addEventListener("click", clickmove);
                            }
                            flag = 1;
                        }
                        if (ram + i <= 6 && player2[ram + i] == 1 && i != 0) {
                            var id = document.getElementById(mapp2[ram + i]);
                            id.addEventListener("click", clickmove);
                            var exp1 = mapp2[ram + i] - 1;
                            var y = exp1 % 5;
                            var x = (exp1 - y)/ 5;
                            console.log("座標" + x + "" + y);
                            if (y -1 >= 0) {
                                exp1 = x * 5 + y;
                                console.log(exp1);
                                var temp = document.getElementById(exp1);
                                temp.addEventListener("click", clickmove);
                            }
                            if (x -1 >= 0) {
                                exp1 = (x - 1) * 5 + y + 1;
                                console.log(exp1);
                                var temp = document.getElementById(exp1);
                                temp.addEventListener("click", clickmove);
                            }
                            if (y - 1 >= 0 && x - 1 >= 0) {
                                exp1 = (x - 1) * 5 + y;
                                console.log(exp1);
                                var temp = document.getElementById(exp1);
                                temp.addEventListener("click", clickmove);
                            }
                            flag = 1;
                        }
                        if (flag) {flag = 0;break;}
                    }
                    play = 1;
                 }
        }
        function clickmove(e) {
            if (click2) {
                console.log("第二次點選");
                //不改變塊的編號
                if (e.target.textContent != "") {
                    if (e.target.style.backgroundColor == 'white') {
                        player1[e.target.textContent]--;
                        // mapp1[e.target.textContent] = e.target.id;
                    }
                    else {
                        player2[e.target.textContent]--;
                        // mapp2[e.target.textContent] = e.target.id;
                    }
                }
                e.target.textContent = tn;
                e.target.style.backgroundColor = tc;
                if(tc == "white"){
                    mapp1[tn] = e.target.id;
                }
                if(tc == "skyblue"){
                    mapp2[tn] = e.target.id;
                }
                click2 = 0;
                console.log(mapp1);
                console.log(mapp2);
                console.log(player1);
                console.log(player2);
                
                //判斷勝負
                var win1 = document.getElementById("1");
                var win2 = document.getElementById("25");
                if(win1.style.backgroundColor == "skyblue"){
                    alert("藍色方勝利");
                    location.reload();
                }
                if(win2.style.backgroundColor == "white"){
                    alert("白色方勝利");
                    location.reload();
                }
                var rw = 1,bw = 1;
                for(var i = 1; i <= 6; ++i){
                    if(player1[i])
                        {bw = 0;break;}
                }
                for(var i = 1; i <= 6; ++i){
                    if(player2[i])
                    {rw = 0; break;}
                }
                if(rw) {
                    alert("白色方勝利");
                    location.reload();
                }
                if(bw){
                    alert("藍色方勝利");
                    location.reload();
                }
            }
            else {
                console.log("第一次點選");
                sel = e.target;
                tc = e.target.style.backgroundColor;
                tn = e.target.textContent;
                e.target.style.backgroundColor = "gray";
                e.target.textContent = "";
                click2 = 1;
            }
        }
    </script>
</body>

</html>

相關文章