C#学习记录--贪吃蛇游戏

 贪吃蛇

本文是按照老师给的类图思路,自己完成的贪吃蛇项目,记录一下,可能会有冗余代码,期盼朋友们的指正批评。

一.需求分析(绘制类图)

二.代码实现

2.1 游戏类

初始化窗口,控制游戏场景的切换

注意此处的游戏帧更新接口,为了练习里氏替换原则,也使用它作为场景类的容器

public enum E_Scenario // 枚举场景 
{
    Start,//游戏开始
    End,//游戏结束
    Game,//游戏中
}
public class Game
{
    public static int width = 100;//窗口宽度
    public static int hight = 30;//窗口高度
    public IUpdate update;//当前所在场景
    public Game()//初始化控制台
    {
        Console.CursorVisible = false;
        Console.SetWindowSize(width, hight);
        Console.SetBufferSize(width, hight);
    }
    public void Context()//游戏主循环
    {
        this.update = new Start_Scenario();
        while (true)
            //主循环在于控制页面之间,而不是页面内容,在本循环中接收转页面指令进行页面跳转
        { 
            if (this.update is Start_Scenario)//若是开始界面
            {
                Console.Clear();//再进入新页面前清空
                this.update = this.Exchange((this.update as Start_Scenario).Update());
                GC.Collect();//页面之间的切换比较少,每一次又重新分配空间,因此使用垃圾回收,提高内存利用率
            }
            else if(this.update is End_Scenario)//若是结束界面
            {
                Console.Clear();//同上
                this.update = this.Exchange((this.update as End_Scenario).Update());
                GC.Collect();
            }
            else if(this.update is Game_Scenario)//若是游戏界面
            {
                Console.Clear();
                this.update = this.Exchange((this.update as Game_Scenario).Update());
                GC.Collect();
            }
            else 
            {
                Console.SetCursorPosition(75, 50);
                Console.WriteLine("选定场景为空,错误!");
            }

        }
    }
    public IUpdate Exchange(E_Scenario scenario)//场景切换
    {
        switch (scenario)
        {
            case E_Scenario.Start:
                return new Start_Scenario();
            case E_Scenario.End:
                return new End_Scenario();
            case E_Scenario.Game:
                return new Game_Scenario();
        }
        return new End_Scenario();

    }

}
 public interface IUpdate//游戏帧更新接口
 {
     E_Scenario Update();
 }

2.2 开始与结束场景类

