「技術」カテゴリーアーカイブ

【C#】【ALTSEED】【数独】文字列を表示する

前回までの状況はこちら。

実際に文字、というか、数字を画面に表示するのですが、

ゲームライブラリを使用しているので、普通に文字を表ずる事ができないのですよ。

このライブラリではフォントジェネレータというツールを使って、使用する文字を別ファイルで用意する必要があります。

こんなテキストファイルを作成して、

こんな感じでフォントファイルを作成します。

このとき生成されるaffファイルとpngファイルを実行プログラムと同じ場所に設置します。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace sudokuGUI
{
    class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            // Altseedを初期化する。
            asd.Engine.Initialize("数独解析ツール", 1000, 800, new asd.EngineOption());

            // 下地
            var background = new asd.GeometryObject2D();
            asd.Engine.AddObject2D(background);
            var bgRect = new asd.RectangleShape();
            bgRect.DrawingArea = new asd.RectF(0, 0, 1000, 800);
            background.Shape = bgRect;

            // テクスチャー
            asd.Texture2D texture = asd.Engine.Graphics.CreateTexture2D("squares.png");

            // マス
            int offsetX = 10;
            int offsetY = 10;
            var square = new asd.TextureObject2D();
            square.Position = new asd.Vector2DF(offsetX, offsetY);
            square.Texture = texture;
            asd.Engine.AddObject2D(square);

            var font = asd.Engine.Graphics.CreateFont("number.aff");
            var obj = new asd.TextObject2D();
            obj.Font = font;
            obj.Position = new asd.Vector2DF(100, 100);
            obj.Text = "1357";
            asd.Engine.AddObject2D(obj);

            // Altseedが進行可能かチェックする。
            while (asd.Engine.DoEvents())
            {
                // Altseedを更新する。
                asd.Engine.Update();
            }

            // Altseedを終了する。
            asd.Engine.Terminate();
        }
    }
}

とりあえず、数字を表示することは問題なさそう。

あとは、フォントサイズと表示位置を微調整する。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace sudokuGUI
{
    class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            // Altseedを初期化する。
            asd.Engine.Initialize("数独解析ツール", 1000, 800, new asd.EngineOption());

            // 下地
            var background = new asd.GeometryObject2D();
            asd.Engine.AddObject2D(background);
            var bgRect = new asd.RectangleShape();
            bgRect.DrawingArea = new asd.RectF(0, 0, 1000, 800);
            background.Shape = bgRect;

            // テクスチャー
            asd.Texture2D texture = asd.Engine.Graphics.CreateTexture2D("squares.png");

            // マス
            int offsetX = 10;
            int offsetY = 10;
            var square = new asd.TextureObject2D();
            square.Position = new asd.Vector2DF(offsetX, offsetY);
            square.Texture = texture;
            asd.Engine.AddObject2D(square);

            int fontOffsetX = 19;
            int fontOffsetY = 9;
            var font = asd.Engine.Graphics.CreateFont("number.aff");
            var obj = new asd.TextObject2D();
            obj.Font = font;
            obj.Position = new asd.Vector2DF(offsetX + fontOffsetX, offsetY + fontOffsetY);
            obj.Text = "1";
            asd.Engine.AddObject2D(obj);

            var obj2 = new asd.TextObject2D();
            obj2.Font = font;
            obj2.Position = new asd.Vector2DF(64 + offsetX + fontOffsetX, 64 + offsetY + fontOffsetY);
            obj2.Text = "2";
            asd.Engine.AddObject2D(obj2);

            // Altseedが進行可能かチェックする。
            while (asd.Engine.DoEvents())
            {
                // Altseedを更新する。
                asd.Engine.Update();
            }

            // Altseedを終了する。
            asd.Engine.Terminate();
        }
    }
}

うん、いいんじゃないでしょうか。

【C#】【Altseed】【数独】マスを作成する

とりあえず、下地となるマスを書いてみます。

最初考えたのは、

こんな画像を用意して、9×9に並べてみました。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace sudokuGUI
{
    class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            // Altseedを初期化する。
            asd.Engine.Initialize("数独解析ツール", 1000, 800, new asd.EngineOption());

            // 下地
            var background = new asd.GeometryObject2D();
            asd.Engine.AddObject2D(background);
            var bgRect = new asd.RectangleShape();
            bgRect.DrawingArea = new asd.RectF(0, 0, 1000, 800);
            background.Shape = bgRect;

            // テクスチャー
            asd.Texture2D texture = asd.Engine.Graphics.CreateTexture2D("square.png");

            // マス
            int offsetX = 10;
            int offsetY = 10;
            int width = 64;
            int height = 64;

            for(int row = 0; row < 9; row++)
            {
                for(int col = 0; col < 9; col++)
                {
                    var square = new asd.TextureObject2D();
                    square.Position = new asd.Vector2DF(width * row + offsetX, height * col + offsetY);
                    square.Texture = texture;
                    asd.Engine.AddObject2D(square);
                }
            }


            // Altseedが進行可能かチェックする。
            while (asd.Engine.DoEvents())
            {
                // Altseedを更新する。
                asd.Engine.Update();
            }

            // Altseedを終了する。
            asd.Engine.Terminate();
        }
    }
}

