这是学C#这么久以来老师第一次布置的算是个完整的小项目了,自己在规定时间做完后又找资料优化了一下,把它弄成不规则窗体了,但是拖动功能没有完美的实现。
遇到的问题:鼠标左键点住窗体,只要一拖动,鼠标指针就跑到窗体的左上角了,即相对窗体坐标(0,0)。
以下是部分功能及界面介绍:
登录界面:
玩家姓名不能为空,如果为空点击“开始游戏”会弹出警告
游戏主界面:
玩家为红色棋子,电脑为黑色棋子。棋盘用的是PictureBox控件,你也可以用按钮。
点击“开始”,即可开始游戏,可选择玩家先下或者电脑先下棋子:
下面是游戏过程,玩家赢的情况:
当然还有电脑赢,平局的情况。
右边的倒计时是100秒,100秒过后可以再战:
成绩统计的是这100秒之内玩家的战况(玩家赢、输一局平局的次数):
下面是帮助界面:
界面就这些,关键是后台代码,涉及到很多方面,这里介绍部分:
先看下部分变量的声明:
1 int time = 100; //计时器倒计时
2 int[,] A = new int[3, 3]; //棋盘数组
3 int i; //用户所下棋位横坐标
4 int j; //用户所下棋位纵坐标
5 int x; //计算机所下棋位横坐标
6 int y; //计算机所下棋位纵坐标
7 string picName; //存放用户点击棋位的名字
8 public static int winUserNum = 0; //玩家赢的次数
9 public static int winPCNum = 0; //电脑赢的次数
10 public static int drawNum = 0; //平局次数
11 public static string user; //玩家姓名
12 PictureBox[,] pics = new PictureBox[3, 3];//将棋盘的9个PictureBox控件定义到一个数组里
将棋盘的9个PictureBox控件绑定到一个数组里(这是个不错的主意):
我放在了默认构造函数里了
注意:控件命名和数组变量名字有联系,这样就可以很方便的编辑每个控件了!
当数组值为1时,表示这个棋是人下的,并且把控件图片改为红色棋子图片
当数组值为-1时,表示这个棋是电脑下的,并且把控件图片改为黑色棋子图片
当数组值为0时,表示这个棋位为空(默认为0)
public frmMain()
{
InitializeComponent();
pics[0, 0] = pic00;
pics[0, 1] = pic01;
pics[0, 2] = pic02;
pics[1, 0] = pic10;
pics[1, 1] = pic11;
pics[1, 2] = pic12;
pics[2, 0] = pic20;
pics[2, 1] = pic21;
pics[2, 2] = pic22;
}
以下是一些方法:
如果是电脑下棋,考虑以下几种可能:
(1)自己有没有2个连着的棋子并且第三个棋位为空,如果有,电脑则可以直接下到空棋位,电脑便赢
(2)如果(1)不成立,电脑就判断玩家是否已经有2个连续的棋子且第三个为空,即:电脑要阻挡玩家赢
(3)如果(2)也不成立,则电脑随机找一个空位下棋,电脑随机下起用到随机数的生成
这里用循环语句遍历每个控件数组,寻找是否有数组元素的值为0,1,或-1,通过计算,进而判断是否有连续2个棋子的可能,请仔细研究!
1 #region 判断电脑自己下的棋子是否有2个棋子在一直线,且第3个为空
2 private bool twoChessPC()
3 {
4 for (int i = 0; i < 3; i++)
5 {
6 if (A[i, 0] + A[i, 1] + A[i, 2] == -2)
7 {
8 for (int j = 0; j < 3; j++)
9 {
10 if (A[i, j] == 0)
11 {
12 A[i, j] = -1;
13 pics[i, j].Image = imageList1.Images[1];
14 pics[i, j].Enabled = false;
15 return true;
16 }
17 }
18 }
19 }
20
21 for (int j = 0; j < 3; j++)
22 {
23 if (A[0, j] + A[1, j] + A[2, j] == -2)
24 {
25 for (int i = 0; i < 3; i++)
26 {
27 if (A[i, j] == 0)
28 {
29 A[i, j] = -1;
30 pics[i, j].Image = imageList1.Images[1];
31 pics[i, j].Enabled = false;
32 return true;
33
34 }
35 }
36 }
37 }
38
39 if (A[0, 0] + A[1, 1] + A[2, 2] == -2)//-45度对角线
40 {
41 for (int i = 0; i < 3; i++)
42 {
43 if (A[i, i] == 0)
44 {
45 A[i, i] = -1;
46 pics[i, i].Image = imageList1.Images[1];
47 pics[i, i].Enabled = false;
48 return true;
49 }
50 }
51 }
52
53 if (A[0, 2] + A[1, 1] + A[2, 0] == -2) //45度对角线
54 {
55 for (int i = 0; i < 3; i++)
56 {
57 if (A[i, 2 - i] == 0)
58 {
59 A[i, 2 - i] = -1;
60 pics[i, 2-i].Image = imageList1.Images[1];
61 pics[i, 2-i].Enabled = false;
62 return true;
63 }
64
65 }
66
67 }
68 return false;
69 }
70 #endregion
1 #region 判断电脑是否阻碍人
2 private bool twoChessUser()
3 {
4 for (int i = 0; i < 3; i++)//阻止行
5 {
6 if (A[i, 0] + A[i, 1] + A[i, 2] == 2)
7 {
8 for (int j = 0; j < 3; j++)
9 {
10 if (this.A[i, j] == 0)
11 {
12 this.A[i, j] = -1;
13 this.pics[i, j].Image = imageList1.Images[1];
14 pics[i, j].Enabled = false;
15 return true;
16 }
17 }
18 }
19 }
20
21 for (int j = 0; j < 3; j++)//阻止列
22 {
23 if (A[0, j] + A[1, j] + A[2, j] == 2)
24 {
25 for (int i = 0; i < 3; i++)
26 {
27 if (A[i, j] == 0)
28 {
29 A[i, j] = -1;
30 pics[i, j].Image = imageList1.Images[1];
31 pics[i, j].Enabled = false;
32 return true;
33
34 }
35 }
36 }
37 }
38
39 if (A[0, 0] + A[1, 1] + A[2, 2] == 2)//-45度对角线
40 {
41 for (int k = 0; k < 3; k++)
42 {
43 if (A[k, k] == 0)
44 {
45 A[k, k] = -1;
46 pics[k, k].Image = imageList1.Images[1];
47 pics[k, k].Enabled = false;
48 return true;
49 }
50 }
51 }
52
53 if (A[0, 2] + A[1, 1] + A[2, 0] == 2) //45度对角线
54 {
55 for (int k = 0; k < 3; k++)
56 {
57 if (A[k, 2 - k] == 0)
58 {
59 A[k, 2 - k] = -1;
60 pics[k, 2 - k].Image = imageList1.Images[1];
61 pics[k, 2 - k].Enabled = false;
62 return true;
63 }
64
65 }
66
67 }
68 return false;
69 }
70 #endregion
1 #region 电脑随机下棋
2 private bool radomPC()
3 {
4 Random ran = new Random();
5 // 默认x和y都为0,所以先随机生成一次
6 x = ran.Next(0, 3);
7 y = ran.Next(0, 3);
8
9 while (true)
10 {
11 x = ran.Next(0, 3); // 随机生成计算机要下棋子的横坐标
12 y = ran.Next(0, 3); // 随机生成计算机要下棋子的纵坐标
13 if (A[x, y] == 0)
14 {
15 A[x, y] = -1;
16 pics[x, y].Image = imageList1.Images[1];
17 pics[x, y].Enabled = false;
18 return true;
19 }
20 }
21
22 }
23 #endregion
下面是玩家下棋方法(这是我最感到满意的一个方法!)9个控件不用写9个事件,而是都调用同一个Click事件
1 #region 玩家下棋,每一个棋子控件都调用此方法
2 private void pic00_Click(object sender, EventArgs e)
3 {
4 PictureBox newPictureBox = (PictureBox)sender;
5 picName = newPictureBox.Name;
6 i = Convert.ToInt32(picName.Substring(3, 1));
7 j = Convert.ToInt32(picName.Substring(4, 1));
8
9 if (A[i,j] == 0)
10 {
11 newPictureBox.Image = imageList1.Images[0];
12 A[i, j] = 1;
13 pics[i,j].Enabled=false;
14
15 if (peopleWin() == false)//判断玩家是否赢
16 {
17 if (draw() == false)//判断是否平局
18 {
19 chessPC();//电脑下棋
20 }
21 }
22 }
23
24 }
以下3个方法是判断电脑是否赢、玩家是否赢、以及是否平局:
说明:这3个方法让我很不爽,因为是把其可能出现的所有情况都写出来了!试想:如果棋盘很大,比如250*250呢??!
还是我的算法没学好,我想应该有好的方法,比如上边判断电脑是否阻挡玩家一样,用循环实现!
如果大家谁有好的算法可以解决这个问题,请告知,谢谢!
1 #region 判断电脑是否胜利
2 private bool computerWin()
3 {
4 if ((A[0, 0] == -1 & A[0, 1] == -1 & A[0, 2] == -1) | (A[1, 0] == -1 & A[1, 1] == -1 & A[1, 2] == -1) | (A[2, 0] == -1 & A[2, 1] == -1 & A[2, 2] == -1) | (A[0, 0] == -1 & A[1, 0] == -1 & A[2, 0] == -1) | (A[0, 1] == -1 & A[1, 1] == -1 & A[2, 1] == -1) | (A[0, 2] == -1 & A[1, 2] == -1 & A[2, 2] == -1) | (A[0, 0] == -1 & A[1, 1] == -1 & A[2, 2] == -1) | (A[2, 0] == -1 & A[1, 1] == -1 & A[0, 2] == -1)
5
6 )
7 {
8 MessageBox.Show("很遗憾,你输了!");
9 picEnabledFlase();
10 winPCNum++;
11 return true;
12 }
13 else
14 {
15 return false;
16 }
17
18 }
19 #endregion
20
21 #region 判断玩家是否胜利
22 private bool peopleWin()
23 {
24 if ((A[0, 0] == 1 & A[0, 1] == 1 & A[0, 2] == 1) | (A[1, 0] == 1 & A[1, 1] == 1 & A[1, 2] == 1) | (A[2, 0] == 1 & A[2, 1] == 1 & A[2, 2] == 1) | (A[0, 0] == 1 & A[1, 0] == 1 & A[2, 0] == 1) | (A[0, 1] == 1 & A[1, 1] == 1 & A[2, 1] == 1) | (A[0, 2] == 1 & A[1, 2] == 1 & A[2, 2] == 1) | (A[0, 0] == 1 & A[1, 1] == 1 & A[2, 2] == 1) | (A[2, 0] == 1 & A[1, 1] == 1 & A[0, 2] == 1))
25 {
26 MessageBox.Show("恭喜,你赢了!");
27 picEnabledFlase();
28 winUserNum++;
29 return true;
30 }
31 else
32 {
33 return false;
34 }
35 }
36 #endregion
37
38 #region 判断是否平局
39 private bool draw()
40 {
41 if (A[0, 0] != 0 & A[0, 1] != 0 & A[0, 2] != 0 & A[1, 0] != 0 & A[1, 1] != 0 & A[1, 2] != 0 & A[2, 0] != 0 & A[2, 1] != 0 & A[2, 2] != 0)
42 {
43 MessageBox.Show("非常棒,平局!");
44 drawNum++;
45 return true;
46 }
47 else
48 {
49 return false;
50 }
51
52 }
53 #endregion
计时器以及统计成绩就不介绍了,这个很容易。
下面看下不规则窗体的实现:
给窗体设置一张背景图片,背景图片为不规则窗体的样式,图片的背景颜色需换一种没有用到的颜色(为了使之在运行时透明)
首先将窗体的FormBoderStyle设置为False
然后将TransparencyKey的颜色设置为何窗体背景图片的背景颜色一样,这样运行程序时此颜色就为透明色了
例如次程序:
然后给窗体添加2个按钮,最小化和关闭,下面是改变这2个按钮形状的代码:
注意,这里代码是写在按钮的Paint事件里的!
1 #region 自定义一个关闭按钮
2 private void btnClose_Paint(object sender, PaintEventArgs e)
3 {
4 //初始化一个GraphicsPath类的对象
5 System.Drawing.Drawing2D.GraphicsPath myGraphicsPath = new
6 System.Drawing.Drawing2D.GraphicsPath();
7 //用AddEllipse方法创建椭圆
8 myGraphicsPath.AddEllipse(3, 3, 20, 15);
9 //将控件的Region属性设置为上面创建的GraphicsPath对象
10 btnClose.Region = new Region(myGraphicsPath);
11 btnClose.Text = "×";
12
13 }
14 #endregion
15
16 ////关闭按钮的Click事件
17 private void btnClose_Click(object sender, EventArgs e)
18 {
19 Application.Exit();
20 }
21
22 #region 自定义一个最小化按钮
23 private void btnMin_Paint(object sender, PaintEventArgs e)
24 {
25 //初始化一个GraphicsPath类的对象
26 System.Drawing.Drawing2D.GraphicsPath myGraphicsPath = new
27 System.Drawing.Drawing2D.GraphicsPath();
28 //用AddEllipse方法创建椭圆
29 myGraphicsPath.AddEllipse(3, 3, 20, 15);
30 //将控件的Region属性设置为上面创建的GraphicsPath对象
31 btnMin.Region = new Region(myGraphicsPath);
32 btnMin.Text = "-";
33 }
34 #endregion
35
36 //最小化按钮的Click事件
37 private void btnMin_Click(object sender, EventArgs e)
38 {
39 this.WindowState = FormWindowState.Minimized;
40 }
最后,把鼠标拖动的代码附上,这里我遇到了文章开头说到的拖动问题,希望大家谁知道,告诉我一下,再次感谢!
这里是方法是写在了窗体的MouseMove事件里:
#region 鼠标拖动,这里遇到点问题还未解决
private void frmMain_MouseMove(object sender, MouseEventArgs e)
{
int currX = 0;
int currY = 0;
if (e.Button == MouseButtons.Left)
{
currX = this.Left + e.X;
currY = this.Top + e.Y;
this.SetDesktopLocation(currX, currY);
}
}
#endregion
这个三子棋的游戏就介绍到这里