用W,S控制选项选择,用J来确定选项选择,获取用户选择后,返回对应信息(下一个场景是什么)给游戏类

    //开始结束场景基类
    public class Basic_Scenario : IUpdate//开始或结束场景类
    {   
        protected string[] Title = { "贪吃蛇", "贪吃蛇" };
        protected string[] Choose1 = { "开始游戏", "回到开始画面" };
        protected string[] Choose2 = { "结束游戏", "结束游戏" };
        public E_Scenario Update()//重写更新接口
        {
            return E_Scenario.Start;
        }

    }
    //开始场景类
    public class Start_Scenario : Basic_Scenario
    {
        public E_Scenario Update()
            //开始场景的帧刷新
        {
            char K = 'W';//初始时默认为选择开始游戏
            int keep = 1;//用于按下J时判断选择哪一个选项
            while (true)
            {   
                Console.Clear();
                //默认绘制不变部分
                Console.SetCursorPosition((Game.width - this.Title[(int)E_Scenario.Start].Length) / 2 + 1, Game.hight / 2);
                Console.ForegroundColor = ConsoleColor.White;
                Console.WriteLine(this.Title[(int)E_Scenario.Start]);
               
                switch (K)
                //根据输入字符来绘制可变部分
                //当按下w则使得开始游戏选项变红
                //当按下s则使得结束游戏选项变红 -- 注意结束游戏不是进入结束界面
                //当按下j则选择当前选项
                {
                    case 'w':
                    case 'W':
                        Console.ForegroundColor = ConsoleColor.Red;
                        Console.SetCursorPosition((Game.width - this.Choose1[(int)E_Scenario.Start].Length) / 2, Game.hight / 2 + 2);
                        Console.WriteLine(this.Choose1[(int)E_Scenario.Start]);
                        Console.ForegroundColor = ConsoleColor.White;
                        Console.SetCursorPosition((Game.width - this.Choose1[(int)E_Scenario.Start].Length) / 2, Game.hight / 2 + 4);
                        Console.WriteLine(this.Choose2[(int)E_Scenario.Start]);
                        keep = 1;
                        break;
                    case 's':
                    case 'S':
                        Console.ForegroundColor = ConsoleColor.White;
                        Console.SetCursorPosition((Game.width - this.Choose1[(int)E_Scenario.Start].Length) / 2, Game.hight / 2 + 2);
                        Console.WriteLine(this.Choose1[(int)E_Scenario.Start]);
                        Console.ForegroundColor = ConsoleColor.Red;
                        Console.SetCursorPosition((Game.width - this.Choose1[(int)E_Scenario.Start].Length) / 2, Game.hight / 2 + 4);
                        Console.WriteLine(this.Choose2[(int)E_Scenario.Start]);
                        keep = 2;
                        break;
                    case 'j':
                    case 'J':
                        Console.Clear();
                        switch (keep)
                        {
                            case 1:
                                return E_Scenario.Game; //让主循环进入游戏界面
                            case 2:
                                Environment.Exit(0);
                                break;
                                ;
                        }
                        break;
                }
                K = Console.ReadKey(true).KeyChar;//等待输入字符
            }
        }
    }
    //结束场景类
    public class End_Scenario : Basic_Scenario 
    {
        public E_Scenario Update()
        //结束场景的帧刷新
        {
            char K = 'W';//初始时默认为选择重新开始游戏
            int keep = 1;//用于按下J时判断选择哪一个选项
            while (true)
            {
                Console.Clear();
                //默认绘制不变部分
                Console.SetCursorPosition((Game.width - this.Title[(int)E_Scenario.End].Length) / 2 + 1, Game.hight / 2);
                Console.ForegroundColor = ConsoleColor.White;
                Console.WriteLine(this.Title[(int)E_Scenario.End]);

                switch (K)
                //根据输入字符来绘制可变部分
                //当按下w则使得重新开始游戏选项变红
                //当按下s则使得结束游戏选项变红 -- 注意结束游戏不是进入结束界面
                //当按下j则选择当前选项
                {
                    case 'w':
                    case 'W':
                        Console.ForegroundColor = ConsoleColor.Red;
                        Console.SetCursorPosition((Game.width - this.Choose1[(int)E_Scenario.End].Length) / 2 - 1, Game.hight / 2 + 2);
                        Console.WriteLine(this.Choose1[(int)E_Scenario.End]);
                        Console.ForegroundColor = ConsoleColor.White;
                        Console.SetCursorPosition((Game.width - this.Choose1[(int)E_Scenario.End].Length) / 2 + 1, Game.hight / 2 + 4);
                        Console.WriteLine(this.Choose2[(int)E_Scenario.End]);
                        keep = 1;
                        break;
                    case 's':
                    case 'S':
                        Console.ForegroundColor = ConsoleColor.White;
                        Console.SetCursorPosition((Game.width - this.Choose1[(int)E_Scenario.End].Length) / 2 -1, Game.hight / 2 + 2);
                        Console.WriteLine(this.Choose1[(int)E_Scenario.End]);
                        Console.ForegroundColor = ConsoleColor.Red;
                        Console.SetCursorPosition((Game.width - this.Choose1[(int)E_Scenario.End].Length) / 2 + 1, Game.hight / 2 + 4);
                        Console.WriteLine(this.Choose2[(int)E_Scenario.End]);
                        keep = 2;
                        break;
                    case 'j':
                    case 'J':
                        Console.Clear();
                        switch (keep)
                        {
                            case 1:
                                return E_Scenario.Start; //让主循环进入开始界面
                            case 2:
                                Environment.Exit(0);//结束游戏
                                break;
                                ;
                        }
                        break;
                }
                K = Console.ReadKey(true).KeyChar;//等待输入字符
            }
        }
    }

