Bootstrap

【全栈计划 —— 编程语言之C#】 C# 实现双人飞行棋小游戏

前言

昨天刚刚完结了C#的基础知识,想着手使用基础的判断语句、循环语句、分支语句、输入输出语句等等来完成一个小Demo,用项目促进学习,差缺补漏。
在这里插入图片描述

在这里插入图片描述

项目结构流程图

首先要清楚项目的整体实现流程,跟着项目流程图走有助于思路上更加清晰。
在这里插入图片描述
在这里插入图片描述

项目实现

一、游戏开始界面

在编写游戏的开始界环节中,值得积累的知识点是控制台字体的背景颜色和字体颜色修改
利用枚举类 ConsoleColor来调取已经存储在其中的颜色字段。
同时使用: Console.ResetColor();来控制颜色的修改

在这里插入图片描述

在利用Console类调用BackgroundColor来修改控制台中字体的背景颜色
在这里插入图片描述

在利用Console类调用ForegroundColor来修改控制台中字体文本的颜色
在这里插入图片描述

public static void GameShow()
        #region 画游戏头
        {
            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.WriteLine("*****************************");
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine("*****************************");
            Console.ForegroundColor = ConsoleColor.Blue;
            Console.WriteLine("*******飞行棋游戏模拟********");
            Console.ForegroundColor = ConsoleColor.Cyan;
            Console.WriteLine("*****************************");
            Console.ForegroundColor = ConsoleColor.Magenta;
            Console.WriteLine("*****************************");
        }
        #endregion

二、初始化游戏地图

初始化游戏地图的目的主要是考虑到一些固定的位置要放置一些固定的关卡。
在这里插入图片描述
这个环节就没有什么值得注意的点了,常规的数组的操作,假如想玩得花一点,可以考虑用随机数来表示关卡的位置。

        public static void InitMap()
        #region 初始化游戏地图
        {
            //1 幸运轮盘
            int[] lucky = { 6, 23, 40, 55, 69, 83 };
            for(int i = 0; i < lucky.Length; i++)
                Maps[lucky[i]] = 1;

            //2 地雷
            int[] landMine = { 5, 13, 17, 33, 38, 50, 64, 80, 94 };
            for (int i = 0; i < landMine.Length; i++)
                Maps[landMine[i]] = 2;

            //3 暂停
            int[] puase = { 9, 27, 60, 93 };
            for (int i = 0; i < puase.Length; i++)
                Maps[puase[i]] = 3;

            //4 时空隧道
            int[] timeTunnel = { 20, 25, 45, 63, 72, 88, 90 };
            for (int i = 0; i < timeTunnel.Length; i++)
                Maps[timeTunnel[i]] = 4;
        }
        #endregion

三、绘制飞行棋地图

绘制飞行棋地图这里技术含量不高,主要是for循环和switch语句的使用。只是有一个需要注意的点:

在绘制每一个关卡的时候,一定要先打印出玩家的位置,再在其他位置打印地图中各个关卡。

这种才能看起来玩家确实是在玩这个游戏。

 //环节三:切实的画游戏地图  —— 总共是100个关卡,画每个关卡之前,一定要先画玩家的位置
        public static void DrawMap()
        {
            Console.WriteLine("图例:幸运轮盘:◎ 地雷:☆ 暂停:△ 时空隧道:◆");
            //第一横行 0 ~ 30
            //绘制具体的每一个关卡 因为要重复使用绘制的逻辑,因此加载为方法
            for (int i = 0; i < 30; i++)
            {
                Console.Write(DrawEveryGrid(i));
            }
            //换行
            Console.WriteLine();
            
            //绘制第一竖行
            for(int i = 30;i < 35; i++)
            {
                //每行的0到29绘制空格,第30个方格绘制具体关卡
                for (int j = 0; j < 29; j++) Console.Write("  ");
                Console.Write(DrawEveryGrid(i));
                //小细节,画完每个竖行,都应该换行
                Console.WriteLine();
            }

            //绘制第二横行
            for(int i = 64;i >= 35;i--)
            {
                Console.Write(DrawEveryGrid(i));
            }
            Console.WriteLine();
            
            //绘制第二竖行
            for(int i = 65;i < 70;i++)
            {
                Console.WriteLine(DrawEveryGrid(i));
            }

            //绘制第三横行
            for (int i = 70; i < 100; i++)
                Console.Write(DrawEveryGrid(i));

            Console.WriteLine();
        }

        //绘制走到每个关卡的逻辑
        public static string DrawEveryGrid(int i)
        #region 绘制每个关卡的逻辑
        {
            string str = "";
            //如果玩家在同一个位置,使用<>表示
            //之前只是放了一个if ,就错了
            if (PlayPos[0] == PlayPos[1] && PlayPos[0] == i)
                str = "<>";
            //其他就单独绘制玩家A和玩家B的位置信息
            else if (PlayPos[0] == i)
                str = "A";
            else if (PlayPos[1] == i)
                str = "B";
            //获取当前位置的信息,switch匹配打印出相应的关卡
            else
            {
                switch (Maps[i])
                {
                    case 0:
                        Console.ForegroundColor = ConsoleColor.DarkYellow;
                        str = "□";
                        break;
                    case 1:
                        Console.ForegroundColor = ConsoleColor.Green;
                        str = "◎";
                        break;
                    case 2:
                        Console.ForegroundColor = ConsoleColor.Red;
                        str = "☆";

                        break;
                    case 3:
                        Console.ForegroundColor = ConsoleColor.Blue;
                        str = "△";
                        break;
                    case 4:
                        Console.ForegroundColor = ConsoleColor.Cyan;
                        str = "◆";
                        break;
                }//switch
            }
            return str;
        }//方法
        #endregion 

