ふるよにエミュレータ:プレイヤータイプと集中力

シェアする

  • このエントリーをはてなブックマークに追加

こんにちは、ふるやん(@furuya1223)です。

最近、Pythonの勉強として「桜降る代に決闘を」(以下、ふるよに)というカードゲームのエミュレータを作っています。

ふるよにはデジタル版が開発中ということもあり、エミュレータをオンライン対戦できるような形で公開することはありませんが、せっかくPythonの知見が溜まってきたのでソースコードをGitHubリポジトリで公開しています。

ようやく、はじまりの決闘の構築済みデッキ(入門用に用意されたもの)での対戦がだいたいできるようになったので、実装の解説を書いてみたいと思います。

ふるよにのルールは知らない方が多いと思うので、必要な範囲で解説をしていきます。

本記事における画像は、ふるよに公式サイトで配布されている「ふるよにコモンズ」のものを使用しています。

©ふるよにコモンズ/BakaFire,TOKIAME

「桜降る代に決闘を」とは

ふるよには、2人で行う対戦ゲームです。

よくあるTCG(遊戯王、デュエルマスターズ、ポケモンカードゲームなど)のように、複数のカードをまとめたデッキを用意して、交互にターンを行いながら相手のライフを削るゲームです。

ふるよにの特徴は、セットを買えば使えるカードが全て手に入るという点です。TCGのようにランダムでカードが入ったパックを購入するということはありません。

プレイするには、まず、たくさんいるメガミの中から自分が使うメガミを2柱だけ選択します。そして、お互いが選んだメガミを同時に公開します。メガミには、近距離攻撃を得意とするもの、防御力が高いもの、特殊効果を多用するものなどの傾向があります。

次に、自分が選んだメガミに対応するカードの中から、対戦で使用するカードを選んでデッキを作ります。相手のメガミ(つまり戦法の傾向)を見てからデッキを構築するのが特徴です。

デッキは、手札や山札になる通常札と、常に伏せて置いておく切札があります。通常札7枚、切札3枚の計10枚でデッキを構成します。

対戦は、以下のようなボードを用います。

中央の間合、右下と左上にそれぞれあるライフオーラに桜の花びらの形をしたチップを置いて、数値を表します。

攻撃札を使用するときは、間合がその攻撃の適正距離に合っていないといけません。

適正距離は、攻撃札の左上に書いてある数の範囲になります。下のカードの場合、"5-9" と書いてあるので、適正距離は「5, 6, 7, 8, 9」です。これ以外の間合では、この攻撃は使えません。

そのため、「前進(間合を減らす)」や「後退(間合を増やす)」などの行動をして、間合を目的の値まで変化させる必要があります。

この行動(基本動作と呼ばれます)を行うためには、「集中力」というポイントを消費しなければいけません(集中力以外の手段で基本動作を行うことも可能ですがここでは省略します)。

今回は、この「集中力」の実装を紹介します。

集中力とは

集中力は、各プレイヤーが持つ、最低0、最大2の整数値です。

下のような「集中力カード」を使用して表します。

自分から見て上にある数を現在の値とします。上の場合は1ですね。

集中力は、最初を除く毎ターンの始め(開始フェイズ)に1だけ回復します。増やすときは反時計回りにカードを回転させます。

また、集中力を1減らす(時計回りにカードを90度回す)ことで、「前進」「後退」のような基本動作を行うことができます。

また、集中力には「畏縮」という特殊な状態があります。畏縮状態は、下の畏縮トークンを集中力カードに乗せることで表現します。

下のような「相手を畏縮させる」という効果で畏縮状態になります。

畏縮状態のとき、ターンの開始フェイズで集中力が1増える代わりに、畏縮トークンを取り除きます。つまり、集中力の値は増えません

集中力の初期値は、先攻なら0、後攻なら1です。後攻が不利にならないようになっているんですね。

以上を踏まえて、集中力を実装します。

プレイヤータイプ・Pythonにおける列挙型

その前に、プレイヤータイプを定義します。これは「先攻」か「後攻」かを表す列挙型です。

列挙型というのは、有限集合(特に数値的な意味を持たないもの。トランプのマークや性別など)を表現するための型です。統計学で言うところの「質的変数(カテゴリカル変数)」ですね。

「スペードは1、ハートは2、クラブは3、ダイヤは4」と定義してそれぞれ定数化しても良いのですが、型が int だと何を表す値なのか分かりにくいですし、足し算などができても困ります。うっかり suit = 2 のように整数のまま渡してしまったりすると、コードから情報を読み取るのが難しくなります。

