💬

強化学習で作る最強のCCレモンAI~ルール編~

2024/12/01に公開

みなさん「CCレモン」という遊びは知っているでしょうか?
どのような遊びかピンと来ていない方はand more様のこちらの動画をご覧ください。
https://www.youtube.com/watch?v=c0O0rmy2xHI&t=921s

最近レコメンドシステムの論文を読んでいく中でちらほら強化学習という単語を観ることがあり興味をもっていました。
また自分はもともと将棋が好きで将棋AIのようなゲームAIを作ってみたいと思っていました。
なので今回は非常にシンプルなゲームであるCCレモンを題材にし、強化学習を用いてゲームAIを作成します。

ちなみに自分の地元では「戦争」という名前でこの遊びが小学校のころ流行っていましたが、「最強の戦争AI」は字面が悪すぎるので本シリーズでの名称は「CCレモン」で統一します。

ルール

ローカルルールが多いゲームなので本シリーズで扱うルールを定義しておきます。

概要

二人のプレイヤーで行う対戦型の遊びです。
プレイヤー同士がタイミングを合わせて同時にコマンド実行しプレイヤーのコマンドにより、「勝利」「敗北」「ゲーム続行」が決まります。

コマンド

コマンドは「チャージ」「ビーム」「ガード」「スペシウム光線」の4つです。
(ローカルルールで反射などがあるらしいですが、本シリーズでは上記の4つのコマンドとします。)

ガード

相手の「ビーム」コマンドから自分を守ります。
相手のコマンドが「ビーム」でもゲームが続行します。

チャージ

「ビーム」「スペシウム光線」を実行するためのチャージを行います。
自分のチャージが1増加します。

ビーム

ビームを撃ち相手プレイヤーを攻撃します。
相手のコマンドがチャージの場合は勝利します。
自分のチャージを1消費します。
自分のチャージが0の場合は実行できません。

スペシウム光線

相手に強力な光線を発射し攻撃します。
相手のコマンドが「ガード」でも貫通し「スペシウム光線」コマンドを実行したプレイヤー勝利になります。
自分のチャージをn消費し、自分のチャージがnより少ない時は実行できません
(nは3~5の範囲で調整を行います。)

コマンドごとの勝敗表

1行目のコマンドを実行したときに相手プレイヤーが1列目のコマンドを実行した場合の1行目のコマンドを実行したプレイヤーからみた勝敗を表示しています。
〇:勝利
× :敗北
ー:ゲーム続行

ガード チャージ ビーム スペシウム光線
ガード - - -
チャージ - -
ビーム - × -
スペシウム光線 × × × -

ソースコード

上記で記載したルールにのっとりソースコードを作成しました。
次回からは強化学習を用いてCCレモンAIを作成していこうと思います!

import random
import numpy as np
from typing import Tuple, List

specium_cost = 5
class Player:
    def __init__(self, name: str) -> None:
        self.name: str = name
        self.charge: int = 0
        self.last_action: str = None

    def charge_point(self) -> None:
        """チャージを1溜める"""
        self.charge += 1
        self.last_action = "チャージ"

    def beam_attack(self, target: "Player") -> None:
        """ビーム攻撃: チャージ1消費"""
        if self.charge >= 1:
            self.charge -= 1
            self.last_action = "ビーム"

    def specium_attack(self, target: "Player") -> None:
        """スペシウム光線攻撃: チャージ{specium_cost}消費"""
        if self.charge >= specium_cost:
            self.charge -= specium_cost
            self.last_action = "スペシウム光線"

    def guard(self) -> None:
        """ガード: 防御"""
        self.last_action = "ガード"

class CCGameEnv:
    """CCレモンゲームの環境"""
    def __init__(self) -> None:
        # プレイヤー1とプレイヤー2の初期設定
        self.player1 = Player("Player 1")
        self.player2 = Player("Player 2")

    def reset(self) -> Tuple[int, int, int, int]:
        """ゲームを初期状態にリセット"""
        self.player1.charge = 0
        self.player2.charge = 0
        self.player1.last_action = None
        self.player2.last_action = None
        return self.get_state()

    def get_state(self) -> Tuple[int, int, int, int]:
        """現在のゲームの状態を返す"""
        return (self.player1.charge, self.player2.charge)

    def step(self, action1: int, action2: int) -> Tuple[Tuple[int, int, int, int], int, bool]:
        """プレイヤー1とプレイヤー2の行動を実行"""
        # プレイヤー1の行動
        self._take_action(self.player1, action1, self.player2)
        # プレイヤー2の行動
        self._take_action(self.player2, action2, self.player1)
        # 勝敗判定
        result = self._check_winner(self.player1, self.player2)
        if result == 1:  # プレイヤー1の勝ち
            return self.get_state(), 1, True
        elif result == -1:  # プレイヤー2の勝ち
            return self.get_state(), -1, True
        return self.get_state(), 0, False # ゲーム続行

    def _take_action(self, player: "Player", action: int, opponent: "Player") -> None:
        """プレイヤーのアクションを実行"""
        if action == 0:  # チャージ
            player.charge_point()
        elif action == 1:  # ビーム
            player.beam_attack(opponent)
        elif action == 2:  # スペシウム光線
            player.specium_attack(opponent)
        elif action == 3:  # ガード
            player.guard()

    def _check_winner(self, player1: "Player", player2: "Player") -> int:
        """勝敗判定"""
        action1 = player1.last_action
        action2 = player2.last_action

        if action1 == "チャージ" and action2 == "チャージ":
            return 0 # ゲーム続行
        elif action1 == "チャージ" and action2 == "ビーム":
            return -1 # プレイヤー1の勝利(ビームを使用したプレイヤー2に勝ち)
        elif action1 == "チャージ" and action2 == "スペシウム光線":
            return -1 # プレイヤー2の勝利(スペシウム光線を使用したプレイヤー2に勝ち)
        elif action1 == "チャージ" and action2 == "ガード":
            return 0 # ゲーム続行
        elif action1 == "ビーム" and action2 == "ビーム":
            return 0 # ゲーム続行
        elif action1 == "ビーム" and action2 == "スペシウム光線":
            return -1 # プレイヤー2の勝利(スペシウム光線を使用したプレイヤー2に勝ち)
        elif action1 == "ビーム" and action2 == "ガード":
            return 0 # ゲーム続行
        elif action1 == "ビーム" and action2 == "チャージ":
            return 1
        elif action1 == "スペシウム光線" and action2 == "スペシウム光線":
            return 0 # ゲーム続行
        elif action1 == "スペシウム光線" and action2 == "ガード":
            return 1 # プレイヤー2の勝利(スペシウム光線を使用したプレイヤー1に勝ち)
        elif action1 == "スペシウム光線" and action2 == "ビーム":
            return 1
        elif action1 == "スペシウム光線" and action2 == "ガード":
            return 1
        elif action1 == "ガード" and action2 == "ガード":
            return 0 # ゲーム続行
        elif action1 == "ガード" and action2 == "ビーム":
            return 0
        elif action1 == "ガード" and action2 == "チャージ":
            return 0
        elif action1 == "ガード" and action2 == "スペシウム光線":
            return -1
        return 0 # デフォルト: ゲーム続行
    def render(self) -> None:
        """ゲームの状態を表示"""
        print(f"{self.player1.name}: チャージ = {self.player1.charge}, 最後の行動 = {self.player1.last_action}")
        print(f"{self.player2.name}: チャージ = {self.player2.charge}, 最後の行動 = {self.player2.last_action}")

株式会社ZOZO

Discussion