2.3 游戏场景类

最主要的类,游戏内容相关的类与对象都是被在本类中使用

#region 游戏场景
//游戏场景类
public class Game_Scenario : IUpdate//游戏场景类
{
    private static Timer time;
    
    Game_Object snake = new Snake();//蛇对象
    Maps map = new Maps();//地图对象 -- 因为地图不是继承Game_Object
    Game_Object food = new Food();//食物对象
    public E_Scenario Update()//重写更新接口
    {
        map.Draw();//墙壁是不变的,不用刷新
        (food as Food).Draw();
        while (true) 
        {
           
            (snake as Snake).Move(food as Food);
            if((snake as Snake).Die(map))
            {
                break;
            }
            (snake as Snake).Draw();
            for (int i = 0; i < 888888888; i++) //可以编写计时器来控制准确时间,但是还没学过事件,不知道怎么写
            {
            }
        }
        return E_Scenario.End;
    }
}
//地图类
public class Maps
{   //仅需要在此处增加成员就可以增加
    public Game_Object wall_top = new Map(0,0,Game.width/2-1,true);
    public Game_Object wall_left = new Map(0, 0, Game.hight, false);
    public Game_Object wall_bottom = new Map(0, Game.hight - 1, Game.width / 2 - 1, true);
    public Game_Object wall_right = new Map(Game.width - 2,0, Game.hight, false);
    public void Draw() 
    {
        (this.wall_top as Map).Draw();
        (this.wall_left as Map).Draw();
        (this.wall_bottom as Map).Draw();
        (this.wall_right as Map).Draw();
    }

}
//食物类 -- 需要告知外界坐标 ,需要在一段设定时间后或者被吃掉后更改位置
public class Food : Game_Object
{
    private Random random = new Random();
    private string pattern = "★";
    private int x;
    private int y;
    public Food() 
    {

        this.location = new Location[1];
    }
    public void Draw()
    {
        this.Clean();
        this.location[0].x = random.Next(2, Game.width - 3);
        if (this.location[0].x %2 != 0)//保证食物不刷新在奇数坐标上
        {
            this.location[0].x += 1;
        }
        this.location[0].y = random.Next(1, Game.hight - 1);
        Console.ForegroundColor = ConsoleColor.Green;
        Console.SetCursorPosition(this.location[0].x, this.location[0].y);
        Console.Write(pattern);
        x = this.location[0].x;
        y = this.location[0].y;
    }
    public void Clean()
    {
        Console.SetCursorPosition(x, y);
        Console.Write("  ");
    }

}
public enum E_Direction
{
    K_top,
    K_left,
    K_bottom,
    K_right,
}
//蛇类
//蛇头负责转向逻辑,蛇身只需要不断赋值前一元素的位置就可以了,但是蛇头应该和蛇身不一样图标,并且蛇头可以进行重合判定
public class Snake : Game_Object
{
    private Game_Object Snake_body = new SnakeBody(E_SnakePart.SnakeBody);//声明一个蛇身
    private Game_Object Snake_head = new SnakeBody(E_SnakePart.SnakeHead);//声明一个蛇头
    private int[] x_origion = [12, 10, 8,6];//初始的蛇头[0]+蛇身[1/2/3]x坐标
    private int[] y_origion = [8, 8, 8,8];//初始的蛇头[0]+蛇身y[1/2/3]坐标
    private E_Direction Key = E_Direction.K_right;//记录下一帧的蛇头运动方向
    public Snake()
    {
        Snake_body.location = new Location[3];
        Snake_head.location = new Location[1];
        #region 用两个数组存储完整的蛇体位置信息,再按如下传值,可以保持位置的连贯性,也可以分别处理头身逻辑
        Snake_head.location[0].x = x_origion[0];
        Snake_head.location[0].y = y_origion[0];
        for (int i = 0; i<Snake_body.location.Length; i++)
        {
            Snake_body.location[i].x = x_origion[i + 1];
            Snake_body.location[i].y = y_origion[i + 1];
        }
        #endregion

    }
    public void Draw()//蛇绘制
    {   

        (Snake_body as SnakeBody).Draw();
        (Snake_head as SnakeBody).Draw();
    }
    public void Move(Food food)//蛇移动
    {
        //先记录下本帧的蛇对象的位置
        //根据运动方向得到下一帧蛇头的位置
        //后续跟着蛇头新位置而挪动
        #region 1、暂存当前位置
        x_origion[0] = Snake_head.location[0].x;
        y_origion[0] = Snake_head.location[0].y;
        for (int i = 0; i < Snake_body.location.Length; i++)
        {
            x_origion[i + 1] = Snake_body.location[i].x;
            y_origion[i + 1] = Snake_body.location[i].y;
        }
        #endregion
        #region 2、是否吃到食物?是否转向?
        if (Eat(food))
        {
            (food as Food).Clean();
            (food as Food).Draw();
            Grow();
            
        }
        Turn();
        #endregion
        #region 3、清除之前的图像,并得到新的位置
        (Snake_head as SnakeBody).Clean();
        (Snake_body as SnakeBody).Clean();
        switch (Key)//得到新的蛇头位置
        {
            case E_Direction.K_top:
                Snake_head.location[0].x = x_origion[0];
                Snake_head.location[0].y = y_origion[0] - 1;
                break;
            case E_Direction.K_bottom:
                Snake_head.location[0].x = x_origion[0];
                Snake_head.location[0].y = y_origion[0] + 1;
                break;
            case E_Direction.K_left:
                Snake_head.location[0].x = x_origion[0] - 2;
                Snake_head.location[0].y = y_origion[0];
                break;
            case E_Direction.K_right:
                Snake_head.location[0].x = x_origion[0] + 2;
                Snake_head.location[0].y = y_origion[0];
                break;
        }
        for(int i = Snake_body.location.Length - 1; i >= 0; i--)
        {
            Snake_body.location[i].x = x_origion[i];
            Snake_body.location[i].y = y_origion[i];
        }
       


        #endregion
    }
    public void Turn() //蛇转向
    {
        if (Console.KeyAvailable)
        {
            switch (Console.ReadKey(true).KeyChar)
            {
                case 'w':
                case 'W':
                    Key = Key != E_Direction.K_bottom ? E_Direction.K_top : E_Direction.K_bottom;
                    break;
                case 's':
                case 'S':
                    Key = Key != E_Direction.K_top ? E_Direction.K_bottom : E_Direction.K_top;
                    break;
                case 'd':
                case 'D':
                    Key = Key != E_Direction.K_left ? E_Direction.K_right : E_Direction.K_left;
                    break;
                case 'a':
                case 'A':
                    Key = Key != E_Direction.K_right ? E_Direction.K_left : E_Direction.K_right;
                    break;
            }  
        }
    }
    