イメージとしては上の画像をテクスチャとして並べただけです。

スクショでは分かりませんが、内側の線が全て太く描画されてしまいました。

これは、ちょっと見づらいですね・・・

よし、9×9マスの画像を作成して、1枚のテクスチャーにしよう。

上の画像を修正して、

これを一つのテクスチャオブジェクトに貼り付けます。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace sudokuGUI
{
    class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            // Altseedを初期化する。
            asd.Engine.Initialize("数独解析ツール", 1000, 800, new asd.EngineOption());

            // 下地
            var background = new asd.GeometryObject2D();
            asd.Engine.AddObject2D(background);
            var bgRect = new asd.RectangleShape();
            bgRect.DrawingArea = new asd.RectF(0, 0, 1000, 800);
            background.Shape = bgRect;

            // テクスチャー
            asd.Texture2D texture = asd.Engine.Graphics.CreateTexture2D("squares.png");

            // マス
            int offsetX = 10;
            int offsetY = 10;
            var square = new asd.TextureObject2D();
            square.Position = new asd.Vector2DF(offsetX, offsetY);
            square.Texture = texture;
            asd.Engine.AddObject2D(square);


            // Altseedが進行可能かチェックする。
            while (asd.Engine.DoEvents())
            {
                // Altseedを更新する。
                asd.Engine.Update();
            }

            // Altseedを終了する。
            asd.Engine.Terminate();
        }
    }
}

うん、いいんじゃないでしょうか。

次は文字の作成をやってみます。

【C#】openTKを使用してみる

前回はAltseedというライブラリを使用してみたのですが、

このライブラリって調べてみると、構造はシンプルでゲーム制作に必要な機能は一通り揃っている(と思う)のですが、

シンプルすぎるというか、基本的なグラフィックス機能が少々貧弱な気がします。

例えば、四角形を表示させる場合、色の指定はできず、基本的に画像をテクスチャとして表示する、というイメージです。

なので、もうちょっと調べてみました。

ゲームライブラリにこだわらず、グラフィックスライブラリという括りで検索してみると、openTKというライブラリを発見しました。

https://opentk.net/index.html

これはopenGLのラッパーのライブラリですね。

openGLの機能はほぼほぼ使用できると考えていいようです。

NuGetに登録されているのでNuGetからopenTKを検索するとダウンロードして参照できるようになります。

サンプルコードを入力してみました。

https://masuqat.net/programming/csharp/OpenTK00-03.php

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using OpenTK.Input;

namespace OpenTK_sample
{
    class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            using (Game window = new Game())
            {
                window.Run(30.0);
            }
        }
    }

    class Game : GameWindow
    {
        public Game() : base(800, 600, GraphicsMode.Default, "0-3:GameWindow")
        {
        }

        //ウィンドウの起動時に実行される。
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);

            GL.ClearColor(Color4.Black);
            GL.Enable(EnableCap.DepthTest);
        }

        //ウィンドウのサイズが変更された場合に実行される。
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);

            GL.Viewport(ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width, ClientRectangle.Height);
            GL.MatrixMode(MatrixMode.Projection);
            Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView((float)Math.PI / 4, (float)Width / (float)Height, 1.0f, 64.0f);
            GL.LoadMatrix(ref projection);
        }

        //画面描画で実行される。
        protected override void OnRenderFrame(FrameEventArgs e)
        {
            base.OnRenderFrame(e);

            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

            GL.MatrixMode(MatrixMode.Modelview);
            Matrix4 modelview = Matrix4.LookAt(Vector3.Zero, Vector3.UnitZ, Vector3.UnitY);
            GL.LoadMatrix(ref modelview);

            GL.Begin(BeginMode.Quads);

            GL.Color4(Color4.White);                            //色名で指定
            GL.Vertex3(-1.0f, 1.0f, 4.0f);
            GL.Color4(new float[] { 1.0f, 0.0f, 0.0f, 1.0f });  //配列で指定
            GL.Vertex3(-1.0f, -1.0f, 4.0f);
            GL.Color4(0.0f, 1.0f, 0.0f, 1.0f);                  //4つの引数にfloat型で指定
            GL.Vertex3(1.0f, -1.0f, 4.0f);
            GL.Color4((byte)0, (byte)0, (byte)255, (byte)255);  //byte型で指定
            GL.Vertex3(1.0f, 1.0f, 4.0f);

            GL.End();

            SwapBuffers();
        }
    }
}

