Steamで圧倒的に高評価のプログラミング&農業ゲーム『農家は Replace() されました』の攻略記事です。前回ざっくりどういうゲームかを解説しましたが、今回は各作物の単純かつ効率的な栽培方法を解説します。

解説のために新しくデータを作ったので、同じ順序で進めることでスムーズにラストの骨の収穫までたどり着けます。農場の広さは最後まで4×4で進行しています。ドローン1機のみの場合、この広さがあれば十分です。カボチャの栽培は6×6が効率的であるため、そこまでは拡張しても良いと思いますが、無駄に拡張するとコードの検証に時間がかかるため、慣れるまではあまり拡張しないことをおすすめします。また、スピードもアップグレードしすぎると処理が速くなりすぎ挙動が追いづらくなるため、ある程度仕様を理解するまではアップグレードしすぎないほうが良いかと思います。

収穫用のコードはプログラミング初心者でも理解しやすいようにできるだけ簡単なアルゴリズムのものを紹介しています。スピードを重視した効率的なコードなど中、上級者向けの攻略は別の記事にて紹介しようと思います。

ゲーム設定

長時間のプレイを快適にするために設定を結構変更しているので一応共有します。無駄なエフェクトなどを一切省いた、コーディングに集中するための設定のためデフォルトである程度遊んだ後に試すのが良いかと思います。また、スペックの低いPCでプレイしているため、動作が軽くなるように設定しています。

設定「一般」タブ

「コードのハイライト」は、実行中のコードで処理中の行をハイライトしてくれます。プログラミング初学者のうちは、コードの実行順が視覚的に把握できるため有効で良いかと思います。

「タブをスペースに変換」は、コーディングにはVS Codeエディタを使用していて、完成したコードをゲームに張り付けて実行しているため互換性のために設定しています。ゲーム内のエディタを使っている場合は無効のままで良いかと思います。

「数値増加エフェクト」は、収穫により左上のアイテム数が増加する際のエフェクトです。チカチカして気が散るので無効にしています。

設定「エラー」タブ
設定「操作」タブ①

インデントを戻すための「Shift+Tab」はよく使いますが、Steamのオーバーレイのショートカットキーと被るため、Steam側の設定を無効にすることをお勧めします。

①スチームアイコンを右クリックし、「設定」をクリック
②「ゲーム中」タブの「設定」をクリック
③ホットキーを「設定なし」にすることで無効にできます。
設定「操作」タブ②

「HUD切り替え」は、左上に常時表示されているアイテム数などのHUDの表示・非表示を切り替えます。インフォメーション読んでるときに被って邪魔になるときなどにたまに切り替えることがありました。

設定「オーディオ」タブ

最初はBGMだけオフにしてたけど途中でマスター音量をオフにしました。快適です。すぐに慣れるかと思います。

設定「グラフィック」タブ

エンドコンテンツではタイムアタックでリーダーボードに挑戦できるので、プログラムを高速処理させるためPCに負荷がかかってきます。そういう場合はこちらでグラフィックの品質を下げると負荷が減らせるかと思います。

ゲームの進め方

ゲームを初めからプレイし、すべての作物が収穫できるようになるまでの攻略方法を順に解説していきます。登場する作物と得られる収穫物は、草→干し草(Hay)、茂み、木→木材(Wood)、ニンジン(Carrot)、カボチャ(Pumpkin)、サボテン(Cactus)、恐竜→骨(Bone)、感染した植物→奇妙な物質(Weird_Substance)、迷路の宝箱→ゴールド(Gold)、ヒマワリ→パワー(Power)で、水(Water)、肥料(Fertilizer)はアンロックすると時間経過で自動的に手に入ります。

harvest()で草を収穫

ゲームが始まると、マップには1×1マスの農場とドローンが存在し、農場の草原からは草が手に入ります。コードエディタに以下のコードを打ち込んで、タブの左上の「実行ボタン」を押して実行しましょう。実行するたびにドローンの下の草を収穫します。

harvest()

画面右上のボタンからアンロックツリー画面を開くことができます。

アンロックツリー画面

要素をアンロックすることで収穫できる作物が増えたり、使える関数(命令コード)が増えたり、農場を拡大したり、ドローンのスピードアップしたりと、農業を効率化するための要素が増えていきます。アンロックに必要な作物を収穫し、すべてのアンロックを目指しましょう。

「ループ」をアンロック

まずは干し草5個でループのアンロックを目指しましょう。

whileを使って草を自動で収穫

ループをアンロックしたらwhileが使えるようになります。条件がTrueの間、ブロック内のコードをループするものです。以下のコードで草を自動収穫できます。

while True:
  harvest()
「スピード」をアンロック

干し草20個でスピードをアンロックします。

can_harvest()で草が成長したら収穫

スピードをアンロックすると、ドローンとコードの実行速度が増加します。以前のコードでは収穫タイミングが草の成長速度を上回るため、育ち切る前にharvest()が実行され、収穫に失敗します。if文(条件分岐)を使い、can_harvest()で収穫可能な場合のみharvest()を実行します。

while True:
  if can_harvest():
    harvest()
「拡大」をアンロック

干し草30個で拡大をアンロックし、農場を1×3マスに拡大します。

move()で移動しながら収穫

拡大をアンロックすると、農場が1×3マスに拡大しました。move()関数を使うことで指定した方向にドローンを移動することができます。方向は、北、東、南、西の4方向で、NorthEastSouthWestで指定します。また、ドローンが農場の端を超えて移動する場合、反対側に移動します。収穫と北に移動を繰り返すことで、成長を待たずに収穫を繰り返すことができます。

while True:
  harvest()
  move(North)
「植え付け」をアンロック

さて、実行ボタンを再度押しプログラムを終了したとき、ドローンの位置が初期位置に戻っていないことに気づくでしょう。そのまま再度実行すると、途中の位置から実行が開始されてしまいます。プログラムは最初から実行されていますが、ドローンは途中の位置から開始されています。今回のような単純なコードでは問題ありませんが、毎回マップ下端の初期位置から開始してくれたほうがプログラムを組みやすいかと思います。

植え付けをアンロックすることでclear()関数が使えるようになります。これはドローンの位置を(0, 0)の初期位置に戻し、農場を草地に戻し、麦わら帽子に戻してくれます。簡単に言うと、状態をリセットしてくれるというわけです。毎回、コードの最初に実行するのも手ですが、専用のコードファイルを用意するのが使い勝手が良いので作ってみましょう。

新しいコードファイルの作成

画面右上の+ボタンから新しいコードファイルを作成することができます。

clear()関数用のコードファイル

中身はclear()だけ。ファイル名もわかりやすくclearに変更します。

clear()

使い方は、別の実行中のプログラムを終了するときにこのプログラムを実行することで農場を初期状態に戻します。あるプログラムが実行中の時に別のプログラムの実行ボタンを押すと、実行中のプログラムが終了します。つまり、clearの実行ボタンを押して他のプログラムの処理を終了させた後、再度押すことで農場がクリアされます。毎回2回押す必要があるということです。

茂みから木材を収穫

さてclearプログラムが用意できたら、新しく追加された木材を栽培していきましょう。木材を栽培するためには茂みを植える必要があります。plant()関数にEntities.Bushを渡すことで、茂みを植えることができます。while文のすぐはじめにplant()していますが、これは草を茂みに置き換えるためのものです。あとは、harvest()で収穫した後にすぐ植え直し、北へ移動します。すでに茂みが植えられているマスでplant()しても無視されるため、最初のplant()は草を茂みに置き換えるためだけのもので、次回以降はharvest()のあとのplant()により新しい茂みが植えられます。

while True:
  plant(Entities.Bush)
  if can_harvest():
    harvest()
    plant(Entities.Bush)
  move(North)
「スピード」を1段階アップグレード

木材20個でスピードをアップグレードします。

以降は必要に応じてアップグレードしてください。また、アンロック済みの各作物の収穫量増加のアップグレードも同様に、適宜行ってください。農場の拡大に関しては紹介する手順と同じに進める場合は4×4まで。また、広げすぎるとプログラムのテスト運行に時間がかかるため、プログラミングに慣れるまでは6×6までにしておくのが良いかと思います。

「拡大」を1段階アップグレード

同じく木材20個で農場を3×3マスに拡大します。

