🚜

たった1日で実務経験が積める神ゲー『農家はReplace()されました』

に公開

こちらの記事は「Medley(メドレー) - Qiita Advent Calendar 2025」の15日目の記事です🎄


👋 はじめに

株式会社メドレーに2025年4月に新卒入社した、おぎのしきぶと申します。

突然ですがエンジニアの皆さん、休日は何をして過ごしていますか?
毎日仕事でコーディングをしていて飽き飽きしていませんか?
そんなとき、休日に行いたいことといえば、もちろん「業務に無関係なコーディング」ですよね。

今日は、そういった気持ちで始めた「プログラミングをするゲーム」が思ったよりも業務だったというお話をします。

🎮 ゲームの紹介

今回紹介するのは「農家は Replace() されました」というゲームです。

このゲームは、Pythonライクなプログラミング言語を使って農場を自動化していくプログラミングシミュレーションゲームです。
プレイヤーはコードを書いてドローンを操作し、農地を耕し、種を植え、水をやり、収穫するといった一連の農作業を自動化していきます。

https://youtu.be/p3drVKS-6z8?si=s2vaED0pFPSGJByP

集めたリソースで上位のテクノロジーをアンロックすると、より複雑な作物や機能が利用できるようになり、プログラムも高度化していきます。プログラミング初心者から経験者まで楽しめる設計で、自分なりの解決方法を見つけていくオープンエンドなゲーム性が特徴です。

なお、本ゲームはSteamで販売されており、日本語にも対応しています。
2025年12月7日現在、960円と非常に安価で購入できるので、ぜひこの記事を読む前にプレイしてみてください。
めちゃくちゃおもしろいです。

💼 感じた業務ポイント

📋 業務ポイント1:次々と現れる仕様

本ゲームでは、アンロックツリーで収穫した作物を消費することで、様々な機能がアンロックされていきます。

アンロックツリー
アンロックツリー(公式サイトより引用)

はじめのうちは、「植えて -> 収穫する」だけの簡単な作物のみだったのですが、すすむにつれて難易度が上がり、様々な作物が登場していきます。

  • 同じ作物を隣接させてはいけなかったり
  • 逆に隣接させるとくっついて、収穫量が多くなったり
  • 何故かソートしてから収穫しないといけなかったり
  • 挙句の果てには、急に迷路を解く羽目になります

迷路
迷路(公式サイトより引用)

農家ってこんなこともしなきゃいけないんですね🚜

そんなこんなで、想定していなかった要件・仕様がドンドン追加されていき、コードの修正が必要になってきます。苦しいね🥺

🏗️ 業務ポイント2:ドメインを意識した設計

次のアンロックでどのような作物が追加されるのか、プレイヤーは知る由もないので、アンロックのたびにコードを大幅に修正する必要があります。

私の場合、最初は「ドローンが何をするか」ベースでモジュールを切って実装していました。

  • 植えるモジュール
# plants.py
def plant_grass():
    plant(Entities.Grass)

def plant_carrots():
    # 人参は土を耕す必要がある
    if get_ground_type() != Grounds.Soil:
        till()
    plant(Entities.Carrot)
  • メイン処理:どこに何を植えるか決め、収穫
# main.py
from plants import plant_grass, plant_carrots

while True:
    move_to(0,0)
    for i in range(3):
        for j in range(3):
            if can_harvest():
                harvest()
            if (i+j)%2 == 0:
                plant_grass()
            else:
                plant_carrots()

この方針は、最終的なドローンへの指示である、main関数の実装では、非常に直感的で理解しやすいコードでした。

ただ、新たな農作物、かぼちゃの登場によってあっけなくこの方針が破綻します。

かぼちゃの仕様:

  • 隣接した正方形の形で大きくなり、サイズが大きいと収穫量が増える
  • 20%の確率で枯れる

かぼちゃ
左下がかぼちゃ。右上が枯れているため3×3にならない。(公式サイトより引用)

つまりかぼちゃを効率よく植えるためには、

  1. 正方形にかぼちゃを植える
  2. 枯れたかぼちゃを植え直す
  3. 枯れたかぼちゃがなくなったら、収穫

といった手順を踏む必要があります。

これを上記の方針でやろうとすると、激太りしたmain関数が誕生してしまいます。

# main.py
from plants import plant_pumpkin

while True:
    move_to(0,0)
    for i in range(3):
        for j in range(3):
            move_to(i, j)
            plant_pumpkin()
    can_harvest_count = 0
    unchecked_pumpkins = [[1,1,1],[1,1,1],[1,1,1]]
    while can_harvest_count < 3*3:
        for i in range(3):
            for j in range(3):
                move_to(i, j)
                if can_harvest():
                    can_harvest_count += unchecked_pumpkins[i][j]
                    unchecked_pumpkins[i][j] = 0
                else:
                    plant_pumpkin()
    harvest()
# plants.py
def plant_pumpkin():
    plant(Entities.Pumpkin)

もはやなぜplants.pyにモジュールを切り出しているのかわかりません🤔
この肥大化は、かぼちゃを植える・収穫するに当たって周囲の条件が複雑に絡んでおり、「植える」「収穫する」を単一に切り出せないことが原因です。
現在のモジュール分割方針では、main関数へロジックを直書きするしかありません。

かぼちゃだけでなく、既存の作物も同時に収穫していきたいので、main関数は更に肥大化します。
そこで、かぼちゃを植えるところから収穫まで行う、かぼちゃモジュールに切り出してみました。

# pumpkin.py
SQUARE_SIZE = 3

def plant_pumpkins():
    for i in range(SQUARE_SIZE):
        for j in range(SQUARE_SIZE):
            move_to(i, j)
            plant(Entities.Pumpkin)
    return

def replant_pumpkins():
    replant_count = 0
    for i in range(SQUARE_SIZE):
        for j in range(SQUARE_SIZE):
            move_to(i, j)
            if not can_harvest():
                replant_count += 1
                plant(Entities.Pumpkin)
    return replant_count

def main():
    plant_pumpkins()
    while replant_pumpkins() > 0:
        continue
    harvest()
# main.py
import pumpkin

while True:
    pumpkin.main()

かぼちゃに関するロジックは全てpumpkin.pyに集約され、非常にシンプルになっています👏

このように、開発当初の「ドローンが何をするか」という実装都合での切り出しではなく、「かぼちゃ」という作物(ドメイン)ごとにモジュールを切り出すことで、コードの見通しが格段に良くなりました。

さらに、この設計は変化に強いという利点があります。
新しい作物が追加されても、それぞれ独立したモジュールとして実装できるため、main関数の肥大化を抑えられます。
既存の作物のロジックも独立しているため、新たな作物追加時に既存コードが壊れるリスクも低減できます。

ドメインを意識した設計、大事ですね💡

⚡ 業務ポイント3:完璧よりも今すぐに使えるものを作る

このゲームには実行時間の概念があります。
そのため、重い処理や無駄な移動があるとドローンの処理時間が伸び、収穫効率が落ちていきます

例えば先程のかぼちゃでは、枯れたかぼちゃを植え直すとき、より効率よく植え直す事によって時間に対する収穫効率が向上します。
上記の実装では、すべてのかぼちゃをチェックして植え直しを行っていましたが、2回目以降は植え直したかぼちゃのみをチェックすればよいです。

実際に自分は非効率な実装をしていました。
ただ、2回目以降の高速化を実装するのは、それなりの難易度があります。
これを実装していたのは深夜だったため、非常に痒い思いをしながら眠りにつきました🛌

ところが翌日、修正しようと思ったときには、アンロックに十分な量のかぼちゃが収穫されていました

つまり、最適化は不要だったのです。
もちろん、あまりにも非効率だとタイムアウトしてアンロックに必要な量を収穫できませんが、クリアに必要な水準さえ満たしていれば、それ以上の最適化は後回しでも問題ありません
「今必要なものは何か」を考えたとき、それは「次のアンロックに進むこと」であり、「完璧に最適化されたかぼちゃ収穫プログラム」ではなかったのです。

もちろん今回はゲームなので、満足行くまで最適化しました。たのしい🎉

🎯 業務ポイント4:オブジェクト指向の威力

ゲームを進めていくと、複数のドローンを並列に実行できるようになります。

複数ドローン
複数ドローン(公式サイトより引用)

これにより、例えば3×3のかぼちゃ畑を3つのドローンに分担させて並列処理することで、かぼちゃの収穫効率を大幅に向上させることができます。

こうなってくると問題になってくるのが、共通のロジック複数ドローンに適用しつつ、持つ変数は異なる状態を、いかに実装するか?です。
こういった課題を解決するのが、まさにオブジェクト指向ですよね。

ただ、このゲームにはクラスが使えないという制約があります。
そこで、グローバル変数や高階関数を使うことで、擬似的にオブジェクト指向的な設計を実現しました。
厳密にはクラスではありませんが、「状態の管理」と「振る舞いのカプセル化」というオブジェクト指向の本質的な部分は同じです。

# pumpkin.py
SIZE = 3

def plant_pumpkins():
    # ...
    return

# ...

def create_pumpkin_drone(size):
    global SIZE
    SIZE = size
    def drone_task():
        while True:
            plant_pumpkins()
            while replant_pumpkins() > 0:
                continue
            harvest()
    return drone_task
# main.py
from pumpkin import create_pumpkin_drone
SIZE = 3

for i in range(3):
    spawn_drone(create_pumpkin_drone(SIZE)) # 関数を渡すことで、その関数を実行するドローンを生成する
    for _ in range(SIZE+1):
        move(East)

このように、クロージャを使って各ドローンに独立した処理をカプセル化することで、オブジェクト指向のような設計が可能になります。
各ドローンは独立して動作し、互いに干渉しません。

普段の業務では当たり前のようにオブジェクト指向プログラミングをしていますが、このゲームでその必要性に駆られて一から実装したことで、オブジェクト指向の威力を改めて実感しました。

✨ まとめ

農家は Replace() されました」は、一見するとプログラミング学習ゲームですが、実際にプレイしてみると驚くほど実務的なソフトウェア開発の体験ができるゲームでした。

次々と追加される仕様変更、それに対応するためのドメインを意識した設計完璧を求めすぎない実装判断、そしてオブジェクト指向の威力。これらは全て、実際の業務で日々向き合っている課題そのものです。

通常、プロダクトが成長していき、それに技術力で対応していくという経験を積むには、数ヶ月から数年の時間が必要です。
しかし、このゲームではそれを一日で体験できます
初期の簡単な実装から始まり、要件の複雑化に伴ってアーキテクチャを見直し、最適な設計パターンを模索する。
この一連の流れを、楽しみながら学べるのです。

新卒1年目が終わろうとしている今、このゲームで直面した設計の課題に対処できたことが、この1年間で積み重ねてきた学びの証だと実感できました。


画像の出典について
本記事内で使用しているゲーム画像の著作権は、開発元である Metaroot に帰属します。
引用元:The Farmer Was Replaced Press Kit


16日目の担当は@tkmt0322さんです!お楽しみに!!

Discussion