好久没有碰过代码了,上次看C#估计还是上个学期233,这次老师突然布置了一个C#语言编写的井字棋程序让我有点措手不及.不过,仗着微软爸爸的vs的强大的代码补全能力,最后还是将这个程序做好了(遇到了很多坑).
该程序的要求还是有点多了,以下一一列举:
1. 在程序初始化时,要求输入玩家用户名,输入后出现提示文字,并选择是先手还是后手.
2. 点击Fight!后,进入游戏界面,在九宫格中下一步棋后,电脑(敌人)会判断形势并下第二部棋,如此直至有三子连成一线(玩家或电脑赢),或者格子被占满(和局).旁边有提示按钮,点击后会出现提示;得分按钮会出现具体的得分界面;退出按钮则会退出程序
3. 得分界面,点开后会显示自己的得分情况.最高得分按钮会记录玩家玩这个游戏以来所得的最高分;打印按钮则会打印自己的具体得分情况
这里要注意的是,只有当输入完姓名后才能出现下面的提示文字等控件,所以应事先将其属性定为隐藏,点击”commit”按钮后触发事件
private void btm_commit_click(object sender, RoutedEventArgs e)
{
if (TBox1.Text.Length>0&&TBox1.Text.Length<10)//判断玩家输入的文字长度,更为严谨的做法应该是判断字母+数字的长度,以及玩家输入的字符串不符合时应出现提示信息
{
storage.name = TBox1.Text;//定义在storage类中的静态变量,为了存储玩家名称,成绩等信息
TBlock2.Text = "Hi " + storage.name + ". Welcome to the battlefield!Now,lets begin the game!!!Hey,do you want to place a bomb first?";//其实应该直接在前台代码中写好内容,不必在后台再赋值,因多次修改代码所致
TBlock2.Visibility = Visibility.Visible;//显示提示文本块
RB1.Visibility = Visibility.Visible;//显示单选按钮,让用户选择是否先手
RB2.Visibility = Visibility.Visible;//同上
btm_fight.Visibility = Visibility.Visible;//显示进入游戏界面的按钮
btm_commit.IsEnabled = false;//禁止玩家再次点击commit按钮
}
}
在玩家确定好是否先手后,就该点击Fight!按钮进入游戏界面了
private void to_main_page_click(object sender, RoutedEventArgs e)
{
if(RB1.IsChecked==true)//玩家点击"yes"按钮时,设置静态布尔类型变量RButton的值并进入游戏界面"mainpage"
{
RButton = true;
this.Content = new mainpage();
}
else if (RB2.IsChecked == true)//玩家点击"no"按钮后,类同yes
{
RButton = false;
this.Content = new mainpage();
}
else//玩家没有点击单选按钮的情形,出现提示信息并不触发进入游戏界面的事件,其实想想是否让程序预先设定好"yes"单选按钮就没有这一步了呢?
{
TBcheck.Visibility = Visibility.Visible;
}
}
样式:
在拖好控件后,自然要去实现他们的功能.首先是九宫格,我的想法是玩家(假设是玩家先手,并永远下”X”棋)下出第一步棋后,按钮打印出”X”字母,并且在后台的对应于该按钮的值变为1;而电脑(永远下”O”棋)下棋后,按钮打印出”O”字母,并且在后台的对应于该按钮的值变为-1;而没有下任何棋的按钮则不会打印出任何字母,并且在后台的对应于该按钮的值为0.
private void Btm0_Click(object sender, RoutedEventArgs e)//按钮点击触发事件,即玩家点击后的结果.9个按钮均与此类似
{
if (btm[btm0]==0)//按钮与后台的值组成键值对的字典,判断当按钮后台的值为0时
{
btm0.Content="X";//按钮btm0的内容变为"X",打印出"X"字母
btm[btm0] = 1;//按钮后台的值变为1
computer();//电脑的操作,事实上除了下另一步棋外,还执行了判断是否赢或输或平局的函数,因此没有单独列出
}
}
三字连成一线的情况总共有8种,为了更快速的判断是否出现了特殊情况(两子一线,三子一线),可以将这些情况列成一个8行3列的数组以供判断函数或者电脑使用
private void update()
{
array_for_judge[0] = new int[3] { btm[btm0], btm[btm1], btm[btm2] };//第一行3子
array_for_judge[1] = new int[3] { btm[btm3], btm[btm4], btm[btm5] };//第二行3子
array_for_judge[2] = new int[3] { btm[btm6], btm[btm7], btm[btm8] };//第三行3子
array_for_judge[3] = new int[3] { btm[btm0], btm[btm3], btm[btm6] };//第一列3子
array_for_judge[4] = new int[3] { btm[btm1], btm[btm4], btm[btm7] };//第二列3子
array_for_judge[5] = new int[3] { btm[btm2], btm[btm5], btm[btm8] };//第三列3子
array_for_judge[6] = new int[3] { btm[btm0], btm[btm4], btm[btm8] };//右斜3子
array_for_judge[7] = new int[3] { btm[btm2], btm[btm4], btm[btm6] };//左斜3子
}
每次玩家或者电脑走完一步棋后,都得判断是否出现赢或输或平局的情况,若出现,将刷新棋盘并记录信息
private void judge()//判断函数
{
update();//更新信息
foreach (int[] tmp in array_for_judge)
{
if (Array.TrueForAll(tmp, isone => (isone == 1)) == true)//判断3子全为1,即玩家连成3子的情况
{
MessageBox.Show("you win!");//弹出消息框,玩家赢
storage.battle++;//storage类的静态变量,存储对局次数
storage.win++;//同,存储玩家赢的次数
flush();//刷新棋盘,即设置按钮文本全为空,后台值全为0,在此不单独列出该函数
}
else if (Array.TrueForAll(tmp, isone => (isone == -1)) == true)//判断3子全为-1,即电脑连成3子的情况
{
MessageBox.Show("you lose!");
storage.battle++;
storage.lose++;
flush();
}
}
if (btm.ContainsValue(0) == false)//最后判断没有子为0,即棋盘下满仍没有胜负的情况,平局
{
MessageBox.Show("draw!");
storage.battle++;
storage.tie++;
flush();
}
}
电脑也需要根据棋盘的形势下棋,在此列出3种情况:
1. 当电脑的棋子已有2子一线,并且该线无第3子时,下第3子取得胜利
2. 当玩家的棋子已有2子一线,并且该线无第3子时,下第3子阻止玩家取得胜利
3. 当无以上情况时,随机选择并落子
private int computer()//返回值是为了跳出该函数用的,并无太大意义
{
judge();//首先为玩家判断是否已出现能赢或平局的可能
if (btm.ContainsValue(1) == false&&btm.ContainsValue(-1)==false)//判断按钮的值全为0的情形,即棋盘已被刷新,跳出该函数
{
return 0;
}
for (int i = 0; i < array_for_judge.Length; i++)//进行特殊情况判断
{
if (array_for_judge[i].Sum() == -2)//判断2子为-1,另1子为0的情况,即第一种情况
{
int j = Array.IndexOf(array_for_judge[i], 0);
btm[to_dic_int(i, j)] = -1;//这里我不知道如何从已列出特殊情形的数组中取得原本按钮的信息,只好采取最笨拙的方式--定义函数建立特殊情形数组与按钮的映射,设置按钮的值为-1
to_dic_int(i, j).Content()="O";//设置按钮的内容为"O"
judge();//再次进行胜负判断
return 0;
}
if (array_for_judge[i].Sum() == 2)//阻止玩家获胜的情形
{
int j = Array.IndexOf(array_for_judge[i], 0);
btm[to_dic_int(i, j)] = -1;
to_dic_int(i, j).Background = storage.getImage2();
judge();
return 1;
}
}
Random random = new Random();//随机落子的情形
while (true)无限循环直至随机落子并且该按钮的值为0
{
int tmp = random.Next(24);
int itmp = tmp % 8;
int jtmp = tmp % 3;
if (array_for_judge[itmp][jtmp] == 0)
{
btm[to_dic_int(itmp, jtmp)] = -1;
to_dic_int(itmp, jtmp).Background = storage.getImage2();
judge();
return 2;
}
}
}
还要设置电脑先手的情况
if (MainWindow.RButton == false)//在游戏界面初始化时进行判断,当玩家之前选择false单选按钮时让电脑先执行一步
{
first_computer();//随机让电脑在九宫格中落子
}
Show Help按钮
private void btm_tips_click(object sender, RoutedEventArgs e)
{
if (text_tips.Text == "")//事先设置好tips文本块,不过内容为空
{
text_tips.Text = "Play The Battle of Waterloo! Start by placing bombs. The enemy will situate a bomb after you.";//显示提示文本
btm_tips.Content = "Hide Help";//更改Show Help按钮的内容
}
else//显示文本块内容后,再次点击按钮
{
text_tips.Text = "";
btm_tips.Content = "Show Help";
}
}
Show Score按钮
private void main_to_score(object sender, RoutedEventArgs e)
{
score_page score = new score_page();//建立score窗口对象
score.Show();//显示score窗口
}
Exit按钮
private void Exit_click(object sender, RoutedEventArgs e)
{
Application.Current.Shutdown();//退出程序
}
拖几个文本块的事情…值得注意的是,右边的分数需事先读取玩家数据并将其显示出来
Score_player.Text = storage.name;
Score_battles.Text = storage.battle.ToString();
Score_win.Text = storage.win.ToString();
Score_lose.Text = storage.lose.ToString();
Score_tie.Text = storage.tie.ToString();
Top Score和Print按钮的功能我还没有实现.Top Score应该是事先读取存储文件中的最高分的数据并用文本块显示出来;而Print则应弹出文件资源管理器让玩家选择保存数据的位置,然后将其保存
这个程序我搞了很长时间才让其具备初始的功能,仍然有许多不完善的地方,比如界面的设计啦,数值的绑定啦等等.不过在这次编写C#程序后可能我的下一次编写就又不知道是什么时候了233,因此我希望在这几天完善完善比较好.
其实老师的要求是埋下地雷而不是井字棋,游戏的名称叫做”滑铁卢战役”,实际上和井字棋一摸一样-.-,附上改”X”“O”为地雷图片的图: