Minecraft PI Editionでプログラミング学習

Minecraft Pi Edition

mcpi-map

プログラミングって遊びながら覚えるのが一番だと思います。
Raspberry PIを購入した時、インストールされていたMinecraft PI Edition。これは遊ばない手はないなあ、と思ったのが、マイクラを始めたきっかけです。
本記事は、まだ本物のマイクラを知る前に、PI Editionで遊んだ記録です。
PI Editionは、本物のマイクラではないのでご注意ください。

Minecraft PI Editionで遊んでみる

アニメ「シンドバッドの冒険」四話のイムチャックがいる極北の地は良かったです。暑がりな私は氷だらけの場所に常に憧れます。いつか氷の世界に旅行できたらなあと思います。

さて、私もようやくマインクラフトデビューとなりました。ラズパイにいれたRaspbianに最初から入っていますので、それを実行します。
minecraft-start

mcpi-start

「Start Game」をクリックして開始します。

mcpi-terrain

地形を作ってるみたいです。ランダムでしょうか。

mcpi-ice

ああ、真っ白、氷の景色です。氷だらけの世界へ行く夢、夢がかないました。

ちなみに本記事を書くにあたり、ラズパイでの画像をキャプチャするコマンドの説明が、ここにあったので、使わせてもらいました。

上記のサイトに従ってビルドした後は、実行するだけ。

root@rasperrypi:~/raspi2png-master# ./raspi2png

でも、これでは画像取りにくいので、勝手に画像を取るようにします。
キャプチャするのに時間がかかるので、sleepは入れません。といってもキャプチャに1秒もかかりません。

root@rasperrypi:~/raspi2png-master# cat t.sh
for i in $(seq -f "%05g" 10000); do
./raspi2png;chown pi.pi snapshot.png;mv snapshot.png /home/pi/img/$i.png
done

forじゃなくて無限ループでもいいかもしれません。

では、マインクラフトの世界に戻って、最初にやることは……、
一番角に行ってみたいと思います。オセロだけでなく何でも角を取れば勝ちだと考えるきらいがあります。
mcpi-kado

左上に座標を示す数字がありますが、120付近なので、きっと角は(127,127,127)、おそらくワールドはこんな風だと思います。

mcpi-world

たぶん。

座標がわかったところで、ちょっとコマンドラインでブロックを表示させてみます。空中に床みたいなものを出現させてようかと。
参考サイトを見ながら、コマンドラインで、pythonを起動します。

>>> import minecraft
>>> mc=minecraft.Minecraft.create()
>>> mc.setBlocks(0,0,80,127,127,80, 1, 1)

mcpi-kabe

ああ、大きな塗り壁になりました。
座標の解釈間違ってましたね。Y軸が縦なのですね。気を取り直して、塗り壁を消してから、もう一回。

>>> mc.setBlocks(0,0,80,127,127,80, 0, 0)
>>> mc.setBlocks(0,80,0,127,80,127, 1, 1)

ああ、またダメです。上空には何もありません。Y座標は、そんな高い場所にワールドはないのかもしれないです。

mcpi-sky

低めに指定してみます。

>>> mc.setBlocks(0,10,0,127,10,127, 1, 1)

mcpi-ceiling

上手くいきました。太陽の光が届かないらしく、暗いです。
穴でも開けてみましょう。

mcpi-hole
あまり明るくなりませんでした。

つまり、こんな感じの座標っぽいです。(ネットで調べるのはそのうち)
mcpi-world2

ランダムを使ってみます。

>>> import random
>>> for i in range(0,40):
...     x=random.randint(0,120)
...     z=random.randint(0,120)
...     mc.setBlocks(x,0,z  x+7,10,z+7, 1,1)

mcpi-dungeon

ダンジョンがたくさん出現しました。しょぼいですが。

APIを使う

Minecraftは始めたばかりで操作もままならず、その真の面白さに到達できる気がしていませんが、APIを使うのは面白いなあと感じております。このままゲームの面白さではなくコマンドライナーとしての面白さを追求していきたいと思っています。
では、Minecraft PI EDITIONをダウンロードして実行します。

