js+canvas黑白棋

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
        <style>
            body{
                font-size: 36px;
            }
            *{
                padding: 0;
                margin: 0;
            }
            .score {
                text-align: center;
                line-height: 50px;
                height: 50px;
            }
            .buttons {
                text-align: center;
            }
            button{
                font-size: 36px;
                line-height: 50px;
                margin-right: 30px;
                padding: 0 30px;
            }
            div{
                text-align: center;
            }
        </style>
    </head>
    <body>
        <div>
            <canvas id="blackWhite"></canvas>
        </div>
        
        <p class="score">
            <!--<span>比分</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-->
            <span id="score">黑:白 = 2:2</span>
        </p>
        <p class="buttons">
            <button id="regret">悔棋</button>
            <button id="giveUp">認輸</button>
            <button id="negotiate">求和</button>
        </p>
    </body>
    
    <!--<script src="js/index.js" type="text/javascript" charset="utf-8"></script>-->
<script type="text/javascript">
    // 黑白棋  又叫反棋
class Reversi {
    constructor (canvasId){
        this.canvasId = canvasId;
        this.resetData();
    }
    
    // 重置數據,再來一局
    resetData (){
        var body = document.documentElement || document.body;
        var minWidth = Math.min(body.clientWidth, body.clientHeight);
        // 屬性
        this.pieces = []; // 棋子數組 二位數組[[],[]]
        this.recommend = [];
        this.rowCount = 8; // 行數
        this.colCount = this.rowCount;// 列數
        this.cellWidth = minWidth/this.rowCount; //每一個格子的寬
        this.width = this.rowCount * this.cellWidth; // 棋盤的寬
        this.height = this.width; // 棋盤的高
        this.R = this.cellWidth * 0.4; // 棋子半徑
        this.HR = this.cellWidth / 4; // 提示格子的半徑 hintRadius
        this.HColor = "#e73480"; // 提示棋子的顏色 
        this.hisStatus = []; // 歷史記錄 history status
        this.noChessCount = 0; // 沒棋走次數
        this.active = "black"; // 當前走棋方
        this.canvas = document.getElementById(this.canvasId); // canvas DOM
        this.ctx = this.canvas.getContext("2d"); // canvas環境
        this.whiteCount = 2; // 白棋數量
        this.blackCount = 2; // 黑棋數量
        this.victor = ""; // 勝利方
        
        this.init();
    }
    
    // 初始化數據
    init (){
        this.initCanvas();
        this.initPiece();
        this.recommed();
        this.renderUi();
    }
    
    // 設置棋盤的寬高
    initCanvas (){
        this.canvas.width = this.width;
        this.canvas.height = this.height;
    }
    
    // 初始化棋子
    initPiece (){
        var initPieces = [];
        for(let i=0;i<this.rowCount;i++){ //
            initPieces.push([]);
            for(let j=0;j<this.colCount;j++){ //
                initPieces[i].push({value:0,color:""});
            }
        }
        // 初始的時候中間有四個棋子
        var center = Math.floor((this.rowCount-1)/2); // 
        initPieces[center][center].value = 1;
        initPieces[center][center].color = "black";
        initPieces[center][center+1].value = 1;
        initPieces[center][center+1].color = "white";
        initPieces[center+1][center].value = 1;
        initPieces[center+1][center].color = "white";
        initPieces[center+1][center+1].value = 1;
        initPieces[center+1][center+1].color = "black";
//        console.log(initPieces);
        
        this.pieces = this.deepClone(initPieces);
        this.hisStatus[0] = this.deepClone(initPieces);
    }
    
    renderUi(){
        //清除以前的畫布
        this.ctx.clearRect(0,0,this.width,this.height);
        
        // 重繪畫布
        this.drawMap();
        this.drawPieces();
        this.recommed();
        this.drawHint();
    }
    
    //畫一個棋子或一個提示圓點
    drawDot(x,y,r,color){
        this.ctx.beginPath();
        this.ctx.arc(x,y,r,0,2*Math.PI);
        this.ctx.closePath();
        
        this.ctx.fillStyle = color;
        this.ctx.fill();
    }
    
