2018年1月19日 星期五

小山的 C# 教學-第46課-五子棋小遊戲(七)-簡單勝利判斷

本課簡介

本課為五子棋系列最後一課
這次的目標就是要寫一個簡單的勝利判斷
來看看怎麼寫吧!

教學影片

注意:影片有高畫質 720P 的選項,可以看得更清楚喔!



重點提示


製作重點

  • 檢查勝利的方式 - 只要檢查最後下的那顆棋子的八個方向是否有五顆棋子連成一直線
  • 在測試程式時,若發生錯誤,可以把滑鼠移到變數上。此時就會顯示該變數的值為多少。
  • 注意在 if 內有多個條件時,若程式發現只要檢查前面的條件就足夠,後面的條件就不會被執行。

製作步驟

  1. 在 Board 內新增一個 private Point 變數,lastPlacedNote,一開始設為 NO_MATCH_NODE
    1. 新增一個 public get 存取器 LastPlacedNote,用來取得 lastPlacedNote
    2. 在 Board 內的 PlaceAPiece 把最後下子的位置存在 lastPlacedNote
  2. 在 Game 內新增一個 private PieceType 變數 winner,一開始設為 PieceType.NONE
    • 新增一個 public get 存取器 Winner,用來取得 winner
  3. 在 Game.PlaceAPiece 內的交換選手前,加入 CheckWinner。代表在交換前會先檢查勝利者。
  4. 實作 Game 的 CheckWinner() (先檢查一個方向)
    1. 先用兩個變數 centerX 跟 centerY 儲存 board.LastPlacedNote 的 X 跟 Y 座標
    2. 加入一個 while 迴圈檢查五顆座標連續的棋子是否顏色相同。檢查棋盤上某個位置的顏色可以用 board.GetPieceType(x, y)。
      • 記得在呼叫 board.GetPieceType(x, y) 前,先檢查座標是否有超出棋盤邊界
      • 若發現有一顆顏色不同就提早跳出 (break)
    3. 檢查是否看到五顆棋子,若沒看到代表沒有勝利者
  5. 在 Form1_MouseDown 內檢查 game.Winner。
    • 若為 PieceType.BLACK,就用 MessageBox 印出「黑色獲勝」。白色也用類似方式處理。
  6. 在 Game.CheckWinner 內增加兩個 for 迴圈檢查八個方向

練習

  1. 嘗試解決最後棋子下在五顆連線的中間,而不是邊邊時無法判斷勝利的 bug
  2. 嘗試實作重新啟動遊戲的功能
  3. 在有人快勝利時提出警告訊息
  4. 想出一個方法讓白色自動下子 (例如:最簡單的作法,隨便找個讓白色隨機亂下)