pi@raspberrypi:~/Downloads $ wget https://s3.amazonaws.com/assets.minecraft.net/pi/minecraft-pi-0.1.1.tar.gz
pi@raspberrypi:~/Downloads $ tar xzf minecraft-pi-0.1.1.tar.gz -C ~/
pi@raspberrypi:~/Downloads $ cd ~/mcpi
pi@raspberrypi:~/mcpi $ ./minecraft-pi

Minecraftを起動したら、別の端末ウィンドウにて、

pi@raspberrypi:~/mcpi $ cd api/python/mcpi

各APIは、本ディレクトリ内にあるminecraft.pyに記載されています。たとえばブロック関連のAPIを確認するには、’def.*Block’で検索すれば良いです

pi@raspberrypi:~/mcpi/api/python/mcpi $ grep -A3 'def.*Block' minecraft.py
    def pollBlockHits(self):
        """Only triggered by sword => [BlockEvent]"""
        s = self.conn.sendReceive("events.block.hits")
        events = [e for e in s.split("|") if e]
--
    def getBlock(self, *args):
        """Get block (x,y,z) => id:int"""
        return int(self.conn.sendReceive("world.getBlock", intFloor(args)))

    def getBlockWithData(self, *args):
        """Get block with data (x,y,z) => Block"""
        ans = self.conn.sendReceive("world.getBlockWithData", intFloor(args))
        return Block(*map(int, ans.split(",")))
--
    def getBlocks(self, *args):
        """Get a cuboid of blocks (x0,y0,z0,x1,y1,z1) => [id:int]"""
        return int(self.conn.sendReceive("world.getBlocks", intFloor(args)))

    def setBlock(self, *args):
        """Set block (x,y,z,id,[data])"""
        self.conn.send("world.setBlock", intFloor(args))

    def setBlocks(self, *args):
        """Set a cuboid of blocks (x0,y0,z0,x1,y1,z1,id,[data])"""
        self.conn.send("world.setBlocks", intFloor(args))

pi@raspberrypi:~/mcpi/api/python/mcpi $ 

ブロックのIDは、block.pyに書かれています。’スペース2個=’でgrepするとうまい具合に一覧が取得できます。

pi@raspberrypi:~/mcpi/api/python/mcpi $ grep '  =' block.py
AIR                 = Block(0)
STONE               = Block(1)
GRASS               = Block(2)
DIRT                = Block(3)
COBBLESTONE         = Block(4)
WOOD_PLANKS         = Block(5)
SAPLING             = Block(6)
BEDROCK             = Block(7)
WATER_FLOWING       = Block(8)
WATER               = WATER_FLOWING
WATER_STATIONARY    = Block(9)
LAVA_FLOWING        = Block(10)
LAVA                = LAVA_FLOWING
LAVA_STATIONARY     = Block(11)
SAND                = Block(12)
GRAVEL              = Block(13)
GOLD_ORE            = Block(14)
IRON_ORE            = Block(15)
COAL_ORE            = Block(16)
WOOD                = Block(17)
LEAVES              = Block(18)
GLASS               = Block(20)
LAPIS_LAZULI_ORE    = Block(21)
LAPIS_LAZULI_BLOCK  = Block(22)
SANDSTONE           = Block(24)
BED                 = Block(26)
COBWEB              = Block(30)
GRASS_TALL          = Block(31)
WOOL                = Block(35)
FLOWER_YELLOW       = Block(37)
FLOWER_CYAN         = Block(38)
MUSHROOM_BROWN      = Block(39)
MUSHROOM_RED        = Block(40)
GOLD_BLOCK          = Block(41)
IRON_BLOCK          = Block(42)
STONE_SLAB_DOUBLE   = Block(43)
STONE_SLAB          = Block(44)
BRICK_BLOCK         = Block(45)
TNT                 = Block(46)
BOOKSHELF           = Block(47)
MOSS_STONE          = Block(48)
OBSIDIAN            = Block(49)
TORCH               = Block(50)
FIRE                = Block(51)
STAIRS_WOOD         = Block(53)
CHEST               = Block(54)
DIAMOND_ORE         = Block(56)
DIAMOND_BLOCK       = Block(57)
CRAFTING_TABLE      = Block(58)
FARMLAND            = Block(60)
FURNACE_INACTIVE    = Block(61)
FURNACE_ACTIVE      = Block(62)
DOOR_WOOD           = Block(64)
LADDER              = Block(65)
STAIRS_COBBLESTONE  = Block(67)
DOOR_IRON           = Block(71)
REDSTONE_ORE        = Block(73)
SNOW                = Block(78)
ICE                 = Block(79)
SNOW_BLOCK          = Block(80)
CACTUS              = Block(81)
CLAY                = Block(82)
SUGAR_CANE          = Block(83)
FENCE               = Block(85)
GLOWSTONE_BLOCK     = Block(89)
BEDROCK_INVISIBLE   = Block(95)
STONE_BRICK         = Block(98)
GLASS_PANE          = Block(102)
MELON               = Block(103)
FENCE_GATE          = Block(107)
GLOWING_OBSIDIAN    = Block(246)

getBlock(x,y,z)を使い、指定座標のブロックIDを取得してみます。

pi@raspberrypi:~/mcpi/api/python/mcpi $ python -c'
> import minecraft
> mc=minecraft.Minecraft.create()
> print mc.getBlock(0,0,0)
> '
1
pi@raspberrypi:~/mcpi/api/python/mcpi $ 

結果に1が表示されましたが、これがブロックIDです。上でgrepしたように、1はSTONEのブロックです。
getBlockを使い、連続で100個ほど取得してみましょう。

pi@raspberrypi:~/mcpi/api/python/mcpi $ python -c'
> import minecraft
> mc=minecraft.Minecraft.create()
> for x in range(0,100):
>   print mc.getBlock(x,0,0)
> ' | pr -t10 | expand
1      1      1      1      1      1      1      1      1      3
1      1      1      1      1      1      1      1      1      1
1      1      1      1      1      1      1      1      1      1
1      1      1      1      1      1      1      1      1      1
1      1      1      1      1      1      1      1      1      1
1      1      1      1      1      1      1      1      1      1
1      1      1      1      1      1      15     3      1      1
1      1      1      1      1      1      1      1      1      1
1      1      1      1      1      1      1      1      1      1
1      1      1      1      1      1      1      1      1      1
pi@raspberrypi:~/mcpi/api/python/mcpi $ 

あれ、何か変です。実際にやってみると分かりますが、少し重たく感じます。
出力をしないようにして、時間計測してみます。

pi@raspberrypi:~/mcpi/api/python/mcpi $ time python -c'
> import minecraft
> mc=minecraft.Minecraft.create()
> for x in range(0,100):
>  mc.getBlock(x,0,0)
'

real	0m5.086s
user	0m0.240s
sys	0m0.030s
pi@raspberrypi:~/mcpi/api/python/mcpi $ 

5秒かかりました。
5秒ぐらい、と思うかもしれませんが、Minecraftのマップは3次元ですので、
yもzも100個分とすると、 5秒 x 100 x 100 =

~$ bc <<< '5*100*100'
50000
~$ !!'/60/60'
bc <<< '5*100*100''/60/60'
13
~$ 

上記コマンドの結果のとおり、50000秒、つまり13時間となります。

取得する空間を小さくして実験してみましょう。

pi@raspberrypi:~/mcpi/api/python/mcpi $ time python -c'
> import minecraft
> mc=minecraft.Minecraft.create()
> for x in range(0,10):
>   for y in range(0,10):
>     for z in range(0,10):
>       print mc.getBlock(x,y,z)
' | pr -t10 | expand > data.txt

real	0m50.165s
user	0m1.250s
sys	0m0.350s
pi@raspberrypi:~/mcpi/api/python/mcpi $ 

約50秒でした。これぐらいなら待てます。
取得したデータを確認してみます。

pi@raspberrypi:~/mcpi/api/python/mcpi $ head data.txt
1      1      1      1      1      1      1      1      1      1
1      1      1      1      1      1      1      1      1      1
1      1      1      1      1      1      1      1      1      1
1      1      1      1      1      1      1      1      1      1
1      1      1      1      1      1      1      1      1      1
1      1      1      1      1      1      1      1      1      1
1      1      1      1      1      1      1      1      1      1
1      1      1      1      1      1      1      1      1      1
1      1      1      1      1      1      1      1      1      1
1      1      1      1      1      1      1      1      1      1
pi@raspberrypi:~/mcpi/api/python/mcpi $ 

ブロックは同じ種類のIDが固まって地形を形成していますので、このように冗長なデータとなります。

神視点

mcpi-top-view
Minecraft PI ED で、視点を変えてみたいと思います。といっても、pythonで操作できそうなのは、上空からの視点のみです。

~$ python -c'
import minecraft
mc=minecraft.Minecraft.create()
mc.camera.setFixed()
mc.camera.setPos(0,20,0)'

setFixedを使えば、上空で視点を固定することができます。

プレイヤーに追従してカメラを移動させるには、setFollowを使います。

~$ python -c'
import minecraft
mc=minecraft.Minecraft.create()
mc.camera.setFollow(mc.getPlayerEntityIds())'

Minecraft PI EDでメイズランナーで、作った迷路も、上空視点で操作すれば、簡単に抜けられるかもしれません。
あれ、プレイヤーはどこでしょうか。
mcpi-maze-top

簡単に見つかったと思いますが、ここです。
mcpi-maze-player

視点を変えて、プレイヤーの顔を見てみようと思っていましたが、桐島と同じく、見ることはできませんでした。

仕方ないので、マルチプレイをして、顔を見てみましょう。
2個目のMinecraft PIを起動して、Join Gameを選択します。
mcpi-join

次に最初に起動したMinecraft PIのIPアドレスを選んで、Join Gameします。一つのラズパイ上に複数Minecraft PIを起動しているだけなので、IPアドレスは全部同じです(私の環境では192.168.0.6)。
mcpi-join2

Raspberry PI 2では1台で4つ起動できました。
mcpi-multi-play
右下画面の迷路の横で剣を持って立って、こちらを見ている男。なぜかメイズランナーのギャリーを思い出しました。ムカっ。

コマンドを使って水流エレベーターを作る

水流エレベータ作ってみます。
入手と実行のおさらい。

pi@raspberrypi:~/Downloads $ wget https://s3.amazonaws.com/assets.minecraft.net/pi/minecraft-pi-0.1.1.tar.gz
pi@raspberrypi:~/Downloads $ tar xzf minecraft-pi-0.1.1.tar.gz -C ~/
pi@raspberrypi:~/Downloads $ cd ~/mcpi
pi@raspberrypi:~/mcpi $ ./minecraft-pi

ゲームを起動して、Create newを選ぶと、地形が生成されます。今回はこのような地形になりました。

良い景色です。田舎はいいですね。
ここで、ESCキーを押して、一旦、Minecraftから抜けます。

適当なファイル名(今回はclear.py)を作成し、平地にするスクリプトを作成します。

pi@raspberrypi:~/mcpi/api/python/mcpi $ cat clear.py
import minecraft

mc = minecraft.Minecraft.create()

mc.setBlocks(-20,0,-20, 20,50,20, 0,0)

pi@raspberrypi:~/mcpi/api/python/mcpi $ 

setBlocksで座標の範囲を指定して、ブロックID=0(なにもない)で、空間を書き換えます。
では実行してみましょう。

pi@raspberrypi:~/mcpi/api/python/mcpi $ python clear.py

実行後、ゲームに戻ると、このような景色になっています。

また、ESCキーを押してゲームを抜け、次はエレベーターを作ります。水のブロックを天空に設置し、水を流すだけです。water.pyというファイル名にしました。

pi@raspberrypi:~/mcpi/api/python/mcpi $ cat water.py
import minecraft

mc = minecraft.Minecraft.create()

mc.setBlocks(-1,-1,-1, 1,0,1, 0,0)
mc.setBlock(0,50,0, 8,0)	# water

pi@raspberrypi:~/mcpi/api/python/mcpi $ 

水が周囲に溢れないように地面に穴もあけておきました。では実行してみます。

pi@raspberrypi:~/mcpi/api/python/mcpi $ python water.py

ゲームに戻ると、何やら空に水色の物体が。

しばらくすると、水が下に落ちて水の柱になります。エレベーター完成です。

中に入ってみました。眺めが良くなくて、イマイチなエレベーターでした。

ダイヤモンド鉱石を見つける

私はお金がもちろん大好きなのですが、最近は、マインクラフトにハマってることもあって、マインクラフト内で鉱石を見つけるのが楽しくて仕方ありません。金鉱石や、ダイヤモンド鉱石を発見すると、なんだか本当に自分がお金持ちになった気分になってしまうんです。

さて、マイクラのPI EDITIONの方は、プログラミングができてしまいますので、ダイヤモンド鉱石を探すのはちょろいですね。
以下はブロックIDを取得して、石とか砂利とか砂だったら、ブロックを消すスクリプトです。

pi@raspberrypi:~/mcpi/api/python/mcpi $ cat delstones.py
import minecraft
mc = minecraft.Minecraft.create()
for x in range(-15,15):
  for y in range(-100,5):
    for z in range(-15,15):
      id=mc.getBlock(x,y,z)
      if 1 <= id <= 13:
        mc.setBlock(x,y,z, 0,0)

1番は、石ブロックのIDで、13番は砂ブロックのIDです。1~13の間に、鉱石はないので削除したいと思います。
python は、範囲比較ができるので、 1 <= id <= 13 みたいな書き方ができますね。 さて、ワールドを作りましょう。 このような涼しい世界ができました。

ワールドが出来たら、ESCキーを押して、ターミナルから先ほどのスクリプトを実行します。

pi@raspberrypi:~/mcpi/api/python/mcpi $ python delstones.py

再びゲームに戻ります。
鉱石はたくさんあるでしょうか。おー、一部のブロックを残し、がらんどうですね。

下に移動していきます。
石炭わんさかありますね。石炭といえども貴重な鉱石です。

さらに下に移動すると、金を見つけました。もう大金持ちです。

もっと下に移動してみましょう。ダイヤモンドです。マイクラにハマってると、この画像見ただけで、うっとりしてしまいます。

さてダイヤモンド鉱石の数を数えながら今日も寝ます。

歩く道が全てダイヤ

今回のスクリプトはこれです。

pi@raspberrypi:~/mcpi/api/python/mcpi $ cat diamond.py
import minecraft
mc = minecraft.Minecraft.create()
while 1:
  (x,y,z) = mc.player.getPos()
  mc.setBlock(x, y-1, z, 56,0)

プレイヤーの座標を取得し、足元にID=56のブロック、つまりダイヤモンド鉱石を配置するスクリプトです。
無限ループさせているので、このスクリプトの実行後は、プロンプトは戻りません。

例によって、新規にワールドを作成します。

オーシャンビューですね。本当にきれいな海はダイヤモンドより綺麗かもしれません。

反対側はビーチです。
一周見渡したところで、ESCキーでゲームを抜けます。
スクリプトを実行して、再びゲームに戻ります。

pi@raspberrypi:~/mcpi/api/python/mcpi $ python diamond.py


何か変わったでしょうか。
足元を見てみましょう。なんと! ダイヤです。

ちょと歩いて後ろを振り返ってみましょう。

次は海でも泳ぎましょうか。

泳ぎたくても、歩く道が全てダイヤになります。ダイヤモンドブリッジです。

空を飛べば、飛行機雲の代わりにダイヤがついてきます。

ダイヤの蚊取り線香です。

プログラミングで海を割る

さて、モーゼのように海を割ります。

import minecraft
import time

mc = minecraft.Minecraft.create()
while 1:
  hits = mc.events.pollBlockHits()
  for h in hits:
    if h.face == 1:
      mc.setBlocks(h.pos.x-7, h.pos.y, h.pos.z, h.pos.x+7, h.pos.y-10, h.pos.z-100, 0,0) 
  time.sleep(0.1)

剣を持った状態で、ブロックを右クリックすると、ヒットイベントが発生します。face変数が1(上面)でクリックされたら、地面を空洞にするスクリプトです。
プレイヤーの向きを気にせず、z軸の-方向に地面を割ります。

適当に海のあるワールドを作成してゲーム開始したら、上記スクリプトを実行します。

綺麗な海ですが、もうすぐそこにファラオの軍勢が迫ってきています。早く海を割りましょう。

剣を持った状態で、地面のブロックを右クリック。

おー、深く割れました。歩いてみましょう。

歩いているうちに、海水がああ。

全てダイヤに変える

またダイヤです。
スクリプトを段階的に作っていきます。
まずはプレイヤーを中心として円を描くスクリプトです。以前「Minecraft PI EDでプラネタリウム」でやってますので、そのスクリプトの改造になります。

pi@raspberrypi:~/mcpi/api/python/mcpi $ cat yome1.py
import minecraft
import time,math

mc = minecraft.Minecraft.create()
while 1:
  hits = mc.events.pollBlockHits()
  for h in hits:
    if h.face == 1:
      pos = mc.player.getPos()
      r=6
      d=60
      for i in range(0,d+1):
        rad= 2 * math.pi/d * i
        z = pos.z + round(r*math.sin(rad))
        x = pos.x + round(r*math.cos(rad))
        mc.setBlock(x, pos.y-1, z, 56,0)
  time.sleep(0.1)

ここで魔法を使いたいと思います。剣を持って地面を右クリック。

ダイヤの円が描かれました。一部上に土ブロックがあるところだけ欠けてます。

次にY座標にも幅を持たせます。プレイヤーの周囲にダイヤの柱が現れます。

pi@raspberrypi:~/mcpi/api/python/mcpi $ cat yome2.py
import minecraft
import time,math

mc = minecraft.Minecraft.create()
while 1:
  hits = mc.events.pollBlockHits()
  for h in hits:
    if h.face == 1:
      pos = mc.player.getPos()
      r=6
      d=60
      for i in range(0,d+1):
        rad = 2 * math.pi/d * i
        z = pos.z + round(r*math.sin(rad))
        x = pos.x + round(r*math.cos(rad))
        for yy in range(0,6):
          y = pos.y + yy - 2
          mc.setBlock(x, y, z, 56,0)

  time.sleep(0.1)

次は雪の上に描いてみましょう。

実行後。上から見ないとよくわかりませんね。

円の形に柱ができています。

いよいよ内側もダイヤで埋めていきます。
半径をfor文で繰り返せばよいです。

pi@raspberrypi:~/mcpi/api/python/mcpi $ cat yome3.py
import minecraft
import time,math

mc = minecraft.Minecraft.create()
while 1:
  hits = mc.events.pollBlockHits()
  for h in hits:
    if h.face == 1:
      pos = mc.player.getPos()
      for r in range(1,5):
        d=20
        for i in range(0,d+1):
          rad=2 * math.pi/d * i
          z=pos.z + round(r*math.sin(rad))
          x=pos.x + round(r*math.cos(rad))
          for yy in range(0,6):
            y = pos.y + yy -2
            mc.setBlock(x,y,z, 56,0)

  time.sleep(0.1)

また別のさらさらパウダー雪の上に円柱を出現させてみます。

中身の詰まったダイヤ円柱ができました。

最後に、ブロックが存在するかどうか(IDが0以外か)判定してから、ブロックが存在している座標だけダイヤですり替えます。

pi@raspberrypi:~/mcpi/api/python/mcpi $ cat yome4.py
import minecraft
import time,math

mc = minecraft.Minecraft.create()
while 1:
  hits = mc.events.pollBlockHits()
  for h in hits:
    if h.face == 1:
      pos = mc.player.getPos()
      for r in range(1,5):
        d=20
        for i in range(0,d+1):
          rad=2 * math.pi/d * i
          z=pos.z + round(r*math.sin(rad))
          x=pos.x + round(r*math.cos(rad))
          for yy in range(0,6):
            y = pos.y + yy -2
            id=mc.getBlock(x,y,z)
            if not id == 0:
              mc.setBlock(x,y,z, 56,0)

  time.sleep(0.1)

この木にかかるように石化の魔法をかけます。

木が半分だけダイヤ鉱石になってしまいました。

コメント

タイトルとURLをコピーしました