四、玩游戏具体逻辑

到了玩家具体开始在地图上掷骰子,玩这个飞行棋的游戏的时候,整体稍微变得有点复杂了,但是跟着咱们之前梳理的流程图走下去,不会出现越写越凌乱的情况的。
在这里插入图片描述

① 创建两个玩家角色

既然是双人游戏,那么创建两个游戏对象是必要的环节了。
这里主要是积累程序设计的思想
程序源于生活,也用于生活。那么玩家取名为空、玩家名字重复等情况,应该避免

        public static void CreatePlayer()
        {
            Console.WriteLine("请输入第一个玩家的姓名(按回车键继续):");
            PlayNames[0] = Console.ReadLine();
            while(Enumerable.SequenceEqual(PlayNames[0]," ")) 
            {
                Console.WriteLine("玩家姓名不可以为空,请重新输入");
                PlayNames[0] = Console.ReadLine();
            }
            Console.WriteLine();

            Console.WriteLine("请输入第二个玩家的姓名(按回车键继续):");
            PlayNames[1] = Console.ReadLine();
            while (PlayNames[1] == " ")
            {
                    Console.WriteLine("玩家姓名不可以为空,请重新输入");
                    PlayNames[1] = Console.ReadLine();
            }
            while(PlayNames[1] == PlayNames[0])
            {
                Console.WriteLine("玩家2姓名不可以和玩家1姓名重复,请重新输入");
                PlayNames[1] = Console.ReadLine();
            }

        }
② 具体走到每个关卡触发的结果

这个环节是具体的去落实游戏规则。

图例:幸运轮盘:◎ 地雷:☆ 暂停:△ 时空隧道:◆
游戏规则:
如果玩家A踩到玩家B,玩家B退6格
如果玩家踩中地雷。退6格
如果玩家踩进时空隧道,进10格
如果玩家踩到幸运轮盘, 1 交换位置,2 轰炸对方(使对方退6格)
如果玩家踩到了暂停,暂停一个回合
如果玩家踩到了方块,走到当前位置,什么都不发生

值得积累的知识点
1、按下任意键继续时,要在ReadKey中加入参数true

 Console.ReadKey(true);

倘若不加参数true,在按下任意键之后,是这种效果
在这里插入图片描述
2、获取随机数需要通过Random类造对象实例,随机数的范围用Next函数确定,其区间左闭右开

			Random random = new Random();
            int ResultNum = random.Next(1, 7);

