C#控制台小项目-飞行棋

前段时间算法强训48天其实已经做完了,但还有好多篇放在草稿箱没发,因为有些题目还没有完全吸收掌握,加上各种事儿堆在一起,时间也很紧张,这段忙完了婚礼,C++的核心课程也已完结,接下来同步学习Linux和C#/unity课程,有多余的时间再来搞搞算法,把草稿箱剩下的算法润色好再发出来,这次主要搞定一个C#基础阶段的小结课作业,控制台飞行棋,承接之前的控制台项目--勇者斗魔王,也顺便复习C#的基础知识点;

项目预览:

大概最后成型就是这样一个控制台界面,通过随机数生成骰子点数,路径上设置不同的道具来控制行为,可以实现人机对战,等后续网络课程结束可以回过头来实现和小伙伴联机对战

实现:

有了前一次控制台小游戏的经验,这一次我打算不要亦步亦趋跟着视频来做了,先尽量参考上次的代码,独立实现整个项目,遇到困难点再查阅视频。

开始构建整体框架:

还是老样子用一个大循环来表示游戏主体,设置舞台

主界面标题,按钮文字写上

完成基础框架,测试完没什么问题,继续

先搞定制作者名单这部分,没什么新知识点,直接copy过来了

再搞搞主舞台,让两个飞机打印在屏幕上,没什么问题,继续