今回は、FIRST(先攻)と SECOND(後攻)の2種類の値を持つ、PlayerType という列挙型クラスを定義します。

Python で列挙型を利用するには、enum.Enum をインポートし、これを継承したクラスを作成します。

クラス変数として、今回用意する FIRST, SECOND を定義します。ここで、値を設定するのですが、enum.auto() を使用するようにしましょう。自動的に異なる整数が割り当てられます。

整数としての意味をもたせたい場合は IntEnum が使えるそうですが、どういうときに使うのか分かりません。

プレイヤータイプには、相手のタイプを返すメソッドが欲しいので、opponent(self) を定義します。カード効果の「相手の集中力を 1 減らす」などで使用します。

Enum は == で同じか判定できるので、これを利用します。self == PlayerType.FIRST なら return PlayerType.SECOND をする、という感じです。

また、str(player_type) などとしたときに "先攻" や "後攻" の文字列を返すように、__str__(self) も定義します。これを定義すると、print(player_type) も可能になります(内部で str(player_type) が実行されます)。

__hoge__() というような名前の関数は特殊関数と呼ばれ、全てのクラスの親クラスである object クラスで用意されています。これをオーバーライドすることで、組み込み関数への対応や演算子オーバーロードなどができるようになります。

ということで、以下のような player.py を作成しました。

この PlayerType は、今後紹介する Player クラスのフィールドにもなります。

集中力・Pythonにおける演算子オーバーロード

ようやく集中力クラス Vigor の実装に入ります。

フィールドとして、「集中力の値 (_value: int)」「畏縮状態かどうか (_cowering: bool)」を持つ必要があります。

コンストラクタには、PlayerType を渡します。FIRST なら集中力の値は 0 、SECOND なら 1 にします。畏縮状態はどちらの場合も False です。

先攻の集中力の初期値 VIGOR_DEFAULT_FIRST=0、後攻の集中力の初期値 VIGOR_DEFAULT_SECOND=1 は、定数をまとめたファイル constants.py で定義しています。

集中力の最大値 VIGOR_MAX=2 と最小値 VIGOR_MIN=0 も constants.py で定義しています。

次に、「畏縮する」というメソッド cower() を用意します。self._cowering を True にするだけです。畏縮状態のときにこれを実行しても、特に問題はありません。

続いて、開始フェイズで行われる「集中力の回復」を行うメソッド recover() を用意します。畏縮状態であれば解除し、そうでなければ集中力の値を 1 増やします。ただし、集中力の最大値(VIGOR_MAX=2)を超えないようにします。

こちらでも文字列化を用意しておきます。集中力の値を表示し、畏縮状態であれば "(畏縮)" をつけて表示します。

次に、集中力の値を増やす方法を用意します。カード効果の「集中力を 1 増やす」などで使用します。畏縮状態でも、このような効果では畏縮が解除されないまま集中力の値が増えます。

今回は、演算子オーバーロードで実装してみます。1 増やすときは vigor += 1 として、加算代入演算子で増やせるようにします。

加算代入演算子をオーバーロードするには、__iadd__(self, other) をオーバーライドします。other が右辺です。other を正整数に限定するような処理を入れるべきかもしれませんね(現状では float などが入ってもエラーにならない)

同じように、集中力の減らすための減算代入演算子も定義します。こちらは __isub__(self, other) をオーバーライドします。

最後に、集中力の値を参照するための処理を実装します。フィールドの変数は _value というアンダースコアから始まる名前であり、外から直接触らないようにしています。(Python には private 変数などが無いので、これは public ですが、コーディング規約として触らないようにします)

vigor.value() のような形で _value の値を返してもいいですが、今回は vigor() のように、Vigor クラスのインスタンスを関数のように呼び出すことで返すようにしてみましょう。

関数のように呼び出せるようにするには、__call__(self) を実装します。引数を受け取りたい場合は、__call__(self, a) のように定義できますが、今回は使用しません。

これで集中力の実装が完成しました。

以下のように動作します。

今回実装した PlayerType, Vigor はどちらも player.py に記述しています。

いずれは Player クラスの紹介もしたいですが、そのためには桜花結晶の存在する領域やカードの実装を紹介しておく必要があるので、当分先になると思います。

次回は桜花結晶の存在する領域を表す Area クラスのそれらを継承したクラスを紹介すると思います。

ではまた。

スポンサーリンク
レクタングル(大)
レクタングル(大)

シェアする

  • このエントリーをはてなブックマークに追加

フォローする