最近在玩过2048这个小游戏后感觉很有意思,想着正在学C#的winfrom的我能不能自己写一个2048游戏呢?于是就有了这个:
目录
1.实现思路;
首先2048的操作比较简单,玩家只需要控制“上下左右”四个方向就可以让框内的图片往一个方向移动,在移动过程中相邻的两个图片的数字如果相等则可以合并成更大的数字最大为2048,另外需要注意的是多个相同数字相邻时只能两两相互合并,合并的方向要根据此时的移动方向来判断,合并后数字需要下一次移动时再判断需不需要继续合并;
然后是主程序的设计思路,由于2048游戏的大小固定在一个确定的空间中,每个图片占据一个方形的控件,那么我们可以用一个二维的数组来表示整个控件和控件的位置关系,数组里的元素值就是需要表示的图片数字;
2.代码实现;
1.初始化地图表示的数组;
这里我们先按照思路给数组赋值,先定义一个二维的数组map大小为5*5,另外定义一个长度等于25的数组由于存放生成的随机数,初始值只有2和4;
int[] map_s = new int[25];
private long[,] map;
private void chushi_map()
{
map = new long[5, 5]
{
{0,0,0,0,0 },
{0,0,0,0,0 },
{0,0,0,0,0 },
{0,0,0,0,0 },
{0,0,0,0,0 },
};
Random r = new Random();
for (int i = 0; i < 25; i++)
{
int s;
while (true)
{
s = r.Next(2, 5);
if (s == 2 || s == 4)
{
break;
}
}
map_s[i] = s;
}
for (int i = 0; i < 5; i++)
{
for (int j = 0; j < 5; j++)
{
map[i, j] = map_s[i * 5 + j];
}
}
}//初始化数据和地图;
2.绘制游戏的边框;
利用OnPaint函数绘制游戏需要的边框;
private int bianjie_x;
private int bianjie_y;
private int fangkuang_width;
private int fangkuang_height;
private void Form1_Load(object sender, EventArgs e)
{
this.Width = 500;
this.Height = 500;
fangkuang_width = 300;
fangkuang_height = 300;
color_fenpei();
bianjie_x = (this.ClientSize.Width - fangkuang_width) / 2 ;
bianjie_y = (this.ClientSize.Height - fangkuang_height) / 2 ;
chushi_map();
map_show();
}
protected override void OnPaint(PaintEventArgs pe)
{
Graphics g = this.CreateGraphics();
Pen bluePen = new Pen(Color.Black, 4);
g.Clear(Color.White);
g.DrawRectangle(bluePen, bianjie_x-2, bianjie_y-2, fangkuang_width + 4, fangkuang_height + 4);
}//绘制显示的边框;
为方便调试,边框的大小都是可以调整的;
3.设置每个数值对应的颜色(可省略);
设置颜色能便于判断数值的大小,提升游戏档次;
private Color color_fore_0, color_fore_2, color_fore_4, color_fore_8, color_fore_16, color_fore_32, color_fore_64, color_fore_128, color_fore_256, color_fore_512, color_fore_1024, color_fore_2048;
private Color color_back_0, color_back_2, color_back_4, color_back_8, color_back_16, color_back_32, color_back_64, color_back_128, color_back_256, color_back_512, color_back_1024, color_back_2048;
private void color_fenpei()
{
color_fore_0 = Color.LightGray;
color_fore_2 = Color.Brown;
color_fore_4 = Color.Sienna;
color_fore_8 = Color.SeaGreen;
color_fore_16 = Color.LimeGreen;
color_fore_32 = Color.MediumAquamarine;
color_fore_64 = Color.BlueViolet;
color_fore_128 = Color.DarkOrchid;
color_fore_256 = Color.Blue;
color_fore_512 = Color.Magenta;
color_fore_1024 = Color.Crimson;
color_fore_2048 = Color.White;
///
color_back_0 = Color.LightGray;
color_back_2 = Color.PaleGreen;
color_back_4 = Color.LawnGreen;
color_back_8 = Color.DarkKhaki;
color_back_16 = Color.PaleGoldenrod;
color_back_32 = Color.Khaki;
color_back_64 = Color.Gold;
color_back_128 = Color.MediumAquamarine;
color_back_256 = Color.DarkTurquoise;
color_back_512 = Color.DeepSkyBlue;
color_back_1024 = Color.Pink;
color_back_2048 = Color.Red;
}//设置颜色;
4.添加控件;
这里我选用label标签来表示,label.text即数值的大小;
private Label[] txt_boxs = new Label[25];
private void map_show()
{
for (int i = 0; i < 5; i++)
{
for (int j = 0; j < 5; j++)
{
Label h = new Label();
h.Text = Convert.ToString(map[i, j]);
h.Anchor = AnchorStyles.None;
h.AutoSize = false;
h.Size = new Size(60, 60);
h.TextAlign = ContentAlignment.MiddleCenter;
h.Font = new Font("Siemens AD Sans", 13.8F, FontStyle.Regular, GraphicsUnit.Point, ((byte)(0)));
h.Location = new Point(j * 60 + bianjie_x, i * 60 + bianjie_y);
switch (map[i, j])
{
case 0:
h.BorderStyle = BorderStyle.None;
h.ForeColor = color_fore_0;
h.BackColor = color_back_0;
break;
case 2:
h.BorderStyle = BorderStyle.Fixed3D;
h.ForeColor = color_fore_2;
h.BackColor = color_back_2;
break;
case 4:
h.BorderStyle = BorderStyle.Fixed3D;
h.ForeColor = color_fore_4;
h.BackColor = color_back_4;
break;
}
//this.Controls.Add(h);
txt_boxs[i * 5 + j] = h;
this.Controls.Add(txt_boxs[i * 5 + j]);
}
}
}//显示初始地图;
由于初始地图只有0、2、4三个值类型,所以只需要判断这三个值就行;
5.四个方向的移动;
四个方向的移动方法大致都是一样的,以右移为例:我们需要将每一行中的“非零”元素都移动到右侧,再去判断移动后的相邻项并从右往左合并相同的项,被合并的置0,然后再执行一遍右移清零;
enum move_player
{
up,
down,
left,
right
}
private bool move_zhongjianbiangliang;
private move_player move_key;
private void move()
{
int cs = 0;
switch (move_key)
{
case move_player.up:
for (int y = 0; y < 5; y++)
{
for (int x = 0; x < 5; x++)
{
while (map[x, y] == 0 && cs < 5)
{
for (int i = x; i < 5; i++)
{
if (i == 4) map[i, y] = 0;
else map[i, y] = map[i + 1, y];
}
cs++;
}
if (cs > 4) { cs = 0; break; }
else cs = 0;
}
}
break;
case move_player.down:
for (int y = 0; y < 5; y++)
{
for (int x = 4; x >= 0; x--)
{
while (map[x, y] == 0 && cs < 5)
{
for (int i = x; i >= 0; i--)
{
if (i == 0) map[i, y] = 0;
else map[i, y] = map[i - 1, y];
}
cs++;
}
if (cs > 5) { cs = 0; break; }
else cs = 0;
}
}
break;
case move_player.left:
for (int x = 0; x < 5; x++)
{
for (int y = 0; y < 5; y++)
{
while (map[x, y] == 0 && cs < 5)
{
for (int i = y; i < 5; i++)
{
if (i == 4) map[x, i] = 0;
else map[x, i] = map[x, i + 1];
}
cs++;
}
if (cs > 4) { cs = 0; break; }
else cs = 0;
}
}
break;
case move_player.right:
for (int x = 0; x < 5; x++)
{
for (int y = 4; y >= 0; y--)
{
while (map[x, y] == 0 && cs < 5)
{
for (int i = y; i >= 0; i--)
{
if (i == 0) map[x, i] = 0;
else map[x, i] = map[x, i - 1];
}
cs++;
}
if (cs > 4) { cs = 0; break; }
else cs = 0;
}
}
break;
}
if (move_zhongjianbiangliang == false)
{
move_add();
}
else
{
move_zhongjianbiangliang = false;
map_add();
}
}//移动地图;
private void move_add()
{
switch (move_key)
{
case move_player.up:
for (int y = 0; y < 5; y++)
{
for (int x = 0; x < 4; x++)
{
if (map[x, y] == map[x + 1, y] && map[x, y] < 2048)
{
map[x, y] += map[x + 1, y];
map[x + 1, y] = 0;
}
}
}
break;
case move_player.down:
for (int y = 0; y < 5; y++)
{
for (int x = 4; x > 0; x--)
{
if (map[x, y] == map[x - 1, y] && map[x, y] < 2048)
{
map[x, y] += map[x - 1, y];
map[x - 1, y] = 0;
}
}
}
break;
case move_player.left:
for (int x = 0; x < 5; x++)
{
for (int y = 0; y < 4; y++)
{
if (map[x, y] == map[x, y + 1] && map[x, y] < 2048)
{
map[x, y] += map[x, y + 1];
map[x, y + 1] = 0;
}
}
}
break;
case move_player.right:
for (int x = 0; x < 5; x++)
{
for (int y = 4; y > 0; y--)
{
if (map[x, y] == map[x, y - 1] && map[x, y] < 2048)
{
map[x, y] += map[x, y - 1];
map[x, y - 1] = 0;
}
}
}
break;
}
move_zhongjianbiangliang = true;
move();
}//合并相同项;
6.生成新数字
移动操作结束后需要在空白处随机生成一个新的数字,新生成的数字只包含2或4且被设定在移动方向的最后一行或是最后一列;
private void map_add()
{
Random r = new Random();
int s = r.Next(0, 5);
switch (move_key)
{
case move_player.up:
while (map[4, s] != 0)
{
s = r.Next(0, 5);
}
map[4, s] = map_s[s];
break;
case move_player.down:
while (map[0, s] != 0)
{
s = r.Next(0, 5);
}
map[0, s] = map_s[s];
break;
case move_player.left:
while (map[s, 4] != 0)
{
s = r.Next(0, 5);
}
map[s, 4] = map_s[s];
break;
case move_player.right:
while (map[s, 0] != 0)
{
s = r.Next(0, 5);
}
map[s, 0] = map_s[s];
break;
}
map_genxin();
}//填补移动空缺;
7.更新地图显示
以上都是对数组map进行的操作,操作结束后还需要打印在窗体中显示出来,当然我们只需要更新对应的标签文本和颜色就行了;
private void map_genxin()
{
for (int i = 0; i < 5; i++)
{
for (int j = 0; j < 5; j++)
{
txt_boxs[i * 5 + j].Text = Convert.ToString(map[i, j]);
switch (map[i, j])
{
case 0:
txt_boxs[i * 5 + j].BorderStyle = BorderStyle.None;
txt_boxs[i * 5 + j].ForeColor = color_fore_0;
txt_boxs[i * 5 + j].BackColor = color_back_0;
break;
case 2:
txt_boxs[i * 5 + j].BorderStyle = BorderStyle.Fixed3D;
txt_boxs[i * 5 + j].ForeColor = color_fore_2;
txt_boxs[i * 5 + j].BackColor = color_back_2;
break;
case 4:
txt_boxs[i * 5 + j].BorderStyle = BorderStyle.Fixed3D;
txt_boxs[i * 5 + j].ForeColor = color_fore_4;
txt_boxs[i * 5 + j].BackColor = color_back_4;
break;
case 8:
txt_boxs[i * 5 + j].BorderStyle = BorderStyle.Fixed3D;
txt_boxs[i * 5 + j].ForeColor = color_fore_8;
txt_boxs[i * 5 + j].BackColor = color_back_8;
break;
case 16:
txt_boxs[i * 5 + j].BorderStyle = BorderStyle.Fixed3D;
txt_boxs[i * 5 + j].ForeColor = color_fore_16;
txt_boxs[i * 5 + j].BackColor = color_back_16;
break;
case 32:
txt_boxs[i * 5 + j].BorderStyle = BorderStyle.Fixed3D;
txt_boxs[i * 5 + j].ForeColor = color_fore_32;
txt_boxs[i * 5 + j].BackColor = color_back_32;
break;
case 64:
txt_boxs[i * 5 + j].BorderStyle = BorderStyle.Fixed3D;
txt_boxs[i * 5 + j].ForeColor = color_fore_64;
txt_boxs[i * 5 + j].BackColor = color_back_64;
break;
case 128:
txt_boxs[i * 5 + j].BorderStyle = BorderStyle.Fixed3D;
txt_boxs[i * 5 + j].ForeColor = color_fore_128;
txt_boxs[i * 5 + j].BackColor = color_back_128;
break;
case 256:
txt_boxs[i * 5 + j].BorderStyle = BorderStyle.Fixed3D;
txt_boxs[i * 5 + j].ForeColor = color_fore_256;
txt_boxs[i * 5 + j].BackColor = color_back_256;
break;
case 512:
txt_boxs[i * 5 + j].BorderStyle = BorderStyle.Fixed3D;
txt_boxs[i * 5 + j].ForeColor = color_fore_512;
txt_boxs[i * 5 + j].BackColor = color_back_512;
break;
case 1024:
txt_boxs[i * 5 + j].BorderStyle = BorderStyle.Fixed3D;
txt_boxs[i * 5 + j].ForeColor = color_fore_1024;
txt_boxs[i * 5 + j].BackColor = color_back_1024;
break;
case 2048:
txt_boxs[i * 5 + j].BorderStyle = BorderStyle.Fixed3D;
txt_boxs[i * 5 + j].ForeColor = color_fore_2048;
txt_boxs[i * 5 + j].BackColor = color_back_2048;
break;
}
}
}
}//更新地图显示;
8.按键控制
游戏需要使用到的键盘操作;
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
switch (keyData)
{
case Keys.Up:
move_key = move_player.up;
move();
return true;
case Keys.W:
move_key = move_player.up;
move();
return true;
case Keys.Down:
move_key = move_player.down;
move();
return true;
case Keys.S:
move_key = move_player.down;
move();
return true;
case Keys.Left:
move_key = move_player.left;
move();
return true;
case Keys.A:
move_key = move_player.left;
move();
return true;
case Keys.Right:
move_key = move_player.right;
move();
return true;
case Keys.D:
move_key = move_player.right;
move();
return true;
}
return base.ProcessCmdKey(ref msg, keyData);
}