    // 畫棋盤
    drawMap(){
        // 背景
        this.ctx.beginPath();
        this.ctx.rect(0,0,this.width,this.height);
        this.ctx.closePath();
        this.ctx.fillStyle = "#0099CC";
        this.ctx.fill();
        
        // 畫橫線
        this.ctx.beginPath();
        for(let i=0;i<this.rowCount;i++){
            this.ctx.moveTo(0,this.cellWidth*i);
            this.ctx.lineTo(this.cellWidth*this.rowCount,this.cellWidth*i);
        }
        this.ctx.stroke();
        
        // 畫縱線
        this.ctx.beginPath();
        for(let i=0;i<this.colCount;i++){
            this.ctx.moveTo(this.cellWidth*i,0);
            this.ctx.lineTo(this.cellWidth*i,this.cellWidth*this.colCount);
        }
        this.ctx.stroke();
    }
    
    // 畫全部的棋子
    drawPieces(){
        for(let i=0;i<this.pieces.length;i++){
            for(let j=0;j<this.pieces[i].length;j++){
                if(this.pieces[i][j].value){
                    var x = i * this.cellWidth + this.cellWidth/2;
                    var y = j * this.cellWidth + this.cellWidth/2;
                    this.drawDot(x,y,this.R,this.pieces[i][j].color);
                }
            }
        }
    }
    
    // 畫全部的提示
    drawHint(){
        for(let i=0;i<this.recommend.length;i++){
            var x = this.recommend[i].x * this.cellWidth + this.cellWidth/2;
            var y = this.recommend[i].y * this.cellWidth + this.cellWidth/2;
            this.drawDot(x,y,this.HR, this.HColor);
        }
    }
    
    // 是否能夠走這一步
    canGo (xx,yy){// 條件 1.雙方無期可走        2.棋盤下滿了          3.沒有本色的棋子了
        var flag = false;
        for(var i=0;i<this.recommend.length;i++){
            if(this.recommend[i].x == xx && this.recommend[i].y == yy){
                flag = true;
                break;
            }
        }
        return flag;
    }
    
    // 吃子
    fire (x,y){
        for(var i=0;i<this.recommend.length;i++){
            if(this.recommend[i].x == x && this.recommend[i].y == y){ // 走推薦位置(吃掉能夠吃的各個方向)
                // console.log("x:"+this.recommend[i].x+";y:"+this.recommend[i].y,"匹配位置");
                var xx =x, yy = y;
                switch(this.recommend[i].direction){ //上加下減y  左加右減x
                    case "": // j在下(大) yy在上(小) xx不變
                        while(++yy < this.recommend[i].j){
                            this.pieces[xx][yy].color = this.active;
                        }
                        break;
                    case "":// x在左(小) xx在右(大) yy不變
                        while(--xx > this.recommend[i].i){
                            this.pieces[xx][yy].color = this.active;
                        }
                        break;
                    case "":// j在上(小) yy在下(大) xx不變
                        while(--yy > this.recommend[i].j){
                            this.pieces[xx][yy].color = this.active;
                        }
                        break;
                    case "":// i在左(小) yy在右(大) i不變
                        while(++xx < this.recommend[i].i){
                            this.pieces[xx][yy].color = this.active;
                        }
                        break;
                    case "右上":
                        while(--xx > this.recommend[i].i){
                            this.pieces[xx][++yy].color = this.active;
                        }
                        break;
                    case "右下":
                        while(--xx > this.recommend[i].i){
                            this.pieces[xx][--yy].color = this.active;
                        }
                        break;
                    case "左下":
                        while(++xx < this.recommend[i].i){
                            this.pieces[xx][--yy].color = this.active;
                        }
                        break;
                    case "左上":
                        while(++xx < this.recommend[i].i){
                            this.pieces[xx][++yy].color = this.active;
                        }
                        break;
                    default: console.log("jjle,吃不了子");
                }
            }
        }
    }
    