棋盘搞一搞,然后弄一些道具,写到这里看看了原视频,发现是分功能函数模块来写的,所以对源代码优化一下,把结构搞一搞(是一直在疑惑,之前的小项目因为初学的关系是一个文件路写到底,然而学C++都是头文件源文件分开,还以为C#是没有分离的,不过我看也只是分了函数没有分文件,可能之后的项目也会分文件吧),在AI解析之后我终于知道了C#是如何实现封装的了,其实就是通过不同的类(类.h)来封装,等下个阶段系统学完C#的类和对象之后就可以考虑用标准的项目管理方式来管理代码了

这样按场景封装之后就好看很多了,接下来继续游戏主体逻辑,增加一些道具说明

看起来不错

补充地图逻辑,之前测试的地图格子是循环画出来的,实际上在这个飞行棋游戏中,地图肯定要和道具(因为走到对应的格子就=触发道具)组合成一个结构体,然后按照一定的逻辑规则去生成

我们可以定义以下一些信息结构,记住所有一切编程开始的方法论------"先描述,再组织"

E_GridType,格子枚举类型------负责控制生成什么格子,用来控制不同的道具

vector2,控制横纵二维坐标,负责格子的生成位置

Grid,格子结构体,包含类型、坐标、构造函数以及生成函数

搞定了这些,来测试测试

非常好,生成无误,接下来就开始铺棋盘

棋盘由地图结构体负责,内部包括格子类型和位置,通过随机数及区间限制来实现道具格子的随机生成,控制计数来实现蛇形地图的生成,也可以根据自己的喜好生成不同地图,这里偷个懒就不做其他类型了。

看看效果

整挺好,接下来完成玩家和AI行动逻辑

玩家结构体包括类型、位置、图标、颜色,因为是在棋盘上出现,所以需要把map传过来获取对应棋盘的位置,棋盘内部是grids[]数组,所以用数组下标.x .y就可以获得对应的坐标了,测试一下,没什么问题,但是现在还需要处理二者重叠的情况

额外写个函数处理重合情况,重合则打印重合图标,否则正常调用各自的绘制方法

搞定,接下来搞搞掷骰子,其实也很简单,一个移动函数,用随机取模就能实现骰子了

大概是这样,然后优化优化,把擦除封装起来,这个幸运7还有点问题,没有实现让玩家手动投掷,是全自动的调用了

改完,顺便优化了下投掷节奏,现在手感比较舒服了

最终整合了一些结构,优化了连续按键响应的一些问题,还有些窗口尺寸关联的现在懒得做了,大概最后的成品就是这样

以下是源代码,供同学参考;

cs 复制代码
using System;
using System.Diagnostics;
using System.Net.Http.Headers;
using System.Runtime.CompilerServices;

namespace 控制台小游戏_飞行棋
{
    /// <summary>
    /// 场景枚举类型
    /// </summary>
    enum E_SceneType
    {
        /// <summary>
        /// 开始场景
        /// </summary>
        Begin,

        /// <summary>
        /// 游戏场景
        /// </summary>
        Game,

        /// <summary>
        /// 制作者场景
        /// </summary>
        Fabri

    }

    class Program
    {
        // 主舞台
        const int sceneWidth = 80;
        const int sceneHeight = 30;

        // 主函数
        static void Main(string[] args)
        {
            //隐藏闪烁的光标
            Console.CursorVisible = false;

            Console.SetWindowSize(sceneWidth, sceneHeight);
            Console.SetBufferSize(sceneWidth, sceneHeight);

            E_SceneType nowScene = E_SceneType.Begin;// 用枚举来代替数字

            // 游戏主体
            while (true)
            {
                switch (nowScene)
                {
                    //开始界面
                    case E_SceneType.Begin:
                        nowScene = BeginScene(nowScene);
                        break;

                    //游戏界面
                    case E_SceneType.Game:
                        nowScene = GameScene(nowScene);
                        break;

                    //制作者名单
                    case E_SceneType.Fabri:
                        nowScene = FabriScene(nowScene);
                        break;
                }
            }
        }

        /// <summary>
        /// 开始界面
        /// </summary>
        static E_SceneType BeginScene(E_SceneType nowScene)
        {
            Console.Clear();

            Console.SetCursorPosition(sceneWidth / 2 - 5, sceneHeight / 2 - 6);
            Console.Write("好运飞行棋");

            Console.SetCursorPosition(sceneWidth / 2 - 10, sceneHeight - 2);
            Console.Write("控制方式:WASD和回车");

            int nowSelIndex = 1; //1-开始游戏,2-制作者名单,3-退出游戏,默认红色高亮在开始游戏上

            while (true)// 负责开始界面阻塞接口之间的切换
            {
                bool isQuitWhile = false;//设置标识,决定是否退出"开始游戏"界面转到其他界面

                Console.SetCursorPosition(sceneWidth / 2 - 4, sceneHeight / 2 - 1);//设置光标位置
                Console.ForegroundColor = nowSelIndex == 1 ? ConsoleColor.Red : ConsoleColor.White;
                Console.Write("开始游戏");//显示UI按钮

                Console.SetCursorPosition(sceneWidth / 2 - 4, sceneHeight / 2 + 1);
                Console.ForegroundColor = nowSelIndex == 2 ? ConsoleColor.Red : ConsoleColor.White;
                Console.Write("作者名单");

                Console.SetCursorPosition(sceneWidth / 2 - 4, sceneHeight / 2 + 3);
                Console.ForegroundColor = nowSelIndex == 3 ? ConsoleColor.Red : ConsoleColor.White;
                Console.Write("退出游戏");

                switch (Console.ReadKey(true).Key)
                {
                    case ConsoleKey.W:
                        nowSelIndex = nowSelIndex == 1 ? 3 : nowSelIndex - 1;
                        break;

                    case ConsoleKey.S:
                        nowSelIndex = nowSelIndex == 3 ? 1 : nowSelIndex + 1;
                        break;
                    case ConsoleKey.Enter:
                        // 定义行为映射
                        Action[] actions =
                        {
                            null,
                            () => { nowScene = E_SceneType.Game; isQuitWhile = true; },
                            () => { nowScene = E_SceneType.Fabri; isQuitWhile = true; },
                            () => { Console.ForegroundColor = ConsoleColor.White; Environment.Exit(0); },
                        };
                        actions[nowSelIndex]();
                        break;
                }
                if (isQuitWhile) return nowScene;
            }
        }

        /// <summary>
        /// 游戏界面
        /// </summary>
        static E_SceneType GameScene(E_SceneType nowScene)
        {
            DrawGameSceInfo();
            Map map = new Map(8, 3, 120);
            map.Draw();

            Player player = new Player(1, E_PlayerType.Player);
            Player computer = new Player(0, E_PlayerType.Computer);
            DrawPlayer(player, computer, map);

            while (true)
            {
                // 玩家回合 - 需要按键
                PrintGameMessage("玩家回合,按任意键掷骰子...");
                SafeWaitForKey();
                if (ThrowDice(ref player, ref computer, map, true))
                {
                    PrintGameMessage("玩家获胜!按任意键返回...");
                    SafeWaitForKey();
                    return E_SceneType.Fabri;
                }
                map.Draw();
                DrawPlayer(player, computer, map);

                // 电脑回合 - 自动进行
                PrintGameMessage("电脑回合,掷骰子...");
                Thread.Sleep(1000); // 给玩家1秒时间看清电脑的行动
                if (ThrowDice(ref computer, ref player, map, false))
                {
                    PrintGameMessage("电脑获胜!按任意键返回...");
                    SafeWaitForKey();
                    return E_SceneType.Fabri;
                }
                map.Draw();
                DrawPlayer(player, computer, map);
            }
        }

        /// <summary>
        /// 制作者界面
        /// </summary>
        static E_SceneType FabriScene(E_SceneType nowScene)
        {
            Console.Clear();

            Console.SetCursorPosition(sceneWidth / 2 - 4, sceneHeight / 2 - 12);//设置光标位置
            Console.Write("THE END:");//结束标题

            Console.SetCursorPosition(sceneWidth / 2 - 4, sceneHeight / 2 - 10);//设置光标位置
            Console.Write("感谢游玩");//对应文字

            Console.SetCursorPosition(sceneWidth / 2 - 5, sceneHeight / 2 - 7);//设置光标位置
            Console.Write("制作者名单");

            Console.SetCursorPosition(sceneWidth / 2 - 5, sceneHeight / 2 - 5);//设置光标位置
            Console.Write("程序 David");

            Console.SetCursorPosition(sceneWidth / 2 - 5, sceneHeight / 2 - 4);//设置光标位置
            Console.Write("美术 David");

            Console.SetCursorPosition(sceneWidth / 2 - 5, sceneHeight / 2 - 3);//设置光标位置
            Console.Write("策划 David");

            Console.SetCursorPosition(sceneWidth / 2 - 9, sceneHeight / 2 - 2);//设置光标位置
            Console.Write("音乐 无 但还是David");

            Console.SetCursorPosition(sceneWidth / 2 - 17, sceneHeight / 2 + 1);//设置光标位置
            Console.Write("走到这里花了不少时间,也费了不少功夫");
            Console.SetCursorPosition(sceneWidth / 2 - 17, sceneHeight / 2 + 2);//设置光标位置
            Console.Write("虽然没有那么困难,但也不比想象中简单");
            Console.SetCursorPosition(sceneWidth / 2 - 17, sceneHeight / 2 + 3);//设置光标位置
            Console.Write("那么就继续吧,不要停下来做你想做的事");

            Console.SetCursorPosition(sceneWidth / 2 - 6, sceneHeight / 2 + 8);//设置光标位置
            Console.Write("回到开始界面");

            Console.SetCursorPosition(sceneWidth / 2 - 4, sceneHeight / 2 + 10);//设置光标位置
            Console.Write("退出游戏");

            int nowSelEndIndex = 0;//开始界面选择按钮的编号,0代表回开始界面,1为退出游戏按钮

            while (true)
            {
                bool isQuitWhile = false;//设置标识,决定是否退出"开始游戏"界面转到其他界面

                Console.SetCursorPosition(sceneWidth / 2 - 6, sceneHeight / 2 + 8);//设置光标位置
                Console.ForegroundColor = nowSelEndIndex == 0 ? ConsoleColor.Red : ConsoleColor.White;
                Console.Write("回到开始界面");//显示UI按钮

                Console.SetCursorPosition(sceneWidth / 2 - 4, sceneHeight / 2 + 10);//设置光标位置
                Console.ForegroundColor = nowSelEndIndex == 1 ? ConsoleColor.Red : ConsoleColor.White;
                Console.Write("退出游戏");

                switch (Console.ReadKey(true).Key)
                {
                    case ConsoleKey.W:
                        nowSelEndIndex = nowSelEndIndex == 0 ? 1 : 0;
                        break;

                    case ConsoleKey.S:
                        nowSelEndIndex = nowSelEndIndex == 1 ? 0 : 1;
                        break;

                    case ConsoleKey.Enter:
                        Action[] actions =
                        {
                            () => { nowScene = E_SceneType.Begin; isQuitWhile = true; },
                            () => { Console.ForegroundColor = ConsoleColor.White; Environment.Exit(0); },
                        };
                        actions[nowSelEndIndex]();
                        break;
                }
                if (isQuitWhile) return nowScene;
            }
        }

        /// <summary>
        /// 按键前清空缓冲区,防止连续按键持续反应
        /// </summary>
        static void SafeWaitForKey()
        {
            // 清空键盘缓冲区
            while (Console.KeyAvailable)
                Console.ReadKey(true);

            // 等待新输入
            Console.ReadKey(true);
        }

        /// <summary>
        /// 绘制游戏运行时的固有信息
        /// </summary>
        static void DrawGameSceInfo()
        {
            Console.Clear();
            //布景,外围的墙壁
            Console.ForegroundColor = ConsoleColor.White;//墙壁颜色
            //横墙
            for (int i = 0; i < sceneWidth; i += 2)
            {
                //上方墙壁
                Console.SetCursorPosition(i, 0);
                Console.Write("▓");
                //下方墙壁
                Console.SetCursorPosition(i, sceneHeight - 1);
                Console.Write("▓");
                //中部分割墙壁
                Console.SetCursorPosition(i, sceneHeight - 8);
                Console.Write("▓");
            }
            //竖墙
            for (int i = 0; i < sceneHeight; i++)
            {
                //左
                Console.SetCursorPosition(0, i);
                Console.Write("▓");
                //右
                Console.SetCursorPosition(sceneWidth - 2, i);
                Console.Write("▓");
            }

            // 道具们
            Console.ForegroundColor = ConsoleColor.Red;
            Console.SetCursorPosition(sceneWidth - 76, sceneHeight - 7);
            Console.Write("●: 炸弹,后退5格");

            Console.ForegroundColor = ConsoleColor.Blue;
            Console.SetCursorPosition(sceneWidth - 56, sceneHeight - 7);
            Console.Write("||: 停止,暂停1回合");

            Console.ForegroundColor = ConsoleColor.Green;
            Console.SetCursorPosition(sceneWidth - 34, sceneHeight - 7);
            Console.Write("⑦: 幸运7,再掷一遍");

            Console.ForegroundColor = ConsoleColor.Gray;
            Console.SetCursorPosition(sceneWidth - 76, sceneHeight - 6);
            Console.Write("⊙: 飞机重叠");

            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.SetCursorPosition(sceneWidth - 56, sceneHeight - 6);
            Console.Write("★: 玩家");

            Console.ForegroundColor = ConsoleColor.Gray;
            Console.SetCursorPosition(sceneWidth - 34, sceneHeight - 6);
            Console.Write("▲: 电脑");

            Console.ForegroundColor = ConsoleColor.White;

            Console.SetCursorPosition(sceneWidth - 76, sceneHeight - 3);
            Console.Write("按回车键掷出骰子!");
        }

        /// <summary>
        /// 绘制玩家重合时的图标
        /// </summary>
        static void DrawPlayer(Player player, Player computer, Map map)
        {
            // 若位置重合
            if (player.nowIndex == computer.nowIndex)
            {
                Grid grid = map.grids[player.nowIndex];
                Console.SetCursorPosition(grid.pos.x, grid.pos.y);
                Console.ForegroundColor = ConsoleColor.Gray;
                Console.Write("⊙");
            }
            // 若不重合
            else
            {
                player.Draw(map);
                computer.Draw(map);
            }
        }


        /// <summary>
        /// 在固定位置输出信息(自动清空行)
        /// </summary>
        /// <param name="message">要显示的信息</param>
        static void PrintGameMessage(string message)
        {
            Console.SetCursorPosition(sceneWidth - 76, sceneHeight - 3);
            Console.Write(new string(' ', 40)); // 清空行(40个空格)
            Console.SetCursorPosition(sceneWidth - 76, sceneHeight - 3);
            Console.Write(message);
        }

        /// <summary>
        /// 扔骰子函数
        /// </summary>
        static bool ThrowDice(ref Player role, ref Player other, Map map, bool isPlayerTurn = true)
        {
            string name = role.type == E_PlayerType.Player ? "先先" : "大伟";
            if (role.skipTurn > 0)
            {
                role.skipTurn--;
                PrintGameMessage($"{name}暂停中,还剩{role.skipTurn}回合");
                Thread.Sleep(1000);
                return false;
            }

            Random r = new Random();
            int randomNum = r.Next(1, 7);

            // 只有玩家回合才显示掷骰子动画
            if (isPlayerTurn)
            {
                PrintGameMessage("。");
                Thread.Sleep(100);
                PrintGameMessage("。。");
                Thread.Sleep(100);
                PrintGameMessage("。。。");
                Thread.Sleep(100);
                PrintGameMessage("。。。。");
                Thread.Sleep(100);
                PrintGameMessage("。。。。。");
                Thread.Sleep(100);
                PrintGameMessage("。。。。。。");
            }

            PrintGameMessage($"{name}掷出了{randomNum}点");
            Thread.Sleep(isPlayerTurn ? 700 : 500); // 电脑回合时等待时间稍短

            // 移动玩家
            role.nowIndex += randomNum;

            // 检查是否到达终点
            if (role.nowIndex >= map.grids.Length - 1)
            {
                role.nowIndex = map.grids.Length - 1;
                map.Draw();
                DrawPlayer(role, other, map);
                PrintGameMessage($"{name}到达了终点!");
                Thread.Sleep(1000);
                return true;
            }

            // 获取当前格子
            Grid grid = map.grids[role.nowIndex];

            switch (grid.type)
            {
                case E_GridType.Normal:
                    break;

                case E_GridType.Boom:
                    map.Draw();
                    DrawPlayer(role, other, map);
                    role.nowIndex -= 5;
                    if (role.nowIndex < 0) role.nowIndex = 0;
                    PrintGameMessage($"{name}踩到炸弹,后退5格!");
                    Thread.Sleep(1000);
                    break;

                case E_GridType.Pause:
                    role.skipTurn += 1;
                    map.Draw();
                    DrawPlayer(role, other, map);
                    PrintGameMessage($"{name}被暂停1回合!");
                    Thread.Sleep(1000);
                    break;

                case E_GridType.No7:
                    PrintGameMessage($"{name}获得幸运7,{(isPlayerTurn ? "按任意键再掷一次" : "自动再掷一次")}!");
                    if (isPlayerTurn)
                    {
                        map.Draw();
                        DrawPlayer(role, other, map);
                        SafeWaitForKey(); // 只有玩家回合需要按键
                    }
                    else
                    {
                        Thread.Sleep(1000); // 电脑回合时稍作延迟
                    }
                    return ThrowDice(ref role, ref other, map, isPlayerTurn); // 递归调用
            }
            return false;
        }


        /// <summary>
        /// 地图格子枚举和格子结构体
        /// </summary>
        enum E_GridType
        {
            /// <summary>
            /// 普通格子
            /// </summary>
            Normal,

            /// <summary>
            /// 炸弹
            /// </summary>
            Boom,

            /// <summary>
            /// 停止
            /// </summary>
            Pause,

            /// <summary>
            /// 幸运7
            /// </summary>
            No7

        }
        // 位置信息结构,包含横纵轴(int整形)
        struct Vector2
        {
            public int x;
            public int y;

            public Vector2(int x, int y)
            {
                this.x = x;
                this.y = y;
            }
        }
        //格子结构
        struct Grid
        {
            //格子类型
            public E_GridType type;
            //格子位置
            public Vector2 pos;

            //初始化构造函数
            public Grid(int x, int y, E_GridType type)
            {
                pos.x = x;
                pos.y = y;
                this.type = type;
            }

            //格子生成函数
            public void Draw()
            {
                Console.SetCursorPosition(pos.x, pos.y);
                switch (type)
                {
                    case E_GridType.Normal:
                        Console.ForegroundColor = ConsoleColor.White;
                        Console.Write("□");
                        break;

                    case E_GridType.Boom:
                        Console.ForegroundColor = ConsoleColor.Red;
                        Console.Write("●");
                        break;

                    case E_GridType.Pause:
                        Console.ForegroundColor = ConsoleColor.Blue;
                        Console.Write("||");
                        break;

                    case E_GridType.No7:
                        Console.ForegroundColor = ConsoleColor.Green;
                        Console.Write("⑦");
                        break;

                }
            }
        }


        /// <summary>
        /// 地图结构体
        /// </summary>
        struct Map
        {
            public Grid[] grids;
            public Map(int x, int y, int num)
            {
                grids = new Grid[num];
                int randomNum, indexX = 0, indexY = 0, stepNum = 2;
                Random r = new Random();

                // 循环生成每个格子
                for (int i = 0; i < num; i++)
                {
                    // 0-100 100个概率值
                    randomNum = r.Next(0, 101);

                    // 85%概率是普通格子(包括起点和终点强制普通)
                    if (randomNum < 85 || i == 0 || i == num - 1)
                    {
                        grids[i].type = E_GridType.Normal;
                    }

                    // 2%概率是炸弹格子
                    else if (randomNum < 87)
                    {
                        grids[i].type |= E_GridType.Boom;
                    }

                    // 3%概率是暂停格子
                    else if (randomNum < 90)
                    {
                        grids[i].type |= E_GridType.Pause;
                    }

                    // 其余10%概率是幸运7格子
                    else
                    {
                        grids[i].type |= E_GridType.No7;
                    }

                    //当前格子生成位置,初始为传入的x y
                    grids[i].pos = new Vector2(x, y);

                    //改变下一个格子的生成位置
                    if (indexX == 31)// 当横着走完31个格子
                    {
                        y += 1;//下移
                        ++indexY;
                        if (indexY == 2)// 下移两格后
                        {
                            indexX = 0;//计数清零
                            indexY = 0;

                            stepNum = -stepNum;//倒着走
                        }
                    }
                    else
                    {
                        x += stepNum;// 正常每次横移2步
                        ++indexX;
                    }
                    //最终形成蛇形地图
                }
            }

            public void Draw()
            {
                for (int i = 0; i < grids.Length; ++i)
                {
                    grids[i].Draw();
                }
            }
        }


        /// <summary>
        /// 玩家类型枚举和玩家结构体
        /// </summary>
        enum E_PlayerType
        {
            /// <summary>
            /// 玩家
            /// </summary>
            Player,

            /// <summary>
            /// 电脑
            /// </summary>
            Computer
        }
        struct Player
        {
            // 玩家类型
            public E_PlayerType type;
            // 在棋盘所处的位置(棋盘索引)
            public int nowIndex;
            //暂停回合数
            public int skipTurn;

            public Player(int index, E_PlayerType type)
            {
                nowIndex = index;
                this.type = type;
                skipTurn = 0;
            }

            public void Draw(Map mapInfo)
            {
                //设置位置,先拿到棋盘信息,再拿到格子索引
                Grid grid = mapInfo.grids[nowIndex];
                Console.SetCursorPosition(grid.pos.x, grid.pos.y);
                //设置图案颜色
                switch (type)
                {
                    case E_PlayerType.Player:
                        Console.ForegroundColor = ConsoleColor.Yellow;//颜色
                        Console.Write("★");
                        break;

                    case E_PlayerType.Computer:
                        Console.ForegroundColor = ConsoleColor.Gray;
                        Console.Write("▲");
                        break;
                }
            }
        }
    }
}
相关推荐
emplace_back19 分钟前
C# 集合表达式和展开运算符 (..) 详解
开发语言·windows·c#
jz_ddk25 分钟前
[学习] C语言数学库函数背后的故事:`double erf(double x)`
c语言·开发语言·学习
萧曵 丶35 分钟前
Rust 所有权系统:深入浅出指南
开发语言·后端·rust
xiaolang_8616_wjl39 分钟前
c++文字游戏_闯关打怪2.0(开源)
开发语言·c++·开源
收破烂的小熊猫~1 小时前
《Java修仙传:从凡胎到码帝》第四章:设计模式破万法
java·开发语言·设计模式
nananaij1 小时前
【Python进阶篇 面向对象程序设计(3) 继承】
开发语言·python·神经网络·pycharm
阿蒙Amon1 小时前
为什么 12 版仍封神?《C# 高级编程》:从.NET 5 到实战架构,进阶者绕不开的必修课
开发语言·c#
无小道1 小时前
c++-引用(包括完美转发,移动构造,万能引用)
c语言·开发语言·汇编·c++
爱莉希雅&&&2 小时前
技术面试题,HR面试题
开发语言·学习·面试