たくのろじぃのメモ部屋

プログラミング関係や数学、物理学などの内容を備忘録として残すブログ。

【宣伝】QiitaにてMinecraft自動化の記事を書きました

2019年12月8日現在のブログの反響?について

本当にありがたい話で、Minecraft自動化の記事を書いてからいろんな方々にお話を伺う機会が増えました。
Microsoftに勤務されている方から直接メッセージをいただいたときは本当に嬉しく、モチベーションにつながりました。

自動化について

今までに書いてきたブログ記事をまとめて、「この手順を踏めばあなたも自動化できます!」という内容でQiitaのほうに投稿しました。

qiita.com

内容としてはサーバの設定方法から自動建築なので、ブログの#1~#4と#7までをまとめました。

これから自動化してみたい!という方にはオススメの記事ですのでぜひご覧ください。

今後について

今後も自動化について検証を行っていきたいと考えています。建築自動化までやるとネタに困ってしまうのですが、こんなものを見つけました。

f:id:takunology:20191208144110j:plain

  • プレイヤーの生存状況
  • 相手プレイヤーまでの距離(何ブロック先か)

についてできるか否かの質問です。回答の中に「メモリを解析」とありますが、そんな低レイヤでアクセスしなくてもできそうです。生存状況はログから、距離は相手の名前でテレポートし、その座標と自分の座標との差をとれば...みたいな...。

まあ、こんな感じで進めていければなと思います。

それから、過去の記事を読んでいただくと分かりますが、実は深層学習についても勉強しております。何に応用しようかと考えていましたが、Minecraftの村人に実装して会話できるようにしたら面白いと思います。私はソードアートオンラインというアニメが好きなのですが、まさに仮想環境でAIを搭載したNPCを作るような感覚です。

今の状況について

学生ですので、課題等に追われております...。
課題を優先していると自動化の記事が後回しになってしまうので、落ち着いたら再開しようと思います。

ただし、課題で使ったプログラムや手法についてはブログ記事に書いていこうと考えているので、ブログ自体の更新はされると思います。

Minecraft Education Edition が素晴らしすぎた話

2020年に小学校でプログラミング教育が必修化します。で、どんな内容か調べてみたらブロックを並べる、いわゆるビジュアルプログラミングでした。

正直、「なーにがビジュアルプログラミングだ」と。「男は黙ってガリガリコードを書け」と。言いたいところでしたが、思わず「スゲェなおい」と驚いてしまいました(笑)。

今回は、そんないい意味で期待を裏切られた内容を書いていきます。

f:id:takunology:20191119194303p:plain

1. 教育版なのに、自由度は高い

これです。これ。普通、教育版というと「ゲーム要素なし」というような機能制限があると思いますが、なんと普通にサバイバルモードで遊べます。マインクラフトできるんです!

f:id:takunology:20191119190310p:plain

ウィンドウのタイトルがしっかり Minecraft Education Edition になってます。

もちろん、マルチプレイも可能です。

f:id:takunology:20191119190555p:plain

f:id:takunology:20191119190623p:plain

教育版と言えど、ここまでできると通常版のマインクラフトと同じですね。

しかし、これだけでないのが教育版です。

2. プログラミング学習コースが充実

View Library をクリックすると、なんと!

f:id:takunology:20191119191822p:plain

選べる4つのモード。そしてLESSONSです。

f:id:takunology:20191119191914p:plain

すごくないですか?こんなにたくさんのコースが!

イクラってこんなに高機能でしたっけ???

私が一番注目しているのはComputer Scienceです。

f:id:takunology:20191119192245p:plain

選り取りみどりですね。緑だけに...。試しにHour Codeをやってみます。

困ったらこのお姉さん先生に聞いてみましょう。

f:id:takunology:20191119194118p:plain

3. 説明が易しく、読み上げにも対応!

これ一番鳥肌立ちました(笑)

f:id:takunology:20191119194006p:plain

f:id:takunology:20191119194024p:plain

まさか読み上げ機能までついているとは思いませんでした。あらゆるユーザに配慮されていますね!

話しかけた後はプログミング環境が自動で立ち上がります。やり方も動き付きなのでとても分かりやすいです。

f:id:takunology:20191119194932p:plain

f:id:takunology:20191119195051p:plain

指示に従ってプログラミングすると...

f:id:takunology:20191119195415p:plain

f:id:takunology:20191119195432p:plain

門が開きました!

これだけでも嬉しいですね。レッドストーン回路や感圧版なしに開閉できるのはなんか不思議な感覚だと思います。

この続きもすごく楽しかったのですが、長くなってしまうので教育版マインクラフトを実況している方たちに任せて、ここで終わります。本当に楽しいツールです。

4. ビジュアライズならではの良さと親切設計

実際にプログラムの動きを視覚的に確認できるので、見てて楽しいです。エージェントを自分の思い通りに動かす楽しさはやってみないと分かりませんが、本当に「ああ、ちゃんと動いているんだな」と実感できます。

また、分からないときは近くにいるガイドさんから説明を受けることができます。さらに、間違えても何度でも挑戦できるため、トライ&エラーを経験できるところも1つの魅力なのではないかと思います。

ちなみに、レッスンを受け終わると賞状を受け取ることができます。達成感ありますね!

f:id:takunology:20191119214559p:plain

5. Code Connection アプリでもっと自由に

ちなみに、このアプリを入れると、自分で好きなコーディングを行うことができます。

f:id:takunology:20191119214850p:plain

もっと自由にプログラミングしたいと思ったらインストールしましょう。

で、このアプリを起動するとコマンドを入力するように指示されるので、マイクラでコマンドを入力します。

f:id:takunology:20191119215113p:plain

f:id:takunology:20191119215128p:plain

コマンドを入力すると、エージェントが出現します。

そしてエディタを選んでプログラムを書くと

f:id:takunology:20191119223635p:plain

エージェントがプログラム通りに動いてくれます。これでタダ働きさせられますね!

f:id:takunology:20191119223659p:plain

また、作ったビジュアルプログラムはJavaScriptに変換することもできます。同時に言語まで勉強できる最強の環境です!

さいごに

Minecraft Education Editionはプログラミング初心者に優しくて楽しいツールだと思います。

ぜひ、日本の学校でも積極的に取り入れて活用してほしいです。おそらく、図形を作る、計算するだけのプログラミングだけだと飽きてくると思います。しかし、教育版マインクラフトは飽きさせないよう、たくさんのレッスンや自由度の高い機能を備えています。

Hello, Worldで入門する時代は終わりました。これからは目に鮮やかなプログラミングで入門する時代が来ると思います。


ビジュアルプログラミングを終えた後は、ぜひコードをガリガリ書きましょう。
そのときはぜひ、C#で。

【C#】Minecraft自動化 #7 建築自動化

はじめに

いよいよ建築自動化を行います。この内容が個人的に一番やりたかったことです。

建築は設計図をもとにブロックを配置していきたいと考えていますが、設計図をどうするかが問題ですね。3次元的に表したいので、CADと行きたいところですが、それはちょっとレベルが高いのでEXCELで作りたいと思います。

EXCELシートの行と列はX方向とZ方向、シートの枚数はY方向として、建築自動化プログラムを書いていきます。

1. EXCELファイルを読み込む

Googleで調べていると、COM参照すべきかしないべきかでいろいろ議論されており、やめたほうがいいという記事を見つけました。

qiita.com

そこで、今回はNPOIパッケージを使います。

www.nuget.org

f:id:takunology:20191116225252p:plain

あとはEXCELデータを読み込む処理を書いていきます。

public class ReadExcelFile
{
    public void OpenExcelFile()
    {
        var Path = "../../../Excel/TestBook.xlsx";
        var Book = WorkbookFactory.Create(Path);
        var Sheet = Book.GetSheetAt(0); //1枚目

        GetValue(Sheet, 0, 0); //シート1枚目の1行A列を取得

    }

    private void GetValue(ISheet Sheet, int Column, int Row)
    {
        var row = Sheet.GetRow(Row) ?? Sheet.CreateRow(Row); //例外対策
        var cell = row.GetCell(Column) ?? row.CreateCell(Column);
        string value;

        value = cell.StringCellValue;

        Console.WriteLine(value);
    }
}

動かしてみます。動かすときはExcelを閉じないとアクセス権を得られないので注意しましょう。

f:id:takunology:20191117010237p:plain

さて、文字列はこのままでいいのですが、数値の場合はちょっと工夫が必要です。NumericCellValueメソッドを用います。しかし、これはdouble型の返り値を返すので他の型へ変換する必要があります。文字列にしたいときはこのようにします。

value = cell.NumericCellValue.ToString();

他の型ならParseメソッドを使うと良いかもしれません。

また、シートごとにデータを見るためのメソッドを作ってみました。Valueを3次元配列で宣言してそれぞれシート数、行、列の順にアクセスできるようにしました。

public static void ShowSheet()
{
    for (int y = 0; y < Value.GetLength(0); y++)
    {
        Console.WriteLine($"{y + 1}枚目のシート");
        for (int x = 0; x < Value.GetLength(1); x++)
        {
            for (int z = 0; z < Value.GetLength(2); z++)
            {
                Console.Write(string.Format("{0, 3}", ($"{Value[y, x, z]}")));
            }
            Console.WriteLine();
        }
    }
}

f:id:takunology:20191117035452p:plain

さて、これでExcelファイルの読み込みはOKです。

2. ブロック名の定義

次に、得た値をマイクラのブロックIDに変換します。といっても、switch文で場合分けすればいいと思います。

public static void Convert()
{
    for (int y = 0; y < Value.GetLength(0); y++)
    {
        for (int x = 0; x < Value.GetLength(1); x++)
        {
            for (int z = 0; z < Value.GetLength(2); z++)
            {
                string value = Value[y, x, z];
                switch (value)
                {
                    case "0":
                        Value[y, x, z] = "air";
                        break;

                    case "1":
                        Value[y, x, z] = "stone";
                        break;

                    case "2":
                        Value[y, x, z] = "grass";
                        break;

                    case "3":
                        Value[y, x, z] = "glass";
                        break;

                    case "4":
                        Value[y, x, z] = "planks";
                        break;

                    case "5":
                        Value[x, y, z] = "sandstone";
                        break;
                }
            }
        }
    }
}

これをマイクラのコマンド送信メソッドに投げればいい感じに動きそうです。

3. Excel設計図をもとにした自動建築

Excelに設計図を書きます。例えば上のプログラムに合わせるならば、1は石、2は草ブロックというようにしてセルを埋めていきます。

セルを埋めたらプログラムを動かしていきます。ブロックのIDはWikiで調べるなどして登録しておくと、建築バリエーションが増えます。ちなみに色々改善して動かした結果はこんな感じです。

www.youtube.com

今回作ったコード

参考にどうぞ。

github.com

参考

NPOIの使い方で参考になりました。ありがとうございます。

www.sejuku.net

【C#】Minecraft自動化 #6 迷路の生成

はじめに

イクラ自動化はもともとC#でどこまでできるかを検証するためにやってきましたが、どうやら本当に色々できるようです。

とりあえず座標移動(テレポート)と配置(ブロック配置)ができれば何でもできそうです。

今回は迷路を作ります。これは建設自動化のための準備みたいなものです。

1. 外壁をつくる

迷路を作るためには、まずは外壁を作る必要があります。

外壁ロジックは、最初と最後の座標だけ囲みます。座標を二次元配列として扱ったほうが分かりやすいので、二次元配列を使ってみました。GetLengthメソッドを用いると、その配列の要素数を得ることができます。

ロジックとしては、まずはX方向の座標から考えます。最初と最後のX方向は壁にするので、そのX座標が最初か最後かを判定させます。Z方向も同様です。これをぶん回せば迷路の壁が生成されます。

static void Wall()
{
    for (int i = 0; i < Maze.GetLength(0); i++)
    {
        if (i == 0 || i == (Maze.GetLength(0) - 1)) // X方向の始めと終り
        {
            for (int j = 0; j < Maze.GetLength(1); j++)
            {
                Maze[i, j] = 1;
                Console.Write(Maze[i, j]); //壁生成
            }
            Console.WriteLine();
        }
        else
        {
            for (int j = 0; j < Maze.GetLength(1); j++)
            {
                if (j == 0 || j == (Maze.GetLength(1) - 1)) // Z方向の始めと終り
                {
                    Maze[i, j] = 1;
                    Console.Write(Maze[i, j]); //壁生成
                }
                else
                {
                    Maze[i, j] = 0;
                    Console.Write(Maze[i, j]);
                }
            }
            Console.WriteLine();
        }
    }
}

f:id:takunology:20191111171230p:plain

2 壁伸ばし法の実装

これは自分のアルゴリズムに対する理解力が十分でないため、不完全なところがあります。

壁伸ばし法は2通りあります。外壁から壁をランダムな方向に伸ばしていく方法と、ランダムな座標から外壁に向かって伸ばしていく方法です。私は後者を選択しました。

手順というかロジックはこんな感じですかね...。

  • ランダムに壁伸ばし開始座標を選ぶ
  • その座標からランダムな方向へ壁を伸ばす
  • 1マスずつ伸ばすと隣接した壁がくっつく可能性があるので、2マスずつ伸ばす
  • このとき、隣接した座標とその隣の座標が通路でないと伸ばせない
  • ただし、その座標の2つ先が外壁だったら1マス伸ばして開始座標を選びなおす
  • 座標が外壁に達したら、再びランダムな開始座標を選びなおす
  • 開始座標候補がすでに全て壁なら迷路完成

ただ、これだと不完全で伸ばしている最中の壁の座標を保持しておき、新しく伸ばそうとしている壁の座標が、すでに存在すればその方向には伸ばさないという手法も必要らしいです。が、今現在理解できていないので組み込んでいません。スタックに保持する必要があるみたいですが...。

一応ロジックの一部を載せますが、微妙です。

static void Generate()
{
    int PositionX; //開始座標
    int PositionY; //開始座標
    int count = 0;
    Random rnd = new Random();
ReGenerate:
    while (true)
    {
        PositionX = rnd.Next(2, (Maze.GetLength(0) - 2));
        PositionY = rnd.Next(2, (Maze.GetLength(1) - 2));
        count++;
        if (PositionX % 2 == 0 && PositionY % 2 == 0)
        {//偶数になるまで再抽選
            if (Maze[PositionX, PositionY] != "■") //その座標に壁が存在しないか
                break;
        }
    }
    int direction = Direction();

    while (PositionX != 0 || PositionY != 0 || PositionX != (Maze.GetLength(0) - 1) || PositionY != (Maze.GetLength(1))) //その座標が外壁でない限り繰り返す
    {
        direction = Direction();
        Maze[PositionX, PositionY] = "■";

        switch (direction)
        {
            case 0://上
                if ((Maze[PositionX - 1, PositionY] == " ") && (Maze[PositionX - 2, PositionY] == " "))
                { //隣接が通路かつその隣も通路
                    Maze[PositionX - 1, PositionY] = "■";
                    Maze[PositionX - 2, PositionY] = "■";
                    PositionX -= 2;
                }
                else if (PositionX - 2 == 0) //もし現在の座標の2マス上が壁だったら、1マス上を壁にする
                {//壁に隣接した部分を壁にしたので作り直し
                    Maze[PositionX - 1, PositionY] = "■";
                    break;
                }
                else if ((Maze[PositionX - 2, PositionY] == "■") && (Maze[PositionX + 2, PositionY] == "■") && (Maze[PositionX, PositionY - 2] == "■") && (Maze[PositionX, PositionY + 2] == "■"))
                { //うずまき対策
                    goto ReGenerate;
                }
                else
                {
                    break;
                }
                break;
        //下、左、右の方向ロジックも書く。
        }
        Console.Clear();
        Console.WriteLine($"X:{PositionX} Y:{PositionY} 方向:{direction}");
        ShowMaze();
    }
}

迷路の途中経過を表示する関数と方向を決める関数

static void ShowMaze()
{
    for (int x = 0; x < Maze.GetLength(0); x++)
    {
        for (int y = 0; y < Maze.GetLength(1); y++)
        {
            Console.Write(Maze[x, y]);
        }
        Console.WriteLine();
    }
}

static int Direction() //方向を決める
{
    Random rnd = new Random();
    int direction = rnd.Next(0, 4);
    return direction;
}

実行してみるとこんな感じですね。

f:id:takunology:20191114200441p:plain

ちょっと不完全ですが、このまま続けます。いつか必ずリベンジを...。

3 Minecraft で実行してみる

あとは Console.WriteLineの部分を RCON 送信メソッドに置き換えるなりすれば迷路が生成できます。