    // 顯示能夠走的位置
    recommed (){
        this.recommend = [];
        for(var i=0;i<this.rowCount;i++){
            for(var j=0;j<this.colCount;j++){
                if(this.pieces[i][j].color == this.active){ // 輪到當前方走棋
                    // console.log("當前棋子顏色:"+active+";x:"+i+";y:"+j);
                    // 判斷8個方向上是否能夠走
                    // 右  1右測第一個是敵方棋子,2而後排查後面的每個但不越界,3遇到己方或者空結束(己方的不能夠走,空能夠走,敵方的繼續排查)
                    if(this.pieces[i+1] && this.pieces[i+1][j].color && this.pieces[i+1][j].color != this.active){ // 有對方的棋才能夠走
                        for(var a=2;i+a<this.rowCount;a++){
                            if(this.pieces[i+a][j].color == ""){ //
                                this.recommend.push({x:i+a,y:j,i:i,j:j,direction:""});
                                // console.log(i+a,j,"右");
                                break;
                            }else if(this.pieces[i+a][j].color == this.active){ // 己方
                                break;
                            }else{ // 敵方
                                continue;
                            }
                        }
                    }
                    
                    //
                    if(this.pieces[i-1] && this.pieces[i-1][j].color && this.pieces[i-1][j].color != this.active){ // 有對方的棋才能夠走
                        for(var a=2;i-a>=0;a++){
                            if(this.pieces[i-a][j] && this.pieces[i-a][j].color){ // 有棋子 判斷是不是敵方棋子
                                if(this.pieces[i-a][j].color != this.active){
                                    continue;
                                }else{
                                    break;
                                }
                            }else{ //沒有棋子  能夠走
                                this.recommend.push({x:i-a,y:j,i:i,j:j,direction:""});
                                // console.log(i-a,j,"左")
                                break;
                            }
                        }
                    }
                    
                    //
                    if(this.pieces[i][j-1] && this.pieces[i][j-1].color && this.pieces[i][j-1].color != this.active){ // 有對方的棋才能夠走
                        for(var a=2;j-a>=0;a++){
                            if(this.pieces[i][j-a] && this.pieces[i][j-a].color){ // 有棋子 判斷是不是敵方棋子
                                if(this.pieces[i][j-a].color != this.active){
                                    continue;
                                }else{
                                    break;
                                }
                            }else{ //沒有棋子  能夠走
                                this.recommend.push({x:i,y:j-a,i:i,j:j,direction:""});
                                // console.log(i,j-a,"上")
                                break;
                            }
                        }
                    }
                    
                    //
                    if(this.pieces[i][j+1] && this.pieces[i][j+1].color && this.pieces[i][j+1].color != this.active){ // 有對方的棋才能夠走
                        for(var a=2;j+a<this.rowCount;a++){
                            if(this.pieces[i][j+a].color){ // 有棋子 判斷是不是敵方棋子
                                if(this.pieces[i][j+a].color != this.active){
                                    continue;
                                }else{
                                    break;
                                }
                            }else{ //沒有棋子  能夠走
                                this.recommend.push({x:i,y:j+a,i:i,j:j,direction:""});
                                // console.log(i,j+a,"下")
                                break;
                            }
                        }
                    }
                    
                    // 右下
                    if(this.pieces[i+1] && this.pieces[i+1][j+1] && this.pieces[i+1][j+1].color && this.pieces[i+1][j+1].color != this.active){ // 有對方的棋才能夠走
                        for(var a=2;a+j<this.rowCount && a+i<this.rowCount;a++){
                            if(this.pieces[i+a][j+a].color){ // 有棋子 判斷是不是敵方棋子
                                if(this.pieces[i+a][j+a].color != this.active){
                                    continue;
                                }else{
                                    break;
                                }
                            }else{ //沒有棋子  能夠走
                                this.recommend.push({x:i+a,y:j+a,i:i,j:j,direction:"右下"});
                                // console.log(i+a,j+a,"右下")
                                break;
                            }
                        }
                    }
                    
                    // 右上
                    if(this.pieces[i+1] && this.pieces[i+1][j-1] && this.pieces[i+1][j-1].color && this.pieces[i+1][j-1].color != this.active){ // 有對方的棋才能夠走
                        for(var a=2;i+a<this.rowCount && j-a>=0;a++){
                            if(this.pieces[i+a][j-a].color){ // 有棋子 判斷是不是敵方棋子
                                if(this.pieces[i+a][j-a].color != this.active){
                                    continue;
                                }else{
                                    break;
                                }
                            }else{ //沒有棋子  能夠走
                                this.recommend.push({x:i+a,y:j-a,i:i,j:j,direction:"右上"});
                                // console.log(i+a,j-a,"右上")
                                break;
                            }
                        }
                    }
                    
                    // 左上
                    if(this.pieces[i-1] && this.pieces[i-1][j-1] && this.pieces[i-1][j-1].color && this.pieces[i-1][j-1].color != this.active){ // 有對方的棋才能夠走
                        for(var a=2;i-a>=0 && j-a>=0;a++){
                            if(this.pieces[i-a][j-a].color){ // 有棋子 判斷是不是敵方棋子
                                if(this.pieces[i-a][j-a].color != this.active){
                                    continue;
                                }else{
                                    break;
                                }
                            }else{ //沒有棋子  能夠走
                                this.recommend.push({x:i-a,y:j-a,i:i,j:j,direction:"左上"});
                                // console.log(i-a,j-a,"左上")
                                break;
                            }
                        }
                    }
                    
                    // 左下
                    if(this.pieces[i-1] && this.pieces[i-1][j+1] && this.pieces[i-1][j+1].color && this.pieces[i-1][j+1].color != this.active){ // 有對方的棋才能夠走
                        for(var a=2;i-a>=0 && j+a<this.rowCount;a++){
                            if(this.pieces[i-a][j+a].color){ // 有棋子 判斷是不是敵方棋子
                                if(this.pieces[i-a][j+a].color != this.active){
                                    continue;
                                }else{
                                    break;
                                }
                            }else{ //沒有棋子  能夠走
                                this.recommend.push({x:i-a,y:j+a,i:i,j:j,direction:"左下"});
                                // console.log(i-a,j+a,"左下")
                                break;
                            }
                        }
                    }
                    
                }else{ // 沒有棋子或不是當前方
                    continue;
                }
            }
        }
        console.log(this.recommend,"推薦位置數組");
        if(this.recommend.length==0){
            var piecesCount = this.getNum(this.active)+this.getNum(this.active == "black" ? "white" : "black");
            if(piecesCount<this.rowCount*this.colCount){
                this.noChess++;
                if(this.noChess<=64-piecesCount){ // 頂多連走 64-棋子數
                    this.active = this.active == "black" ? "white" : "black";
//                    console.log(this.active+"沒有棋走,另外一方繼續");
                    alert(this.active+"沒有棋走,另外一方繼續");
                    this.renderUi();
                }else{
                    this.noChess=0;
                    this.over();
                }    
            }else{ // 下滿了
                this.over();
            }
        }
    }
    
