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

【C#】【ピクロス】【ALTSEED】解析パターンその3

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

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

https://github.com/takishita2nd/Picross

次は、ここを攻めていきます。

この赤い部分、すでに塗られているマスから、開いているマスは全て塗ることができないことが分かると思います。

これを処理していきましょう。

パターン2では塗られていないマスを数えましたが、パターン3では塗られているマスを数えます。

コードはパターン2と似ていますが、条件が変わっています。

        // 解析パターンその3
        // すでに塗りつぶされたマスからマスクをする
        private void pattern3()
        {
            // Row
            pattern3Row();
            // Col
            pattern3Col();
        }

        private void pattern3Row()
        {
            int row = 0;
            foreach (var rowlist in rowNumbers)
            {
                if (rowlist.IsAnalyzed())
                {
                    row++;
                    continue;
                }

                // 塗ったマスを数える
                int col = 0;
                int count = 0;
                List<int> countList = new List<int>();
                while (col < colNumbers.Count)
                {
                    if (_bitmapData[row, col].IsPainted())
                    {
                        count++;
                    }
                    else
                    {
                        if (count != 0)
                        {
                            countList.Add(count);
                            count = 0;
                        }
                    }
                    col++;
                    if (col == colNumbers.Count)
                    {
                        if (count != 0)
                        {
                            countList.Add(count);
                        }
                    }
                }
                // 数えた数字が全て一致すれば確定とする
                bool result = true;
                if (rowlist.AnalyzeDatas.Count != countList.Count)
                {
                    row++;
                    continue;
                }
                for (int i = 0; i < countList.Count; i++)
                {
                    if (rowlist.AnalyzeDatas[i].Value != countList[i])
                    {
                        result = false;
                    }
                }
                if (result)
                {
                    // 開いているところをマスクする
                    col = 0;
                    while (col < colNumbers.Count)
                    {
                        if (_bitmapData[row, col].IsValid() == false)
                        {
                            _bitmapData[row, col].Mask();
                        }
                        col++;
                    }
                    rowlist.Analyzed();
                }
                row++;
            }
        }

        private void pattern3Col()
        {
            int col = 0;
            foreach (var collist in colNumbers)
            {
                if (collist.IsAnalyzed())
                {
                    col++;
                    continue;
                }

                // 塗ったマスを数える
                int row = 0;
                int count = 0;
                List<int> countList = new List<int>();
                while (row < rowNumbers.Count)
                {
                    if (_bitmapData[row, col].IsPainted())
                    {
                        count++;
                    }
                    else
                    {
                        if (count != 0)
                        {
                            countList.Add(count);
                            count = 0;
                        }
                    }
                    row++;
                    if (row == rowNumbers.Count)
                    {
                        if(count != 0)
                        {
                            countList.Add(count);
                        }
                    }
                }
                // 数えた数字が全て一致すれば確定とする
                bool result = true;
                if (collist.AnalyzeDatas.Count != countList.Count)
                {
                    col++;
                    continue;
                }
                countList.Reverse();
                for (int i = 0; i < countList.Count; i++)
                {
                    if (collist.AnalyzeDatas[i].Value != countList[i])
                    {
                        result = false;
                    }
                }
                if (result)
                {
                    // 開いているところをマスクする
                    row = 0;
                    while (row < rowNumbers.Count)
                    {
                        if (_bitmapData[row, col].IsValid() == false)
                        {
                            _bitmapData[row, col].Mask();
                        }
                        row++;
                    }
                    collist.Analyzed();
                }
                col++;
            }
        }

実行結果はこちら。

ここまで来たら、パターン2とパターン3の繰り返しで全て塗れるのでは無いでしょうか?

では、パターン2とパターン3をループ処理させるのに、終了条件を判定する処理を作成します。

        private bool checkPainedCount()
        {
            int newPaintedCount = 0;
            for (int row = 0; row < rowNumbers.Count; row++)
            {
                for(int col = 0; col < colNumbers.Count; col++)
                {
                    if(_bitmapData[row, col].IsValid())
                    {
                        newPaintedCount++;
                    }
                }
            }
            if(oldPaintedCount == newPaintedCount)
            {
                return false;
            }
            oldPaintedCount = newPaintedCount;
            return true;
        }

考え方は、全てのマスが確定したかを判定し、その数を数えます。

ループ前とループ後のカウント数を比較し、変化があればカウント数を更新、変化が無ければループを終了します。

        public BitmapData[,] Run()
        {
            pattern1();
            while (checkPainedCount())
            {
                pattern2();
                pattern3();
            }
            return _bitmapData;
        }

実行結果はこうなりました。

これは・・・黒電話かな?

解けたようです。

まだ完全ではないですが、簡単な問題ならこれで解けそうです。

【C#】【ピクロス】【ALTSEED】解析パターンその2

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

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

https://github.com/takishita2nd/Picross

つぎはどう攻めていくかというと、ここに注目しましょう。

ここも塗れるのが確定していますよね。

ここを塗るための条件を確認しましょう。

マスクされていないマスを数え、連続したマスの数と、範囲の数が全て一致した場合、確定とする。

つまり、

