C#を使ってコンソールで動くスネークゲーム作成

www.youtube.com
 こんなのを作った。


 突発性難聴は継続中。貰ったお薬飲んでるものの効いてるのか効いてないのか。
あと、片耳がほぼ聞こえないのに慣れてきて人間の適応力って凄いと感心した。
(余計な情報)

実行ファイル

drive.google.com
 64bit windows10以降なら動くはず

コード

Sceneクラス

 一番適当なクラス。追加のゴミ捨て場みたいな感じ。

namespace SnakeGame
{
    static class Scene
    {
        public const int WindowHeight = 30;
        public const int WindowWidth = 60;

        public static int HiScore = 0;
        public static int Score = 0;

        private static bool isPlay = true;

        public static void Start()
        {
            Console.WindowHeight = WindowHeight;
            Console.WindowWidth = WindowWidth;
            Console.BufferHeight = WindowHeight;
            Console.BufferWidth = WindowWidth;

            Title();

            while (isPlay)
            {
                Play();
                End();
            }
        }

        static void Title()
        {
            Console.SetCursorPosition(0, WindowHeight / 2 - 1);
            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine(">> Snake Game <<");
            Console.WriteLine("Push any key");
            Console.ReadKey();
        }

        static void Play()
        {
            new Snakegame();
        }

        static void End()
        {
            Console.SetCursorPosition(0, WindowHeight / 2 - 1);
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine("** Game Over **");
            Console.WriteLine($"** Score {Scene.Score} **");
            if (HiScore < Score) HiScore = Score;
            Score = 0;
            Console.WriteLine($"** Score {Scene.HiScore} **");

            Console.ReadKey(true);
            System.Threading.Thread.Sleep(1000);
            Console.WriteLine("Z key is Restart / Other key is close");

            if(Console.ReadKey().Key != ConsoleKey.Z) isPlay = false;
            
        }
    }
}

 static にしてどこからでも呼び出せるようにしてあるが、正直な理由としてはスコアとか別にするのめんどくさくなったから。

入力停止処理
Console.ReadKey(true);
System.Threading.Thread.Sleep(1000);
Console.WriteLine("Z key is Restart / Other key is close");

 Thread.Sleepを使っても入力が止まらないのが悩みだった。
直前にConsole.ReadKey(true)を入れることでスリープ中のキー入力の破棄ができた。

Snakegameクラス

namespace SnakeGame
{
    class Snakegame
    {
        private int gameSpeed = 100;
        private Snake _snake;
        private Food _food;

        public Snakegame()
        {
            _snake = new Snake();
            _food = new Food();

            GameLoop();
        }

        private void GameLoop()
        {

            while (true)
            {
                InputKey();

                _snake.Update();

                if (_snake.IsDead(Scene.WindowHeight, Scene.WindowWidth)) break;

                if (_snake.IsEat(_food))
                {
                    _snake.Growth();
                    _food.Pop(Scene.WindowHeight, Scene.WindowWidth);

                    Scene.Score++;
                    if (gameSpeed > 30) gameSpeed -= 10;
                }

                _snake.Draw();
                _food.Draw();

                Console.SetCursorPosition(0, 0);
                System.Threading.Thread.Sleep(gameSpeed);
                Console.Clear();
            }
        }

        private void InputKey()
        {
            // キー入力を受け取る
            if (Console.KeyAvailable)
            {
                switch (Console.ReadKey(true).Key)
                {
                    case ConsoleKey.UpArrow:
                        _snake.ChangeDirection(Snake.Direction.UP);
                        break;
                    case ConsoleKey.DownArrow:
                        _snake.ChangeDirection(Snake.Direction.DOWN);
                        break;
                    case ConsoleKey.LeftArrow:
                        _snake.ChangeDirection(Snake.Direction.LEFT);
                        break;
                    case ConsoleKey.RightArrow:
                        _snake.ChangeDirection(Snake.Direction.RIGHT);
                        break;
                }
            }
        }
    }
}

 キー入力はSnakeクラスで作ってしまってもよかったかなとは思う。
SnakeのChangeDirectionと中身がかぶってるからね。

Foodクラス

using System.Drawing;

namespace SnakeGame
{
    class Food
    {
        public Point p;
        private Random r;

        //コンストラクタ
        public Food()
        {
            p = new Point(5, 5);
            r = new Random();
        }

        public void Pop(int h, int w)
        {
            p.X = r.Next(0, w);
            p.Y = r.Next(0, h);
        }

        public void Draw()
        {
            Console.SetCursorPosition(p.X, p.Y);
            Console.ForegroundColor = ConsoleColor.Red;
            Console.Write("@");
        }
    }
}

 特に言うことはない。
蛇に食べられたらPop()で位置を変えるだけ。

Snakeクラス

using System.Drawing;

namespace SnakeGame
{
    class Snake
    {
        public enum Direction
        {
            UP, DOWN, LEFT, RIGHT
        }

        public Point head;
        private Point _headDirection;
        private List<Point> _body;

        public Snake()
        {
            head = new Point(3, 3);
            _headDirection = new Point(0, 0);
            _body = new List<Point>();
        }

        public bool IsDead(int h, int w)
        {
            if (head.X < 0 || head.X >= w || head.Y < 0 || head.Y >= h) return true;
            if (_body.IndexOf(head) != -1) return true;

            return false;
        }

        public bool IsEat(Food f)
        {
            if (head == f.p) return true;

            return false;
        }

        public void Growth()
        {
            _body.Add(new Point(head.X, head.Y));
        }

        public void Update()
        {
            if (_body.Count > 0)
            {
                _body.Add(new Point(head.X, head.Y));
                _body.RemoveAt(0);
            }

            AddPoint(ref head, _headDirection);
        }

        public void ChangeDirection(Direction d)
        {
            switch (d)
            {
                case Direction.UP:
                    SetPoint(ref _headDirection, 0, -1);
                    break;
                case Direction.DOWN:
                    SetPoint(ref _headDirection, 0, 1);
                    break;
                case Direction.LEFT:
                    SetPoint(ref _headDirection, -1, 0);
                    break;
                case Direction.RIGHT:
                    SetPoint(ref _headDirection, 1, 0);
                    break;
            }
        }

        private void SetPoint(ref Point p, int x, int y)
        {
            p.X = x;
            p.Y = y;
        }

        private void AddPoint(ref Point p1, Point p2)
        {
            p1.X += p2.X;
            p1.Y += p2.Y;
        }

        public void Draw()
        {
            Console.SetCursorPosition(head.X, head.Y);
            Console.ForegroundColor = ConsoleColor.White;
            Console.Write("O");

            foreach (var p in _body)
            {
                Console.SetCursorPosition(p.X, p.Y);
                Console.ForegroundColor = ConsoleColor.Green;
                Console.Write("#");
            }
        }
    }
}

 Pointクラスの使い勝手の悪さよ…足し算と引き算くらいはあってほしかった。