    // 遊戲結束      判斷輸贏、再來一局
    over (){
        switch (this.winner()){
            case "white":
//                console.log("恭喜恭喜","白棋贏");
                alert("白棋贏");
                break;
            case "black":
//                console.log("恭喜恭喜","黑棋贏");
                alert("黑棋贏");
                break;
            default: 
//                console.log("恭喜恭喜","和棋");
                alert("和棋");
        }
    }
    
    // 走一步  1.判斷這一步是否能夠走 2.改變路途的棋子顏色
    goStep (x,y){
//        this.recommed();
        if(this.canGo(x,y)){
            this.pieces[x][y].value = 1;
            this.pieces[x][y].color = this.active;
            
            this.fire(x,y);
            
            // 這裏有個大坑
//            var curPieces = Object.assign([],this.pieces);
//            this.hisStatus.push(Object.assign([],curPieces));
            this.hisStatus.push(this.deepClone(this.pieces));
//            console.log(this.hisStatus,"棋局")
            
            // 輪流走棋
            this.active = this.active == "black" ? "white" : "black";
            
            this.renderUi();
            
            this.whiteCount = this.getNum ("white");
            this.blackCount = this.getNum ("black");
        }else{
//            console.log("別搗亂,好好走");
            alert("別搗亂,好好走");
        }
    }
    