27 則留言:

  1. 作者已經移除這則留言。

    回覆刪除
  2. 不好意思,我所想的方法还是有bug。所以我删除了。

    回覆刪除
  3. 您好,您的练习题第一个,我想出了一种方法:检查一个方向的棋子,同时检查相对方向的棋子。我把您的check Winner的方法中增加了一些代码:在两个for循环中增加了一个:int count2 = 1;
    while (count2 < 5)
    {
    int targetX = centerX - count2 * xDir;
    int targetY = centerY - count2 * yDir;

    if (targetX < 0 || targetX >= Board.NODE_COUNT ||
    targetY < 0 || targetY >= Board.NODE_COUNT ||
    board.GetPieceType(targetX, targetY) != currentPlayer)
    {
    break;
    }
    count2++;
    }
    同时在最后增加了一个判断:
    //检查是否看到5颗棋子
    if (count + count2 > 5)
    winner = currentPlayer;

    不知道我测试的一下,没有问题。如果有更好的方法或者我这个方法有问题,请多多指教,谢谢您。

    回覆刪除
    回覆
    1. 竟然有人想法跟我一樣 來留言下
      不過count2=0才對 你這樣會多算一次就會變4子棋了

      刪除
  4. 上面這個方法會讓程式有多餘程式,基本上只要迴圈有四次並且使用以上的方法,
    就等於找完八個地方了,應該是有更好解。

    回覆刪除
    回覆
    1. 作者已經移除這則留言。

      刪除
    2. 谢谢指点,但是我还是不太明白,能给出稍微详细的解释吗?

      刪除
    3. 作者已經移除這則留言。

      刪除
    4. 我的想法是找完後存成二維array
      然後在一相對應位置相加看有沒有 4 個

      public int[,] countPieceRecord = new int[3,3]; // 紀錄八個方向相同顏色棋子個數



      // 判斷有無連線
      private void CheckWinner()
      {
      int centerX = board.LastPlaceNode.X;
      int centerY = board.LastPlaceNode.Y;

      // 檢查八個方向
      for( int xDir = -1; xDir <=1; xDir ++)
      {
      for( int yDir = -1; yDir <=1; yDir ++)
      {
      // 排除檢查自己(中心)
      if( xDir ==0 && yDir ==0)
      continue;

      // 紀錄相同顏色棋子個數
      int count = 1;

      while(count < 5)
      {
      int targetX = centerX + count * xDir;
      int targetY = centerY + count * yDir;

      //Console.WriteLine("targetX:" + targetX);
      // Console.WriteLine("targetY:" + targetY);


      // 檢查顏色是否相同
      if (targetX < 0 || targetX >= Board.NODE_COUNT ||
      targetY < 0 || targetY >= Board.NODE_COUNT ||
      board.GetPieceType(targetX, targetY) != currentPlayer)
      break;
      count++;

      }

      countPieceRecord[xDir + 1, yDir + 1] = count - 1;


      if (isWinnerExist(countPieceRecord))
      winner = currentPlayer;
      }

      }


      }

      //check winner exist or not
      private bool isWinnerExist(int[,] record)
      {
      // int recordCenter_X = 1;
      // int recordCenter_Y = 1;

      int result1 = record[0,1] + record[2,1]; // 橫
      int result2 = record[1,0] + record[1,2]; // 直
      int result3 = record[0,2] + record[2,0]; // 斜
      int result4 = record[0,0] + record[2,2]; // 反斜

      if (result1 == 4 ||
      result2 == 4 ||
      result3 == 4 ||
      result4 == 4 )
      {
      // winner exist
      return true;
      }


      return false;
      }

      刪除
  5. 看完你寫五子棋程式的過程,收穫真是非常的多.
    以前在書上看的語法,有讀沒有懂,經過模仿你寫程式的過程,
    對於物件導向.重構.物件導向的設計原則.設計模式的理解和如何運用,
    突然變得好清楚.真是非常感謝你!非常感謝你!

    希望以後你有空,能夠再出一些這種從無到有的專案類型的教學影片.真是非常感謝你!

    回覆刪除
  6. 作者已經移除這則留言。

    回覆刪除
  7. 嘗試實作重新啟動遊戲的功能


    private void removeAll()
    {
    int nodeCount = Board.NODE_COUNT;

    for(int i=0; i<nodeCount ; i++)
    {
    for(int j=0; j<=nodeCount ; j++)
    {
    Piece p = game.GetPiece(i, j);
    this.Controls.Remove(p);
    // this.Controls.remove(p);
    }
    }
    }

    回覆刪除
    回覆
    1. 請問你的這段程式碼是放在哪一個位置?

      刪除
    2. 我是直接放Controls.Clear(); 再把其他值改成原本的樣子

      刪除
  8. 重作的話 我直接clear()掉棋子
    並且把pieces = new pieces[9,9];

    回覆刪除
  9. 安安 小山,不知道小山方不方便開一門針對msdn lib 的教學影片
    舉個例子
    比方說public IButtonControl AcceptButton { get; set; } 這個
    當看了這些敘述之後下一步該怎麼使用...等等之類的
    有些在msdn 下方有例子,有些則沒有,可否請小山老師可以幫忙教如何看msdn 與用
    謝謝

    回覆刪除
  10. 大神 能繼續新增嗎
    我是剛學C#的新手 真的很需要您啊 拜託拜託><

    回覆刪除
  11. 我也試著重做了
    但我發現直接使用Clear()方法好像會佔用內存,所以找了個應該算是蠻好的方法吧,
    這裡會用到一個新的 集合 叫消息隊列
    我在Game類裡添加一個新的變數:
    public Queue QControls = new Queue();//消息隊列
    還有一個新的方法:
    public void ClearGame(Control item)
    {
    for (int i = 0; i < item.Controls.Count; i++)
    {
    //這裡又調用了自己 我也不太清楚,估計是為了防止出現bug
    if (item.Controls[i].HasChildren)
    {
    ClearGame(item.Controls[i]);
    }
    else
    {
    //把找到的控件加入到消息隊列
    QControls.Enqueue(item.Controls[i]);
    }
    }
    //清理完後好像原來有棋子的地方就下不了棋了 所以重設了值
    currentPlayer = PieceType.BLACK;
    winner = PieceType.NONE;
    Board.pieces = new Piece[Board.NODE_COUNT, Board.NODE_COUNT];
    }
    form1:
    public void RemoveControls()
    {
    game.ClearGame(this);// this是指把form這個窗體傳進去
    while (game.QControls.Count != 0)
    {
    game.QControls.Dequeue().Dispose();//釋放消息隊列
    }
    }
    //如果還有不對的地方希望大佬指點

    回覆刪除
  12. 練習1,解決了,詳:
    https://github.com/oscarsun72/c-sharp-course-sample-code/tree/master/class-40-50/Class46Ex_1
    感恩感恩 讚歎讚歎 南無阿彌陀佛

    回覆刪除
    回覆
    1. 練習2,解決了,詳:
      https://github.com/oscarsun72/c-sharp-course-sample-code/tree/master/class-40-50/Class46Ex_2
      感恩感恩 讚歎讚歎 南無阿彌陀佛

      刪除
  13. 練習3,OK了,詳:
    https://github.com/oscarsun72/c-sharp-course-sample-code/tree/master/class-40-50/Class46Ex_3
    但怕有bug,請再多試、留意。感恩感恩 南無阿彌陀佛
    【在有人快勝利時提出警告訊息】

    回覆刪除
  14. 小弟分享我的方法和大家一起學習成長:
    1.在Board中建立一個method找出對應當下PlaceAPiece的對應棋盤最: 左上角座標、右上角座標、上方座標、左方座標
    2.在Game中建立一個掃描盤的method分別對上述四個點位掃描:左上到右下掃描、右上到左下掃描、上方至下掃描、左方至右掃描
    掃描出看有沒有連續五個棋子等於CurrentPlayer,如果有winnerPlayer=CurrentPlayer
    簡單說就是對當下放下去的那顆棋子進行米自行掃描,所以跳四變Win也能掃出來
    3.在Board中增加Reset的method把pieces=new Piece[9,9]
    4.在Game也增加一個Reset method調用Board的Reset,並把winner=PieceType.NONE;
    5.在form code的buttom down下判斷game.winner!=PieceType.NONE就執行game.Reset();

    回覆刪除
    回覆
    1. 3.~5.是指遊戲結束重來,5.要在加一行removeall的指令來清除棋子

      刪除
  15. 1)在for迴圈前增加一個int count_x的計數器
    2)在第二個for迴圈count++,下面增加一行count_x++
    3)在第一個for迴圈的底部增加判斷 if(count_x >= 5)則勝利,因為連線時有可能超過5棵棋子!

    回覆刪除