溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

C#實(shí)現(xiàn)五子棋游戲

發(fā)布時(shí)間:2020-08-25 04:31:27 來(lái)源:腳本之家 閱讀:229 作者:steveliu13 欄目:編程語(yǔ)言

曾經(jīng)自學(xué)C#做計(jì)算機(jī)圖形學(xué)的作業(yè),GDI+畫(huà)圖確實(shí)好用,目前在找.NET的實(shí)習(xí),嘗試做了一個(gè)最基本的五子棋,復(fù)習(xí)一下C#的基本語(yǔ)法,目前只能當(dāng)跟基友一起玩的單機(jī)小游戲,之后再加入AI和聯(lián)網(wǎng)對(duì)戰(zhàn)功能。目前我還是小菜鳥(niǎo),過(guò)程設(shè)計(jì)和代碼有不合理或者能優(yōu)化的地方歡迎各位大神指正。

首先是界面設(shè)計(jì),最后就是這樣,控件一共有一個(gè)PictureBox;一個(gè)開(kāi)始Button,命名為btnStart;一個(gè)重置Button,命名為btnReset;一個(gè)Label,用于顯示游戲狀態(tài)。

五子棋界面

C#實(shí)現(xiàn)五子棋游戲

然后是各基本類(lèi)。新建一個(gè)MainSize類(lèi)用于存放界面上的可能用到的參數(shù),主框體大小520*460,棋盤(pán)是一個(gè)PictureBox控件,大小401*401,棋盤(pán)20行20列,每個(gè)格子邊長(zhǎng)20,棋子直徑16。新建一個(gè)ChessBoard類(lèi)表示棋盤(pán),有一個(gè)靜態(tài)函數(shù)DrawBoard。之前做計(jì)算機(jī)圖形學(xué)作業(yè)畫(huà)函數(shù)時(shí)用到的畫(huà)坐標(biāo)系方法在這里同樣適用,函數(shù)代碼如下。

class ChessBoard
 {

  static readonly Color color = Color.Black;
  static readonly float penWid = 1.0f;
  static readonly Pen pen = new Pen(color, penWid);  

  public static void DrawCB(Graphics gra,PictureBox pic)
  {
   //每排數(shù)量
   int horC = MainSize.CBWid / MainSize.CBGap;
   //間隔
   int gap = MainSize.CBGap;
   Image img = new Bitmap(MainSize.CBWid, MainSize.CBHei);
   gra = Graphics.FromImage(img);
   gra.Clear(Color.White);
   gra.DrawRectangle(pen, 0, 0, MainSize.CBWid, MainSize.CBHei);
   //畫(huà)棋盤(pán)
   for (int i = 0; i < horC; i++)
   {
    gra.DrawLine(pen, 0, i * gap, MainSize.CBWid, i * gap);
    gra.DrawLine(pen, i * gap, 0, i * gap, MainSize.CBHei);
   }
   gra.DrawLine(pen, 0, horC * gap, MainSize.CBWid, horC * gap - 1);
   gra.DrawLine(pen, horC * gap - 1, 0, horC * gap, MainSize.CBHei);
   pic.Image = img;
  }

 }

還有一個(gè)基本類(lèi)Chess,用來(lái)表示棋子,有一個(gè)靜態(tài)函數(shù)DrawChess,代碼如下。這里代碼有點(diǎn)亂,寫(xiě)的時(shí)候沒(méi)加注釋。bool型變量用于表示下棋的雙方,pen1和pen2用于繪制雙方的棋子,顏色設(shè)置為紅藍(lán),因?yàn)樽怨偶t藍(lán)出CP【其實(shí)是本來(lái)想用黑白的但picturebox背景白色再畫(huà)白色棋子就看不出來(lái)】。整型變量nexX和newY用于表示棋子在棋盤(pán)上的坐標(biāo),根據(jù)四舍五入就近原則落點(diǎn)。這里的四舍五入我花了很長(zhǎng)時(shí)間,寫(xiě)了很長(zhǎng)的一段判斷代碼但都會(huì)出錯(cuò),最后借鑒了github上Xu Pu同學(xué)的數(shù)據(jù)結(jié)構(gòu)假期作業(yè)中的方法才完成了這個(gè)函數(shù),在此感謝這位同學(xué)~