    public void Grow() //蛇长大,在尾部增长,且尾部形状不变
    {
        int new_length = x_origion.Length + 1;
       
        int[] newx = new int[new_length];
        int[] newy = new int[new_length];
        for (int i = 0; i < new_length-1; i++)
        {
            newx[i] = x_origion[i];
            newy[i] = y_origion[i];
        }
        

       //保证新增尾部形状不变
        newx[^1] = x_origion[^1] == x_origion[^2] ? x_origion[^1] : x_origion[^1] * 2 - x_origion[^2];
        newy[^1] = y_origion[^1] == y_origion[^2] ? y_origion[^1] : y_origion[^1] * 2 - y_origion[^2];
        x_origion = new int[new_length];
        y_origion = new int[new_length];
        for(int i = 0; i < new_length; i++)
        {
            x_origion[i] = newx[i];
            y_origion[i] = newy[i];
        }
        Snake_body.location = new Location[new_length - 1];//蛇身也要增长


    }
    public bool Eat(Food food) //当蛇头和食物重合,蛇吃食物
    {
        if (food.location[0] == Snake_head.location[0])
        {
            return true;
        }
        else return false;
    }
    public bool Die(Maps map)//当蛇头与墙壁重合,蛇死亡
    {
        for(int i = 0; i < map.wall_left.location.Length; i++)
        {
           if(Snake_head.location[0] == map.wall_left.location[i] || Snake_head.location[0] == map.wall_right.location[i])
            {
                return true;
            }
        }
        for (int i = 0; i < map.wall_top.location.Length; i++)
        {
            if (Snake_head.location[0] == map.wall_top.location[i] || Snake_head.location[0] == map.wall_bottom.location[i])
            {
                return true;
            }
        }
        for(int i = 0; i < Snake_body.location.Length; i++)
        {
            if (Snake_head.location[0] == Snake_body.location[i])
            {
                return true;
            }
        }
        return false;

    }
}
public class SnakeBody : Game_Object//蛇身类
{
    public E_SnakePart bodytype;
    public SnakeBody(E_SnakePart A)
    {
        this.bodytype = A;
    }
    public void Draw()//分别绘制蛇身和蛇头
    {
        Console.ForegroundColor = ConsoleColor.Yellow;
        switch (bodytype)
        {
            case E_SnakePart.SnakeBody:
                for (int i = 0; i < this.location.Length; i++)
                {
                    Console.SetCursorPosition(this.location[i].x, this.location[i].y);
                    Console.Write('●') ;
                }
                break;
            case E_SnakePart.SnakeHead:
                Console.SetCursorPosition(this.location[0].x, this.location[0].y);
                Console.Write('◎');
                break;
        }
    }
    public void Clean()//蛇身清除
    {
        for (int i = 0; i < this.location.Length; i++)
        {
            Console.SetCursorPosition(this.location[i].x, this.location[i].y);
            Console.Write("  ");
        }
    }
}
public enum E_SnakePart
{
    SnakeHead,//蛇头
    SnakeBody,//蛇身
}
//地图墙壁类
public class Map : Game_Object
    //希望实现在本模块控制单一墙壁的位置和朝向
{
    private string pattern = "■";
    public Map(int Start_x,int Start_y,int length, bool direction)
           //Start_x为墙壁开始x坐标,Start_y为墙壁开始y坐标,length为墙壁长度,direction是墙壁的朝向(true为横,false为竖)
    {
        this.location = new Location[length];
        for (int i = 0; i < length; i++)
        {
            this.location[i].x = direction ? Start_x + i*2 : Start_x;//为横时,x坐标逐渐增2,y坐标不变
            this.location[i].y = direction ? Start_y : Start_y + i;//为竖时,x坐标不变,y坐标逐渐增1
        }
    } 
    public void Draw()
    {
        Console.ForegroundColor = ConsoleColor.Red;
        for(int i = 0; i < this.location.Length; i++)
        {
            Console.SetCursorPosition(this.location[i].x, this.location[i].y);
            Console.Write(pattern);
        }

    }
}
//绘制接口
public interface IDrawing
{
    void Draw();
}
//游戏对象基类 -- 游戏过程中相互独立的对象
public class Game_Object : IDrawing
{
    //考虑到在功能拓展中可能会出现无规律,仍然记录所有元素的坐标
    public Location[] location;
    
    public void Draw()
    {
    }

}
//边界位置结构体
public struct Location
{
    public int x;
    public int y;
    public static bool operator ==(Location A,Location B)
    {
        if(A.x == B.x && A.y == B.y)
        {
            return true;
        }
        return false;
    }
    public static bool operator !=(Location A, Location B)
    {
        if (A.x == B.x && A.y == B.y)
        {
            return false;
        }
        return true;
    }

}
#endregion

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值