【Python】コンソールで虫さんを飼いたい

https://qiita.com/y-tetsu/items/b845f494ffd19450015e

こちらの記事を見て「いいなー、俺もコンソールで命を見守りてぇな～」と思ったのでChatGPT4にお願いしてはっぱを食べる虫さんのお遊びコードを作ってもらった。

今回の目的はお仕事の息抜きで、「視覚的になんか動いてくれて楽しそうなのを見ること」である。
ライフゲームのルールはちゃんとしていない。
（そのうち真面目につくりたいもんだ）。

ChatGPTまかせで、自分ではコードは１行も書いていない。
（出力の、中身もほとんど読んでいない。
コピー＆ペーストである。）

# 雑な依頼

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439672/4791d348-c784-4cd6-b928-1923c9a069f4.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439672/4a1129eb-ba78-c33c-851e-0c046ff3f103.png)

コードは考えずにコピペして動かしてみる。
（このくらいなら動くことだけわかってたらいいし、シリアスなもんでもない。）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439672/be3b512c-6453-1624-5d25-05aad46f1d1d.png)

エンターを押すとなにやらアリっぽいのが動いてはっぱをもしゃってくれてそうである。

コンソールって絵文字出せるんだな。

### 調整（雑）
とりあえず升目は揃えていただきたい。そのために空欄も絵文字を使ってもらったりする。
中身についてはミリしらである。
動かしてみて、動きを見て、動きの要件だけで注文を付けて、「動いてない」とか「葉っぱが足りない」とか、ああだこうだとか言って、適当に書いてもらう。

すでにChatGPTが何となく知っている概念（ライフゲーム）なので、あまりオリジナルのことをやらせなさ過ぎれば手放しでも書ける。質を問わなければ。ぜんぜんもう、書ける。

やっぱり、なんか脱線したときに戻してやれるくらいの力があると便利なのだが、ほんとうに注文を付け続けているだけなので、Pythonの書き方を知らなくても（動かし方を知ってれば）何とかなる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439672/e792725d-d36a-67a0-916e-d71277085a40.png)


その気がないならコードは1行も読まなくていい。動けばいい。エラーが出たらエラーごと投げていい。
ノンストップ・ノンストレスである。
（もちろん1から考えて実装したほうがいいが、お遊びなので……）

これを味わうとやめられなくなってしまうんだな……。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439672/2b95ce07-eaa3-649a-92da-96289ef28dad.png)

紆余曲折あって、こんなルールにした。

# 説明

## 初期設定
- グリッドのサイズ: 高さ10、幅10の2次元グリッド。
- アイコンの定義:
EMPTY（空）: ⬛
LEAF（葉）: 🍃
DRY_LEAF（枯れ葉）: 🍂
SOIL（土）: 🟫
CATERPILLAR（毛虫）: 🐛
BUTTERFLY（蝶）: 🦋
初期化: グリッドをランダムに初期化し、一部のセルに毛虫を配置。
ゲームの進行

## 葉の変化:
- 🍃（葉）は時間が経つと🍂（枯れ葉）になる確率がある（10%）。
- 🍂（枯れ葉）は時間が経つと🟫（土）になる確率がある（10%）。
- 🟫（土）は時間が経つと🍃（葉）になる確率がある（30%）。また、周囲の空いているセルに🍃（葉）を生成する確率もある（30%）。

## 毛虫の行動:
- 毛虫は一定の寿命を持ち、時間が経つと寿命が減る。
- 寿命が尽きると🟫（土）になる。
- 毛虫は🍃（葉）を食べると寿命が回復する。
- 毛虫は小さな確率（5%）で🦋（蝶）に変態する。この時、スコアが100点増加。
- 毛虫は繁殖できる。寿命が一定以上（7以上）になると周囲の空いているセルに新しい毛虫を生むことができる。この時、寿命が3減少し、スコアが10点増加。

## 蝶の行動:

- 🦋（蝶）は周囲8セルに新しい毛虫を生成し、自分は消える。この時、スコアが1点増加。
ゲームオーバー条件
毛虫がすべて消えるとゲームオーバー。
ゲームオーバー時にスコアが表示され、ハイスコアが更新されるとハイスコアも表示される。

最終的に（？）こんなコードを書いてもらった。

```python
import random
import time
import os

# Define constants for the icons
EMPTY = "⬛"
LEAF = "🍃"
DRY_LEAF = "🍂"
SOIL = "🟫"
CATERPILLAR = "🐛"
BUTTERFLY = "🦋"

# Define the dimensions of the grid
WIDTH = 10
HEIGHT = 10

# Initialize high score
high_score = 0

# Caterpillar class to handle lifespan and transformation
class Caterpillar:
    def __init__(self, lifespan):
        self.lifespan = lifespan

# Define the initial grid
def create_initial_grid():
    grid = [[random.choice([EMPTY, LEAF, SOIL]) for _ in range(WIDTH)] for _ in range(HEIGHT)]
    # Add a few caterpillars
    caterpillar_count = random.randint(5, 10)
    for _ in range(caterpillar_count):
        x, y = random.randint(0, WIDTH - 1), random.randint(0, HEIGHT - 1)
        grid[y][x] = Caterpillar(random.randint(5, 7))  # Increase initial lifespan
    return grid

# Define a function to print the grid
def print_grid(grid):
    os.system('cls' if os.name == 'nt' else 'clear')
    for row in grid:
        print("".join([CATERPILLAR if isinstance(cell, Caterpillar) else cell for cell in row]))
    print("\n")

# Define the rules of the game
def update_grid(grid, score):
    new_grid = [[EMPTY for _ in range(WIDTH)] for _ in range(HEIGHT)]
    caterpillar_count = 0
    for y in range(HEIGHT):
        for x in range(WIDTH):
            cell = grid[y][x]
            if cell == LEAF:
                # Leaf becomes dry leaf over time
                new_grid[y][x] = DRY_LEAF if random.random() < 0.1 else LEAF
            elif cell == DRY_LEAF:
                # Dry leaf becomes soil over time
                new_grid[y][x] = SOIL if random.random() < 0.1 else DRY_LEAF
            elif cell == SOIL:
                # Soil becomes leaf with an increased probability or spawns a new leaf in a nearby empty cell
                if random.random() < 0.3:  # Increase probability
                    new_grid[y][x] = LEAF
                elif random.random() < 0.3:  # Increase probability
                    for nx, ny in get_neighbors(x, y):
                        if new_grid[ny][nx] == EMPTY:
                            new_grid[ny][nx] = LEAF
                            break
                else:
                    new_grid[y][x] = SOIL
            elif isinstance(cell, Caterpillar):
                caterpillar_count += 1
                cell.lifespan -= 1
                # Caterpillar dies and becomes soil if lifespan is 0
                if cell.lifespan <= 0:
                    new_grid[y][x] = SOIL
                else:
                    # Caterpillar becomes butterfly with a small probability
                    if random.random() < 0.05:
                        new_grid[y][x] = BUTTERFLY
                        score += 100  # Increase score when a butterfly is born
                    else:
                        # Caterpillar eats leaf and recovers lifespan
                        if any(grid[ny][nx] == LEAF for nx, ny in get_neighbors(x, y)):
                            for nx, ny in get_neighbors(x, y):
                                if grid[ny][nx] == LEAF:
                                    new_grid[ny][nx] = Caterpillar(cell.lifespan + 3)
                                    new_grid[y][x] = EMPTY
                                    break
                        else:
                            # Move the caterpillar
                            new_x, new_y = move_caterpillar(x, y, grid)
                            if new_grid[new_y][new_x] == EMPTY:
                                new_grid[new_y][new_x] = Caterpillar(cell.lifespan)
                            else:
                                new_grid[y][x] = Caterpillar(cell.lifespan)
                        # Check if the caterpillar can reproduce
                        if cell.lifespan > 7:  # Increase lifespan requirement for reproduction
                            for nx, ny in get_neighbors(x, y):
                                if new_grid[ny][nx] == EMPTY:
                                    new_grid[ny][nx] = Caterpillar(3)
                                    cell.lifespan -= 3  # Reduce lifespan after reproduction
                                    score += 10  # Increase score when a new caterpillar is born
                                    break
            elif cell == BUTTERFLY:
                # Butterfly spawns caterpillars in all 8 surrounding cells and disappears
                for nx, ny in get_neighbors_8(x, y):
                    if new_grid[ny][nx] == EMPTY:
                        new_grid[ny][nx] = Caterpillar(3)
                new_grid[y][x] = EMPTY
                score += 1
    # If no caterpillars are present, print game over and retry
    if caterpillar_count == 0:
        print(f"Game over! Your score: {score}")
        global high_score
        if score > high_score:
            high_score = score
            print(f"New high score: {high_score}")
        input("Press Enter to retry...")
        return create_initial_grid(), 0
    return new_grid, score

# Define function to get neighbors (4 directions)
def get_neighbors(x, y):
    directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
    neighbors = []
    for dx, dy in directions:
        new_x, new_y = x + dx, y + dy
        if 0 <= new_x < WIDTH and 0 <= new_y < HEIGHT:
            neighbors.append((new_x, new_y))
    return neighbors

# Define function to get neighbors (8 directions)
def get_neighbors_8(x, y):
    directions = [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)]
    neighbors = []
    for dx, dy in directions:
        new_x, new_y = x + dx, y + dy
        if 0 <= new_x < WIDTH and 0 <= new_y < HEIGHT:
            neighbors.append((new_x, new_y))
    return neighbors

# Define the movement of the caterpillar
def move_caterpillar(x, y, grid):
    directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
    random.shuffle(directions)
    for dx, dy in directions:
        new_x, new_y = x + dx, y + dy
        if 0 <= new_x < WIDTH and 0 <= new_y < HEIGHT and not isinstance(grid[new_y][new_x], Caterpillar):
            return new_x, new_y
    return x, y

# Main game loop
def main():
    grid = create_initial_grid()
    score = 0
    while True:
        print_grid(grid)
        grid, score = update_grid(grid, score)
        print(f"Score: {score}  High Score: {high_score}")
        time.sleep(1)

if __name__ == "__main__":
    main()

```

見た目としてはこんなんである。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439672/49881e4e-83eb-3308-e5d7-eeb907ee8fb5.png)

VisualStudioCodeで動いているよ。

バランスはほぼなく、ルールもぜんぜんちゃんとしてない。
ただ、ランダムで始まって、要件の通り、コンソールでもいもい動いてくれて増えたり減ったりちょうちょになったりするのでそこそこ満足だ。
159行くらいあるようだが、あんまり見てないし書いてもいない。

これが多くなってファイルまたがって複雑になるとごちゃってきて付け焼刃じゃどうにもならなくてすみませんでしたになるのだが。

テキトーコーディングしてもらえて楽しい。