class Chess
 {
  public static void DrawChess(bool type,PictureBox pic,Graphics graphic,MouseEventArgs e)
  {
   graphic = pic.CreateGraphics();
   Pen pen1 = new Pen(Color.Red, 1);
   Brush bru1 = new SolidBrush(Color.Red);
   Pen pen2 = new Pen(Color.Blue, 1);
   Brush bru2 = new SolidBrush(Color.Blue);
   int newX = (int)((e.X + MainSize.CBGap / 2) / MainSize.CBGap) * MainSize.CBGap - MainSize.ChessRadious / 2;
   int newY = (int)((e.Y + MainSize.CBGap / 2) / MainSize.CBGap) * MainSize.CBGap - MainSize.ChessRadious / 2;
   if (type)
   {
    graphic.DrawEllipse(pen1, newX, newY, MainSize.ChessRadious, MainSize.ChessRadious);
    graphic.FillEllipse(bru1, newX, newY, MainSize.ChessRadious, MainSize.ChessRadious);
   }
   if (!type)
   {
    graphic.DrawEllipse(pen2, newX, newY, MainSize.ChessRadious, MainSize.ChessRadious);
    graphic.FillEllipse(bru2, newX, newY, MainSize.ChessRadious, MainSize.ChessRadious);
   }
   graphic.Dispose();
  }

 }

最后是主程序,一共設(shè)置了四個(gè)全局變量,Graphics graphic用于畫(huà)圖,bool type用于表示下棋雙方,bool start表示游戲是否開(kāi)始,二維數(shù)組ChessBack用于模擬下棋場(chǎng)景并進(jìn)行計(jì)算。

主程序的構(gòu)造函數(shù)對(duì)主框體和PictureBox的大小進(jìn)行初始化,在Form1_Load函數(shù)中添加函數(shù)InitializeThis()對(duì)游戲進(jìn)行初始化,包括將ChessBack數(shù)組全部置0,type設(shè)為true,start設(shè)為false,繪制棋盤(pán),按鍵開(kāi)始的enabled屬性設(shè)為true,按鍵重置設(shè)為false。

按鍵開(kāi)始和重置的功能較為簡(jiǎn)單,代碼如下。

private void btnStart_Click(object sender, EventArgs e)
  {
   start = true;
   label1.Text = "游戲開(kāi)始!";
   btnStart.Enabled = false;
   btnReset.Enabled = true;
  }

  private void btnReset_Click(object sender, EventArgs e)
  {

   if (MessageBox.Show("確定要重新開(kāi)始?", "提示", MessageBoxButtons.YesNo) == DialogResult.Yes)
   {       
    InitializeThis();
   }
  }

最重要的部分是點(diǎn)擊picturebox的函數(shù),先判斷游戲是否開(kāi)始,否則不會(huì)有反應(yīng)。游戲開(kāi)始后點(diǎn)擊即可落子,并修改ChessBack矩陣,紅色為1,藍(lán)色為2,如果已經(jīng)有棋子則返回,即落子失敗。如果棋盤(pán)已滿(mǎn)但沒(méi)有分出勝負(fù)則彈出平局的提示框并給出提示。之后判斷是否分出勝負(fù),添加函數(shù)bool Victory(int bx,int by),分出勝負(fù)后提示勝利,如果沒(méi)有則返回。最后換人,type=!type即可,然后修改label的文字表面到哪一方落子了。代碼如下。

private void pictureBox1_MouseClick(object sender, MouseEventArgs e)
  {
   if (start)
   {
    //在計(jì)算矩陣中的位置
    int bX = (int)((e.X + MainSize.CBGap / 2) / MainSize.CBGap);
    int bY = (int)((e.Y + MainSize.CBGap / 2) / MainSize.CBGap);
    //防止在同一個(gè)位置落子
    if (ChessBack[bX, bY] != 0)
     return;
    Chess.DrawChess(type, pictureBox1, graphic, e);
    ChessBack[bX,bY] = type?1:2;
    //判斷棋盤(pán)是否滿(mǎn)了

    if (IsFull() && !Victory(bX,bY))
    {
     if (MessageBox.Show("游戲結(jié)束,平局") == DialogResult.OK)
      InitializeThis();
     return;
    }
    //判斷勝利
    if (Victory(bX,bY))
    {
     string Vic = type ? "紅" : "藍(lán)";
     if (MessageBox.Show(Vic + "方勝利!") == DialogResult.OK)
      InitializeThis();
     return; 
    }

    //換人
    type = !type;
    label1.Text = type ? "紅方's trun!" : "藍(lán)方's turn!";
   }
   else
    return;
  }

判斷勝負(fù)的函數(shù)有點(diǎn)復(fù)雜,我自己是用遞歸的方式判斷,先寫(xiě)了一個(gè)橫向的進(jìn)行測(cè)試,如果橫向兩端的值與當(dāng)前值相同則變量count++,最后返回count的值,如果>4則表示勝利。但是這個(gè)函數(shù)運(yùn)行出錯(cuò),顯示為stackoverflow,但我不知道錯(cuò)誤在哪,只好換一種判斷方法。后來(lái)才想明白兩端的值都是0則會(huì)溢出,應(yīng)該判斷兩端的值是否為1或2而不是當(dāng)前值。此處借鑒了實(shí)驗(yàn)樓網(wǎng)站上的C語(yǔ)言版五子棋的判斷勝負(fù)方式,從當(dāng)前落子的矩陣中,橫豎斜4個(gè)方向任意一個(gè)方向有連續(xù)5個(gè)數(shù)的值與當(dāng)前的值相同則勝利,實(shí)現(xiàn)也不復(fù)雜,細(xì)分為三個(gè)函數(shù)實(shí)現(xiàn)。代碼如下。