これだけではちょっと難しそうだけど、必要なものだけ使用すればうまくいきそう。

ただ、一番の問題は座標系。

openGLの世界では、(0,0)がウインドウの中心にあり、左上が(-1.0-1.0)で右下が(1.0,1.0)になる。

これはこれで扱いが難しい。

うーん、ハードルが高い・・・。

CGを使うにはちょうどいいかもしれないけどね。

【C#】Altseedを導入する

数独解析ツールをGUI化するにあたって、

まずはAltseedライブラリが使用できることを確認します。

まずは、以下のサイトからライブラリをダウンロード。

https://altseed.github.io/index.html

zipファイルを解凍すると、ドキュメント、ライブラリ本体、サンプルプログラムなどが入っています。

基本的にドキュメントの中にあるイントロダクションを参照すれば良いのですが、内容が古いのか、この通りにやっても上手くいかないので、手順をまとめておきます。

あ、開発環境はWindows10、Visual Studio 2019です。

まず、Visual Studio 2019を立ち上げでプロジェクトを新規作成します。

作成するプロジェクトの種類は.NET Frameworkのコンソールアプリです。

プロジェクトを作成したら、参照の追加で、Altseed.dllを追加します。

Altseed_core.dllは、たぶんネイティブのDLLなので、C#から参照できません。プロジェクトをビルドした後、実行ファイルと同じフォルダにコピーしてください。

サンプルプログラムは以下の通りです。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace sudokuGUI
{
    class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            // Altseedを初期化する。
            asd.Engine.Initialize("Empty", 640, 480, new asd.EngineOption());

            // Altseedが進行可能かチェックする。
            while (asd.Engine.DoEvents())
            {
                // Altseedを更新する。
                asd.Engine.Update();
            }

            // Altseedを終了する。
            asd.Engine.Terminate();
        }
    }
}

実行すると、こうなります。

【C#】【数独】数独解析ツールGUI化計画

さて、数独解析のアルゴリズム部分は完成して、大抵の問題は解くことができました。

これをもっと使いやすいものにしたい。

CUIではなく、GUIに。

C#はWindows FormsとかWPFというGUIがありますが、多分、これをそのまま使用すると、プルダウンリストが画面に大量に配置することになり、見た目が汚くなると思う。

そういうのは嫌なので、そういったコントロールは使用せずに、実現したいと思う。例えばゲームエンジンのGUIを使うとか。

C#でゲームエンジンというと、真っ先に思い浮かぶのはUnity。

でも、Unityは違うと思うんすよね。

Unityは3Dは得意だけど、2Dはあまり得意としていない(できなくは無いけど)

そこで思い出したのが、昔調べたゲームライブラリで、Altseedというライブラリがあります。

https://altseed.github.io/index.html

C++、C#、Javaで使用できるライブラリで、クロスプラットフォームでも使用できます。

もっと調べれば、他にライブラリがあるかもしれないけど、できることは大差ないので、これで行こうと思う。

今後はこのライブラリを使って数独解析ツールのGUI化に乗り出します。

【リリース】数独解析プログラム

ダウンロード(gitHub)

https://github.com/takishita2nd/sudoku/releases/tag/1.0.0

使用方法

> sudoku.exe [入力ファイル名] [出力ファイル名]

入力ファイル

例えば、以下のような問題があった場合、

以下のように入力ファイルを作成します。

カンマ区切りの9×9の数字を入力してください。

回答が入るところは数字の0を入力してください。

実行すると出力ファイルの最後に、以下の様に出力されます。

【C#】【数独】数独解析プログラム完成

前回までの状況はこちら。

前回の修正でほぼほぼ解析ロジックは完成したので、もうちょっと使い勝手が良いように、ファイル入出力とエラー処理に手を加えます。

まずは、いままで出力ファイル名が固定化されていたので、コマンドの第二パラメータに出力ファイルを入力するようにします。

具体的には、ファイル入出力が一つのstaticクラスで行っていたのを分割します。

入力ファイルの読み込みは、最初の1回しか実行しないので、staticクラスで、出力ファイルは何度も使用するので、通常のクラスで定義し直します。