マスクされていないマスが2個、1個、2個と並んでいて、これらが全て左の数字と一致するため、この開いているマスは全て塗ることができます。

これをコーディングします。

    // 解析パターンその2
    private void pattern2()
    {
        // Row
        pattern2Row();
        // Col
        pattern2Col();
    }
        private void pattern2Row()
        {
            int row = 0;
            foreach (var rowlist in rowNumbers)
            {
                if (rowlist.IsAnalyzed())
                {
                    row++;
                    continue;
                }

                // マスクされていない連続したマスを数える
                int col = 0;
                int count = 0;
                List<int> countList = new List<int>();
                while (col < colNumbers.Count)
                {
                    if (_bitmapData[row, col].IsMasked() == false)
                    {
                        count++;
                    }
                    else
                    {
                        if (count != 0)
                        {
                            countList.Add(count);
                            count = 0;
                        }
                    }
                    col++;
                    if(col == colNumbers.Count)
                    {
                        countList.Add(count);
                    }
                }
                // 数えた数字が全て一致すれば確定とする
                bool result = true;
                if (rowlist.AnalyzeDatas.Count != countList.Count)
                {
                    row++;
                    continue;
                }
                for (int i = 0; i < countList.Count; i++)
                {
                    if (rowlist.AnalyzeDatas[i].Value != countList[i])
                    {
                        result = false;
                    }
                }
                if (result)
                {
                    // 開いているところを塗る
                    col = 0;
                    while (col < colNumbers.Count)
                    {
                        if(_bitmapData[row, col].IsValid() == false)
                        {
                            _bitmapData[row, col].Paint();
                        }
                        col++;
                    }
                    rowlist.Analyzed();
                }
                row++;
            }
        }

        private void pattern2Col()
        {
            int col = 0;
            foreach (var collist in colNumbers)
            {
                if (collist.IsAnalyzed())
                {
                    col++;
                    continue;
                }

                // マスクされていない連続したマスを数える
                int row = 0;
                int count = 0;
                List<int> countList = new List<int>();
                while (row < rowNumbers.Count)
                {
                    if (_bitmapData[row, col].IsMasked() == false)
                    {
                        count++;
                    }
                    else
                    {
                        if (count != 0)
                        {
                            countList.Add(count);
                            count = 0;
                        }
                    }
                    row++;
                    if (row == rowNumbers.Count)
                    {
                        countList.Add(count);
                    }
                }
                // 数えた数字が全て一致すれば確定とする
                bool result = true;
                if (collist.AnalyzeDatas.Count != countList.Count)
                {
                    col++;
                    continue;
                }
                countList.Reverse();
                for (int i = 0; i < countList.Count; i++)
                {
                    if (collist.AnalyzeDatas[i].Value != countList[i])
                    {
                        result = false;
                    }
                }
                if (result)
                {
                    // 開いているところを塗る
                    row = 0;
                    while (row < rowNumbers.Count)
                    {
                        if (_bitmapData[row, col].IsValid() == false)
                        {
                            _bitmapData[row, col].Paint();
                        }
                        row++;
                    }
                    collist.Analyzed();
                }
                col++;
            }
        }

数える→数を確認する→塗るの順で処理しています。