3×3マスに拡大した農場で木材を収穫

これまでは植物の成長がドローンの周回に追い付かずに収穫が空振りしてしまうことがあったかと思います。拡張された3×3のスペースをフルに使ってそれを回避していきましょう。「植える&採取して上に移動」を3回繰り返し、右に移動という動作をwhileで無限ループすることで実現します。追加されたfor文を使うことでrange()で指定した回数だけ処理を繰り返します。

while True:
  for y in range(3):
    plant(Entities.Bush)
    if can_harvest():
      harvest()
      plant(Entities.Bush)
    move(North)
  move(East)
「ニンジン」をアンロック

木材50個でニンジンをアンロックします。

地面を耕しニンジンを栽培

ニンジンの植え付けには地面を耕す必要があります。追加されたtill()関数で地面を耕します。ただしtill()を使う度に土壌と草地が入れ替わるので、耕すための処理を先に済ませた後で栽培用の無限ループに入ります。また、効率を上げるために耕した直後ついでにニンジンを植えています。

for x in range(3):
  for y in range(3):
    till()
    plant(Entities.Carrot)
    move(North)
  move(East)

while True:
  for y in range(3):
    if can_harvest():
      harvest()
      plant(Entities.Carrot)
    move(North)
  move(East)
「拡大」を2段階アップグレード

木材30個、ニンジン20個で農場を4×4マスに拡大します。

農場が4×4マスに拡大したので、これまで見てきた草、木材、ニンジンのプログラムを保守性の高いものに変更していきましょう。

スピードアップに対応した草の収穫

これまでは移動しながら草を刈ることで草の成長確認が不要でしたが、今後スピードのアップグレードを重ねることで収穫作業に草の成長が追い付かなくなるかもしれません。can_harvest()による成長確認を加えることでそれを回避しましょう。以下のようにすることで、足元の草が成長するまでドローンが立ち止まります。

while True:
  while True:
    if can_harvest():
      break
  harvest()
  move(North)
農場拡大に対応した木材の収穫

これまではrange()の中に農場の1辺のマス数を直接入れることで、for文のループ回数を指定していました。農場の辺の長さを返すget_world_size()関数に置き換えることで、どんなサイズにも対応するプログラムになります。

while True:
  for y in range(get_world_size()):
    plant(Entities.Bush)
    if can_harvest():
      harvest()
      plant(Entities.Bush)
    move(North)
  move(East)
農場拡大に対応したニンジンの栽培

木材の収穫と同様に、直接数字で指定していた辺の長さをget_world_size()に置き換えましょう。

for x in range(get_world_size()):
  for y in range(get_world_size()):
    till()
    plant(Entities.Carrot)
    move(North)
  move(East)

while True:
  for y in range(get_world_size()):
    if can_harvest():
      harvest()
      plant(Entities.Carrot)
    move(North)
  move(East)
「木」をアンロック

木材50個、ニンジン70個で木をアンロックします。

「演算子」をアンロック

干し草150個、木材10個で演算子をアンロックします。木の栽培で必要になります。

格子状に木を栽培

木は隣接して植えると成長が遅くなるため、格子状に植えることでそれを回避します。if (y + x) % 2 == 0:とすることで、x座標とy座標の合計を2で割った余りが0の時、つまり偶数回目の時だけ木の栽培を行います。

while True:
  for x in range(get_world_size()):
    for y in range(get_world_size()):
      if (y + x) % 2 == 0:
        plant(Entities.Tree)
        if can_harvest():
          harvest()
          plant(Entities.Tree)
      move(North)
    move(East)
「水やり」をアンロック

木材50個で水やりをアンロックします。

木とニンジンは成長が遅いので、水やりをして成長を早めましょう。木材は茂みよりも木を栽培するほうが効率的なので茂みに関しては省略します。

木の栽培に水やりを追加

get_water()関数は、ドローンの下の地面の水位を返します。水位は0から1の間で、水位が高いほど植物の成長が早くなります。use_item(Items.Water)では水タンク1個を消費し、地面に0.25の水を与えます。if get_water() < 0.5:とし、水位が0.5より小さい場合に水を与えましょう。

while True:
  for x in range(get_world_size()):
    for y in range(get_world_size()):
      if (y + x) % 2 == 0:
        plant(Entities.Tree)
        if get_water() < 0.5:
          use_item(Items.Water)
        if can_harvest():
          harvest()
          plant(Entities.Tree)
      move(North)
    move(East)
ニンジンの栽培に水やりを追加

木と同様に、ニンジンの栽培にも水やりを追加しましょう。

for x in range(get_world_size()):
  for y in range(get_world_size()):
    till()
    plant(Entities.Carrot)
    move(North)
  move(East)

while True:
  for y in range(get_world_size()):
    if get_water() < 0.5:
      use_item(Items.Water)
    if can_harvest():
      harvest()
      plant(Entities.Carrot)
    move(North)
  move(East)
「カボチャ」をアンロック

木材500個、ニンジン200個でカボチャをアンロックします。

「変数」をアンロック

ニンジン35個で変数をアンロックします。ガボチャの栽培で必要になります。

カボチャを栽培

カボチャは大きく育てるほど収穫量が上乗せされるため、最大サイズに育ってから収穫することにします。すべてのマスを移動しながらカボチャを植えていきます。成長途中や無事に成長しきったカボチャに植え付けを行った場合、失敗するため何も起こりません。枯れたカボチャに植え付けを行った場合、新しいカボチャが上書きされて植え付けられます。変数を使い、すべてのカボチャが成長しきったかの確認をしていきます。無限ループに入る前にn = 0で変数nを初期化します。if can_harvest():で、収穫可能な場合、つまり無事に育ったカボチャの場合、nに1を足します。if n == get_world_size()**2:で、nが全てのマスの数と等しい場合、すべてのカボチャが無事に育ったことを意味するので、収穫と植え付けをし、nを0に戻します。if can_harvest():の話に戻りますが、収穫できなかった場合、つまりまだ育ち切っていない場合、else:を使い、n = 0nを0に戻します(枯れてしまった場合は1行上のplant(Entities.Pumpkin)ですぐに植えなおされるため、can_harvest()の時点ではすでに新しいカボチャが植えられていることになります。)。つまり、収穫可能なカボチャが全てのマスの数だけ連続した場合、すべてのカボチャが無事に育ったことになります。つまり、nは無事に成長したカボチャが連続で現れた数を表しています。

無限ループに入る前に2重のfor文で行っているのは、最初にtill()で草地を耕すためのものです。そのあとは、巨大カボチャが完成したら収穫し、nをリセットしてカボチャを植えなおすという栽培を永久的に繰り返します。

for x in range(get_world_size()):
  for y in range(get_world_size()):
    till()
    plant(Entities.Pumpkin)
    move(North)
  move(East)

n = 0
while True:
  for x in range(get_world_size()):
    for y in range(get_world_size()):
      plant(Entities.Pumpkin)
      if can_harvest():
        n += 1
        if n == get_world_size()**2:
          harvest()
          plant(Entities.Pumpkin)
          n = 0
      else:
        n = 0
      move(North)
    move(East)
「肥料」をアンロック

木材500個で肥料をアンロックします。

まずは奇妙な物質を1個獲得

植物を即座に成長させることのできる肥料ですが、効率よく使うためには使用後の植物の感染状態を切り替える、奇妙な物質が必要になってきます。奇妙な物質を量産するためにまずは1個必要なので、以下のコードで獲得しましょう。肥料を与えて、感染した草を刈り取ることで奇妙な物質を獲得するためのものです。

use_item(Items.Fertilizer)
harvest()
奇妙な物質を量産

先ほどのコードでは奇妙な物質を獲得する度に肥料が必要になりますが、正常な植物に奇妙な物質を使うことで、足元と隣接する植物をまとめて感染させることができるため無限に複製することができます。

move(North)
move(East)

while True:
  use_item(Items.Weird_Substance)
  harvest()

  for i in range(4):
    if i == 0:
      move(North)
    elif i == 1:
      move(East)
    elif i == 2:
      move(South)
    elif i == 3:
      move(West)

    harvest()

    if i == 0:
      move(South)
    elif i == 1:
      move(West)
    elif i == 2:
      move(North)
    elif i == 3:
      move(East)
「ヒマワリ」をアンロック