    // 深拷貝
    deepClone (values) {
      var copy;
    
      // Handle the 3 simple types, and null or undefined
      if (null == values || "object" != typeof values) return values;
    
      // Handle Date
      if (values instanceof Date) {
        copy = new Date();
        copy.setTime(values.getTime());
        return copy;
      }
    
      // Handle Array
      if (values instanceof Array) {
        copy = [];
        for (var i = 0, len = values.length; i < len; i++) {
          copy[i] = this.deepClone(values[i]);
        }
        return copy;
      }
    
      // Handle Object
      if (values instanceof Object) {
        copy = {};
        for (var attr in values) {
          if (values.hasOwnProperty(attr)) copy[attr] = this.deepClone(values[attr]);
        }
        return copy;
      }
    
      throw new Error("Unable to copy values! Its type isn't supported.");
    }
    
    // 判斷誰贏winner
    winner (){
        if(this.getNum("white") > this.getNum("black")){
            this.victor = "white";
            return "white";
        } else if(this.getNum("white") < this.getNum("black")){
            this.victor = "black";
            return "black";
        } else {
            this.victor = "和棋";
            return "和棋";
        }
    }
    
    // 白棋或黑棋的數量
    getNum (color){
        var count = 0;
        for(var i=0,len=this.pieces.length;i<len;i++){
            for(var j=0,len1=this.pieces[i].length;j<len1;j++){
                if(color == this.pieces[i][j].color){
                    count++;
                }
            }
        }
        return count;
    }
    
    // return 贏家
    getVictor (){
        switch(this.victor){
            case "white":
                alert("白棋勝");
                break;
            case "black":
                alert("黑棋勝");
                break;
            case "和棋":
                alert("和棋");
                break;
            default: alert("出bug了");
        }
    }
    
    // 認輸
    giveUp (){
        this.victor = this.active == "black" ? "white" : "black";
        this.getVictor();
        this.resetData();
    }
    
    // 求和
    negotiate (){
        this.victor = "和棋";
        this.getVictor();
        this.resetData();
    }
    
    // 悔棋
    regret (){
        if(this.hisStatus.length>1){
            this.hisStatus.pop();
//            console.log(this.hisStatus,"刪除一步的棋局");
            this.pieces = Object.assign([],this.hisStatus[this.hisStatus.length-1]);
            //[...this.hisStatus[this.hisStatus.length-1]];
            this.active = this.active == "black" ? "white" : "black";
//            console.log(this.pieces,"當前棋局");
            this.renderUi();
            this.whiteCount = this.getNum ("white");
            this.blackCount = this.getNum ("black");
        }else {
            alert("沒有棋能夠悔了");
        }
            
    }
}

</script>
    <script type="text/javascript">
        var score = document.getElementById("score");
        var regret = document.getElementById("regret");
        var giveUp = document.getElementById("giveUp");
        var negotiate = document.getElementById("negotiate");
        var offset;
        var reversi = new Reversi("blackWhite");
        reversi.canvas.addEventListener("click",function(e){
            offset = reversi.canvas.getBoundingClientRect();
            var x = Math.floor((e.clientX - offset.left) / reversi.cellWidth);
            var y = Math.floor((e.clientY - offset.top) / reversi.cellWidth);
            console.log(x,y,"點擊位置");
            // 走棋
            reversi.goStep(x,y);
            
            getScore();
        },false);
        
        function getScore(){
            score.innerHTML = "黑:白 = "+reversi.blackCount + ":"+reversi.whiteCount;
        }
        
        giveUp.onclick = function(){
            if(confirm("你肯定認輸嗎?")){
                reversi.giveUp();
                getScore();
            }
        }
        
        negotiate.onclick = function (){
            if(confirm("你肯定求和嗎?")){
                if(confirm("你贊成和棋嗎?")){
                    reversi.negotiate();
                    getScore();
                }else{
                    alert("對方拒絕和棋!");
                }
            }
        }
        
        regret.onclick = function(){
            if(confirm("對方請求悔棋,是否贊成?")){
                reversi.regret();
                getScore();
            }else{
                alert("對方拒絕悔棋");
            }
        }
    </script>
</html>

 


更多專業前端知識,請上 【猿2048】www.mk2048.com
相關文章
相關標籤/搜索