C#实现五子棋详细教程
C#實現(xiàn)五子棋詳細教程
一、引言
算是做的第一個小游戲。
任務(wù)
創(chuàng)建一個五子棋程序
二、實驗環(huán)境
Visual stdio 2019
Windows窗體應(yīng)用
三、實驗過程
思路
0.導(dǎo)入資源
1.棋盤棋子分析
2.創(chuàng)建棋子
3.尋找最接近鼠標(biāo)位置的棋子中心位置
4.得到真實坐標(biāo),正確放置棋子
5.代碼重構(gòu),繼承重寫
6.簡單勝利判斷
7.所有種勝利判斷
8.玩家與贏家提示
0.導(dǎo)入資源
如下圖操作,制作并導(dǎo)入棋盤和棋子資源
(properties–>resources.resx–>現(xiàn)有資源–>添加現(xiàn)有文件)
1.棋盤棋子分析
打開圖片屬性
通過分析棋盤與棋子,明確放置棋子時的坐標(biāo)
棋子:50*50(留出上下10的空格)
棋盤:630*630= 70 * 9(8個子+邊緣半個+邊緣半個)
加入棋盤
容器panel–>放入棋盤(SIZE 630*630)
加入棋子
控件picturebox–>放入棋子(SIZE 50*50)
定位點是在框的左上角
左上角這個點是(10,10) 往右一個棋子+70 往下一個棋子+70
2.創(chuàng)建棋子
這里我學(xué)到繼承:
class Piece : PictureBox
冒號表示繼承 子類繼承父類 自動獲取父類的功能
2.1代碼創(chuàng)建棋子
類 Piece
新建一個類 Piece 表示 棋子 下面代碼很多由后面步驟填上~
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.Drawing;namespace 五子棋 {class Piece:PictureBox{private const int IMAGE_WIDTH = 50;private const int IMAGE_HEIGHT = 50;//定義圖片高度public Piece(int x,int y){//this.Image =Properties.Resources.white;//黑棋白棋得再用子類this.Location = new Point(x- IMAGE_WIDTH/2, y- IMAGE_HEIGHT/2);//位置指定this.Size = new Size(IMAGE_WIDTH, IMAGE_HEIGHT);}// public abstract PieceType GetPieceType();//抽象方法 實現(xiàn)多態(tài)//【在第八步中加入以下代碼】public Image GetNextImage()//直接判斷 沒有使用抽象方法實現(xiàn)多態(tài) {if(this is BlackPiece)//is 判斷是什么類型的數(shù)據(jù) x is int 返回bool數(shù)據(jù){return Properties.Resources.white;}else{return Properties.Resources.black;}}//public abstract Image GetNextImage();public Image GetImage(){return this.Image;}} }類BlackPiece 與 類WhitePiece(Piece的子類)
新建 類 BlackPiece{作為 黑棋 } 與 類 WhitePiece{作為 白棋 }(作為Piece的子類)
class BlackPiece:Piece {public BlackPiece(int x,int y):base(x,y)//調(diào)用父親{this.Image = Properties.Resources.black;}}類的關(guān)系: PictureBox**—>Piece–>**BlackPiece/WhitePiece
PictureBox:系統(tǒng)定義控件 -----> Piece:自定義 獲取 Piece 功能 指定寬與高 **-----> ** BlackPiece/WhitePiece:指定圖片為黑棋/白棋
2.2鼠標(biāo)創(chuàng)建棋子
分析:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-mgOiLNkv-1629278808843)(C#實現(xiàn)五子棋詳細教程.assets/圖5.png)]
picturebox坐標(biāo)在左上角(藍色) 我們要的是中間(綠色) 因此
完整:
private const int IMAGE_WIDTH = 50;private const int IMAGE_HEIGHT = 50;//定義圖片高度public Piece(int x,int y){//this.Image =Properties.Resources.white;//黑棋白棋得再用子類this.Location = new Point(x- IMAGE_WIDTH/2, y- IMAGE_HEIGHT/2);//位置指定this.Size = new Size(IMAGE_WIDTH, IMAGE_HEIGHT);}在棋盤中加入MouseDown事件
private void pnlGobang_MouseDown_1(object sender, MouseEventArgs e){//此代碼是暫時的前期的 文檔后面我們即將根據(jù)需要繼續(xù)改進if(isBlack==true)//黑白棋交替放置{this.pnlGobang.Controls.Add(new BlackPiece(e.X, e.Y));isBlack = false;}else{this.pnlGobang.Controls.Add(new WhitePiece(e.X, e.Y));isBlack = true;}}終于 我們能在上面放棋子了
3.尋找最接近鼠標(biāo)位置的棋子中心位置
問題:我發(fā)現(xiàn)玩家竟然能在棋盤上隨便放棋 但這是不行的棋子只能放在棋子該放的地方
目的:棋子只能放在棋子該放的地方 ->定位棋盤交叉點–> 尋找最接近鼠標(biāo)位置的棋子中心位置
分析:
1.鼠標(biāo)在框內(nèi)時,才能放置棋子
2.(能放置光標(biāo)顯示小手 不能放置的地方光標(biāo)顯示禁止)
3.靠近左邊 放入左邊(紫色)
中間 無效
靠近右邊 放入右邊(紅色)
變量聲明
public static readonly int NODE_COUNT = 9; // public類外面要用//棋子/交叉點個數(shù)//private const Point NO_MATCH_NODE=new Point(-1,-1);//private static readonly Point NO_MATCH_NODE = new Point(-1, -1);//記錄一個無效的點 //改成?//改為靜態(tài)才能賦值private readonly int OFFSET = 35;//邊界值 我:這個就像defineprivate readonly int NODE_DISTANCEA = 70;//交叉點距離 也是棋子空間 private readonly int NODE_RADIUS = 20;//交叉點附近有效區(qū)間點的半徑board類中一維查找交叉點
//一維查找public int FindTheClosestNode(int pos){if (pos < OFFSET - NODE_RADIUS)//在邊界{return -1;}pos -= OFFSET;int quotient = pos / NODE_DISTANCEA;//求商 恰恰是對應(yīng)的第幾個70的坐標(biāo)int remainder = pos % NODE_DISTANCEA;//求余數(shù) 恰恰是這個點和左邊交叉點的距離if (remainder<=NODE_RADIUS){return quotient;//第幾個棋子 返回左邊的棋子 左邊交叉點下標(biāo)}else if(remainder>=NODE_DISTANCEA- NODE_RADIUS){return quotient+1;//返回右邊的棋子 右邊交叉點下標(biāo)}else{return -1;//有效下標(biāo) 從零開始 返回-1是無效}}board類中二維查找交叉點
//二維查找交叉點public Point FindTheClosestNode(int x,int y){int nodeIdx = FindTheClosestNode(x);if(nodeIdx==-1){return NO_MATCH_NODE;}int nodeIdy = FindTheClosestNode(y);if (nodeIdy == -1){return NO_MATCH_NODE;}//升維return new Point(nodeIdx, nodeIdy);}board類中 判斷能否放置
public bool CanBePlaced(int x,int y){Point nodeId = FindTheClosestNode(x,y);if(nodeId==NO_MATCH_NODE){return false;}return true;}返回界面類中 顯示能否放置
(能放置光標(biāo)顯示小手 不能放置的地方光標(biāo)顯示禁止)
private void pnlGobang_MouseMove(object sender, MouseEventArgs e){//鼠標(biāo)移動事件判斷能不能放棋子//通過切換鼠標(biāo)樣子if(game.CanBePlaced(e.X,e.Y)==true)//兩個參數(shù)不變 都是界面代碼{this.Cursor = Cursors.Hand;//設(shè)置鼠標(biāo)樣式 出現(xiàn)小手}else{this.Cursor = Cursors.No;}}4.得到真實坐標(biāo),正確放置棋子
終于 我們得到最接近的棋子,我們的現(xiàn)在擁有了他的坐標(biāo)(例如[ 4,4 ] / [ 0,5 ] / [ 3 ,3 ])
但是 真正放在棋盤上確實需要真實的坐標(biāo),比如[290,290]
那么接下來我們就來得到他的真實坐標(biāo),正確放置棋子
需要用二維數(shù)組來存放已經(jīng)下過的棋子
private Piece[,] pieces =new Piece[9,9];1 數(shù)組的下標(biāo)正好和交叉點的下標(biāo)一一對應(yīng)
2 交叉點轉(zhuǎn)化為真真實的棋子位置坐標(biāo)
3 通過數(shù)組可以判斷有沒有下過棋子
轉(zhuǎn)化為真實棋子位置
private Point ConvertToRealPosition(Point nodeID)//參數(shù)是找到交叉點 //M:NODE_DISTANCEA=70 { //nodeID.X nodeID.Y 獲取真實下標(biāo)x,yPoint realPoint = new Point(OFFSET + NODE_DISTANCEA * nodeID.X, OFFSET + NODE_DISTANCEA * nodeID.Y);//邊界35+3*70return realPoint; }放入一顆棋子( x,y ,枚舉類型的type)
//放入一顆棋子 x,y ,枚舉類型的typepublic Piece PlaceAPiece(int x,int y,PieceType type)新生成一個類 枚舉類型的type PieceType type
enum PieceType{NONE,BLACK,WHITE}回到PlaceAPiece
[nodeId.X, nodeId.Y] 是[5,3]這種 而不是真實下標(biāo)
[realPoint.X, realPoint.Y] 是真實下標(biāo)
回到主設(shè)計界面吧,來運用PlaceAPiece
將e.X e.Y 替換成相應(yīng)的交叉點來添加新的棋子 -》真實點
Piece piece= borad.PlaceAPiece(x,y,currentPlayer);if(piece!=null){//生成新的棋子后 馬上判斷有沒有贏家CheckWinner();if(currentPlayer==PieceType.BLACK)//PieceType enum 枚舉{currentPlayer = PieceType.WHITE;}else if(currentPlayer == PieceType.WHITE){currentPlayer = PieceType.BLACK;}}OK 我們終于可以放棋子
5.代碼重構(gòu),繼承重寫
如果所有代碼只放在一個類中 ,不利于分解和封裝,不利于重用
所以我們模塊化出新類 讓 Game類 負責(zé)游戲邏輯 即方法重構(gòu),方法重寫
因為游戲的平臺和界面是多樣化的,所以就希望將我們的【后臺邏輯】和【前端界面】分離,后臺寫好了,前端可以改變 ,后臺固定。
所以就把FrmGobang的代碼的邏輯部分放到專門的游戲類 game類,進行重構(gòu),收拾一下
例如:
左邊的這段代碼是從FrmGobang窗體代碼中抽出來的邏輯代碼 封裝到【Game】中 不抽離界面代碼 即重構(gòu)
右邊是FrmGobang窗體界面代碼
后面我們還要進行勝負判斷,確定誰該下棋,確實誰贏了
所以我們得知道xy的位置上放置什么棋子—GetPieceType
是黑棋 ,白棋,還是 沒東西
對應(yīng)的是枚舉類【PieceType】
BLACK,WHITE 還是 NONE
同樣 GetPieceType 也能用來確定誰該下棋,確實誰贏了
得到xy的位置上放置什么棋子
public PieceType GetPieceType(int nodeIdX, int nodeIdY ){if(pieces[nodeIdX, nodeIdY]==null){return PieceType.NONE;}if (pieces[nodeIdX, nodeIdY] is BlackPiece)//is 判斷是什么類型{return PieceType.BLACK;}if (pieces[nodeIdX, nodeIdY] is WhitePiece)//is 判斷是什么類型{return PieceType.WHITE;}return PieceType.NONE;}6.簡單勝負判斷
漫長的重構(gòu)之后,我們終于要進行簡單的一個橫豎勝負判斷!
要如何簡單判斷勝負呢?
1.五子連心
2.同色 和最后一步同色
3.最后一步的棋子 冠軍得出
記錄最后一步棋子的交叉點
private Point lastPlaceNode = NO_MATCH_NODE;//NO_MATCH_NODE;改為靜態(tài)才能賦值public Point LlastPlaceNode//公有只讀的屬性{get{return lastPlaceNode;//記錄最后一步棋子的交叉點}}在 PlaceAPiece里面 “return pieces[nodeId.X, nodeId.Y];//返回之前判斷黑或白”之前加入:
lastPlaceNode = nodeId;//如果成功放入黑棋/白棋 記錄最新的交叉點就成功記錄最后一步棋子的交叉點
CheckWinner()判斷五子連珠 【橫向 】
最后下的這個棋子
int centerX = borad.LlastPlaceNode.X;//中心點的X int centerY = borad.LlastPlaceNode.Y;//中心點的Y考察他旁邊有沒有相連接的,考察他旁邊的子
checkDx, checkDy不是同色 結(jié)束
while (count<5)if ( borad.GetPieceType(targetX, targetY) != currentPlayer)break;else count++;五子連心
if (count == 5) { winner = currentPlayer; }Orz 好像可以判斷很簡單的單邊的橫著的判斷了
CheckWinner()判斷五子連珠 【八方 】
以下圖表示一個子往不同方向走,坐標(biāo)的變化(ps丑陋畫圖不要介意)
這時 我們需要一個count來記錄連心的次數(shù)
count作用:
1.記錄同色的棋子個數(shù)----從一到五 有五子連心就是贏了
2.考察點targe和中心點center的距離 絕對值
3.循環(huán)次數(shù) 考察次數(shù)
一個人扮演了三個角色
上代碼和注釋!
if (game.Winner != PieceType.NONE)//已經(jīng)決出勝負!比賽結(jié)束啦 不再下棋了{return;} if (piece!=null){this.pnlGobang.Controls.Add(piece);//放置 某方下完 可以提示下一個this.picCurrentPlayer.Image = piece.GetNextImage();//該誰下棋的照片切換if(game.Winner==PieceType.BLACK){this.lblCurrentPlayer.Text="你贏啦";this.picCurrentPlayer.Image = piece.GetImage();//贏了就固定MessageBox.Show("恭喜你!黑棋勝利啦!!!");}else if (game.Winner == PieceType.WHITE){this.lblCurrentPlayer.Text = "你贏啦";this.picCurrentPlayer.Image = piece.GetImage();//贏了就固定MessageBox.Show("恭喜你!白棋勝利啦!!!");}所有種勝利判斷
但是這個代碼并不能考慮,形如oo@oo的情況下,o表示放了棋子,@表示沒放,但最后一個子放在@的位置,放在中間就不行啦
因此,中心點在中間,方向就不是單一的
當(dāng)一個方向找不到同色的棋子時,需要轉(zhuǎn)向 反向找
距離回復(fù)到從1開始找,直到同色的棋子總數(shù)為5為止
例如:我把棋子下在這里
開始尋找啦,左邊找不到了,往右邊找
找了五次的過程中,兩邊都沒有同色的棋子
這時我們要記錄的是:
1.同色棋子個數(shù)
2.距離絕對值
3.循環(huán)次數(shù)
所以count就不能一個人扮演3個角色,所以要拆成三個變量
如果例如左右方向都沒有滿足五子連珠的。就看看上下等其他三個方向,把以下看做四個方向
一個方向走不通 ,就換一個,不要把雞蛋放在一個盒子里,直到走通就往這方向走下去,
我們需要變量
1.同色棋子個數(shù) count
2.距離絕對值 distance
3.循環(huán)次數(shù) step 循環(huán)四次就要結(jié)束了,不然就轉(zhuǎn)向重復(fù)了
4.防止反向重復(fù)reverse,如下圖
? 要避免不斷反向 用reverse記錄
那么就要修改CheckWinner()了,上新的改進的代碼!【?新加入】表示比上個代碼新加入的變量
public void CheckWinner(){int centerX = borad.LlastPlaceNode.X;//中心點的Xint centerY = borad.LlastPlaceNode.Y;//中心點的Y//dx,dy循環(huán)固定的方向 checkDx checkDy 是查找的方向 可能反向for (int dx = -1; dx <= 1; dx++)//dx方向變量 通過正負 0 控制增加 減少 不動{for (int dy = -1; dy <= 1; dy++){if (dx == 0 && dy == 0) //自己和自己不用判斷 只要判斷其他八個方向{continue;}int reverse = 0;//反向的次數(shù) 默認為0 【?新加入】int checkDx = dx;//動態(tài)考察 /移動的方向X 可能出現(xiàn)反向 【?新加入】int checkDy = dy;//動態(tài)考察 /移動的方向Y 可能出現(xiàn)反向 【?新加入】int count = 1;//這個棋子有一個了 反正是某一種 黑/白/NULLint step = 0;//考察次數(shù)/移動的步子的次數(shù) 最多不能超過4【?新加入】int distance = 1;//考察點和中心點的距離 絕對值 【?新加入】while (step < 4)//默認循環(huán)次數(shù)是4次 五顆同色的 則count=5{step++;//先往某一個方向走1步int targetX = centerX + distance * checkDx;//考察點的Xint targetY = centerY + distance * checkDy;考察點的Y//如果找到的 超出了邊界 或者不是同色的棋子 那么需要反向 這一步不算數(shù) (中心和考察的)距離回到1if ( targetX <0 || targetX >= Borad.NODE_COUNT|| targetY < 0 || targetY >= Borad.NODE_COUNT|| borad.GetPieceType(targetX, targetY) != currentPlayer){reverse++;checkDx = -checkDx;checkDy = -checkDy;step--;distance = 1;}//如果沒有出邊界 且是同色的 那么同色的棋子個數(shù)+1 距離加1else{count++;distance++;}if(reverse==2){break;//得到一顆不同色的棋子 達不到5個同色子} }if (count == 5){winner = currentPlayer;}}}}終于可以玩并且準(zhǔn)確判斷勝負
8.玩家與贏家提示
我發(fā)現(xiàn) ,有時候自己和自己玩的時候,常常忘記現(xiàn)在該誰下棋,白子還是黑子呢,所以我決定加一個判斷框;來判斷該誰下棋了。
完成后如下:
窗體中加入label,與picturebox,分別放字和棋子
判斷當(dāng)前玩家
黑棋白棋交替
當(dāng)前玩家是黑棋 (圖片:黑棋)文字:該你下棋啦
下完 (點完)
當(dāng)前玩家變白棋 文字:(圖片:白棋)文字:該你下棋啦
下完 (點完)
當(dāng)前玩家變黑棋 文字:(圖片:黑棋)文字:該你下棋啦
。。。。
結(jié)束循環(huán):有贏家
(圖片:* (贏的) 棋) 文字:(* (贏的)棋)贏啦
窗體代碼中
if (piece!=null){this.pnlGobang.Controls.Add(piece);//放置 某方下完 可以提示下一個this.picCurrentPlayer.Image = piece.GetNextImage();//該誰下棋的照片切換if(game.Winner==PieceType.BLACK){this.lblCurrentPlayer.Text="你贏啦";this.picCurrentPlayer.Image = piece.GetImage();//贏了就固定MessageBox.Show("恭喜你!黑棋勝利啦!!!");}else if (game.Winner == PieceType.WHITE){this.lblCurrentPlayer.Text = "你贏啦";this.picCurrentPlayer.Image = piece.GetImage();//贏了就固定MessageBox.Show("恭喜你!白棋勝利啦!!!");}markdown語法學(xué)習(xí)小記
記得右下方給代碼選擇語言 才會有高亮
四、總結(jié)
完成五子棋的步驟包括:0.導(dǎo)入資源 —>1.棋盤棋子分析—> 2.創(chuàng)建棋子—> 3.尋找最接近鼠標(biāo)位置的棋子中心位置 —>4.得到真實坐標(biāo),正確放置棋子—> 5.代碼重構(gòu),繼承重寫—> 6.簡單勝利判斷—> 7.所有種勝利判斷—> 8.玩家與贏家提示
首先我學(xué)到如果所有代碼只放在一個類中 ,不利于分解和封裝,不利于重用。就像不同抽屜里要放不同的東西,才會井井有條,需要時好及時拿出來。
其次我學(xué)會利用Windows窗體可視化編程的特點,與傳統(tǒng)編程相結(jié)合,不斷迭代,止于至善。
總結(jié)
以上是生活随笔為你收集整理的C#实现五子棋详细教程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: iOS图案解锁(九宫格)
- 下一篇: 推荐4款非常实用的电脑软件