前端談談實現五子棋

秉承着會就分享,不會就折騰的技術宗旨。本身利用週末的時間將休閒小遊戲-五子棋從新梳理了一下,整理成一個小的教程,分享出來給你們指點指點。javascript

喬巴圖01

五子棋規則

五子棋的規則我簡單梳理而且改造以下哈:java

  1. 對局雙方各執一色棋子;
  2. 空棋盤開局;
  3. 黑先、白後或者白先、黑後,交替下子,每次只能下一子;
  4. 橫線、豎線或者斜線上有連續五個同一色的棋子,則遊戲結束;

正式比賽的規則,能夠戳百度百科瞭解下哈--五子棋git

代碼骨架

這裏實現的五子棋小遊戲是使用javascript語言進行編寫的,使用到了es6語法,面向對象的思想進行。es6

// 設置五子棋類
class Gobang { 
  constructor(options={}){
    this.options = options;
    this.init();
  }
  init() {
    const { options } = this;
  }
}
// 實例化對象
let gobang = new Gobang({}); 
複製代碼

上面的Gobang類中,包含了一個constructor和init方法。其中constructor方法是類默認的方法,經過new命令生成對象實例時候,自動調用該方法。一個類必須有一個constructor方法,若是沒有顯式定義,一個空的constructor方法會默認添加。而後就是init方法了,這裏我是整個類的初始化的入口方法。使用類進行的面向對象方法進行編寫,比較好管理代碼和功能的擴展。github

繪製棋盤

棋盤分爲兩種,一種是視覺(物理)上的棋盤,另一個是邏輯上的棋盤,你是看不見的。下面的一張圖就很形象地展現了20*20棋盤的物理和邏輯方式。canvas

物理和邏輯棋盤

繪製物理棋盤,咱們這裏使用到了canvas的相關知識點,控制畫筆繪製棋盤:數組

// 繪製出物理棋盤
drawChessBoard() {
  const context = this.chessboard.getContext('2d');
  const {padding, count, borderColor} = this.options.gobangStyle;
  let half_padding = padding/2;
  this.chessboard.width = this.chessboard.height = padding * count;
  context.strokeStyle = borderColor;
  // 畫棋盤
  for(var i = 0; i < count; i++){
    context.moveTo(half_padding+i*padding, half_padding);
    context.lineTo(half_padding+i*padding, padding*count-half_padding);
    context.stroke(); // 這裏繪製出的是豎軸
    context.moveTo(half_padding, half_padding+i*padding);
    context.lineTo(count*padding-half_padding, half_padding+i*padding);
    context.stroke(); // 這裏繪製出的是橫軸
  }
}
複製代碼

這裏使用到的padding,count,borderColor等都是在實例化的時候傳進去的。這樣提升了可配置性和管理。上面的代碼是繪製物理上的棋盤,那麼邏輯上的棋盤雖然不可以繪製出來,可是咱們能夠表示出來。這裏咱們使用了二維數組的方法去記錄邏輯位置,好比(0,0)點對應的數組下標是[0][0];而後(1,2)點對應的下標是[1][2]...以此類推。而後咱們再爲這個邏輯點賦值爲0,表示當前點沒有落子。ui

// 繪製邏輯矩陣棋盤
initChessboardMatrix(){
  const {count} = this.options.gobangStyle;
  const checkerboard = [];
  for(let x = 0; x < count; x++){
    checkerboard[x] = [];
    for(let y = 0; y < count; y++){
      checkerboard[x][y] = 0;
    }
  }
}
複製代碼

物理棋盤和邏輯棋盤有了以後,就能夠考慮到將物理棋盤和邏輯棋盤關聯起來了。這個比較簡單,就是要計算真實的單元格位置進行除法操做便可。這步的管理在後面的落子步驟有提到。this

繪製棋子

五子棋的棋子有且僅有兩種--黑色棋子或者白色棋子。這裏也是使用canvas的知識點來繪製棋子。spa

黑色棋子和白色棋子

// 繪製黑棋或白棋
drawChessman(x , y, isBlack){
  const context = this.chessboard.getContext('2d');
  let gradient = context.createRadialGradient(x, y, 10, x-5, y-5, 0);
  context.beginPath();
  context.arc(x, y, 10, 0, 2 * Math.PI);
  context.closePath();
  if(isBlack){
    gradient.addColorStop(0,'#0a0a0a'); 
    gradient.addColorStop(1,'#636766'); 
  }else{ 
    gradient.addColorStop(0,'#d1d1d1');
    gradient.addColorStop(1,'#f9f9f9');
  }
  context.fillStyle = gradient;
  context.fill();
}
複製代碼

落子實現人人對戰

上一節的繪製黑棋和白棋的方法是在單獨一個頁面出來繪製的。如今咱們將繪製棋子和棋盤整合,並實現人人對戰的下棋模式。

落子實現人人對戰

咱們要監聽點擊在棋盤上的事件,而後關聯物理棋盤和邏輯棋盤點,以後在相應的地方刻畫棋子便可。