#region 判斷勝利
  private bool Victory(int bx,int by)
  {
   if (HorVic(bx, by))
    return true;
   if (VerVic(bx, by))
    return true;
   if (Vic45(bx, by))
    return true;
   else
    return false;
  }

  private bool Vic45(int bx, int by)
  {

   int b1 = (bx - 4) > 0 ? bx - 4 : 0;
   int b2 = (by - 4) > 0 ? by - 4 : 0;
   //int buttom = b1 > b2 ? b2 : b1;
   int val = ChessBack[bx, by];
   for (int i = b1,j=b2; i < 16&&j<16; i++,j++)
   {
    if (ChessBack[i, j] == val && ChessBack[i + 1, j + 1] == val &&
     ChessBack[i + 2, j + 2] == val && ChessBack[i + 3, j + 3] == val
     && ChessBack[i + 4, j + 4] == val)
     return true;
   }
   for (int i = b1, j = b2; i < 16 && j < 16; i++, j++)
   {
    if (ChessBack[i, j] == val && ChessBack[i + 1, j - 1] == val &&
     ChessBack[i + 2, j - 2] == val && ChessBack[i + 3, j - 3] == val
     && ChessBack[i - 4, j - 4] == val)
     return true;
   }
   return false;
  }

  private bool VerVic(int bx, int by)
  {
   int buttom = (by - 4) > 0 ? by - 4 : 0;
   int val = ChessBack[bx, by];
   for (int i = buttom; i < 16; i++)
   {
    if (ChessBack[bx, i] == val && ChessBack[bx, i+1] == val &&
     ChessBack[bx, i+2] == val && ChessBack[bx ,i+3] == val
     && ChessBack[bx, i+4] == val)
     return true;
   }
   return false;
  }

  private bool HorVic(int bx, int by)
  {
   int left = (bx-4)>0?bx-4:0;
   int val = ChessBack[bx,by];
   for (int i = left; i < 16; i++)
   {
    if (ChessBack[i, by] == val && ChessBack[i + 1, by] == val &&
     ChessBack[i + 2, by] == val && ChessBack[i + 3, by] == val
     && ChessBack[i + 4, by] == val)
     return true;
   }
   return false;
  }
  #endregion

完成后進(jìn)行測(cè)試,都沒(méi)有問(wèn)題,即認(rèn)為大功告成了。總結(jié)了一下編寫(xiě)過(guò)程中問(wèn)題,變量命名不太好,type,start等變量容易與關(guān)鍵詞混淆;主函數(shù)代碼行數(shù)還是太多,不方便閱讀,或許應(yīng)該把判定勝負(fù)和判定棋盤(pán)是否已滿(mǎn)也移到棋盤(pán)類(lèi)中;之后添加新游戲模式不方便,比如添加AI和聯(lián)機(jī)對(duì)戰(zhàn),需要修改的代碼有點(diǎn)多,個(gè)人想法是分別新建一個(gè)帶AI的框體和聯(lián)機(jī)的框體,然后修改基本類(lèi),在這種情況下最大化的代碼復(fù)用。

回想一個(gè)這個(gè)小程序編寫(xiě)并不復(fù)雜,但我花了很多時(shí)間在改錯(cuò)上,落子的函數(shù)和判斷勝利的函數(shù)花的時(shí)間最多,事前用筆進(jìn)行一下簡(jiǎn)單的演算再寫(xiě)或許能省一點(diǎn)時(shí)間。這是我的第一篇博客,暫時(shí)當(dāng)實(shí)驗(yàn)報(bào)告來(lái)寫(xiě)吧,雖然是出于興趣做的,但還是希望各位大神能指出不足,給出建議,我會(huì)虛心學(xué)習(xí)的。最后再次感謝實(shí)驗(yàn)樓網(wǎng)站【不是軟廣】和github上的Xu Pu同學(xué),還有各位看到最后的朋友們~

更多有趣的經(jīng)典小游戲?qū)崿F(xiàn)專(zhuān)題,分享給大家:

C++經(jīng)典小游戲匯總

python經(jīng)典小游戲匯總

python俄羅斯方塊游戲集合

JavaScript經(jīng)典游戲 玩不停

java經(jīng)典小游戲匯總

javascript經(jīng)典小游戲匯總

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI