Minecraft Education Editionでコードビルダーを使って円周率を計算してみた


# はじめに
　マインクラフト（マイクラ）には、Bedrock Edition（BE）やJava Edition（JE）のほかに、教育向けに機能拡張されているEducation Edition（EE）があります。EEのコードビルダーという機能を用いると、MakeCodeというビジュアルプログラミングツールや、Javascript、Python等を使って、オーバーワールド（マインクラフトの世界）の中でプログラムを動かすことができます。
　今回は、オーバーワールド内でブロックを使って円を描くプログラムを作成し、円の直径と面積の比率から円周率を計算してみようと思います。

# 考え方
　マイクラでは、オーバーワールド内の位置を$x,y,z$の3軸の座標で表現します。$x$と$z$が水平方向に直行している座標で、$y$が高さ方向の座標です。この座標を使うと、任意の二つの座標$P_1(x_1,y_1,z_1)$と$P_2(x_2,y_2,z_2)$間の距離$d_{12}$は

```math
d_{12}=\sqrt{(x_1-x_2)^2+(y_1-y_2)^2+(z_1-z_2)^2} 
```
で求めることができます[^1]。 
[^1]:厳密には、マイクラの座標は点ではなく単位長さの立方体の範囲を意味しているので、座標を点と考えて計算した結果にはずれがあるのですが、単位長さよりも十分に大きい距離においては無視できるだろうと考えます。

　今回は、簡単のために、水平方向に円を描くことを前提として、垂直方向の座標を無視します。円の中心の座標を$P_c$として半径$r$である円を考えると、この円は中心から$±r$の正方形に内接します。$r=10$で例を図示したものが図1です。

![図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/872f5c73-95cd-c495-84d7-119578929e72.png)
図1:半径rの円が内接する正方形の領域（r=10の例）
 
 したがって、この正方形の内側を探索エリアとして、中心から距離が半径$r$以下となるブロック（図1中のダイヤモンドブロック）を判定できれば円を描けるはずです。そして、半径$r$の円を描くのに使われたブロックの数$N_r$が、円の面積になるので、半径$r$の円に対する円周率$\pi_r$は

```math
\pi_r=N_r / r^2
```

 で計算できます[^2]。
 [^2]:実際には、正方形のエリア内をくまなく探索せず、もっと効率的に中心から距離rに収まる範囲を求める方法が存在します。が、そのあたりの探索方法の改良は別の記事でまとめます。


# コードの作成
　今回は、前節で述べた考え方を、MakeCodeでプログラミングしていきます。MakeCodeを使えるようにするために、マイクラEEの中でコードビルダーを起動します。既定では「C」キーで起動することができます。コードビルダーを起動させた画面が図2です。新しくコードを作成する場合には新規プロジェクトを選択します。既に作成済みのコードは保存済みのプロジェクトから読み込むことができます。
 ![MakeCode_startup.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/02f8c109-3ac1-caef-e69d-426d66daf920.png)
図2:コードビルダー起動画面

　コードビルダーの新規プロジェクトでは、MakeCodeのコーディング用GUIが表示されています。MakeCode中では、変数定義や計算・論理式・ループ・条件分岐の様に、通常のプログラムで必要となる要素は一通りビジュアルなコードブロックとして扱うことができます。また、特定の位置にブロックを置くなど、マイクラ特有の操作についても、事前定義済みのブロックがあるため、パズルを組み合わせる要領でプログラムを組むことができます。
![スクリーンショット (32).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/d51c91d9-d49e-a213-e134-32d55a8a53a9.png)
図3:新規プロジェクト起動時のGUI
 
　前節で述べた考え方をMakeCodeでコーディングしたサンプルプログラムが図4です。半径$r$、中心の$x$座標、中心の$z$座標を引数として、フラットに生成されたワールドの地表に円を描きます。※既定で設定可能な引数が3個まででした。本当は中心の$Y$座標（高さ）も引数指定させたかったのですが、今回はフラットワールドの地表にあたる-60を固定値と使っています。
 
![スクリーンショット 2023-11-29 011038.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/b809e62e-c771-d1ef-1344-2214064fc1c6.png)
図4:MakeCodeでコーディングした円の描画＆円周率計算プログラム


　MakeCodeのコードブロックはJavaScriptと相互に変換が可能なので、JavaScriptに変換したコードも記載しておきます。

```javascript
let y中心座標 = 0
let x開始座標 = 0
let z開始座標 = 0
let x変位 = 0
let ブロック数 = 0
let z変位 = 0
let 距離の二乗 = 0
let 円周率 = 0
player.onChat("circle", function (半径, x中心座標, z中心座標) {
    y中心座標 = -60
    x開始座標 = x中心座標 - 半径
    z開始座標 = z中心座標 - 半径
    x変位 = 0 - 半径
    ブロック数 = 0
    while (x変位 <= 半径) {
        z変位 = 0 - 半径
        while (z変位 <= 半径) {
            距離の二乗 = x変位 ** 2 + z変位 ** 2
            if (距離の二乗 <= 半径 ** 2) {
                ブロック数 += 1
                blocks.place(DIAMOND_BLOCK, world(x中心座標 + x変位, y中心座標, z中心座標 + z変位))
            }
            z変位 += 1
        }
        x変位 += 1
    }
    円周率 = ブロック数 / 半径 ** 2
    player.say("面積：" + ブロック数)
    player.say("円周率：" + 円周率)
})
```

# コードの実行
　では、このコードを使って、実際にオーバーワールドに円を描き、円周率を計算してみます。
## r=1,2
$r=1$と$r=2$で描いたものが図5,6です。流石にこの大きさでは単位ブロックのサイズ誤差の影響が大きくて円形には見えませんね。

![r001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/6dccbf2d-d3b8-a085-6489-42ce85a57a90.png)
図5:r=1の描画結果

![r002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/a0aadcf9-94ee-4c42-c7fc-2ad566b0d321.png)
図6:r=2の描画結果

## r=4,8,16
$r$が4を超えるあたりから、徐々に描かれる形が円に近づいていくように見えます。
円周率として、およそ3という精度でよければ、これくらいの大きさの円でもよさそうです。
が、私は円周率は3.14と習った世代なので、せめてそこに到達するまで円を広げていきたいと思います。

![r004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/ac017734-ce3c-bef0-caa9-a317020e87ef.png)
図7:r=4の描画結果

![r008.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/df45f360-1873-c998-3f43-3759c0707a00.png)
図8:r=8の描画結果

![r016.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/4f90ed43-8b78-5f1a-5491-3b831c48cb57.png)
図9:r=16の描画結果

## r=32,64,128
　ここまで大きくするとかなり円として綺麗に描かれているように見えます。$r=128$は、マイクラEEとして設定可能な視界の限界に近い大きさになっています。円の上に降り立った時にダイヤモンドブロックの地平が広がって見えます。

![r032.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/57271211-14d7-c9cf-6c5f-95e5266d5540.png)
図10:r=32の描画結果

![r064.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/5a24d467-af11-dcd7-60cf-13999d1c1cf0.png)
図11:r=64の描画結果

![r128.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/779328ce-cd09-8035-f4f4-d696f38d783e.png)
図12:r=128の描画結果

![r128-horizon.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/9c362033-16ad-1523-fe49-c3e7bed00803.png)
図13:ダイヤモンドの地平線

　なお、マイクラEEではブロックの配置などの演算を行えるのは、自身がいる座標から最大で8チャンク（128ブロック）離れたところまでとなっているため、これ以上大きな円を描くことはできないようです。


# まとめ

ここまでの計算で得られた結果を表にまとめたものが以下の表です。

|半径|面積|円周率|
|:-:|:-:|:-:|
|1|5|5|
|2|13|3.25||4|49|3.0625|
|8|197|3.0781|
|16|797|3.1133|
|32|3209|3.1338|
|64|12853|3.1379|
|128|51433|3.1392|

$r$が大きくなるにつれて真の円周率に近づいていく傾向が見て取れます。この傾向をもう少し分析してみます。横軸に半径、縦軸に真の円周率とのずれをプロットしたものがグラフ1です。両対数軸で直線を描くようにプロットされているので、半径$r$の冪で差が収束していく傾向が見て取れます。

![グラフ1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/1057736a-3d26-0961-a3aa-6dd105c8610d.png)
グラフ1:各rでの計算結果を両対数軸でプロット

　今回作成した円を描くプログラムは、直感的なわかりやすさを優先し、円に外接する正方形のエリアをくまなく探索するため、$r$の二乗に比例する形で計算量が爆発してしまいます。より高速に円を描くためにrに比例する計算量に収まるようにプログラムを改良することが可能です。このプログラムの改良は、 ~~12/20のアドベントカレンダーに掲載しようと思います。~~ 12/2のアドベントカレンダーに掲載いたします。

# おまけ
　今回の記事で描画した円を、使用するブロックを変えて同心円状に描いてみました。
![同心円.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/bb12262b-f705-d693-977e-b519b0531e8b.png)