出力クラスのコンストラクタで出力ファイル名を指定します。

    class FileOutput
    {
        private string _filename;
        public FileOutput(string filename)
        {
            _filename = filename;
            if (File.Exists(_filename) == true)
            {
                File.Delete(_filename);
            }
        }

        public void Output(Square[,] sq)
        {
            using (var stream = new StreamWriter(_filename, true))
            {
                for (int row = 0; row < 9; row++)
                {
                    for (int col = 0; col < 9; col++)
                    {
                        stream.Write(sq[row, col].GetValue());
                    }
                    stream.Write("\r\n");
                }
                stream.Write("\r\n");
            }
        }

        public void Output(bool[,] sq)
        {
            using (var stream = new StreamWriter(_filename, true))
            {
                for (int row = 0; row < 9; row++)
                {
                    for (int col = 0; col < 9; col++)
                    {
                        if (sq[row, col] == true)
                        {
                            stream.Write(1);
                        }
                        else
                        {
                            stream.Write(0);
                        }
                    }
                    stream.Write("\r\n");
                }
                stream.Write("\r\n");
            }
        }

        public void Output(int row, int col, int value)
        {
            using (var stream = new StreamWriter(_filename, true))
            {
                stream.Write("[{0},{1}] => {2}\r\n", row, col, value);
            }
        }

        public void Output(string text)
        {
            using (var stream = new StreamWriter(_filename, true))
            {
                stream.Write("{0}\r\n", text);
            }
        }

        public void Output(List<Square> list)
        {
            using (var stream = new StreamWriter(_filename, true))
            {
                stream.Write("候補 ");
                foreach (Square s in list)
                {
                    stream.Write("[{0},{1}]", s.Row, s.Col);
                }
                stream.Write("\r\n");
            }
        }
    }

メイン関数で出力ファイル名を取得します。

第二パラメータが無かった場合は、自動的に入力ファイイル名.outputというファイル名で出力させます。

        static void Main(string[] args)
        {
            // パラメータチェック
            if (args.Length < 1)
            {
                Console.WriteLine("usage : sudoku.exe [input file] [output file]");
                return;
            }

            // ファイルの存在を確認
            string filePath = Environment.CurrentDirectory + "\\" + args[0];
            if (File.Exists(filePath) == false)
            {
                Console.WriteLine("File not found.");
                return;
            }

            // 出力ファイル名
            string outfile = null;
            if (args.Length == 2)
            {
                outfile = Environment.CurrentDirectory + "\\" + args[1];
            }
            else
            {
                outfile = filePath + ".output";
            }

            var sq = FileRead.OpenFile(filePath);
            if(sq == null)
            {
                return;
            }

            Sudoku sudoku = new Sudoku(sq, outfile);
            sudoku.run();
        }

最後に、入力データのエラー処理を改善します。

具体的には、例えば、行と列が10以上あった場合、そして、数字以外の値が入っていた場合、数字が10以上入っていた場合はエラーで終了させます。

    static class FileRead
    {
        /**
         * ファイルからデータを取得する
         */
        public static Square[,] OpenFile(string filePath)
        {
            int[,] matrix = new int[9, 9];

            // ファイルを開く
            bool error = false;
            using (var stream = new StreamReader(filePath))
            {
                int row = 0;
                while (stream.EndOfStream == false)
                {
                    if (row >= 9)
                    {
                        error = true;
                        break;
                    }
                    string lineText = stream.ReadLine();
                    var val = lineText.Split(',');
                    if (val.Length > 9)
                    {
                        error = true;
                        break;
                    }
                    int col = 0;
                    foreach (var v in val)
                    {
                        int i;
                        if (int.TryParse(v, out i))
                        {
                            if(i >= 10)
                            {
                                error = true;
                                break;
                            }
                            matrix[row, col] = i;
                        }
                        else
                        {
                            error = true;
                            break;
                        }
                        col++;
                    }
                    if (error)
                    {
                        break;
                    }
                    row++;
                }
            }
            if (error)
            {
                Console.WriteLine("Illegal format.");
                return null;
            }

            Square[,] ret = new Square[9, 9];
            for (int row = 0; row < 9; row++)
            {
                for (int col = 0; col < 9; col++)
                {
                    Square sq = new Square(matrix[row, col], row, col);
                    ret[row, col] = sq;
                }
            }

            return ret;
        }
    }

そして、最終的にgitHubのようなソースになりました。

https://github.com/takishita2nd/sudoku

【C#】【数独】ログ出力を強化し不具合を探す

前回までの状況はこちら。

最新ソースはこちら(gitHub)

https://github.com/takishita2nd/sudoku

前回のソースでは、以下のような問題で解析が途中で失敗していました。