少しデータの持ち方も変えています。

    private List<AnalyzeListData> rowNumbers;
    private List<AnalyzeListData> colNumbers;
    class AnalyzeListData
    {
        private bool _analyzed;
        public List<AnalyzeData> AnalyzeDatas;

        public AnalyzeListData()
        {
            _analyzed = false;
            AnalyzeDatas = new List<AnalyzeData>();
        }

        public bool IsAnalyzed()
        {
            return _analyzed;
        }

        public void Analyzed()
        {
            _analyzed = true;
        }
    class AnalyzeData
    {
        private bool _analyzed;
        public int Value { get; }

        public AnalyzeData(int value)
        {
            Value = value;
            _analyzed = false;
        }

        public bool IsAnalyzed()
        {
            return _analyzed;
        }

        public void Analyzed()
        {
            _analyzed = true;
        }

    }

どのデータを塗ったかどうかを確認できるようにしています。

実行結果はこうなりました。

うん、順調ですね。

【C#】【ピクロス】【ALTSEED】解析パターンその1

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

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

https://github.com/takishita2nd/Picross

さぁ、解析処理を作っていくぞ。

例題として、こちらの問題を用意しました。

データを入力。

まず、ピクロス解法の定石パターンとして、確実に塗れるところから塗っていくというもの。

この例題からすると、

この赤い部分は全て塗れるのは、すぐに分かりますよね。

さらに、

ここも塗れるのは分かりますでしょうか。

例えば、[1,4,3]と合った場合は、

■×■■■■×■■■

と10マスが確定しますよね?

これを公式化すると、

数字の合計+(数字の数ー1)=マスの数

※1+4+3+(3-1) = 10

となった場合、その列または行の塗れるマスは自動的に確定します。

これを実装します。

        // 解析パターンその1
        private void pattern1()
        {
            // Row
            pattern1Row();
            // Col
            pattern1Col();
        }

        /**
         * Rowに対して解析パターン1を適用
         */
        private void pattern1Row()
        {
            int row = 0;
            foreach (var rowlist in rowNumbers)
            {
                int total = 0;
                foreach (var v in rowlist)
                {
                    total += v;
                }
                total += rowlist.Count - 1;
                if (total == colNumbers.Count)
                {
                    // 塗れるマス確定
                    // リストを反転コピー
                    List<int> revRowList = new List<int>();
                    foreach (var v in rowlist)
                    {
                        revRowList.Add(v);
                    }
                    revRowList.Reverse();

                    int col = 0;
                    foreach (var v in revRowList)
                    {
                        int c;
                        for (c = 0; c < v; c++)
                        {
                            _bitmapData[row, col + c].Paint();
                        }
                        if(col + c < colNumbers.Count)
                        {
                            _bitmapData[row, col + c].Mask();
                            c++;
                        }
                        col += c;
                    }
                }
                row++;
            }
        }

        /**
         * Colに対して解析パターン1を適用
         */
        private void pattern1Col()
        {
            int col = 0;
            foreach (var collist in colNumbers)
            {
                int total = 0;
                foreach (var v in collist)
                {
                    total += v;
                }
                total += collist.Count - 1;
                if (total == colNumbers.Count)
                {
                    // 塗れるマス確定
                    // リストを反転コピー
                    List<int> revColList = new List<int>();
                    foreach (var v in collist)
                    {
                        revColList.Add(v);
                    }
                    revColList.Reverse();

                    int row = 0;
                    foreach (var v in revColList)
                    {
                        int r;
                        for (r = 0; r < v; r++)
                        {
                            _bitmapData[row + r, col].Paint();
                        }
                        if (row + r < rowNumbers.Count)
                        {
                            _bitmapData[row + r, col].Mask();
                            r++;
                        }
                        row += r;
                    }
                }
                col++;
            }
        }

列の数字データは右から順に並んでいるので、これを反転させる必要があります。

ただ、直接Reverse()を使用すると、インプットデータが壊れてしまうので、コピーを作成してからReverse()を使用します。

あとは、Listの先頭からピクロスのルールに従って塗っていきます。

実行結果はこうなりました。

解析がしやすくなるように、塗れないマスは×を表示するようにしました。

まぁ、ここまでは順調ですな。

【C#】【ピクロス】【ALTSEED】解析結果をUIに反映させる

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

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

https://github.com/takishita2nd/Picross

実際に解析を行うクラスを作成していきます。

まずは解析中の状態を保持するクラスを定義します。

class BitmapData
{
    public int Row { get; }
    public int Col { get; }
    // 塗るで確定
    private bool _isPaint;
    // 塗らないで確定
    private bool _isFilter;

    public BitmapData(int row, int col)
    {
        Row = row;
        Col = col;
        _isPaint = false;
        _isFilter = false;
    }

    public bool IsPainted()
    {
        return _isPaint;
    }

    public bool IsFilted()
    {
        return _isFilter;
    }

    public bool IsValid()
    {
        if(_isPaint | _isFilter)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}

多分、解析に必要なデータは、塗ったマスか、塗ることができないマスか、まだ確定していないマスか、の三つが必要になると思います。

なので、それらの状態を保持し返す事ができるメソッドを一通り用意してみました。

とりあえず、これでいいや。

必要になったら、その都度追加していく。

これを使用します。

class PicrossAnalyze
{
    List<List<int>> rowNumbers;
    List<List<int>> cowNumbers;
    BitmapData[,] _bitmapData;

    public PicrossAnalyze(List<List<NumberSquare>> rowNumberSquares, List<List<NumberSquare>> colNumberSquares)
    {
        _bitmapData = new BitmapData[rowNumberSquares.Count, colNumberSquares.Count];
        for(int r = 0; r < rowNumberSquares.Count; r++)
        {
            for(int c = 0; c < colNumberSquares.Count; c++)
            {
                _bitmapData[r, c] = new BitmapData(r, c);
            }
        }

        rowNumbers = new List<List<int>>();
        foreach(var rowList in rowNumberSquares)
        {
            List<int> list = new List<int>();
            foreach(var s in rowList)
            {
                list.Add(s.GetValue());
            }
            rowNumbers.Add(list);
        }

        cowNumbers = new List<List<int>>();
        foreach (var colList in colNumberSquares)
        {
            List<int> list = new List<int>();
            foreach (var s in colList)
            {
                list.Add(s.GetValue());
            }
            cowNumbers.Add(list);
        }
    }

    public bool[,] Run()
    {
        // モック
        bool[,] ret = new bool[10, 10] {
            {true, false, true, false, true, true, false, true, false, true },
            {true, false, true, false, true, true, false, true, false, true },
            {true, false, true, false, true, true, false, true, false, true },
            {true, false, true, false, true, true, false, true, false, true },
            {true, false, true, false, true, true, false, true, false, true },
            {true, false, true, false, true, true, false, true, false, true },
            {true, false, true, false, true, true, false, true, false, true },
            {true, false, true, false, true, true, false, true, false, true },
            {true, false, true, false, true, true, false, true, false, true },
            {true, false, true, false, true, true, false, true, false, true }
        };

        return ret;
    }
}

コンストラクタで必要なデータを渡し、必要なデータを取り出します。

とりあえず、この処理はここまで。

先に解析結果をUIに反映させる処理を書きましょう。

表示するデータは、とりあえず固定値を返すモックにしておきます。

これをUI側に実装します。

        anlyzeButton.SetAction(() =>
        {
            PicrossAnalyze picross = new PicrossAnalyze(rowNumberSquare, colNumberSquare);
            bool[,] ret = picross.Run();
            for(int row = 0; row < rowNumberSquare.Count; row++)
            {
                for(int col = 0; col < colNumberSquare.Count; col++)
                {
                    if(ret[row,col] == true)
                    {
                        drawSquares[row][col].Paint();
                    }
                }
            }
        });

実行結果はこんな感じになります。

解析状況を可視化しなければ、解析処理を作成するのに困るので、先にこちらの処理を作成した、と言うわけです。

次回は実際に問題データを入力して、簡単なロジックから作成していきましょう。

【C#】【ピクロス】【ALTSEED】セーブ、ロード機能を追加

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

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

https://github.com/takishita2nd/Picross

前回、汚いUIをどうしようか、ということを書きましたが、

見た目は後回し、という方針で行こうと思います。

さっさとUIを作って、解析ロジックに取りかかりたい、というのが本音です。

今回は、デバッグで必要になるであろう、セーブ、ロード機能を作成していきます。

デバッグの度に数字を入力するの、めんどくさいんで。

まずは、ボタン配置。

            // 解析ボタン
            var anlyzeButton = new Button(10, 50, "解析");
            anlyzeButton.Show();
            asd.Engine.AddObject2D(anlyzeButton.getBackTexture());
            asd.Engine.AddObject2D(anlyzeButton.getTextObject());
            buttons.Add(anlyzeButton);
            anlyzeButton.SetAction(() =>
            {
            });

            // セーブボタン
            var saveButton = new Button(10, 90, "セーブ");
            saveButton.Show();
            asd.Engine.AddObject2D(saveButton.getBackTexture());
            asd.Engine.AddObject2D(saveButton.getTextObject());
            buttons.Add(saveButton);
            saveButton.SetAction(() =>
            {
                FileAccess.Save(rowNumberSquare, colNumberSquare);
            });

            // ロードボタン
            var loadButton = new Button(10, 130, "ロード");
            loadButton.Show();
            asd.Engine.AddObject2D(loadButton.getBackTexture());
            asd.Engine.AddObject2D(loadButton.getTextObject());
            buttons.Add(loadButton);
            loadButton.SetAction(() =>
            {
                FileAccess.Load(ref rowNumberSquare, ref colNumberSquare);
            });

入力データを保存、読み取りするコードを書いていきます。

[JsonObject("dataModel")]
class Data
{
    [JsonProperty("rowdata")]
    public List<List<int>> RowData { get; set; }
    [JsonProperty("coldata")]
    public List<List<int>> ColData { get; set; }
}
class FileAccess
{
    private const string _filename = "save.dat";

    public static void Save(List<List<NumberSquare>> row, List<List<NumberSquare>> col)
    {
        if (File.Exists(_filename) == true)
        {
            File.Delete(_filename);
        }
        Data data = new Data();
        data.RowData = new List<List<int>>();
        foreach(var r in row)
        {
            List<int> list = new List<int>();
            foreach(var s in r)
            {
                if(s.GetValue() != 0)
                {
                    list.Add(s.GetValue());
                }
            }
            data.RowData.Add(list);
        }
        data.ColData = new List<List<int>>();
        foreach (var c in col)
        {
            List<int> list = new List<int>();
            foreach (var s in c)
            {
                if (s.GetValue() != 0)
                {
                    list.Add(s.GetValue());
                }
            }
            data.ColData.Add(list);
        }
        var str = JsonConvert.SerializeObject(data);
        using (var stream = new StreamWriter(_filename, true))
        {
            stream.Write(str);
        }
    }

    public static void Load(ref List<List<NumberSquare>> rowList, ref List<List<NumberSquare>> colList)
    {
        foreach(var r in rowList)
        {
            foreach(var s in r)
            {
                asd.Engine.RemoveObject2D(s.getBackTexture());
                asd.Engine.RemoveObject2D(s.getTextObject());
            }
        }
        foreach (var c in colList)
        {
            foreach (var s in c)
            {
                asd.Engine.RemoveObject2D(s.getBackTexture());
                asd.Engine.RemoveObject2D(s.getTextObject());
            }
        }

        string str = string.Empty;
        using (var stream = new StreamReader(_filename, true))
        {
            str = stream.ReadToEnd();
        }
        var data = JsonConvert.DeserializeObject<Data>(str);
        rowList = new List<List<NumberSquare>>();
        {
            int row = 0;
            foreach (var r in data.RowData)
            {
                List<NumberSquare> list = new List<NumberSquare>();
                int col = -1;
                foreach (var v in r)
                {
                    var s = new NumberSquare(row, col);
                    s.SetValue(v.ToString());
                    asd.Engine.AddObject2D(s.getBackTexture());
                    asd.Engine.AddObject2D(s.getTextObject());
                    list.Add(s);
                    col--;
                }
                {
                    var s = new NumberSquare(row, col);
                    s.SetValue("0");
                    asd.Engine.AddObject2D(s.getBackTexture());
                    asd.Engine.AddObject2D(s.getTextObject());
                    list.Add(s);
                }
                rowList.Add(list);
                row++;
            }
        }
        colList = new List<List<NumberSquare>>();
        {
            int col = 0;
            foreach (var c in data.ColData)
            {
                List<NumberSquare> list = new List<NumberSquare>();
                int row = -1;
                foreach (var v in c)
                {
                    var s = new NumberSquare(row, col);
                    s.SetValue(v.ToString());
                    asd.Engine.AddObject2D(s.getBackTexture());
                    asd.Engine.AddObject2D(s.getTextObject());
                    list.Add(s);
                    row--;
                }
                {
                    var s = new NumberSquare(row, col);
                    s.SetValue("0");
                    asd.Engine.AddObject2D(s.getBackTexture());
                    asd.Engine.AddObject2D(s.getTextObject());
                    list.Add(s);
                }
                colList.Add(list);
                col++;
            }
        }
    }
}

保存処理は、入力したデータをJsonに変換して、保存させます。

ロード処理は、一旦数字入力マスを全て削除し、管理データを作り直します。

C#ってrefを付けないとオブジェクトの参照渡しできないんですね。

まだまだ勉強しなくちゃいけないことが多いなぁ。

まだ問題とか、不具合あるかもしれないけど、それはその都度修正していくきます。

この昨日は完成後に削除する予定なので。

さて、次回からいよいよロジックに着手できるー!

【C#】【ピクロス】【ALTSEED】数字入力マスに数字を入力する

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

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

https://github.com/takishita2nd/Picross

前回追加した数字入力マスに数字を入力する処理を作っていきます。

数字入力はサイズ変更ダイアログと同じパレットを使用します。

            // パレット
            Palette palette = new Palette();
            palette.SetEngine();
            palette.Hide();

数字入力マスをクリックすると、パレットを表示させます。

パレット表示中は、マウスカーソルの所にある数字マスのテクスチャを色つきに変更します。

そして、数字をクリックすると、その数字が数字マスに入ります。

パレットの外をクリックすると、パレットを閉じ、入力終了になります。

これをコードに書いたらこうなりました。

            while (asd.Engine.DoEvents())
            {
                asd.Vector2DF pos = asd.Engine.Mouse.Position;
                if (!dialog.IsShow())
                {
                    if (!palette.IsShow())
                    {
                        foreach (var rowList in rowNumberSquare)
                        {
                            foreach (var s in rowList)
                            {
                                s.UpdateTexture(pos);
                            }
                        }
                        foreach (var colList in colNumberSquare)
                        {
                            foreach (var s in colList)
                            {
                                s.UpdateTexture(pos);
                            }
                        }
                        foreach (Button button in buttons)
                        {
                            button.UpdateTexture(pos);
                        }
                    }
                    else
                    {
                        palette.UpdateTexture(pos);
                    }
                }
                else
                {
                    dialog.UpdateTexture(pos);
                }

                if (asd.Engine.Mouse.LeftButton.ButtonState == asd.ButtonState.Push)
                {
                    if (!dialog.IsShow())
                    {
                        if (!palette.IsShow())
                        {
                            int rowIndex;
                            int colIndex;
                            rowIndex = 0;
                            foreach (var rowList in rowNumberSquare)
                            {
                                colIndex = -1;
                                foreach (var s in rowList)
                                {
                                    if (s.IsClick(pos))
                                    {
                                        selectedNumberSquare = s;
                                        selectedRowIndex = rowIndex;
                                        selectedColIndex = colIndex;
                                        palette.Show(pos);
                                    }
                                    colIndex--;
                                }
                                rowIndex++;
                            }
                            colIndex = 0;
                            foreach (var colList in colNumberSquare)
                            {
                                rowIndex = -1;
                                foreach (var s in colList)
                                {
                                    if (s.IsClick(pos))
                                    {
                                        selectedNumberSquare = s;
                                        selectedRowIndex = rowIndex;
                                        selectedColIndex = colIndex;
                                        palette.Show(pos);
                                    }
                                    rowIndex--;
                                }
                                colIndex++;
                            }
                            foreach (Button button in buttons)
                            {
                                if (button.IsClick(pos))
                                {
                                    button.OnClick();
                                }
                            }
                        }
                        else
                        {
                            if (palette.IsClick(pos))
                            {
                                string v = palette.GetClickValue(pos);
                                updateTextValue(selectedNumberSquare, v);
                            }
                            else
                            {
                                palette.Hide();
                                if(selectedNumberSquare != null)
                                {
                                    if(selectedNumberSquare.GetStringValue() != string.Empty)
                                    {
                                        if (selectedRowIndex >= 0)
                                        {
                                            var square = new NumberSquare(selectedRowIndex, selectedColIndex - 1);
                                            asd.Engine.AddObject2D(square.getBackTexture());
                                            asd.Engine.AddObject2D(square.getTextObject());
                                            rowNumberSquare[selectedRowIndex].Add(square);
                                        }
                                        else if (selectedColIndex >= 0)
                                        {
                                            var square = new NumberSquare(selectedRowIndex - 1, selectedColIndex);
                                            asd.Engine.AddObject2D(square.getBackTexture());
                                            asd.Engine.AddObject2D(square.getTextObject());
                                            colNumberSquare[selectedColIndex].Add(square);
                                        }
                                    }
                                }
                            }
                        }
                    }
                    else
                    {
                        dialog.OnClick(pos);
                    }
                }

実行結果はこんな感じになります。

数字入力を終えると、さらに入力スペースが増えるように細工しています。

ただ、このあたりの処理は変更するかもしれません。

ここどうするのが良いのかなーと言うのを未だに考えています。

【ラズパイ】Sense HATのジャイロセンサー

Sense Hatのジャイロセンサーを使っていろいろやってみようと思ったものの、思ったような結果が得られなかったので、もっとドキュメントを読み込んでいこうと思います。

ドキュメントはこれ(英語)。

https://github.com/raspberrypilearning/astro-pi-guide/blob/master/sensors/movement.md

Sense HatにはIMUというものが搭載されているらしい。

IMUにはジャイロセンサー、加速度センサー、方位磁石が搭載されています。

向きは、Pitch、Roll、Yawの三つの軸で表されます。

まぁ、これはこないだ見た。

この動画で三つの軸の関係性が分かります。

これに関するサンプルプログラムがありました。

使い方書いてあるけど、このやり方では動かなかったので、実際に試したやり方を書いておきます。

> git clone git://github.com/astro-pi/apollo-soyuz
> cd apollo-soyuz
> sudo pip3 install pi3d
> sudo ./soyuz.py

SDカードスロットがメインブースターになるイメージです。

実際に動かしてみました。

Roll軸で回転させてみたところ、それに伴って向き自体が左右に動いている事が分かりました。

Sense Hat(IMU)に搭載されているジャイロセンサーは宇宙船を操作することに特化されていて、単純に傾けたときの3軸の角度が得られる訳では無い(ような気がする)。

宇宙船補正みたいなものがあるんじゃないか。

しらんけど。

多分、思ったような数値が出なかったのは、このせいなんだろうな。

そうだよな、Sense Hatって宇宙の科学実験用のアタッチメントだもんな。

まぁ、ちょっと宇宙船の技術に触れられたのは良かったと思う(という風に、前向きに捉える)。

【ラズパイ】受信したセンサーデータを表示する。

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

前回はラズパイからセンサーで収集したデータを送信させていましたが、フォーマットがJsonなので、このままでは使用できません。

ラズパイ側のソースコードをgitHubにアップしました。

https://github.com/takishita2nd/pi/blob/master/http.py

今回はこのデータを見やすいように加工します。

GUIのほうが見やすいと思って、軽い気持ちで作ってみたのですが、思った以上にがっつりなことになってしまいました。

WPFを使用しようと思うのですが、今回はPrismという拡張機能を使用してMVVMモデルで作成しようと思います。

メニューの拡張機能からPrismを検索し、Prismをインストールします。

Visual Studioを再起動すると、新規プロジェクトにPrismが追加されているので、Prismのプロジェクトを作成します。

まずは、XAMLの作成。

<Window x:Class="BlankApp1.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:prism="http://prismlibrary.com/"
        prism:ViewModelLocator.AutoWireViewModel="True"
        Title="{Binding Title}" Height="180" Width="350">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Label Grid.Row="0" Grid.Column="0" Content="温度" HorizontalAlignment="Center" VerticalAlignment="Center"/>
        <Label Grid.Row="1" Grid.Column="0" Content="湿度" HorizontalAlignment="Center" VerticalAlignment="Center"/>
        <Label Grid.Row="2" Grid.Column="0" Content="気圧" HorizontalAlignment="Center" VerticalAlignment="Center"/>
        <Label Grid.Row="0" Grid.Column="1" Content="{Binding Temperature}"  HorizontalAlignment="Center" VerticalAlignment="Center"/>
        <Label Grid.Row="1" Grid.Column="1" Content="{Binding Humidity}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
        <Label Grid.Row="2" Grid.Column="1" Content="{Binding Pressure}"  HorizontalAlignment="Center" VerticalAlignment="Center"/>
        <Button Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" Content="Monitor" Command="{Binding ButtonClickCommand}" HorizontalAlignment="Center" VerticalAlignment="Center" Height="20" />
    </Grid>
</Window>

温度、湿度、気圧を表示するだけです。

あとはデータの待ち受けを開始するボタンを配置します。

Jsonを展開するためのクラスを定義します。

    [JsonObject("sensorModel")]
    class Sensor
    {
        [JsonProperty("temperature")]
        public int Temperature { get; set; }
        [JsonProperty("humidity")]
        public int Humidity { get; set; }
        [JsonProperty("pressure")]
        public int Pressure { get; set; }
    }

クラス名は何でもいいですが、パラメータの名前は送信側と合わせる必要があります。

最後にViewModelの処理です。

    public class MainWindowViewModel : BindableBase
    {
        private string _title = "ラズパイモニター";
        public string Title
        {
            get { return _title; }
            set { SetProperty(ref _title, value); }
        }
        private int _temperature;
        private int _humidity;
        private int _pressure;
        public int Temperature 
        { 
            get { return _temperature; } 
            set { SetProperty(ref _temperature, value); }
        }
        public int Humidity
        {
            get { return _humidity; }
            set { SetProperty(ref _humidity, value); }
        }
        public int Pressure
        {
            get { return _pressure; }
            set { SetProperty(ref _pressure, value); }
        }

        public DelegateCommand ButtonClickCommand { get; }

        public MainWindowViewModel()
        {
            ButtonClickCommand = new DelegateCommand(() =>
            {
                Thread thread = new Thread(new ThreadStart(() => {
                    try
                    {
                        HttpListener listener = new HttpListener();
                        listener.Prefixes.Add("http://192.168.1.3:8000/");
                        listener.Start();
                        while (true)
                        {
                            HttpListenerContext context = listener.GetContext();
                            HttpListenerRequest req = context.Request;
                            using (StreamReader reader = new StreamReader(req.InputStream, req.ContentEncoding))
                            {
                                string s = reader.ReadToEnd();
                                Sensor sensor = JsonConvert.DeserializeObject<Sensor>(s);
                                Temperature = sensor.Temperature;
                                Humidity = sensor.Humidity;
                                Pressure = sensor.Pressure;
                            }
                            HttpListenerResponse res = context.Response;
                            res.StatusCode = 200;
                            byte[] content = Encoding.UTF8.GetBytes("HELLO");
                            res.OutputStream.Write(content, 0, content.Length);
                            res.Close();
                        }
                    }
                    catch (Exception ex)
                    {
                    }
                }));

                thread.Start();

            });
        }
    }

ボタンを押すとHTTPを待ち受け開始し、HTTPを受信したらデータ(Json)を展開しオブジェクト化します。

そして、各パラメータをプロパティに設定します。

動作結果はこうなりました。

うん、想定通り。

【C#】【ピクロス】【ALTSEED】数字入力マスを追加する。

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

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

https://github.com/takishita2nd/Picross

ピクロス解析のヒントとなる数字の入力マスを作成していきます。

イメージとしては、マウスカーソルが上にあると色が変化し、クリックするとパレットを表示する、という感じです。

まずはマスを作成しましょう。

数字を入力するマスのクラスを作成します。

class NumberSquare : ObjectBase
{
    protected int _row;
    protected int _col;
    protected const int setPositionX = 200;
    protected const int setPositionY = 200;
    protected const int fontOffsetX = 19;
    protected const int fontOffsetY = 9;
    private string _value;

    public NumberSquare(int row, int col)
    {
        width = 32;
        height = 32;
        _value = string.Empty;
        _row = row;
        _col = col;
        _x = col * width + setPositionX;
        _y = row * height + setPositionY;

        _backTexture = new asd.TextureObject2D();
        _backTexture.Texture = Resource.GetPicrossTexture();
        _backTexture.Position = new asd.Vector2DF(_x, _y);

        _valueText = new asd.TextObject2D();
        _valueText.Font = Resource.getFont();
        _valueText.Position = new asd.Vector2DF(_x + fontOffsetX, _y + fontOffsetY);
    }

    public void SetValue(string value)
    {
        _value = value;
        _valueText.Text = _value;
    }

    public void UpdateTexture(asd.Vector2DF pos)
    {
        if (pos.X > _x && pos.X < _x + width
            && pos.Y > _y && pos.Y < _y + height)
        {
            _backTexture.Texture = Resource.GetSelectedNumberTexture();
        }
        else
        {
            _backTexture.Texture = Resource.GetPicrossTexture();
        }
    }

    public int getValue()
    {
        return int.Parse(_value);
    }
}

これをUIに配置します。

マスのオブジェクトは

List<List<NumberSquare>>

の形で保持します。

一番外のList<>はROWまたはCOLのサイズ分だけ持ち、List<List<>>は入力した数字の数だけ保持します。

これをROW、COLの二つ持ちます。

    private List<List<NumberSquare>> rowNumberSquare = new List<List<NumberSquare>>();
    private List<List<NumberSquare>> colNumberSquare = new List<List<NumberSquare>>();

配置。

        // 数字の入力するマス
        for(int row = 0; row < 10; row++)
        {
            NumberSquare square = new NumberSquare(row, -1);
            asd.Engine.AddObject2D(square.getBackTexture());
            asd.Engine.AddObject2D(square.getTextObject());
            List<NumberSquare> rowList = new List<NumberSquare>();
            rowList.Add(square);
            rowNumberSquare.Add(rowList);
        }
        for (int col = 0; col < 10; col++)
        {
            NumberSquare square = new NumberSquare(-1, col);
            asd.Engine.AddObject2D(square.getBackTexture());
            asd.Engine.AddObject2D(square.getTextObject());
            List<NumberSquare> colList = new List<NumberSquare>();
            colList.Add(square);
            colNumberSquare.Add(colList);
        }

マウスがマスの上にきたときの処理。

        while (asd.Engine.DoEvents())
        {
            asd.Vector2DF pos = asd.Engine.Mouse.Position;
            if(!dialog.IsShow())
            {
                foreach (var rowList in rowNumberSquare)
                {
                    foreach (var s in rowList)
                    {
                        s.UpdateTexture(pos);
                    }
                }
                foreach (var colList in colNumberSquare)
                {
                    foreach (var s in colList)
                    {
                        s.UpdateTexture(pos);
                    }
                }
                foreach (Button button in buttons)
                {
                    button.UpdateTexture(pos);
                }
            }

そして、忘れてはいけないのが、サイズ変更を行ったとき。

サイズ変更を行ったら、それに合わせてListの数も変更しなければいけません。

        dialog.SetAction(() =>
        {
            dialog.Hide();
            int row = dialog.GetRowValue();
            int col = dialog.GetColValue();
            while(row != drawSquares.Count)
            {
                if(drawSquares.Count > row)
                {
                    {
                        // マスの削除
                        var rowList = drawSquares[drawSquares.Count - 1];
                        foreach (var c in rowList)
                        {
                            asd.Engine.RemoveObject2D(c.getBackTexture());
                        }
                        drawSquares.RemoveAt(drawSquares.Count - 1);
                    }

                    {
                        // 数字のマスの削除
                        var rowList = rowNumberSquare[rowNumberSquare.Count - 1];
                        foreach (var s in rowList)
                        {
                            asd.Engine.RemoveObject2D(s.getBackTexture());
                            asd.Engine.RemoveObject2D(s.getTextObject());
                        }
                        rowNumberSquare.RemoveAt(rowNumberSquare.Count - 1);
                    }
                }
                else
                {
                    {
                        // マスの追加
                        List<DrawSquare> rowList = new List<DrawSquare>();
                        for (int c = 0; c < drawSquares[0].Count; c++)
                        {
                            var square = new DrawSquare(drawSquares.Count, c);
                            asd.Engine.AddObject2D(square.getBackTexture());
                            rowList.Add(square);
                        }
                        drawSquares.Add(rowList);
                    }

                    {
                        // 数字のマス追加
                        var square = new NumberSquare(rowNumberSquare.Count, -1);
                        asd.Engine.AddObject2D(square.getBackTexture());
                        asd.Engine.AddObject2D(square.getTextObject());
                        List<NumberSquare> list = new List<NumberSquare>();
                        list.Add(square);
                        rowNumberSquare.Add(list);
                    }
                }
            }
            while(col != drawSquares[0].Count)
            {
                if(drawSquares[0].Count > col)
                {
                    {
                        // マスの削除
                        foreach (var r in drawSquares)
                        {
                            asd.Engine.RemoveObject2D(r[r.Count - 1].getBackTexture());
                            r.RemoveAt(r.Count - 1);
                        }
                    }

                    {
                        // 数字のマスの削除
                        var colList = colNumberSquare[colNumberSquare.Count - 1];
                        foreach (var s in colList)
                        {
                            asd.Engine.RemoveObject2D(s.getBackTexture());
                            asd.Engine.RemoveObject2D(s.getTextObject());
                        }
                        colNumberSquare.RemoveAt(colNumberSquare.Count - 1);
                    }
                }
                else
                {
                    {
                        // マスの追加
                        int rowindex = 0;
                        int colindex = drawSquares[0].Count;
                        foreach (var r in drawSquares)
                        {
                            var square = new DrawSquare(rowindex, colindex);
                            asd.Engine.AddObject2D(square.getBackTexture());
                            r.Add(square);
                            rowindex++;
                        }
                    }

                    {
                        // 数字のマス追加
                        var square = new NumberSquare(-1, colNumberSquare.Count);
                        asd.Engine.AddObject2D(square.getBackTexture());
                        asd.Engine.AddObject2D(square.getTextObject());
                        List<NumberSquare> list = new List<NumberSquare>();
                        list.Add(square);
                        colNumberSquare.Add(list);
                    }
                }
            }
        });

動作はこんな感じになりました。

次回は実際に数字を入力する処理を追加します。

【ラズパイ】ネットワークでセンサーデータを送信する。

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

今回は実際にセンサーの情報をWindows側に送信したいと思います。

ラズパイ側のPythonソースをこのように作成しました。

import json
import urllib.request
import time
from sense_hat import SenseHat

sense = SenseHat()

url = 'http://192.168.1.3:8000'
headers = {
    'Content-Type': 'application/json',
}

while True:
    temp = int(sense.get_temperature())
    hum = int(sense.get_humidity())
    press = int(sense.get_pressure())
    data = {
        'temperature': temp,
        'humidity': hum,
        'pressure': press,
    }
    req = urllib.request.Request(url, json.dumps(data).encode(), headers)
    with urllib.request.urlopen(req) as res:
        body = res.read()
    time.sleep(1)

センサーから温度、湿度、気圧の測定データを取得し、このデータをJson形式にして送信しています。

この処理を1秒周期で実行します。

最近のWebAPIは送受信するデータの量が増えてきているので、どのようなデータ形式も一行の文字列で表現できるJsonを使用するのが今の一般的です。

Windows側(サーバ側)の出力はこうなりました。(受信側のコードは前回から変更していません。)

温度と湿度は、ラズパイ本体からの熱の影響を受けているので、実際の部屋の中の数値とは異なりますが、まぁ、良い感じです。