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クラスの使い勝手の悪さよ…足し算と引き算くらいはあってほしかった。


p5.jsでスネークゲーム作成


 突発性難聴で左耳が死んだ(余計な情報)
p5.jsを初めて使うのでスネークゲームを作ってみた。

p5.js

p5js.org

コード

    let snake;
    let apple;

    const canvasSizeWidth = 400;
    const canvasSizeHeight = 400;
    const frameRateValue = 10;
    const cell = 20;
    let cellSizeWidth;
    let cellSizeHeight;

    //初期設定
    function setup() {
      snake = new Snake();
      apple = new Apple();

      cellSizeWidth = floor(canvasSizeWidth / cell);
      cellSizeHeight = floor(canvasSizeHeight / cell);

      createCanvas(canvasSizeWidth, canvasSizeHeight);
      frameRate(frameRateValue);
    }

    //描画
    function draw() {
      background(51);
      snake.Update();
      snake.Death();

      if (snake.headPosition.x === apple.position.x && snake.headPosition.y === apple.position.y) {
        snake.Growth();
        apple.Pop();
      }

      snake.Draw();
      apple.Draw();
    }

    //キー入力
    function keyPressed() {
      if (keyCode === UP_ARROW) {
        snake.Direction(0, -1);
      } else if (keyCode === DOWN_ARROW) {
        snake.Direction(0, 1);
      } else if (keyCode === LEFT_ARROW) {
        snake.Direction(-1, 0);
      } else if (keyCode === RIGHT_ARROW) {
        snake.Direction(1, 0);
      } else if (key == ' ') {
        //snake.grow();
      }
    }

    class Snake {
      //コンストラクタ
      constructor() {
        this.Init();
      }

      Init() {
        this.body = [createVector(cell - 1, 0)];
        this.headPosition = createVector(cell - 1, 0);
        this.directionVector = createVector(0, 0);
      }

      //移動方向の設定
      Direction(x, y) {
        if (this.directionVector.x != -x || this.directionVector.y != -y) {
          this.directionVector.x = x;
          this.directionVector.y = y;
        }
      }

      //死
      Death() {
        //境界から出て死亡
        if (this.headPosition.x < 0 || this.headPosition.x >= cell || this.headPosition.y < 0 || this.headPosition.y >= cell) {
          this.Init();
        }

        //自身に当たって死亡
        for (let i = 0; i < this.body.length; i++) {
          if (this.body[i].x === this.headPosition.x && this.body[i].y === this.headPosition.y) {
            this.Init();
          }
        }
      }

      //成長
      Growth() {
        this.body.push(this.headPosition);
      }

      //更新
      Update() {
        this.body.unshift(createVector(this.headPosition.x, this.headPosition.y));
        this.body.pop();
        this.headPosition.x += this.directionVector.x;
        this.headPosition.y += this.directionVector.y;
      }

      //描画
      Draw() {
        fill(255, 255, 255);
        rect(this.headPosition.x * cellSizeWidth, this.headPosition.y * cellSizeHeight, cellSizeWidth, cellSizeHeight);

        fill(0, 255, 0);
        for (let i = 0; i < this.body.length; i++) {
          rect(this.body[i].x * cellSizeWidth, this.body[i].y * cellSizeHeight, cellSizeWidth, cellSizeHeight);
        }
      }
    }

    //リンゴクラス
    class Apple {
      //コンストラクタ
      constructor() {
        this.position = createVector(5, 5);
      }

      //描画
      Draw() {
        fill(255, 0, 0);
        rect(this.position.x * cellSizeWidth, this.position.y * cellSizeHeight, cellSizeWidth, cellSizeHeight);
      }

      //出現
      Pop() {
        this.position.x = floor(random(0, cell));
        this.position.y = floor(random(0, cell));
      }
    }


 動かす場合は上記のコードを p5.js Web editer に貼りつけて実行。
editor.p5js.org


JavaScriptでコラッツ予想

コラッツの問題 証明と別解 への ガイド版: 21世紀の算数 整数譜


 コラッツ予想をJavaScriptで作ってみた。
JavaScriptとか久しぶりに触るから結構忘れてて困った困った。

コラッツ予想

 ある値に対して次の操作をくり返しおこなうと1になる、という予想。


n が偶数ならば n / 2
n が奇数ならば 3 × n + 1


 言ってることは全然難しくないので小学生でも理解できるけど、未だに証明されていない難問。
たしか、証明に1億円の賞金がかけられていたはず。

コラッツ予想

 数字を入力するとコラッツ操作を実行する(1以上の値でお願い)




結果

コード

<script>
        function CollatzProblem() {
            let result = "";
            let number = document.getElementById("number").value;
            result += number;

            while (number != 1) {
                result += " -> ";
                if (number % 2 == 0) number = number / 2;
                else number = number * 3 + 1;
                result += number;
            }

            document.getElementById("id_result").innerHTML = result;
        }
</script>

<input type="number" min="1" step="1" id="number">
<button onclick="CollatzProblem()">計算</button>


<p id="id_result">結果</p>


『Ponge画像類似度調査』を作った


 最終的には「似たような画像を消すソフト」が欲しいので、前段階として作った。

テスト

DALL·E 2023-01-31 16.42.49.png
DALL·E 2023-01-31 16.44.16.png

 上の2つの画像をDALL·E 2で作成。



 同じ画像だと類似度は100。



 違う画像だと71くらい。


類似度95以上で同じ画像とみなしてもいいかもしれない。

ソフト置き場

drive.google.com

コード

//MIT License

//Copyright (c) 2017 Coen Munckhof

//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:

//The above copyright notice and this permission notice shall be included in all
//copies or substantial portions of the Software.

//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//SOFTWARE.


using CoenM.ImageHash;
using CoenM.ImageHash.HashAlgorithms;
using Microsoft.Win32;
using System.IO;
using System.Windows;

namespace Ponge画像類似度調査
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Btn_CalcSimilarity_Click(object sender, RoutedEventArgs e)
        {
            var hashAlorithm = new PerceptualHash();

            var fstream1 = File.OpenRead(this.Path1.Text);
            var fstream2 = File.OpenRead(this.Path2.Text);

            var hash1 = hashAlorithm.Hash(fstream1);
            var hash2 = hashAlorithm.Hash(fstream2);

            var similarity = CompareHash.Similarity(hash1, hash2);

            fstream1?.Dispose();
            fstream2?.Dispose();

            this.SimilarityValue.Content = similarity;
        }

        private void Path1_GotFocus(object sender, RoutedEventArgs e)
        {
            var dialog = new OpenFileDialog();

            dialog.Filter = "全てのファイル (*.*)|*.*";

            if (dialog.ShowDialog() == true)
            {
                this.Path1.Text = dialog.FileName;
            }
        }

        private void Path2_GotFocus(object sender, RoutedEventArgs e)
        {
            var dialog = new OpenFileDialog();

            dialog.Filter = "全てのファイル (*.*)|*.*";

            if (dialog.ShowDialog() == true)
            {
                this.Path2.Text = dialog.FileName;
            }
        }
    }
}

ImageHash

github.com
 ライセンスはMIT。
画像をハッシュ化して類似度を求めるならコレ、らしい。
NuGetでインストール後、VisualStudioからライセンスを見ると謎の歯抜け状態になってて困った。
結局GitHubからライセンス表記を取ってきた。