其他的代码是逐渐去落实上述的6条游戏规则,难度不大.~
在这里插入图片描述

 //具体玩游戏的逻辑
        public static void PlayGame(int PlayID)
        {
            Console.ResetColor();
            Console.WriteLine("玩家{0}按下任意键掷骰子:", PlayNames[PlayID]);
            Console.ReadKey(true);//加了这个参数之后,会不显示出任意键的值
            Random random = new Random();
            int ResultNum = random.Next(1, 7);
            Console.WriteLine("玩家{0}掷出的结果是{1}", PlayNames[PlayID], ResultNum);
            Console.WriteLine("按下任意键,玩家{0}开始在棋盘上移动",PlayNames[PlayID]);
            Console.ReadKey(true);
            //处理掷骰子后玩家的移动
            PlayPos[PlayID] += ResultNum;
            TurnRight();
            Console.WriteLine("玩家{0}移动完啦,按任意键继续游戏",PlayNames[PlayID]);

            //处理移动之后,在地图之后触发的关卡
            //玩家可以踩到 另一个玩家、普通方块、幸运轮盘、地雷、暂停、时空隧道
            //踩到另一个玩家,另一个玩家退后6格
            if(PlayPos[PlayID] == PlayPos[1 - PlayID])
            {
                Console.WriteLine("玩家{0}踩到了玩家{1},玩家{2}退回6格", PlayNames[0], PlayNames[1],PlayNames[1]);
                PlayPos[1] -= 6;

                //这里存在一步异常处理的矫正,玩家不能退出地图,也不能超出地图
                TurnRight();
                Console.ReadKey(true);

            }
            else //踩到关卡
            {
                switch (Maps[PlayPos[PlayID]])
                {
                    case 0://踩到普通方块
                        Console.WriteLine("玩家{0}猜中的普通方块,正常到达该位置",PlayNames[PlayID]);
                        Console.ReadKey(true);
                        break;
                    case 1://踩到幸运盘
                        Console.WriteLine("玩家{0}踩到了幸运轮盘,请选择:1 - 交换位置  2 - 轰炸对方(对手退回6格)",PlayNames[PlayID]);
                        Console.ReadKey(true);
                        string choice = Console.ReadLine();
                        if (choice == "1")
                        {
                            Console.WriteLine("玩家{0}选择与玩家{1}交换位置", PlayNames[PlayID], PlayNames[1 - PlayID]);
                            Console.WriteLine("按下任意键执行操作");
                            Console.ReadKey(true);
                            int temp = PlayPos[PlayID];
                            PlayPos[PlayID] = PlayPos[1 - PlayID];
                            PlayPos[1 - PlayID] = temp;
                            Console.WriteLine("操作执行完成,按任意键继续游戏");
                            Console.ReadKey(true);
                            break;
                        }
                        else if (choice == "2")
                        {
                            Console.WriteLine("玩家{0}选择轰炸玩家{1}", PlayNames[PlayID], PlayNames[1 - PlayID]);
                            Console.WriteLine("按下任意键执行操作");
                            PlayPos[1 - PlayID] -= 6;
                            TurnRight();
                            Console.WriteLine("操作执行完成,按任意键继续游戏");
                            Console.ReadKey(true);
                            break;

                        }
                        else
                        {
                            Console.WriteLine("请遵守游戏规则,在1和2之间进行选择");
                            Console.ReadKey(true);
                            break;
                        }
                       
                    case 2://踩到地雷
                        Console.WriteLine("玩家{0}踩到地雷,倒退6格", PlayNames[PlayID]) ;
                        Console.ReadKey(true);
                        PlayPos[PlayID] -= 6;
                        TurnRight();
                        Console.WriteLine("玩家倒退完毕。按任意键继续游戏");
                        Console.ReadKey(true);
                        break;
                    case 3://踩到暂停
                        Console.WriteLine("玩家{0}踩到暂停,暂停一个回合", PlayNames[PlayID]);
                        Flags[PlayID] = true;
                        Console.ReadKey(true);
                        break;
                    case 4://踩到时空隧道
                        Console.WriteLine("玩家{0}踩到时空隧道,前进10格", PlayNames[PlayID]);
                        PlayPos[PlayID] += 10;
                        TurnRight();
                        Console.WriteLine("玩家前进完毕,按任意键继续游戏");
                        Console.ReadKey(true);
                        break;
                }//switch
            }//if - else
            //当前轮的关卡处理完成之后。清屏,重新打印
            Console.Clear();
            DrawMap();//重新画地图
③ 判断胜利

两个玩家是放在一个长度为100的数组表示的飞行棋棋盘进行游戏的。那么当有玩家的索引变成100的时候,TA就获得了最终的胜利。

需要注意的点是:因为关卡中有暂停关卡,当玩家踩到暂停了,他就不能够参与当前轮的掷骰子

			while(PlayPos[0] < 100 && PlayPos[1] < 100)
            {
                if (!Flags[0])PlayGame(0);
                else Flags[0] = false;
                //判断胜利的条件
                if (PlayPos[0] == 100)
                {
                    Console.WriteLine("恭喜玩家{0}获得胜利", PlayNames[0]);
                    break;
                }

                if (!Flags[1]) PlayGame(1);
                else Flags[1] = false;
                    if (PlayPos[1] == 100)
                {
                    Console.WriteLine("恭喜玩家{0}获得胜利", PlayNames[1]);
                    break;
                }
            }
④ 回首调优

玩着玩着,可以发现,自己的玩家被搞出地图之外了。
在这里插入图片描述

无论如何,玩家在棋盘中移动的时候,是不能到棋盘之外的位置,因此在玩家进行位置移动的代码后方,都应该判断新出现的位置是否合法。

	//异常处理,矫正玩家位置
        public static void TurnRight()
        {
            if (PlayPos[0] < 0) PlayPos[0] = 0;
            if (PlayPos[0] >= 99) PlayPos[0] = 99;

            if(PlayPos[1] < 0) PlayPos[1] = 0;
            if (PlayPos[1] >= 99) PlayPos[1] = 99;
        }

总结

整个飞行棋项目的gitee地址

1、写项目流程图蛮重要的吧,跟着自己梳理好的流程板块去逐一落实代码,不会出现迷失在项目中的情况
2、积累修改字体背景颜色、字体文本颜色
3、按任意键后在控制台无显示依赖于ReadKey函数中传入参数true

;