本周的周作业是2048,遗憾是没有时间做AI。
总结分为: 教训、思路、代码三个部分。
一.教训
1.第一次做还是采用老的编程思想,去琢磨算法,做了一个改良版的冒泡排序来交换每个块的数字,但是忙活了一天以失败而告终,因为好不容易换到边界,又被后面的循环换回来了;
2.第二次做还是采用了3层for循环的结构,单独做把非零数字交换到目标方向的操作,然后就研究网上一个可以实现矩阵旋转的方法,简单的说就是先按一个方向做好算法,然后写一个方法把整个二维数组顺时针倒转到写好算法的方向,排序完再逆时针倒转回去,这是个巧妙的办法,但是自己没弄完全弄懂,也没写出来。
3.周五下午老师开始讲怎么用面向对象的思想做这个游戏,先分析整个游戏世界的组成,找出最基础的元素,然后将这些共性写成一个基类,然后其他的块都在这个基类上画自己的,16个块的数字交换也是面向对象的,给每一个块做好自己的移动方向,这个方法根据移动方向接受一个目标块,然后向目标块移动和判断。主函数里只写了输入4个方向后二维数组遍历的顺序,其他都是整理数据,就这么简单的做完了。
4.其实在自己第一次做的时候就想过用面向对象的思想,给每个块写方法,但是不知道如何下手。
5.罗列一下周末自己重写卡住的地方:写继承于基类的CELL类时想不清继承后重写Draw方法的逻辑;画完记分板和16个块后发现数字投影下一小块的底色是黑色和设置颜色突兀,其实是重写时没有将前景色设置成属性里的forecolor;写每个块的移动方法时,想不清为啥写2个方法,然后为何互相调用,以及和主函数4个遍历for循环的关系;写死亡判定时卡在if和if else,后面完成后测试发现没区别;写随机数产生的方法时卡在如果没有等于0的块但是有能合并块时跳不出循环;当前分数和最高分数的逻辑;输入方向的x,y与数组下标[0,1]的对应关系;最后就是外部引用的调色基类colorsetter的使用。
二.思路
1.先分析游戏的规律,写下来
2.然后从共同的基类开始一层一层写下来
3.重点是写单个块的移动逻辑,根据输入方向给出一个目标块,使得朝目标块移动,交换数字或合并,标记状态是否移动或合并过,然后用for循环按既定方向调用每个块的方法,例如如果按下的是向左,就从左至右遍历,目标块在左。
4.常见BUG:一行或一列合并次数超过1次;有移动或合并才能产生新的随机数;数字满屏且没死亡时无法移动;真正死亡时无法结束游戏;清屏console.clear()带来的闪屏问题。
三.代码
一共有4个类:Program,GameObject,Cell,Board,另外还引用了一个调色的类库ColorSetter
Program:
class Program
{
public static Cell[,] cells; //定义静态字段Cell数组
public static Board scoreBoard;//定义静态字段计分板
public static GameObject go;//定义静态字段画大背景
public static Random roll = new Random();//定义静态字段随机数
public static int randomNum;//随机数变量
public static int consoleX = GameObject.startPoint.x;//代替起始点
public static int consoleY = GameObject.startPoint.y;//代替起始点
public static bool isGameOver = false;//检测游戏是否结束
public static bool isNewRound = false;//检测游戏是否结束
static void Main(string[] args)
{
char input = ' ';
bool isRightInput = false;
int directionX = 0;//记录方向向量
int directionY = 0;//记录方向向量
Console.Title = "2048";
Console.WindowWidth = 56;
Console.WindowHeight = 26;
Console.CursorVisible = false;//隐藏光标
//初始化16个Cell
SetCells();
//起始为2个随机数
RandomNum();
RandomNum();
//画大背景
DrawBackGroud();
//画小背景
DrawSmallBackGroud();
//循环开始 ↓
while (!isGameOver)
{
//部分清理屏幕
ClearPartConsole();
//如果是重新开始,则给出2个开始数
if (isNewRound)
{
RandomNum();
RandomNum();
}
isNewRound = false;
//画历史最高分数
DrawRecordBoards();
//画计分板
DrawScoreBoard();
//画数字和背景格
DrawCells();
//检测输入,选择方向
#region 输入检测
input = Console.ReadKey(true).KeyChar;
while (!isRightInput)
{
switch (input)
{
case 'w': isRightInput = true; directionX = GameObject.up.x; directionY = GameObject.up.y; break;
case 's': isRightInput = true; directionX = GameObject.down.x; directionY = GameObject.down.y; break;
case 'a': isRightInput = true; directionX = GameObject.left.x; directionY = GameObject.left.y; break;
case 'd'