あと、いままで遅延を発生するために

Thread.Sleep(10);

と書いていましたが、正しくは

await Task.Delay(10);

でした。どうやら非同期処理中に非同期でない遅延メソッドを使うとそこで止まってしまうようです。なぜかはわかりませんが...。

動かしてみるとこんな感じになります。

www.youtube.com

おわりに

今回は迷路(?)を作りました。まだアルゴリズムが未熟な部分があるのでいつか修正したいと思います。

次は建築やってみたいですね。エクセルシートの設計図をもとに自動建築を行いたいと考えています。

今回作ったコード

少し汚いコードですが、参考にどうぞ。

github.com

参考

壁伸ばし法で参考になりました。ありがとうございます。

www5d.biglobe.ne.jp

algoful.com

こちらは非同期遅延メソッドで参考にしました。ありがとうございます。

qiita.com

【C#】JSONファイルを読み込む

JSONファイル読み込みを行うことになったのでやり方をメモ。

1. JSONファイルの読み込み

まずはjsonファイルのパスを指定して内容を読み込みます。jsonldファイルもいけます。

string FilePath = @"hoge/huga.json"; //ファイルパス
var json = File.ReadAllText(FilePath); // ファイル内容をjson変数に格納

jsonを読み込むにはメモリストリームが必要です。メモリストリームはデータの読み込みをバイト単位で行えます。 エンコードはUTF8で行います。

var ms = new MemoryStream(Encoding.UTF8.GetBytes((json)));

2. jsonデータの格納

また、jsonにはKeyとValueといった具合にデータが記述されていますので、これを取り出してValueを変数に格納したいところです。 jsonのKeyを取得するにはデータコントラクト属性を用います。

データコントラクト属性はもともとXmlファイルのデータをシリアル化するものでしたが、jsonにも対応しています。シリアライズ(書き込み)とデシリアライズ(読み込み)ができます。

例えば、jsonファイルの "id" 属性を取得したい場合はデータコントラクト属性のクラスを作成し、データメンバ属性でメンバ変数を宣言します。データメンバの Name に取得したいjsonのKeyを入れると、その値が取得できます。ただし、Key名が正しくないと取得できないので注意が必要です。Valueも型を合わせます。

[DataContract] //データコントラクト属性
public partial class JsonData
{
    [DataMember(Name = "id")] //データメンバ属性
    public int Value { get; set; }
}

このようにしてjsonから取得したいデータをデータコントラクトになげて、そのデータをメンバ変数で管理します。

3. JSONファイルのデータ格納

jsonファイルの読み込みは先頭ビットから読み込みたいので、ストリームの位置を先頭にします。読み込み次第、そのデータを変数に格納したいので、as演算子で変数を宣言したクラスに変換します。

ms.Seek(0, SeekOrigin.Begin); // ストリームの先頭
var data = serializer.ReadObject(ms) as JsonData; //データをJsonDataクラスに変換

あとはデータが正しく入力されているかを確認するだけです。

Console.WriteLine($"ID : {data.Value}");

4. 試した結果

私が読み込みたいのはちょっと複雑なjsonでしたので、いくつかクラスを作って試してみました。 読み込むjsonファイルはこちらです。人狼ゲームのデータです。

github.com

ちなみに、このjsonファイル、配列の中に構造体が入っているような複雑なファイルです。なので、クラスの中にクラスをいれることで配列の中に構造体を格納するようなデータ構造にします。

例えば、morning.jsonldファイルの67行目からcharacter配列の中には"@id"や"@context"などが入っていますが、72行目"name"が構造体になっています。なので、クラス1つだけではデータを正しく受け取れません。このままプログラムを実行すると例外 (item要素が~的な例外) が発生します。

そこで、characterクラスの中にnameクラスを作成すれば解決です。jsonのname構造体にはjaとenのKeyが含まれているので、これらを宣言します。

[DataContract]
public partial class Character
{
    [DataMember(Name = "name")]
    public Name Name { get; set; } //Nameクラスを参照
}

[DataContract]
public partial class Name //Nameクラス
{
    [DataMember(Name = "en")]
    public string En { get; set; }

    [DataMember(Name = "ja")]
    public string Ja { get; set; }
}

