飞行棋的例子是对基础知识的一次很好很全面的训练,这篇笔记里我尝试对制作飞行棋程序的整体思路做一次回顾。
首先是要显示游戏名称、欢迎信息、游戏说明等,这部分固定不变,可以做写一个ShowUI()。
第二步是要获得玩家名字,用string[] names=new string[2] 存放两个玩家的名字,这样后面只需要改变数组下标就可以选中不同玩家,比较方便。需要验证字符串和检测重名,写一个GetNames(),用while(){try-catch}来处理错误的输入。再写一个GetNames(string name0)重载方法,传入玩家A的名字,验证并获得玩家B的名字。
然后是最重要的部分,绘制地图。
用int[] Map=new int[100] 来存放地图中100个格的状态。状态有5种,用int数值0-4来表示,分别是普通格和幸运轮盘、地雷、暂停一次、时空隧道。后4种特殊格分别用4个数组来存放它们在Map[] 中的下标,于是Map[pause[0]]就代表地图中第一处暂停一次的格子,依此类推。此外,一个格子还可能有3种状态,被玩家A占据、被玩家B占据、被玩家AB同时占据。这只需要一个数组int playerPos[]两个元素分别存放两个玩家在Map[]中的下标就可以了,这两个下标是可以变化的。这部分地图数据初始化可以独立出来写进InitialMap()。
然后是把地图输出到屏幕,根据Map[]的元素中存放的状态数字判断是哪种格子,从而选定要显示的符号,用 GetMapString(int i)和switch得到Map[i]对应的符号,在DrawMap()中的Write()或WriteLine()中调用GetMapString()输入符号。 在GetMapString()中通过 Console.ForegroundColor 和ConsoleColor改变符号显示的颜色,并不DrawMap()中Console.ResetColor()重置地图绘完后后面字符的颜色。
地图有4次转折把地图分隔成左右翻转的‘S’形的5部分,关键是第二和第三部分。第二部分是Map[]下标从30到34的5格,输出符号时要在前面补29个全角空格,使它们在视觉上显示从上向下对齐排列。第三部分是Map[]下标从35到64的30格,方向是从右向左,for循环的三个表达式这样写for (int i = 64; i >=35; i--)。
---------------------- ---------------------- ---------------------- ---------------------- ---------------------- ----------------------
(之前写的时候没写完,过了段时间再来看果然对后面的内容记忆不清了,重新看了下代码才理清思路。)
地图绘好之后,接下来该开始游戏了。这部分是程序中要不断重复执行的部分,要用循环语句来处理,用while(){}循环就可以了。
一开始A和B都在Map[0]的位置上,AB在Map中的下标是变动的,用一个数组playerPos[]来表示,Map[playerPos[0]]就代表A所在的关卡。
游戏的视觉表现就是改变Map[playerPos[0]]的图案符号为代表A的符号,以及playerPos[0]的变动。
前者清屏重绘地图就行了。后者是游戏的核心,依据玩家掷出的骰子点数(Random()),决定playerPos增加量,然后根据Map[playerPos[0]]的图案(对应的值)判断玩家走到何种关卡执行奖励或是惩罚,即加或减playerPos的值。
玩家playerPos变动完成后就是清屏重绘地图,并移交操作权(改变playerPos[0]为playerPos[1]或相反)。再重复写一遍playerPos[1]的代码就太麻烦了,有简单的办法来解决。把playerPos写成playerPos[c],playerPos[t],c代表当前玩家,t代表另一个玩家,代码里只写playerPos[c]的代码,执行完一次代码后就交换c和t的值,即0和1,这样就实现了同一段代码玩家AB轮流执行。
这里又有一个问题,关卡中存在一种叫“暂停一次”,当于家走到这种关卡时,就必须中断一次交换,即让对方执行两次。需要设置一个标志isStop来判断要不要交换c和 t 的值。这里需要注意的是,“不交换”是发生在下一次对方执行完一次循环代码后,本次仍然要交换,这就要保证isStop是在下一次被识别为要交换。办法是对isStop设置3个值,默认0(在循环外定义)表示正常交换,当遇到“暂停一次”关卡时isStop=1,然后在执行完主要代码后判断if(isStop==1),则交换c和 t 并把isStop赋值为2,这样当下一次执行完主要代码后判断进入else if(isStop==2),这时不交换c和 t ,同时把isStop重置为0。这样这个问题就解决了。
这部分看着代码更便于理解,以下是Main()部分的代码:
static void Main(string[] args)
{
//控制台窗口初始化,指定标题和高度
Console.Title = "飞行棋";
Console.WindowHeight = 35;
//显示游戏名称
ShowUI();
//获取玩家名字
names[0] = GetNames();
names[1] = GetNames(names[0]);
//游戏开始
Console.WriteLine("开始游戏。。。");
ShowUI();
//初始化地图
InitialMap();
//绘制地图
DrawMap();
Console.WriteLine("游戏开始......");
//产生随机数并存放
Random r = new Random();
int step = 0;
//设置标记,交换AB行动次序
//isStop = 0 正常交换,isStop = 1 下次不交换(暂停一次),isStop = 2 ,不交换
int a = 0, b = 1, ctemp = 0;
int isStop = 0;
//AB轮流掷骰子,当坐标>=99,结束循环
while (playerPos[0] < 99 && playerPos[1] < 99)
{
#region 玩家掷骰子
Console.WriteLine("{0}按任意键开始掷骰子......", names[a]);
Console.ReadKey(true);
step = r.Next(1, 7);
Console.WriteLine("{0}掷出了{1},前进{1}步,按任意键开始行动......", names[a], step);
Console.ReadKey(true);
playerPos[a] = playerPos[a] + step;
CheckPos();
if (playerPos[a] == playerPos[b])
{
Console.WriteLine("踩到对方,把TA踢回起点!");
playerPos[b] = 0;
}
else
{
switch (Map[playerPos[a]])
{
case 0:
Console.WriteLine("一路畅通,前进!");
Console.ReadKey(true);
break;
case 1://幸运轮盘
DrawMap();
Console.WriteLine("你走到了幸运轮盘,请选择:");
Console.WriteLine("1--交换位置 2--轰炸对方");
int userSelect = ReadInt(1, 2);
if (userSelect == 1)
{
Console.WriteLine("交换位置");
int temp = playerPos[a];
playerPos[a] = playerPos[b];
playerPos[b] = temp;
}
else
{
Console.WriteLine("轰炸对方,对方被炸退6格");
playerPos[b] -= 6;
CheckPos();
}
Console.ReadKey(true);
break;
case 2:
Console.WriteLine("踩到地雷,炸退6格");
playerPos[a] -= 6;
CheckPos();
Console.ReadKey(true);
break;
case 3:
Console.WriteLine("前方暴风雨,暂停一次。");
isStop = 1;
Console.ReadKey(true);
break;
case 4:
Console.WriteLine("进入时空隧道,穿越10格!");
playerPos[a] += 10;
CheckPos();
Console.ReadKey(true);
break;
}
}
Console.WriteLine("{0}掷出了{1},行动完成!", names[a], step);
Console.ReadKey(true);
DrawMap();
Console.WriteLine("**********玩家A和玩家B的位置**************");
Console.WriteLine("{0}当前位置是第{1}格", names[0], playerPos[0] + 1);
Console.WriteLine("{0}当前位置是第{1}格", names[1], playerPos[1] + 1);
Console.WriteLine("********** 游 戏 继 续 **************");
//交换AB行动次序标记
if (isStop==0)
{
ctemp = a;
a = b;
b = ctemp;
}
else if (isStop == 1)
{
ctemp = a;
a = b;
b = ctemp;
isStop = 2;
}
else
{
isStop = 0;
}
#endregion
}
if (a == 1)//因为发生了AB次序交换,a==1说明刚刚行动的是 names[0]
{
Console.WriteLine("\n*********** 恭喜!{0} 获胜! ***********\n", names[0]);
Console.WriteLine("请按任意键退出游戏...");
}
else //a==0
{
Console.WriteLine("\n*********** 恭喜!{0} 获胜! ***********\n", names[1]);
Console.WriteLine("请按任意键退出游戏...");
}
Console.ReadKey();
}
最后,是玩家胜利游戏中止,也就是循环中止,条件是playerPos[]< 99写进循环条件里就好了。
这样整个游戏就写完了。
最后吐槽下,.NET的平台依赖性太坑人了。对游戏做了些修改后我发布成安装包给朋友玩,结果朋友电脑上没有装.Net Framework 4.5 ,于是就不能玩。。。