// 監聽落子
listenDownChessman() {
  // 監聽點擊棋盤對象事件
  this.chessboard.onclick = event => {
    let {padding} = this.options.gobangStyle;
    let {
        offsetX: x,
        offsetY: y,
    } = event;
    x = Math.abs(Math.round((x-padding/2)/this.lattice.width));
    y = Math.abs(Math.round((y-padding/2)/this.lattice.height));
    if(this.checkerboard[x][y] !== undefined && Object.is(this.checkerboard[x][y],0)){
      this.checkerboard[x][y] = this.role;
      // 這裏調用刻畫棋子的方法
      this.drawChessman(x,y,Object.is(this.role , 1));
      // 切換棋子的角色
      this.role = Object.is(this.role , 1) ? 2 : 1;
    }
  }
}
複製代碼

實現悔棋

在雙方下棋的時候,容許雙方對已經下的棋子進行調整,也就是悔棋。以下截圖展現功能:

悔棋

實現悔棋功能的時候,須要知道下棋的歷史記錄和當前的落子步數和角色。對於歷史的記錄,這裏對每一步的落子都使用一個對象進行存儲,並放到一個history的數組裏面進行保存。

// 悔棋
regretChess() {
  if(this.history.length){
    const prev = this.history[this.currentStep - 1];
    if(prev){
      const {
        x,
        y,
        role
      } = prev;
      this.minusStep(x,y);
      this.checkerboard[prev.x][prev.y] = 0;
      this.currentStep--;
      this.role = Object.is(role,1) ? 1 : 2;
    }
  }
}
// 銷燬棋子
minusStep(x, y) {
  const context = this.chessboard.getContext('2d');
  const {padding, count} = this.options.gobangStyle;
  context.clearRect(x*padding, y*padding, padding,padding);
}
複製代碼

上面的代碼確實是實現了悔棋功能,可是,在實現悔棋的時候,已經破壞掉了棋盤的UI,由於咱們是使用canvas的clearRect方法,將撤銷的棋子使用新的四邊形進行覆蓋,那也就覆蓋了撤銷棋子處的物理棋盤了。爲了彌補這個被覆蓋的物理棋盤,咱們得從新繪製出此處座標的新物理棋盤線條。這裏的修復要考慮到落子在棋盤的不一樣位置,要分九種不一樣的狀況進行修復:

  • 左上角棋盤
  • 左邊緣棋盤
  • 左下角棋盤
  • 下邊緣棋盤
  • 右下角棋盤
  • 右邊緣棋盤
  • 右上角棋盤
  • 上邊緣棋盤
  • 中間(非邊界)棋盤
// 修補刪除後的棋盤,將九種狀況的不一樣參數傳過來便可
fixchessboard (a , b, c , d , e , f , g , h){
  const context = this.chessboard.getContext('2d');
  const {borderColor, lineWidth} = this.options.gobangStyle;
  context.strokeStyle = borderColor;
  context.lineWidth = lineWidth;
  context.beginPath();
  context.moveTo(a , b);
  context.lineTo(c , d);
  context.moveTo(e, f);
  context.lineTo(g , h);
  context.stroke();
}
複製代碼

實現撤銷悔棋

有容許悔棋,那麼就有容許撤銷悔棋這樣子才合理。同悔棋功能,撤銷悔棋是須要知道下棋的歷史記錄和當前的步驟和棋子角色的。

// 撤銷悔棋
revokedRegretChess(){
  const next = this.history[this.currentStep]; 
  if(next) {
    this.drawChessman(next.x, next.y, next.role === 1);
    this.checkerboard[next.x][next.y] = next.role;
    this.currentStep++; 
    this.role = Object.is(this.role, 1) ? 2 : 1; 
  }
}
複製代碼

實現撤銷悔棋

勝利提示/遊戲結束

五子棋的的結束也就是必需要決出勝利者,或者是棋盤沒有位置能夠下棋了。這裏考慮決出勝利爲遊戲結束的切入點,上面也說到了如何纔算是一方獲勝--橫線、豎線或者斜線上有連續五個同一色的棋子。那麼咱們就對這四種狀況進行處理,咱們在矩陣中記錄當前點擊的數組點中是否有連續的五個1(黑子)或者連續的五個2(白子)便可。以下截圖的x軸上的白子獲勝狀況,注意gif圖右側打印出來的數組內容:

勝利提示/遊戲結束

// 裁判觀察棋子,判斷獲勝一方
checkReferee(x , y , role) {
  if((x == undefined)||(y == undefined)||(role==undefined)) return;
  const XContinuous = this.checkerboard.map(x => x[y]); // x軸上連殺
  const YContinuous = this.checkerboard[x]; // y軸上連殺
  const S1Continuous = []; // 存儲左斜線連殺
  const S2Continuous = []; // 存儲右斜線連殺
  this.checkerboard.forEach((_y,i) => {
    // 左斜線
    const S1Item = _y[y - (x - i)];
    if(S1Item !== undefined){
      S1Continuous.push(S1Item);
    }
    // 右斜線
    const S2Item = _y[y + (x - i)];
    if(S2Item !== undefined) {
      S2Continuous.push(S2Item);
    }
  });
}
複製代碼

至此,已經一步步講解完如何開發一個可以在pc上愉快玩耍的休閒小遊戲-五子棋了。不妥之處還請指正哈 @~@

喬巴圖02

後話

五子棋的體驗地址--休閒遊戲-五子棋

文章首發地址--github-五子棋遊戲

代碼倉庫地址--github-五子棋教程

創做文章不易,既然都看到這裏了,留個贊再走唄~

喬巴圖03
相關文章
相關標籤/搜索