接触游戏有一段时间了,也写了一些东西,效果还不错,今天没事,我就把2048 c# 版本的实现贴出来,代码已经测试过,可以正常、完美运行。当然了,在网上有很多有关2048的实现方法,但是没有提出到类里面,只是写的测试代码,我在这里已经完全提到类里面,核心类和核心方法都经过测试,没有问题。由于本人学习有漏,或者不足,也请大家批评指正,大家共同进步。
该文章分为三个部分,我们分门别类说的,大家也会很清楚。目录如下:
第一部分:图片展示,最开始,我还是先把程序的运行效果贴出来,大家有一个初步感受。
第二部分:代码的前端部分,因为我这个程序就是一个在控制台中运行的程序,没有界面。但是有些操作需要在控制台中操作,这部分代码在控制台中。
第三部分:2048核心的类和辅助类型,所有核心算法和实现都在这里,都已经封装了方法,直接调用就可以。
其实这三个部分很简单,每个部分都有自己的职责,废话不多说了,直接上代码。
一、2048 在控制台中的运行效果(主要以贴图为主。)
1、数据初始化
这是程序第一次执行的效果,数据刚刚完成初始化。
2、操作开始,点击键盘的 a 字母代表向做移动,分为两张图,移动前和移动后的效果。
左移前
左移后
3、点击键盘的 D 字符代表向右移动,分为两个图片,分别是移动前和移动后。
移动前
移动后
4、点击键盘的 w 字符代表向上移动,分为两个图片,分别是移动前和移动后。
移动前
移动后
5、点击键盘的 s 字符代表向下移动,分为两个图片,分别是移动前和移动后。
移动前
移动后
二、在控制台中控制逻辑和一些辅助方法,逻辑很简单,就不多说了,直接上代码。
1 GameCoreManager game = new GameCoreManager(5); 2 3 game.Initail(); 4 5 Console.WriteLine("原始数组:"); 6 PrintArray(game.DataContainer); 7 Console.WriteLine(); 8 9 while (true) 10 { 11 if (game.CalculateEmptyElements(game.DataContainer).Count <= 0) 12 { 13 Console.WriteLine("游戏结束"); 14 break; 15 } 16 switch (Console.ReadLine()) 17 { 18 case "w": 19 game.Move(Direction.Up); 20 break; 21 case "s": 22 game.Move(Direction.Down); 23 break; 24 case "a": 25 game.Move(Direction.Left); 26 break; 27 case "d": 28 game.Move(Direction.Right); 29 break; 30 case "exit": 31 break; 32 } 33 if (game.IsChange) 34 { 35 game.GenerateRandomNumber(); 36 PrintArray(game.DataContainer); 37 } 38 }
以上代码就是放在控制台的 Main 方法中药执行的代码。
这个代码主要适用于打印二维数组的,逻辑不复杂,用于在控制台中显示移动效果。
1 /// <summary> 2 /// 打印二维数组在控制台上。 3 /// </summary> 4 /// <param name="array">要打印数据的数组。</param> 5 private static void PrintArray(int[,] array) 6 { 7 Console.Clear(); 8 if (array == null || array.Length <= 0) 9 { 10 Console.WriteLine("没有任何元素可以打印。"); 11 } 12 13 for (int row = 0; row < array.GetLength(0); row++) 14 { 15 for (int column = 0; column < array.GetLength(1); column++) 16 { 17 Console.Write(array[row,column]+"\t"); 18 } 19 Console.WriteLine(); 20 } 21 }
在控制台中的代码就是这些,是不是很简单,其实不是很复杂,接下来我们看看核心类的实现。
三、2048 核心类 GameCoreManager 的实现,里面有完整的备注,所以我就不多说了。大家可以直接使用,测试。
1 /// <summary> 2 /// 游戏核心算法的管理器类型,该类型定义 2048 游戏的核心算法。 3 /// </summary> 4 public sealed class GameCoreManager 5 { 6 #region 实例字段 7 8 private int[,] _dataContainer; 9 10 #endregion 11 12 #region 构造函数 13 14 /// <summary> 15 /// 初始化 GameCoreManager 类型的新实例,数据容器维度默认值:4. 16 /// </summary> 17 public GameCoreManager() : this(4) { } 18 19 /// <summary> 20 /// 通过制定的数据维度初始化 GameCoreManager 类型的新实例。 21 /// </summary> 22 /// <param name="capacity">数据容量。</param> 23 public GameCoreManager(int capacity) 24 { 25 if (capacity <= 0 || capacity >= 32) 26 { 27 throw new ArgumentNullException("dimensional is null."); 28 } 29 DataContainer = new int[capacity, capacity]; 30 } 31 32 #endregion 33 34 #region 实例属性 35 36 /// <summary> 37 /// 获取数据 38 /// </summary> 39 public int[,] DataContainer { get => _dataContainer; private set => _dataContainer = value; } 40 41 #endregion 42 43 #region 实例接口方法 44 45 /// <summary> 46 /// 初始化游戏数据。 47 /// </summary> 48 public void Initail() 49 { 50 int length = DataContainer.GetLength(0) / 2 + DataContainer.GetLength(0) % 2; 51 for (int i = 0; i < length; i++) 52 { 53 GenerateRandomNumber(); 54 } 55 } 56 57 /// <summary> 58 /// 数据移动。 59 /// </summary> 60 /// <param name="direction">要移动的方向</param> 61 public void Move(Direction direction) 62 { 63 //判断原数组是否发生了变化。 64 //记录原数组。 65 int[,] destinationArray = new int[DataContainer.GetLength(0), DataContainer.GetLength(1)]; 66 Array.Copy(DataContainer, destinationArray, DataContainer.Length); 67 IsChange = false; 68 69 switch (direction) 70 { 71 case Direction.Up: 72 MoveUp(); 73 break; 74 case Direction.Down: 75 MoveDown(); 76 break; 77 case Direction.Left: 78 MoveLeft(); 79 break; 80 case Direction.Right: 81 MoveRight(); 82 break; 83 } 84 85 //比较现在的数组和以前的数组比较 86 for (int row = 0; row < DataContainer.GetLength(0); row++) 87 { 88 for (int column = 0; column < DataContainer.GetLength(1); column++) 89 { 90 if (DataContainer[row, column] != destinationArray[row, column]) 91 { 92 IsChange = true; 93 return; 94 } 95 } 96 } 97 } 98 99 /// <summary> 100 /// 获取或者设置数组元素是否发生变动。true 表示发生变动,false 表示没有变动。 101 /// </summary> 102 public bool IsChange { get; private set; } 103 104 /// <summary> 105 /// 计算空元素的个数,并保存元素的索引位置。 106 /// </summary> 107 /// <param name="sourceArray">要处理的二维数组。</param> 108 /// <returns>返回保存空元素索引位置的列表对象。</returns> 109 public IList<Position> CalculateEmptyElements(int[,] sourceArray) 110 { 111 IList<Position> elements = new List<Position>(DataContainer.GetLength(0) * DataContainer.GetLength(1)); 112 if (sourceArray == null || sourceArray.Length <= 0) 113 { 114 return elements; 115 } 116 117 for (int row = 0; row < sourceArray.GetLength(0); row++) 118 { 119 for (int column = 0; column < sourceArray.GetLength(1); column++) 120 { 121 if (sourceArray[row, column] == 0) 122 { 123 elements.Add(new Position(row, column)); 124 } 125 } 126 } 127 return elements; 128 } 129 130 /// <summary> 131 /// 随机生成数字元素填充空的数组元素。 132 /// </summary> 133 public void GenerateRandomNumber() 134 { 135 var list = CalculateEmptyElements(this.DataContainer); 136 if (list.Count > 0) 137 { 138 Random random = new Random(); 139 var position = list[random.Next(0, list.Count)]; 140 DataContainer[position.Row, position.Column] = random.Next(0, 10) == 4 ? 4 : 2; 141 } 142 } 143 144 #endregion 145 146 #region 实例私有方法(核心算法) 147 148 /// <summary> 149 /// 将数组中的为 0 的元素移动到数组最后面。[1,0,0,2],结果为【1,2,0,0】 150 /// </summary> 151 /// <param name="array">要处理的数组。</param> 152 private void MoveZeroToLast(int[] array) 153 { 154 if (array == null || array.Length <= 0) 155 { 156 return; 157 } 158 159 int[] myarray = new int[array.Length]; 160 161 int index = 0; 162 for (int i = 0; i < array.Length; i++) 163 { 164 if (array[i] != 0) 165 { 166 myarray[index++] = array[i]; 167 } 168 } 169 //通过引用修改元素才可以,修改引用对外界没有影响。 170 myarray.CopyTo(array, 0); 171 } 172 173 /// <summary> 174 /// 合并数组中相邻相同的数字元素,后一个元素清零。[2,2,0,2],结果为【4,2,0,0】 175 /// </summary> 176 /// <param name="array">要处理的数组。</param> 177 private void MergeSameNumber(int[] array) 178 { 179 if (array == null || array.Length <= 0) 180 { 181 return; 182 } 183 184 MoveZeroToLast(array);//2,2,2,0 185 186 for (int i = 0; i < array.Length - 1; i++) 187 { 188 if (array[i] != 0 && array[i] == array[i + 1]) 189 { 190 array[i] += array[i + 1]; 191 array[i + 1] = 0; 192 } 193 } 194 195 //4,0,2,0 196 197 MoveZeroToLast(array);//4,2,0,0 198 } 199 200 /// <summary> 201 /// 向上移动数据。 202 /// </summary> 203 private void MoveUp() 204 { 205 //从上往下取数据。 206 if (DataContainer == null || DataContainer.Length <= 0) 207 { 208 return; 209 } 210 211 int[] tempArray = new int[DataContainer.GetLength(0)]; 212 213 for (int column = 0; column < DataContainer.GetLength(1); column++) 214 { 215 for (int row = 0; row < DataContainer.GetLength(0); row++) 216 { 217 tempArray[row] = DataContainer[row, column]; 218 } 219 220 MergeSameNumber(tempArray); 221 222 for (int row = 0; row < DataContainer.GetLength(0); row++) 223 { 224 DataContainer[row, column] = tempArray[row]; 225 } 226 } 227 } 228 229 /// <summary> 230 /// 向下移动数据。 231 /// </summary> 232 private void MoveDown() 233 { 234 //从下往上取 235 if (DataContainer == null || DataContainer.Length <= 0) 236 { 237 return; 238 } 239 240 int[] tempArray = new int[DataContainer.GetLength(0)]; 241 242 for (int column = 0; column < DataContainer.GetLength(1); column++) 243 { 244 for (int row = DataContainer.GetLength(0) - 1; row >= 0; row--) 245 { 246 tempArray[DataContainer.GetLength(0) - 1 - row] = DataContainer[row, column]; 247 } 248 249 MergeSameNumber(tempArray); 250 251 for (int row = DataContainer.GetLength(0) - 1; row >= 0; row--) 252 { 253 DataContainer[row, column] = tempArray[DataContainer.GetLength(0) - 1 - row]; 254 } 255 } 256 } 257 258 /// <summary> 259 /// 向左移动数据。 260 /// </summary> 261 private void MoveLeft() 262 { 263 if (DataContainer == null || DataContainer.Length <= 0) 264 { 265 return; 266 } 267 //从左往右 268 269 int[] tempArray = new int[DataContainer.GetLength(1)]; 270 271 for (int i = 0; i < DataContainer.GetLength(0); i++) 272 { 273 for (int j = 0; j < DataContainer.GetLength(1); j++) 274 { 275 tempArray[j] = DataContainer[i, j]; 276 } 277 278 MergeSameNumber(tempArray); 279 280 for (int j = 0; j < DataContainer.GetLength(1); j++) 281 { 282 DataContainer[i, j] = tempArray[j]; 283 } 284 } 285 } 286 287 /// <summary> 288 /// 向右移动数据。 289 /// </summary> 290 private void MoveRight() 291 { 292 if (DataContainer == null || DataContainer.Length <= 0) 293 { 294 return; 295 } 296 297 //从右向左取 298 299 //{ 2,2,4,8 },0,4,4,8 300 //{ 2,4,4,4 }, 301 //{ 0,8,4,0 }, 302 //{ 2,4,0,4 } 303 304 int[] tempArray = new int[DataContainer.GetLength(1)]; 305 306 for (int i = 0; i < DataContainer.GetLength(1); i++) 307 { 308 for (int j = DataContainer.GetLength(1) - 1; j >= 0; j--) 309 { 310 //8,4,2,2 311 tempArray[DataContainer.GetLength(1) - 1 - j] = DataContainer[i, j]; 312 } 313 314 //8,4,4,0 315 MergeSameNumber(tempArray); 316 317 for (int j = DataContainer.GetLength(1) - 1; j >= 0; j--) 318 { 319 DataContainer[i, j] = tempArray[DataContainer.GetLength(1) - 1 - j]; 320 } 321 } 322 } 323 324 #endregion 325 }
另外,还有两个枚举类型,代码很简单,直接贴代码了。
1 /// <summary> 2 /// 元素的坐标位置。 3 /// </summary> 4 public struct Position 5 { 6 /// <summary> 7 /// 获取或者设置元素的行坐标。 8 /// </summary> 9 public int Row { get; set; } 10 11 /// <summary> 12 /// 获取或者设置元素的列坐标。 13 /// </summary> 14 public int Column { get; set; } 15 16 /// <summary> 17 /// 通过行坐标、列坐标初始化 Position 对象实例。 18 /// </summary> 19 /// <param name="row">元素的行坐标。</param> 20 /// <param name="column">元素的列坐标。</param> 21 public Position(int row, int column) 22 { 23 if (row >= 0 && column >= 0) 24 { 25 this.Row = row; 26 this.Column = column; 27 } 28 else 29 { 30 throw new ArgumentNullException("parameter is null."); 31 } 32 } 33 } 34 35 /// <summary> 36 /// 移动的方向。 37 /// </summary> 38 public enum Direction 39 { 40 /// <summary> 41 /// 向上移动 42 /// </summary> 43 Up, 44 45 /// <summary> 46 /// 向下移动 47 /// </summary> 48 Down, 49 50 /// <summary> 51 /// 向左移动 52 /// </summary> 53 Left, 54 55 /// <summary> 56 /// 向右移动 57 /// </summary> 58 Right 59 }
这就是2048 核心类的实现,代码都有备注,其实,逻辑也不复杂,大家看也是可以看得懂的,只是需要点耐心。
文章就到此为止了,这是有关2028的核心算法,我发的代码都是经过测试的,大家可以拿过去直接使用,修改和测试。而且代码都很完整,所以我就没有把源码贴出来。