これで配列の中にある構造体データを格納できます。これに合わせれば、配列の中に配列や構造体の中に構造体といった、複雑なデータ構造を扱えます。名付けて ”クラスの中にクラス大作戦” とかですかね...。

これを用いて、実際にやってみました。

string FilePath = @"C:\werewolfworld-gh-pages\village\example\0.3\server2client\morning.jsonld";
var serializer = new DataContractJsonSerializer(typeof(JsonData));
var json = File.ReadAllText(FilePath);
var ms = new MemoryStream(Encoding.UTF8.GetBytes((json)));
ms.Seek(0, SeekOrigin.Begin);
var data = serializer.ReadObject(ms) as JsonData;

for (int i = 0; i < data.Context.GetLength(0); i++)
Console.WriteLine($"context{i} : {data.Context[i].ToString()}");

Console.WriteLine($"Token : {data.Token}");
Console.WriteLine($"Phase : {data.Phase}");
Console.WriteLine($"Date : {data.Date}");
Console.WriteLine($"PhaseTimeLimit : {data.PhaseTimeLimit}");
Console.WriteLine($"ServerTimestamp : {data.ServerTimestamp}");
Console.WriteLine($"ClientTimestamp : {data.ClientTimestamp}");

for(int i = 0; i<data.Character.GetLength(0); i++) //キャラクター情報の表示
{
    Console.Write($"参加者ID {data.Character[i].CharacterId} : "); //プレイヤーID
    Console.Write($"{data.Character[i].Name.Ja} ({data.Character[i].Name.En})"); // プレイヤー名
    Status = data.Character[i].Status;
    if(Status == "alive")
    {
        Console.ForegroundColor = ConsoleColor.Green;
        Console.WriteLine($" {data.Character[i].Status}");
        Console.ForegroundColor = ConsoleColor.White;
    }
    else if(Status == "dead")
    {
        Console.ForegroundColor = ConsoleColor.Red;
        Console.ForegroundColor = ConsoleColor.White;
    }
                
}

JsonDataクラスはこんな感じです

[DataContract]
public class JsonData
{
    [DataMember(Name = "@context")]
    public string[] Context { get; set; }

    [DataMember(Name = "@id")]
    public string Id { get; set; }

    [DataMember(Name = "token")]
    public string Token { get; set; }

    [DataMember(Name = "phase")]
    public string Phase { get; set; }

    [DataMember(Name = "date")]
    public int Date { get; set; }

    [DataMember(Name = "phaseTimeLimit")]
    public int PhaseTimeLimit { get; set; }

    [DataMember(Name = "serverTimestamp")]
    public string ServerTimestamp { get; set; }

    [DataMember(Name = "clientTimestamp")]
    public string ClientTimestamp { get; set; }

    [DataMember(Name = "character")]
    public Character[] Character { get; set; }
}

[DataContract]
public partial class Character
{
    [DataMember(Name = "@context")]
    public Uri Context { get; set; }

    [DataMember(Name = "@id")]
    public Uri Id { get; set; }

    [DataMember(Name = "isMine")]
    public bool IsMine { get; set; }

    [DataMember(Name = "name")]
    public Name Name { get; set; }

    [DataMember(Name = "image")]
    public Uri Image { get; set; }

    [DataMember(Name = "id")]
    public int CharacterId { get; set; }

    [DataMember(Name = "status")]
    public string Status { get; set; }

    [DataMember(Name = "update")]
    public Update Update { get; set; }

    [DataMember(Name = "isAChoice")]
    public bool IsAChoice { get; set; }
}

[DataContract]
public partial class Name
{
    [DataMember(Name = "en")]
    public string En { get; set; }

    [DataMember(Name = "ja")]
    public string Ja { get; set; }
}

[DataContract]
public partial class Update
{
    [DataMember(Name = "@id")]
    public Uri Id { get; set; }

    [DataMember(Name = "phase")]
    public string Phase { get; set; }

    [DataMember(Name = "date")]
    public long Date { get; set; }
}

これで動かしてみます。

f:id:takunology:20191113000648p:plain

構造体の中身が表示されています。

今回作成したコード

参考にどうぞ。

github.com

参考サイト

参考にしたサイトです。ありがとうございます。

シリアライズ

blog.okazuki.jp

データコントラクト

docs.microsoft.com