ニンジン500個でヒマワリをアンロックします。

「ヒマワリを効率良く収穫するには、農場にヒマワリが10本以上あり、その中で最も花びらの数が多いものを収穫すると、5倍のパワーが手に入ります!ヒマワリの花びらは最低 7枚、最高 15枚です。」

ヒマワリを栽培してパワーを獲得

ヒマワリを収穫することで、ドローンの移動、収穫、植え付けなどのスピードを2倍にする、パワーを獲得します。ヒマワリを効率良く収穫するには、花びらが最低枚数である7枚のもので9マスを埋めた後、10マス目に留まり、肥料を使って植え付け、収穫を高速で繰り返します。ここで収穫するヒマワリは全体で最も花びらが多い(もしくは同量)ことが確定するため、毎回5倍の量のパワーを獲得できる仕組みです。肥料を使うため収穫前に奇妙な物質を使い、感染状態を取り除きます。

for x in range(get_world_size()):
  for y in range(get_world_size()):
    till()
    if y + x * get_world_size() == 9:
      while True:
        plant(Entities.Sunflower)
        if get_water() < 0.5:
          use_item(Items.Water)
        use_item(Items.Fertilizer)
        while True:
          if can_harvest():
            break
        use_item(Items.Weird_Substance)
        harvest()
    while True:
      plant(Entities.Sunflower)
      if measure() == 7:
        break
      harvest()
    move(North)
  move(East)
「迷路」をアンロック

奇妙な物質1k個で迷路をアンロックします。

1×1の迷路で宝箱を回収

迷路を攻略する最も簡単な方法は、1×1の迷路を生成し、足元の宝を回収というサイクルを繰り返すことです。

while True:
  plant(Entities.Bush)
  use_item(Items.Weird_Substance)
  harvest()

ゴールド獲得の効率を上げるため、フィールド全体に広がる迷路を生成し、探索により多くのゴールド獲得を目指しましょう。

「感覚」をアンロック

干し草100個で感覚をアンロックします。

「リスト」をアンロック

ニンジン500個でリストをアンロックします。

フィールドいっぱいの迷路を解いてより多くのゴールドを獲得

迷路はループしない作りになっていて、これは全ての壁が繋がっていることを意味します。つまり、壁にそって進み続ければすべてのマスに到達できるということです。今回のロジックでは、常に左を優先して進むことでそれを実現します。左に進める場合進み、進めない場合は前、前も無理なら右、それも無理なら後ろ、という形で進路を選択し、最後の移動を変数に記憶することで、現在ドローンの向いている方向を表すため、それをもとに次の進路を選択します。

まずはmove()で移動方向を指定するための方角を、D(「方向」を意味するdirectionの略)にリストで定義します。次に、d = 0で、前回の移動方向を表すd(こちらもdirectionの略)を初期化します。dには0から3の数字が入り、Dのインデックス番号と対応しています。つまりdが0の場合はNorthを意味します。無限ループの最初で、最大サイズの迷路を作成します。こちらのコードに関してはゲーム内の迷路に関する情報で説明されています。次の無限ループは、実際に迷路を探索するためのもので、最初のif文により足元に宝箱があるかを判定し、あれば回収してループを抜け、新しい迷路を生成するという繰り返しになります。

for i in range(-1, 3):では、-1から2までの整数が順にiに代入され、計4回ループします。この数値をdに足し合わせることで次の移動方向を決めます。-1は左、0は前、1は右、2は後ろを意味します。ただし、足し合わせた結果が0から3の範囲を超えてしまうことがあるため、4で割った余りに置き換えます。-1 % 4は3であり、4 % 4は0であるため、0から3の範囲に収めることができます。一旦その結果をddに代入します。

if move(D[dd]):では、先ほど算出した方向に進んでみて、移動に成功した場合、d = ddで、最後の移動方向を上書きし、ループを抜けます。つまり、4方向を左から優先的に移動を試みるということです。

D = [North, East, South, West]
d = 0
while True:
  plant(Entities.Bush)
  n = get_world_size() * 2**(num_unlocked(Unlocks.Mazes) - 1)
  use_item(Items.Weird_Substance, n)
  while True:
    if get_entity_type() == Entities.Treasure:
      harvest()
      break
    for i in range(-1, 3):
      dd = (d + i) % 4
      if move(D[dd]):
        d = dd
        break
「混植」をアンロック

カボチャ3k個で混植をアンロックします。※以後マニュアルでは混作と呼ばれます。

「辞書」をアンロック

カボチャ2.5k個で辞書をアンロックします。混作で必要になります。

混作とは、収穫する作物に対して適切なコンパニオンプラントがあれば収穫量が増える仕組みです。収穫する植物を左下のマスに配置し、好みのコンパニオンを調べて適切な位置に植えた後、左下に戻って収穫するという動作を繰り返すことでこれを実現します。混作は草、茂み、木、ニンジンで有効なため、それぞれコードを書いていきましょう。木材の収穫に関しては木で行うのが効率的なため茂みは省略します。

混作で草を収穫

{位置: 植物, 位置: 植物 ...}の形式で存在するコンパニオンを管理するため、companion = {}で空の辞書を定義します。entity, (x, y) = get_companion()で好みのコンパニオンを取得し、entityに植物、(x, y)に位置を代入します。次の行で、取得した(x, y)のキーが辞書に存在しないか、取得した植物が辞書のものと一致しない場合、その位置に移動して適切な植物を植え直し、左下に戻ります。その後、(好みのコンパニオンが適切な位置に存在するうえで)左下の作物を収穫します。この挙動を無限ループの中で繰り返すことで混作を実現します。

また、ニンジンを植えるためには耕す必要があるので、if get_ground_type() != Grounds.Soil:で、移動先の地面が土壌じゃなければ耕すという処理を行います。他の植物も土壌に植えることができるため、ニンジンとの区別は不要です。

companion = {}
while True:
  if get_water() < 0.5:
    use_item(Items.Water)

  entity, (x, y) = get_companion()
  if not (x, y) in companion or entity != companion[x, y]:
    for _ in range(x):
      move(East)
    for _ in range(y):
      move(North)

    if get_ground_type() != Grounds.Soil:
      till()
    harvest()
    plant(entity)

    for _ in range(x):
      move(West)
    for _ in range(y):
      move(South)
  while not can_harvest():
    pass
  harvest()
混作で木を栽培

木は草に比べ成長が遅いため、水やりの条件を「0.75より少ない場合」に変更します。また、木は植え付けが必要なため、plant(Entities.Tree)を書き足します。木は収穫する前に肥料を使い成長を促進し、収穫する前に奇妙な物質で感染を取り除きます。また、奇妙な物質を与える処理を削ることで、木の収穫量の半分が奇妙な物質に変わるため、奇妙な物質を素早く稼ぐことができます。

companion = {}
while True:
  plant(Entities.Tree)
  if get_water() < 0.75:
    use_item(Items.Water)

  entity, (x, y) = get_companion()
  if not (x, y) in companion or entity != companion[x, y]:
    for _ in range(x):
      move(East)
    for _ in range(y):
      move(North)

    if get_ground_type() != Grounds.Soil:
      till()
    harvest()
    plant(entity)

    for _ in range(x):
      move(West)
    for _ in range(y):
      move(South)
  use_item(Items.Fertilizer)
  while not can_harvest():
    pass
  use_item(Items.Weird_Substance)
  harvest()
混作でニンジンを栽培

ニンジンも木とほぼ同じですが、植えるために最初に耕す必要があるのと、好みのコンパニオンにニンジン自身は選ばれないため、取得したコンパニオンを植えるときに地面を耕す処理は不要になります。肥料に余裕がなければ、肥料と奇妙な物質を与える処理は削っても良いかと思います。

companion = {}
till()
while True:
  plant(Entities.Carrot)
  if get_water() < 0.75:
    use_item(Items.Water)

  entity, (x, y) = get_companion()
  if not (x, y) in companion or entity != companion[x, y]:
    for _ in range(x):
      move(East)
    for _ in range(y):
      move(North)

    harvest()
    plant(entity)

    for _ in range(x):
      move(West)
    for _ in range(y):
      move(South)
  use_item(Items.Fertilizer)
  while not can_harvest():
    pass
  use_item(Items.Weird_Substance)
  harvest()
「サボテン」をアンロック

カボチャ5k個でサボテンをアンロックします。

サボテンを栽培

サボテンは正しい順序に並び替えた後収穫することで収穫量が増えるため、正しくソートしていきます。縦列は、下から上に大きくなるように、横列は左から右に大きくなるようにすることで条件を満たします。まずは縦列を並び替えながら全ての列植えていき、次に横列を1列ずつ並び替えることでこれを実現します。

get_world_size()を多用するため、SIZEに代入して扱いやすくします。サボテンを植えるためには耕す必要があるためtill()しますが、サボテンの栽培は繰り返し行うため、if get_ground_type() != Grounds.Soil:で、「地面が土壌じゃなければ」という条件を付け足します。

n = measure()でサボテンのサイズを取得し、nに代入します。サイズは0から9の整数値です。moved = 0で、ソートのために移動した回数を0で初期化します。元の位置に戻る処理で使います。for i in range(y):で、現在のy座標の回数だけ繰り返す処理を書きます。if n >= measure(South):で、もし今植えたサボテンのサイズが下のサボテンのサイズ以上なら、breakでループを抜けます。つまり、ソートは正常なためこれ以上繰り返す必要がないということです。そうでない場合はループ内の処理を続けます。下のサボテンのサイズのほうが大きいということなので、swap(South)で下のサボテンと入れ替えます。その後下に移動し、movedに1を足します。今植えたサボテンが正しい位置にソートされるまで、下のものと入れ替えるという処理を繰り返しているわけです。ソートが完了した場合、for _ in range(moved + 1):で、move(North)を繰り返し、ソートのために下に移動した分、上に移動して元の位置に戻ります。説明をスルーしましたが、if i == y - 1:の場合にbreakしているのは、繰り返しの最後ではもう下に移動する必要がないため、move(South)の前にループを抜けています。

横方向でも同様の処理を行うため、コードを複製した後に進む方向を修正しましょう。縦方向の処理ですでにサボテンを植え終わっているため、植え付けに関する処理は不要になります。縦、横方向ともにソート処理が終わった後にドローンは左下に戻るので、そこで収穫することでソート済みのすべてのサボテンが一斉に収穫されてループは最初の処理から繰り返されます。また、サボテンの成長は1秒と短いため水やりは不要です。

SIZE = get_world_size()

while True:
  for x in range(SIZE):
    for y in range(SIZE):
      if get_ground_type() != Grounds.Soil:
        till()
      plant(Entities.Cactus)
      n = measure()
      moved = 0
      for i in range(y):
        if n >= measure(South):
          break
        swap(South)
        if i == y - 1:
          break
        move(South)
        moved += 1
      for _ in range(moved + 1):
        move(North)
    move(East)

  for y in range(SIZE):
    for x in range(SIZE):
      n = measure()
      moved = 0
      for i in range(x):
        if n >= measure(West):
          break
        swap(West)
        if i == x - 1:
          break
        move(West)
        moved += 1
      for _ in range(moved + 1):
        move(East)
    move(North)

  harvest()
「恐竜」をアンロック

サボテン2k個で恐竜をアンロックします。

「関数」をアンロック

ニンジン40個で関数をアンロックします。恐竜の攻略に必要になります。

いよいよ最後は恐竜の養殖です。アンロックされた専用の帽子を被ることで恐竜に変身します。恐竜は、フィールドのランダムな場所に出現するリンゴを食べることで尻尾が伸び、すべてのマスを尻尾で埋め尽くして帽子を外すことで多くの骨を獲得します。要するにスネークゲームです。恐竜の間はフィールドの端で反対側に折り返して進めなくなり、尻尾を引きづって動くため、尻尾のあるマスへは移動できなくなります。

恐竜を育てて骨を獲得

全てのマスを一筆書きで繰り返し移動し続けることで、永続的にリンゴを集めることができ、フィールドはいずれ尻尾で埋め尽くされるという単純な処理で攻略しようと思います。最終的には移動先の尻尾が邪魔で移動できなくなるため、move()が失敗してFalseが返ったときに帽子を外して骨を獲得、また尻尾がない状態に戻るのでこの動作を繰り返します。

範囲を分けて考える

移動は、一番下の列と、それ以外のエリアで分けて考えます。上のエリアでは、偶数列では上方向に移動し、奇数列では下方向に移動します。また、端に到達するたび右に移動します。一番下の列では常に左に移動して初期位置に戻ります。実装をざっくりイメージできたところでコードに落とし込んでいきましょう。

まずは定数を定義していきます。定数とは不変の値で、使い方は変数と同じですが、コードの中で値がコロコロ変わらずに一定のため、変数と区別するために定数名はすべて大文字で表現します。SIZE = get_world_size()で、定数SIZEで農場の辺の長さを定義しました。同じように、NS = [North, South]とし、移動方向の指定を便利にするため定数NSを定義します。

さて、新しく追加されたdef(「定義する」を意味するdefineの略)による関数定義ですが、いったん無視してその下の処理を見ていきましょう。change_hat(Hats.Dinosaur_Hat)で、恐竜の帽子を被ります。while True:で無限ループに入り、まずはm(North)で上に移動し、青いエリアの開始地点へ。この後の2重のfor文により青いエリアでの上下のくねくねした動きを実現します。m(NS[x % 2])では、x座標が偶数ならm(North)、奇数ならm(South)が実行します。ここは本来move()関数なのですが、move()するたびにクリア判定(全マス尻尾で埋まったか)をしたいので、そのために定義したm()関数を使っています。それでは定義したm()関数の中を見てみましょう。

まず初めにdef m(dir):として、m()関数を定義していますが、()カッコの中にdirという引数を受け取ります。dir(「方向」を意味するdirectionの略)は、NorthEastSouthWestのいずれかの方向を受け取ります。if move(dir):で、move(dir)の返り値がTrueなら、returnします。この時、move()が実行されているため、その方向に移動可能なら移動します。移動できたときにはTrueが返るので、クリアしていないためreturnします。移動に失敗した場合、Falseが変えるため、returnせずに次の処理へと進みます。つまり、伸びた尻尾が邪魔で進めなかったということなので、クリアした状態です。帽子を通常のものに被りなおすことで、尻尾の長さを2乗した数の骨を獲得します。その後、恐竜の帽子を被り、move(dir)で、先ほど失敗した移動を再度試みます。すでに報酬を獲得し、尻尾が無い状態に戻っているため移動は成功します。ここまでが独自定義したm()関数の中身です。move()するたびにコードの中に同じようなクリア判定処理を書くとゴチャゴチャしちゃうので、このように使いまわせる関数として定義すると便利になります。

コードの続きに戻りますが、if x < SIZE - 1:の成立時のみm(East)しているのは、右端ではそれ以上右に進めないためです。

青エリアの上下のくねくね移動を抜けた後は、m(South)して赤エリアへ。for _ in range(SIZE - 1):で、m(West)SIZE - 1回繰り返すことで左下の初期位置に戻ります。左下の初期位置から始まり、左下に戻るまでのここまでの流れを無限ループすることで永久的に骨を獲得する挙動を実現しています。

SIZE = get_world_size()
NS = [North, South]

def m(dir):
  if move(dir):
    return
  change_hat(Hats.Straw_Hat)
  change_hat(Hats.Dinosaur_Hat)
  move(dir)

change_hat(Hats.Dinosaur_Hat)
while True:
  m(North)

  for x in range(SIZE):
    for y in range(SIZE - 2):
      m(NS[x % 2])
    if x < SIZE - 1:
      m(East)

  m(South)
  for _ in range(SIZE - 1):
    m(West)

さいごに

最後まで読んでいただきお疲れさまでした。『農家は Replace() されました』は初めて触れたプログラミングを題材としたゲームですが、すごく丁寧に作られていて、初めてプログラミングに触れる人にとってもとても良い教材だと思います。関数定義や、今回は紹介しきれませんでしたが、モジュールなんかも実装されていて、極めれば本当にこれだけでも無限に楽しめちゃう網羅性を感じます。Webプログラミングを独学した僕から見ても、これを極めると本格的にプログラミング言語の学習を始めたときにもきっと生きるので、是非やり込んでみてもらえればと思います。ゲームはまだまだ世界ランキングのリーダーボードへの挑戦など今回紹介しきれなかった要素がたくさんあるので、是非挑戦してみてください!