这是中国大学mooc上C#网课的作业,原文件下载地址:Game2048.zip,里面已经实现了2048游戏,要做的改进如下:
- 给程序加上显示分数的功能(score这个变量程序中已有了)。
- 给程序加上选择模式的功能(gameMode这个变量已有了,想办法用上)。
- 给程序加上记录最高分的功能(可以写到一个文本文件中)。如果用户得到的分数比记录高,则更新这个记录,并给用户以祝贺。
- 其他方面的修改,如美化界面,加上下左右4个按钮(让用户可以使用鼠标)来完成,或者是其他方面的修改(就要发挥你的想像了)。
成品展示
- 原程序运行后界面:
- 改进的程序运行后的界面:
- 改进后的完整代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Text;
using System.Windows.Forms;
namespace Game2048
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Add_Handler(); //为容器中的按钮的click事件注册并绑定,发生时统一处理
this.BackgroundImage = Image.FromFile("../../../pic.jpg"); //添加窗体背景图
}
const int N = 4; //方块在纵向及横向上的个数
private int[,] board; //记录每个方块上的数
Button[] btns;
private int score; //当前分数
private int highest; //最高分
bool renew = false; //记录是否更新了最高分
int gameMode;
string[] str1 = { "夏", "商", "周", "秦", "汉", "隋", "唐", "宋", "元", "明", "清" };
string[] str2 = { "贱民", "良民", "九品", "八品", "七品", "六品", "五品", "四品", "三品", "二品", "一品"};
string[] str3 = { "贱民", "良民", "士兵", "排长", "连长", "营长", "旅长", "师长", "军长", "司令", "大帅" };
private void Add_Handler()
{
this.btnMode0.Click += new System.EventHandler(btn_ModeClick);
this.btnMode1.Click += new System.EventHandler(btn_ModeClick);
this.btnMode2.Click += new System.EventHandler(btn_ModeClick);
this.btnMode3.Click += new System.EventHandler(btn_ModeClick);
this.btnUp.Click += new System.EventHandler(btn_DirClick);
this.btnDown.Click += new System.EventHandler(btn_DirClick);
this.btnLeft.Click += new System.EventHandler(btn_DirClick);
this.btnRight.Click += new System.EventHandler(btn_DirClick);
}
private void Form1_Load(object sender, EventArgs e)
{
board = new int[N, N];
score = 0;
gameMode = 0;
this.Text = "Game2048";
this.DoubleBuffered = true;
ReadFile(); //读入文件,读取最高分数
if (highest < 0) //若读出的最高分为负数,则置最高分为0并写入文件
{
highest = 0;
WriteFile();
}
lblScore.Text = "当前分数:" + score;
lblHighest.Text = "最高分:" + highest;
StartGame();
}
private void StartGame()
{
//设置两个不同的块为随机的2或4
Random rnd = new Random();
int n1 = rnd.Next(N * N);
int n2;
do
{
n2 = rnd.Next(N * N);
}
while (n1 == n2);
board[n1 / N, n1 % N] = rnd.Next(2) * 2 + 2; //设为2或4
board[n2 / N, n2 % N] = rnd.Next(2) * 2 + 2;
InitialUI();
}
//初始化界面
private void InitialUI()
{
//将所有容器的背景色均设为透明,但会使程序加载变慢
gbxDirection.BackColor = Color.Transparent;
gbxMode.BackColor = Color.Transparent;
lblHighest.BackColor = Color.Transparent;
lblScore.BackColor = Color.Transparent;
//pnlBoard.BackColor = Color.Transparent;这个不建议使用,会让按钮加载、刷新都变慢
//生成按钮
GenerateAllButtons();
}
//产生所有的按钮
private void GenerateAllButtons()
{
btns = new Button[N * N];
int x0 = 5, y0 = 5, w = 60, d = w+5;
for (int i = 0; i < btns.Length; i++)
{
Button btn = new Button();
int r = i / N; //行
int c = i % N; //列
btn.Left = x0 + c * d;
btn.Top = y0 + r * d;
btn.Width = w;
btn.Height = w;
btn.Font = new Font("楷体", 16);
btn.Text = GetTextOfButton(board[r, c]);
btn.BackColor = GetColorOfButton(board[r, c]);
btn.Visible = true;
btns[i] = btn;
this.pnlBoard.Controls.Add(btn);
}
}
//更新界面
private void RefreshUI()
{
RefreshAllButtons();
lblScore.Text = "当前分数:" + score; //更新当前分数
if (score > highest) //更新最高分并写入文件,置renew=true
{
renew = true;
highest = score;
lblHighest.ForeColor = Color.Red; //改变最高分的字体颜色
lblHighest.Text = "最高分:" + highest;
WriteFile();
}
}
private void RefreshAllButtons()
{
for (int i = 0; i < btns.Length; i++)
{
int r = i / N; //行
int c = i % N; //列
btns[i].Text = GetTextOfButton(board[r, c]);
btns[i].BackColor = GetColorOfButton(board[r, c]);
}
}
//得到方块上应有的文字
string GetTextOfButton(int n)
{
if (n < 2) return "";
int k = (int)Math.Log(n, 2) - 1; //若n==2,则k==1;若n==4,则k==2
if (gameMode == 0)
{
return n.ToString();
}
else if (gameMode == 1)
{
return str1[k];
}
else if (gameMode == 2)
{
return str2[k];
}
else if (gameMode == 3)
{
return str3[k];
}
return "";
}
//得到方块上应有的颜色
Color GetColorOfButton(int n)
{
if (n == 0) return Color.FromArgb(100, 200, 200, 200);
int tmp = 230-(int)Math.Log(n, 2) * 20;
return Color.FromArgb(250, tmp, tmp, 0);
}
//处理键盘消息
//要注意的是:由于按钮等元素的存在,窗体得不到KeyDown事件,所以在覆盖ProcessCmdKey
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
bool bMoved = false;
if (keyData == Keys.Right)
{
bMoved = rightMove();
}
else if (keyData == Keys.Left)
{
bMoved = leftMove();
}
else if (keyData == Keys.Down)
{
bMoved = downMove();
}
else if (keyData == Keys.Up)
{
bMoved = upMove();
}
if (bMoved)
{
generateNewData();
RefreshUI();
if (IsGameOver() == true)
{
string mesg = "Game Over!\n你的得分为:" + score;
mesg = renew ? mesg += "\n祝贺你刷新了最高分!太棒了!" : mesg; //若刷新了最高分则给出祝贺
MessageBox.Show(mesg);
}
return true;
}
return base.ProcessCmdKey(ref msg, keyData);
}
//产生新的随机数据
private void generateNewData()
{
Random rnd = new Random();
//随机找到一个空的块
int nCount;
do
{
nCount = rnd.Next(N * N);
}
while (board[nCount / N, nCount % N] != 0);
board[nCount / N, nCount % N] = rnd.Next(2) * 2 + 2; //其值设为2或者4
}
#region 关于四个方向的业务逻辑
private bool rightMove()
{
bool isMoved = false;
for (int j = 0; j < board.GetLength(0); j++) //针对所有的行
{
int x1 = -1, y1 = -1, value1 = -1;// x2=-1, y2=-1,value2=-1;
for (int i = board.GetLength(1) - 1; i > -1; i--) //从右边起,针对每个块进行处理
{
if (board[j, i] == 0) continue; //空的块不处理
if (board[j, i] == value1)//如果与上一次找到的相等,则合并
{
board[x1, y1] = board[x1, y1] * 2;
board[j, i] = 0;
value1 = -1;
score += board[x1, y1];
isMoved = true;
}
else
{
int k;
for (k = i + 1; k < board.GetLength(1); k++) //向右找到一个非空的块
{
if (board[j, k] > 0)
break;
}
if (k - 1 != i)//如果这个非空的块左边有空位置
{
isMoved = true;
board[j, k - 1] = board[j, i]; //“移动”到这里(将其上的数字显示到这里)
board[j, i] = 0;
}
value1 = board[j, k - 1]; //记下这个值(非空的块)
x1 = j;
y1 = k - 1;
}
}
}
return isMoved;
}
private bool upMove()
{
bool isMoved = false;
for (int i = 0; i < board.GetLength(1); i++)
{
int x1 = -1, y1 = -1, value1 = -1;// x2=-1, y2=-1,value2=-1;
for (int j = 0; j < board.GetLength(0); j++)
{
if (board[j, i] != 0)
{
if (value1 < 0)
{
int k;
for (k = j - 1; k > -1; k--)
{
if (board[k, i] > 0)
break;
}
board[k + 1, i] = board[j, i];
if (k + 1 != j)
{
board[j, i] = 0;
isMoved = true;
}
value1 = board[k + 1, i];
x1 = k + 1;
y1 = i;
}
else
{
if (board[j, i] == value1)//合并
{
board[x1, y1] = board[x1, y1] * 2;
board[j, i] = 0;
value1 = -1;
score += board[x1, y1];
isMoved = true;
}
else
{
int k;
for (k = j - 1; k > -1; k--)
{
if (board[k, i] > 0)
break;
}
board[k + 1, i] = board[j, i];
if (k + 1 != j)
{
isMoved = true;
board[j, i] = 0;
}
value1 = board[k + 1, i];
x1 = k + 1;
y1 = i;
}
}
}
}
}
return isMoved;
}
private bool leftMove()
{
bool isMoved = false;
for (int j = 0; j < board.GetLength(0); j++)
{
int x1 = -1, y1 = -1, value1 = -1;// x2=-1, y2=-1,value2=-1;
for (int i = 0; i < board.GetLength(1); i++)
{
if (board[j, i] != 0)
{
if (value1 < 0)
{
int k;
for (k = i - 1; k > -1; k--)
{
if (board[j, k] > 0)
break;
}
board[j, k + 1] = board[j, i];
if (k + 1 != i)
{
isMoved = true;
board[j, i] = 0;
}
value1 = board[j, k + 1];
x1 = j;
y1 = k + 1;
}
else
{
if (board[j, i] == value1)//合并
{
board[x1, y1] = board[x1, y1] * 2;
board[j, i] = 0;
value1 = -1;
score += board[x1, y1];
isMoved = true;
}
else
{
int k;
for (k = i - 1; k > -1; k--)
{
if (board[j, k] > 0)
break;
}
board[j, k + 1] = board[j, i];
if (k + 1 != i)
{
isMoved = true;
board[j, i] = 0;
}
value1 = board[j, k + 1];
x1 = j;
y1 = k + 1;
}
}
}
}
}
return isMoved;
}
private bool downMove()
{
bool isMoved = false;
for (int i = 0; i < board.GetLength(1); i++)
{
int x1 = -1, y1 = -1, value1 = -1;// x2=-1, y2=-1,value2=-1;
for (int j = board.GetLength(0) - 1; j > -1; j--)
{
if (board[j, i] != 0)
{
if (value1 < 0)
{
int k;
for (k = j + 1; k < board.GetLength(0); k++)
{
if (board[k, i] > 0)
break;
}
board[k - 1, i] = board[j, i];
if (k - 1 != j)
{
board[j, i] = 0;
isMoved = true;
}
value1 = board[k - 1, i];
x1 = k - 1;
y1 = i;
}
else
{
if (board[j, i] == value1)//合并
{
board[x1, y1] = board[x1, y1] * 2;
board[j, i] = 0;
value1 = -1;
score += board[x1, y1];
isMoved = true;
}
else
{
int k;
for (k = j + 1; k < board.GetLength(0); k++)
{
if (board[k, i] > 0)
break;
}
board[k - 1, i] = board[j, i];
if (k - 1 != j)
{
board[j, i] = 0;
isMoved = true;
}
value1 = board[k - 1, i];
x1 = k - 1;
y1 = i;
}
}
}
}
}
return isMoved;
}
#endregion
private bool IsGameOver()
{
int nCount = 0; //计算非空的格子的个数
for (int i = 0; i < board.GetLength(0); i++)
{
for (int j = 0; j < board.GetLength(1); j++)
{
if (board[i, j] > 0) nCount++;
}
}
if (nCount != N * N) return false;
//如果满了,并且没有可以相邻相同(可合并),则GameOver
for (int i = 0; i < board.GetLength(0) - 1; i++)
{
for (int j = 0; j < board.GetLength(1) - 1; j++)
{
if (board[i, j] == 0) continue;
if (board[i, j] == board[i + 1, j] || board[i, j] == board[i, j + 1])
return false;
}
}
return true;
}
private void btn_ModeClick(object sender, EventArgs e)
{
Button btn = (Button)sender;
gameMode = btn.TabIndex;
RefreshAllButtons();
}
private void btn_DirClick(object sender, EventArgs e)
{
Button btn = (Button)sender;
bool bMoved = false;
if (btn.TabIndex == 0)
{
bMoved = upMove();
}
else if (btn.TabIndex == 1)
{
bMoved = downMove();
}
else if (btn.TabIndex == 2)
{
bMoved = leftMove();
}
else if (btn.TabIndex == 3)
{
bMoved = rightMove();
}
if (bMoved)
{
generateNewData();
RefreshUI();
if (IsGameOver() == true)
{
string msg = "Game Over!\n你的得分为:" + score;
msg = renew ? msg += "\n祝贺你刷新了最高分!太棒了!" : msg; //若刷新了最高分则给出祝贺
MessageBox.Show(msg);
}
}
}
private void ReadFile() //读取最高分文件,没有则创建
{
string path = @"..\..\..\Highest Score.txt";
FileStream fs = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite);
StreamReader sr = new StreamReader(fs, Encoding.UTF8);
if (sr.Peek() == -1) //若文件中没有内容,则写入0(即最高分为0)
{
StreamWriter sw = new StreamWriter(fs, Encoding.UTF8);
sw.Write(0);
sw.Flush(); //一定要用,不然流将无法读取
sw.Close();
sr = new StreamReader(fs, Encoding.UTF8); //需要重新创建读取流
}
highest = int.Parse(sr.ReadToEnd()); //不能用int,读出来不对
sr.Close();
fs.Close();
}
private void WriteFile() //写入最高分到文件中
{
string path = @"..\..\..\Highest Score.txt";
StreamWriter sw = new StreamWriter(path,false,Encoding.UTF8);
sw.Write(highest);
sw.Flush();
sw.Close();
}
}
}
改进过程中犯过的傻事
- 模式的选择控件应该用listbox来做的,只用按钮的话当模式多了的时候占位置就太多了。而且写更改模式的代码也会更简单。
- 统一处理几个按钮的事件可以按照如下操作:
private void Add_Handler()
{
this.btnMode0.Click += new System.EventHandler(btn_ModeClick);
this.btnMode1.Click += new System.EventHandler(btn_ModeClick);
this.btnMode2.Click += new System.EventHandler(btn_ModeClick);
this.btnMode3.Click += new System.EventHandler(btn_ModeClick);
this.btnUp.Click += new System.EventHandler(btn_DirClick);
this.btnDown.Click += new System.EventHandler(btn_DirClick);
this.btnLeft.Click += new System.EventHandler(btn_DirClick);
this.btnRight.Click += new System.EventHandler(btn_DirClick);
}
private void btn_ModeClick(object sender, EventArgs e)
{
Button btn = (Button)sender;
gameMode = btn.TabIndex;
RefreshAllButtons();
}
private void btn_DirClick(object sender, EventArgs e)
{
Button btn = (Button)sender;
bool bMoved = false;
if (btn.TabIndex == 0)
{
bMoved = upMove();
}
else if (btn.TabIndex == 1)
{
bMoved = downMove();
}
else if (btn.TabIndex == 2)
{
bMoved = leftMove();
}
else if (btn.TabIndex == 3)
{
bMoved = rightMove();
}
if (bMoved)
{
generateNewData();
RefreshUI();
if (IsGameOver() == true)
{
string msg = "Game Over!\n你的得分为:" + score;
msg = renew ? msg += "\n祝贺你刷新了最高分!太棒了!" : msg; //若刷新了最高分则给出祝贺
MessageBox.Show(msg);
}
}
}
- 读取最高分文件
private void ReadFile() //读取最高分文件,没有则创建
{
string path = @"..\..\..\Highest Score.txt";
FileStream fs = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite);
StreamReader sr = new StreamReader(fs, Encoding.UTF8);
if (sr.Peek() == -1) //若文件中没有内容,则写入0(即最高分为0)
{
StreamWriter sw = new StreamWriter(fs, Encoding.UTF8);
sw.Write(0);
sw.Flush(); //一定要用,不然流将无法读取
sw.Close();
sr = new StreamReader(fs, Encoding.UTF8); //需要重新创建读取流
}
highest = int.Parse(sr.ReadToEnd()); //不能用sr.Read(),读出来不对
sr.Close();
fs.Close();
}
-
其中
sw.Flush();
一定要用,不然后面读取文件的时候会显示“流无法读取”。 -
执行if语句的最后要用
sr = new StreamReader(fs, Encoding.UTF8);
不然就会显示“不能读取已经关闭的文件”。 -
文件的编码方式要指明为utf-8,用default或其他编码都不行。
-
highest = int.Parse(sr.ReadToEnd());
这条语句不能写成 highest = sr.Read(),读出来的数字不对。 -
不建议用FileStream读写,byte[ ]麻烦。
-
吐槽一下notepad3,用它看最高分文本文件的时候,即使是指定了正确的编码也显示的是乱码,害得我以为自己代码写错了,找了好久的原因。后来发现用记事本或者notepad++就正常显示,就把notepad3卸了换成notepad++了。
-
切换模式后执行的语句应该是
RefreshAllButtons();
来刷新按钮,而不是StartGame();
来重新开始游戏,否则无法正常运行(按键盘或者点方向button都不执行相应的代码)。一开始我写的是StartGame(); 然后找了好久好久的原因,才发现自己用错函数了。(我玩游戏的时间都没了啊!)
Tips: 让容器的背景色变透明可以用如lblScore.BackColor = Color.Transparent;
的语句。