c#撸的控制台版2048小游戏
2021-01-09 22:29
标签:二维数组 复杂 als com amp random break 变化 span 最近心血来潮,突然想写一个2048小游戏。于是搜索了一个在线2048玩玩,熟悉熟悉规则。 只谈核心规则:(以左移为例) 以行为单位,忽略0位,每列依次向左进行合并,且每列只能合并一次。被合并列置0。 每列依次向左往0位上移动,不限次数。 [成功]就是合并后值为2048,[失败]则是没有任何一个方向上能进行合并或者移动了。 成品截图如下 一样只谈核心的东西。网上大多数的实现算法有这么几种。 这种太过繁琐,其实算法逻辑都差不多,只是方向不同而已,冗余代码太多 这种做到了各个方向上一定的通用,但是增加了额外的两次矩阵运算。 其实只需实现一个方向的算法,然后抽离出和方向有关的变量,封装为参数,通过参数控制方向。 比如左方向:以行为单位,处理每列的数据。那么第一层循环将是按行的数量进行迭代。处理列索引将是0-最后一列。 比如右方向:以行为单位,处理每列的数据。那么第一层循环将是按行的数量进行迭代。处理列索引将是最后一列-0。 比如上方向:以列为单位,处理每行的数据。那么第一层循环将是按列的数量进行迭代。处理列索引将是0-最后一行。 比如下方向:以列为单位,处理每行的数据。那么第一层循环将是按列的数量进行迭代。处理列索引将是最后一行-0。 变量抽取为: 第一层循环的loop,可以传入行或者列数量。 第二层循环的起始值starti,结束值endi,因为有正和反两个方向,所以还需要一个步长step来控制方向,+1为正,-1为反。 因为是二维数组,所以还需要一个委托,来重定义[x,y]的取值和设置值。比如以行为外层循环的,返回[x,y],以列为外层循环的,返回[y,x] 因为涉及到取值和赋值,用到了指针,也可以用两个方法替代取值和赋值。 代码如下 调用的核心代码: 网上大多数的算法都是复制一份矩阵数据,然后依次从各个方向上进行合并和移动,之后和原矩阵进行比较,如果数据相同则说明没有变化,从而判定失败。 这种太复杂,太死板了,太低效了。仔细分析可知,失败的判定其实很简单: 1.已经没有空位可以随机数字了,说明不可移动。 2.每个坐标的数字和它旁边的数字都不相等。说明不可合并。 代码如下: 因为这个判定必然发生在随机生成数字之后,即上面move返回true时,那么调用代码: Game类: Main入口: c#撸的控制台版2048小游戏 标签:二维数组 复杂 als com amp random break 变化 span 原文地址:https://www.cnblogs.com/cleymore/p/13084270.html1.分析
1.1合并
1.2移动
1.3判定
2.实现
2.1为每个方向上的合并和移动实现一个算法。
2.2以某一个方向作为算法基础,其他方向进行矩阵旋转,直到和基础算法方向一致,处理完成之后,再旋转矩阵到原来方向。
1 private unsafe bool Move(int loop, int si, int ei, int step, Funcint, int, IntPtr> getInt)
2 {
3 //算法基于左向移动
4
5 bool moved = false;
6
7 for (int x = 0; x )
8 {
9 //第一步 合并
10 for (int y = si; y * step step)
11 {
12 var val1 = (int*)getInt(x, y);
13
14 if (*val1 != 0)
15 {
16 for (var y2 = y + step; y2 != ei + step; y2 += step)
17 {
18 var val2 = (int*)getInt(x, y2);
19 //忽略0
20 if (*val2 == 0) continue;
21 //合并
22 if (*val1 == *val2)
23 {
24 *val1 *= 2;
25 *val2 = 0;
26 moved = true;
27
28 Score += *val1;
29
30 if (*val1 == 2048) State = GameState.Succ;
31
32 //移动处理列索引
33 y = y2;
34 }
35 else y = y2 - step;//不相等
36 break;
37 }
38 }
39
40 }
41
42 //第二步 往0位上移动
43 int? lastY = null;
44 for (int y = si; y != ei; y += step)
45 {
46 var val1 = (int*)getInt(x, y);
47
48 if (*val1 == 0)
49 {
50 var y2 = lastY ?? y + step;
51 for (; y2 != ei + step; y2 += step)
52 {
53 var val2 = (int*)getInt(x, y2);
54
55 if (*val2 != 0)
56 {
57 *val1 = *val2;
58 *val2 = 0;
59 moved = true;
60
61 lastY = y2 + step;
62 break;
63 }
64 }
65 //最后一列了
66 if (y2 == ei) break;
67 }
68 }
69 }
70
71 return moved;
72 }
1 switch (direction)
2 {
3 case MoveDirection.Up:
4 move = Move(C, 0, R - 1, 1, (x, y) => {
5 fixed (int* _ = &_bs[0, 0])
6 {
7 return (IntPtr)(_ + y * C + x);
8 }
9 });
10 break;
11 case MoveDirection.Down:
12 move = Move(C, R - 1, 0, -1, (x, y) => {
13 fixed (int* _ = &_bs[0,0])
14 {
15 return (IntPtr)(_ + y * C + x);
16 }
17 });
18 break;
19 case MoveDirection.Left:
20 move = Move(R, 0, C - 1, 1, (x, y) => {
21 fixed (int* _ = &_bs[0, 0])
22 {
23 return (IntPtr)(_ + x * C + y);
24 }
25 });
26 break;
27 case MoveDirection.Right:
28 move = Move(R, C - 1, 0, -1, (x,y)=> {
29 fixed(int* _ = &_bs[0, 0])
30 {
31 return (IntPtr)(_ + x * C + y);
32 }
33 });
34 break;
35 }
2.3结果判定
1 ///
1 if (move && State != GameState.Succ)
2 {
3 //有移动 随机在空位生成数字
4 var emptyNum = GenerateNum();
5
6 //判断是否结束
7 if(emptyNum == 0) CheckGame();
8 }
3.完整的代码如下:
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading.Tasks;
6
7 namespace _2048
8 {
9 public enum MoveDirection{
10 Up,
11 Down,
12 Left,
13 Right
14 }
15
16 public enum GameState
17 {
18 None,
19 Fail,
20 Succ,
21 }
22
23 public class Game
24 {
25 public static int R = 4, C = 4;
26
27 private int[,] _bs;
28 private Random _rnd = new Random();
29 public GameState State = GameState.None;
30 public int Score, Steps;
31 public (MoveDirection direction, int[,] data)? Log;
32 public bool ShowPre;
33
34 public Game()
35 {
36 Restart();
37 }
38
39 public unsafe void Move(MoveDirection direction)
40 {
41 if (State != GameState.None) return;
42
43 var move = false;
44 var bs = (int[,])_bs.Clone();
45
46 switch (direction)
47 {
48 case MoveDirection.Up:
49 move = Move(C, 0, R - 1, 1, (x, y) => {
50 fixed (int* _ = &_bs[0, 0])
51 {
52 return (IntPtr)(_ + y * C + x);
53 }
54 });
55 break;
56 case MoveDirection.Down:
57 move = Move(C, R - 1, 0, -1, (x, y) => {
58 fixed (int* _ = &_bs[0,0])
59 {
60 return (IntPtr)(_ + y * C + x);
61 }
62 });
63 break;
64 case MoveDirection.Left:
65 move = Move(R, 0, C - 1, 1, (x, y) => {
66 fixed (int* _ = &_bs[0, 0])
67 {
68 return (IntPtr)(_ + x * C + y);
69 }
70 });
71 break;
72 case MoveDirection.Right:
73 move = Move(R, C - 1, 0, -1, (x,y)=> {
74 fixed(int* _ = &_bs[0, 0])
75 {
76 return (IntPtr)(_ + x * C + y);
77 }
78 });
79 break;
80 }
81
82 if (move && State != GameState.Succ)
83 {
84 Steps++;
85
86 Log = (direction, bs);
87
88 //有移动 随机中空位生成数字
89 var emptyNum = GenerateNum();
90
91 //判断是否结束
92 if(emptyNum == 0) CheckGame();
93 }
94 }
95
96 ///
1 static void Main(string[] args)
2 {
3 Game.R = 4;
4 Game.C = 4;
5
6 var game = new Game();
7
8 while (true)
9 {
10 game.Show();
11
12 var key = Console.ReadKey();
13 switch (key.Key)
14 {
15 case ConsoleKey.UpArrow:
16 game.Move(MoveDirection.Up);
17 break;
18 case ConsoleKey.DownArrow:
19 game.Move(MoveDirection.Down);
20 break;
21 case ConsoleKey.RightArrow:
22 game.Move(MoveDirection.Right);
23 break;
24 case ConsoleKey.LeftArrow:
25 game.Move(MoveDirection.Left);
26 break;
27 case ConsoleKey.R:
28 game.ShowPre = !game.ShowPre;
29 break;
30
31 }
32 if (game.State == GameState.None) continue;
33
34 game.Show();
35
36 var res = MessageBox.Show("需要重新开始吗?", game.State == GameState.Succ ? "恭喜你!!!成功过关!!!" : "很遗憾!!!失败了!!!",MessageBoxButtons.YesNo);
37 if (res == DialogResult.Yes)
38 {
39 game.Restart();
40 continue;
41 }
42 break;
43 }
44
45 Console.ReadKey();
46 }