この原因を探るため、ログ出力を強化します。

まず、マスに値を入れるタイミングでログを出します。

次に、仮置きロジックを適用する時と、仮置き前に巻き戻しをする時に、それが分かるようにログを出します。

さらに、仮置きロジックを適用する場所をログに出力するようにします。

ここまでやって、 仮置きロジックを適用する場所を探す処理に不具合がある事が分かりました。

        /**
         * 仮置き対象のマスを抽出する
         *
         * squares マス
         */
        private List<Square> searchKariokiSquare(Square[,] squares)
        {
            List<Square> ret = null;
            for (int row = 0; row < 9; row += 3)
            {
                for (int col = 0; col < 9; col += 3)
                {
                    List<Square> temp = new List<Square>();
                    for (int i = 0; i < 3; i++)
                    {
                        for (int j = 0; j < 3; j++)
                        {
                            if (squares[row + i, col + j].isConfirmed() == false)
                            {
                                temp.Add(_square[row + i, col + j]);
                            }
                        }
                    }
                    if (ret != null) //←★ここ
                    {
                        if (ret.Count > temp.Count && temp.Count != 0)
                        {
                            ret = temp;
                        }
                    }
                    else
                    {
                        ret = temp;
                    }
                }
            }
            return ret;
        }

★の箇所で、ret=nullで、temp.Count = 0の時に、仮置きロジックを適用する箇所がない(ret.Count=0)となり、解析が終了していることが分かりました。

なので、★の箇所を修正します。

                    if (ret != null)
                    {
                        if (ret.Count > temp.Count && temp.Count != 0)
                        {
                            ret = temp;
                        }
                    }
                    else if(temp.Count != 0)
                    {
                        ret = temp;
                    }

解けました。

難易度★5の問題も解けるようになったので、大抵の問題は解けるようになりました。

もう少し動かしてみます。

【Laravel】【ホテル予約管理】解錠APIを追加する

前回までの状況はこちら。

最新ソースはこちら(gitHub)

https://github.com/takishita2nd/hotel-mng

さて、前回からだいぶ期間が空いてしまいましたが、

続きやります。(思い出すのに時間かかった)

今回は、前回メールに添付したQRコードに書かれた解錠コードを使って解錠する処理を実装するのですが、

実際に解錠を行うのは、外部のデバイスを使用する、という前提として考え、ここではデバイスに入力した解錠コードをサーバに送信し、照合結果を返すAPIを実装します。

このAPIのレスポンスをデバイスが受信して、デバイスが実際に解錠を行います。そういう想定で実装します。

まず、APIのルーティングを作成します。

routes配下のapi.phpに以下を追加します。

Route::group(['middleware' => ['api']], function(){
    Route::post('/unlock', 'Api2Controller@unlock');
});

なぜ、今までのようにweb.phpではないかというと、web.phpには

Auth::routes();

がかかれてあり、ログイン状態でアクセスしなければAPIは受け付けられません。

デバイス側にログイン状態というのは意識できないので、この制約が入らないルートに設定しなければなりません。

では、コントローラーを作成します。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use App\Repository\RegisterManagementRepository;
use App\Repository\RoomRepository;
use App\Repository\UserRepository;

class Api2Controller extends Controller
{
    public function __construct()
    {
        $this->registerManagement = new RegisterManagementRepository();
        $this->room = new RoomRepository();
        $this->user = new UserRepository();
    }

    public function unlock(Request $request)
    {
        \Log::debug(print_r($request->input('number'), true));
        \Log::debug(print_r($request->input('room'), true));

        $registers = $this->registerManagement->getItemsByDate(date('Y-m-d'));
        if(is_null($registers) == false)
        {
            foreach ($registers as $register)
            {
                $room = $register->rooms()->first();
                if(is_null($room) == false)
                {
                    if($room->id == $request->input('room') &&
                        strcmp($register->lock_number, $request->input('number')) == 0)
                    {
                        return response()->json(['result' => true]);
                    }
                }
            }
        }
        return response()->json(['result' => false]);
    }
}

Api2Controller.phpを追加しました。

ApiController.phpでは、コンストラクタに

        $this->middleware('auth');

と記述しているため、これも上記と同じ理由で使用できません。

入力は部屋番号と解錠コードをjsonで入力します。

現在の日付から予約情報を取得し、部屋番号を解錠コードの比較を行い、一致したときにtrue、一致しない場合はfalseを返します。

思ったように動いているようです。

ただし、このままだと誰でもアクセスできてしまうので、セキュリティ的にガバガバ状態です。

デバイス情報を登録して、それも入力情報に含めるようにしましょうか。

次回やります。