私はゲームの達人であると同時に、ゲームのストーリーの作者でもあり、またゲームの開発者でもあります。そのため、開発中にゲームを実行したり、ゲームについて計画したり、あるいはゲームをプレイしたりしますが、そういった場合に役立つ簡単なユーティリティーやスクリプトをよく作成します。アイデアがすぐに必要な場合もあれば、NPC (Non-Player Character: ノンプレイヤー・キャラクター) 用の名前が大量に必要な場合もあります。時には数字を操作したり、何かのオッズを計算したり、あるいは何らかの言葉のパズルをゲームに組み込んだりしなければならないこともあります。こうしたタスクの多くは、あらかじめちょっとしたスクリプトを用意しておくだけで扱いやすくなります。
この記事では、さまざまなタイプのゲームに使用できる 10 種類の基本的なスクリプトについて説明します。コード・アーカイブには、ここで説明する各スクリプトの完全なソースが含まれており、またそうしたスクリプトの実際を chaoticneutral で見ることができます。
ここではこれらのスクリプトを駆け足で説明します。ホストを探したり、サーバーをセットアップしたりすることに関する内容は省略します。というのも、Web ホスティング・サービスを提供している会社で、PHP を提供しているところは数多くあり、また独自のホストをセットアップしたい場合には XAMPP インストーラーを使えば簡単に行うことができるからです。PHP でのベスト・プラクティスやゲームの設計テクニックについてはあまり時間をかけません。ここで紹介するスクリプトは、理解しやすく、使いやすく、そして即座に使えるように設計してあります。
多くのゲームやゲーム・システムにはサイコロが必要です。まず簡単なものとして、6 面体のサイコロを 1 個振ることから始めましょう。基本的に、6 面体のサイコロを 1 個振ることと 1 から 6 までのランダムな数を選択することとの間に違いはありません。これを PHP で実現するのは簡単で、echo rand(1,6); と記述するだけです。
多くの場合は、これでほぼ十分です。しかし目の出る確率を重視するゲームの場合には、もう少し優れたものを使いたいところですが、PHP にはもっと優れたランダム数生成関数、mt_rand() が用意されています。この 2 つの関数の違いについて詳しくは説明しませんが、mt_rand の方が高速で優れたランダム数生成関数と考えておけばよいでしょう。実際には、echo mt_rand(1,6); のように記述します。これを関数の中で使っておけば、さらに幅広く使える関数になるでしょう。
リスト 1. ランダム数生成関数
mt_rand() を使う
function roll () {
return mt_rand(1,6);
}
echo roll();
|
次に、振りたいサイコロのタイプを関数のパラメーターとして渡します。
リスト 2. サイコロのタイプをパラメーターとして渡す
function roll ($sides) {
return mt_rand(1,$sides);
}
echo roll(6); // roll a six-sided die
echo roll(10); // roll a ten-sided die
echo roll(20); // roll a twenty-sided die
|
これにより、必要に応じて複数のサイコロを同時に振って結果の配列を返したり、あるいは異なる種類のサイコロをすべて同時に振ったりすることができます。このスクリプトは単純ですが、大部分のタスクで使用することができます。
ゲームを実行する際、ストーリーを作成する際、あるいは大量のキャラクターをすべて一度に作成する際には、新しい名前が次々と浮かんでこないことがあるものです。この問題を解決するために使用できる簡単なランダム・ネーム・ジェネレーターを作成しましょう。まず、2 つの単純な配列を作成します (1 つは苗字 (last name) 用、もう 1 つは名前 (first name) 用です)。
リスト 3. 苗字用と名前用の 2 つの単純な配列
$male = array(
"William",
"Henry",
"Filbert",
"John",
"Pat",
);
$last = array(
"Smith",
"Jones",
"Winkler",
"Cooper",
"Cline",
);
|
すると、それぞれの配列からランダムな要素を選択すればよくなります (echo $male[array_rand($male)] . ' ' . $last[array_rand($last)];)。一度に大量の名前を抽出したい場合には、単純にこの 2 つの配列をシャッフルすれば、必要な数だけ名前を抽出することができます。
リスト 4. 名前の配列をシャッフルする
shuffle($male);
shuffle($last);
for ($i = 0; $i <= 3; $i++) {
echo $male[$i] . ' ' . $last[$i];
}
|
この基本的な概念を利用すると、苗字と名前を保持するテキスト・ファイルを作成することができます。このテキスト・ファイルの各行に 1 つずつ名前を配置するようにすると、このファイルの内容を改行文字で分割することで、ソースとなる配列を容易に作成することができます。
リスト 5. 名前用のテキスト・ファイルを作成する
$male = explode('\n', file_get_contents('names.female.txt'));
$last = explode('\n', file_get_contents('names.last.txt'));
|
適当な名前ファイルを作成するか、あるいは見つけると (「コード・アーカイブ」にはいくつかの名前ファイルが含まれています)、名前を考え出すために二度と悩む必要がなくなるでしょう。
ネーム・ジェネレーターで使用した基本原理と同じ原理を利用すると、シナリオ・ジェネレーターと呼ばれるものを作成することができます。シナリオ・ジェネレーターはロール・プレイング・ゲームで役立つほか、ロール・プレイやインプロビゼーション、ストーリー作成などに使用できる擬似ランダム環境セットが必要な状況でも役立ちます。私のお気に入りのゲームの 1 つであるパラノイアの GM パックには Mission blender と呼ばれるシナリオ・ジェネレーターが含まれています。この Mission blender を使うと、サイコロを素早く振るだけで完全なミッションを作り上げることができます。では、独自のシナリオ・ジェネレーターを作成してみましょう。
例えば次のようなシナリオを考えてみます。あなたは目を覚ますと森の中で道に迷っていました。あなたは自分がニューヨークに行く必要があることを知っていますが、その理由は知りません。あなたは犬が吠える声と、紛れもなく敵の追っ手が近くにいることを示す音を耳にします。あなたは寒くて震えており、武器は持っていません。このシナリオの各文によって、このシナリオの特定の局面が導入されます。
- 「あなたは目を覚ますと森の中で道に迷っていました」 — これによって設定が確立されます。
- 「あなたは自分がニューヨークに行く必要があることを知っています」 — これは目的を記述しています。
- 「あなたは犬が吠える声を耳にします」 — これによって敵が導入されます。
- 「あなたは寒くて震えており、武器は持っていません」 — これによって厄介な状況が追加されます。
苗字用と名前用にテキスト・ファイルを作成したのとまったく同じように、まず、設定、目的、敵、厄介な事態それぞれのためのテキスト・ファイルを作成します。コード・アーカイブにはサンプルのファイルが含まれています。これらのファイルを用意できると、シナリオを生成するためのコードは名前を生成するためのコードとほとんど同じようになります。
リスト 6. シナリオを生成する
$settings = explode("\n", file_get_contents('scenario.settings.txt'));
$objectives = explode("\n", file_get_contents('scenario.objectives.txt'));
$antagonists = explode("\n", file_get_contents('scenario.antagonists.txt'));
$complications = explode("\n", file_get_contents('scenario.complications.txt'));
shuffle($settings);
shuffle($objectives);
shuffle($antagonists);
shuffle($complications);
echo $settings[0] . ' ' . $objectives[0] . ' ' . $antagonists[0] . ' '
. $complications[0] . "<br />\n";
|
新しいテキスト・ファイルを追加することでシナリオに要素を追加することができます。あるいは厄介な状況を複数追加したい場合があるかもしれません。ベースとなるテキスト・ファイルに多くのものを追加すればするほど、シナリオは時間が経つにつれて変化に富んだものになります。
カード・ゲームをする人でカード関連のスクリプトの作成に関心がある人なら、シャッフラーを組み込んだデッキ・ビルダーを作成したくなるものです (訳注: デッキとはカード・ゲームで使用する一揃いのカードのことで、トランプの場合は通常 52 枚のカードのこと)。まず、標準的なカード・ゲームの基本となるデッキを作成します。そのためには 2 つの配列を作成する必要があります。1 つはスーツ (訳注: スーツとはトランプで言うマーク (スペード、ハート、ダイヤ、クラブ) のこと) を保持するための配列で、もう 1 つはフェイス (訳注: フェイスとはトランプで言うマークと数字が書かれた表面のことで、ここでは単に表面に書かれている数字のこと) を保持するための配列です。こうすると、後で新しいスーツやカードのタイプを追加したい場合の作業を柔軟に行えるようになります。
リスト 7. カード・ゲーム用の基本的なデッキを作成する
$suits = array (
"Spades", "Hearts", "Clubs", "Diamonds"
);
$faces = array (
"Two", "Three", "Four", "Five", "Six", "Seven", "Eight",
"Nine", "Ten", "Jack", "Queen", "King", "Ace"
);
|
次に、すべてのカードの値を保持するデッキの配列を作成します。このためには単純に二重の foreach ループを使います。
リスト 8. デッキの配列を作成する
$deck = array();
foreach ($suits as $suit) {
foreach ($faces as $face) {
$deck[] = array ("face"=>$face, "suit"=>$suit);
}
}
|
デッキの配列が作成できると、容易にデッキをシャッフルしてランダムなカードを出すことができるようになります。
リスト 9. デッキをシャッフルしてランダムなカードを出す
shuffle($deck); $card = array_shift($deck); echo $card['face'] . ' of ' . $card['suit']; |
これにより、一定数のカードを出したりマルチデッキのシュー (訳注: シューとは、複数のデッキ (例えばトランプ 6 組) を使用するゲームで未使用のカードを入れておくボックスのこと) を作成したりするのは簡単です。
上で説明したようにデッキを作成したので、各カードのフェイスとスーツを個別に追跡すると、このデッキをプログラムで利用して特定のカードを引くオッズを計算する、といったことができます。まず、それぞれ 5 枚のカードを持つ 2 つの手を作ります。
リスト 10. 5 枚のカードを持つ 2 つの手を作る
$hands = array(1 => array(), 2=>array());
for ($i = 0; $i < 5; $i++) {
$hands[1][] = implode(" of ", array_shift($deck));
$hands[2][] = implode(" of ", array_shift($deck));
}
|
次にデッキの中を調べ、カードが何枚残っているか、そして特定のカードを引くオッズがいくつかを調べます。カードが何枚残っているかは簡単です。単に $deck 配列の中に要素がいくつあるかを数えればよいだけです。特定のカードを引くオッズがいくつかを計算するためには、デッキ全体を調べて残りのカードを評価し、一致するものがあるかどうかを調べる関数が必要です。
リスト 11. 特定のカードを引くオッズを計算する
function calculate_odds($draw, $deck) {
$remaining = count($deck);
$odds = 0;
foreach ($deck as $card) {
if ( ($draw['face'] == $card['face'] && $draw['suit'] ==
$card['suit'] ) ||
($draw['face'] == '' && $draw['suit'] == $card['suit'] ) ||
($draw['face'] == $card['face'] && $draw['suit'] == '' ) ) {
$odds++;
}
}
return $odds . ' in ' $remaining;
}
|
この状態で、これから引こうとするカードを選択します。これを簡単にするためには、1 枚のカードのように見える配列を渡します。すると特定のカードを検索することができます。
リスト 12. 特定のカードを検索する
$draw = array('face' => 'Ace', 'suit' => 'Spades');
echo implode(" of ", $draw) . ' : ' . calculate_odds($draw, $deck);
|
あるいは、指定されたフェイスまたはスーツのカードを検索することもできます。
リスト 13. 指定されたフェイスまたはスーツのカードを検索する
$draw = array('face' => '', 'suit' => 'Spades');
$draw = array('face' => 'Ace', 'suit' => '');
|
さて、デッキ・ビルダーと、特定のカードを引くオッズを計算する際に役立つスクリプトが用意できたので、ポーカー・ハンド (ポーカーの役) を作成するための非常に単純なディーラーを作成します。この例では、ファイブカード・ドロー用のディーラーを作成します (訳注: ファイブカード・ドローとは、各プレイヤーに 5 枚のカードが配られ、1 回だけ交換 (ドロー) できるポーカーのこと)。このディーラーはデッキから 5 枚のカードを渡します。どのカードを捨てるかを番号で指定すると、ディーラーはそれらのカードをデッキの新しいカードと交換します。ここではドローの制限やハウス・ルールの設定はしませんが、その設定をしてみると個人的な演習として収穫があるかもしれません。
上で説明したように、デッキを生成してシャッフルし、5 枚のカードからなる 1 つのハンド (訳注: ハンドとは手札の組み合わせのこと) を作成します。これらのカードを配列のインデックスに従って表示し、どのカードを返すかを指定できるようにします。チェックボックスを使ってこれを行うと、どのカードを交換するのかを示すことができます。
リスト 14. チェックボックスを使って、交換するカードを示す
foreach ($hand as $index =>$card) {
echo "<input type='checkbox' name='card[" . $index . "]'>
" . $card['face'] . ' of ' . $card['suit'] . "<br />";
}
|
次に、入力配列 $_POST['card'] を評価し、交換用としてチェックされたカードはどれかを調べます。
リスト 15. 入力を評価する
$i = 0;
while ($i < 5) {
if (isset($_POST['card'][$i])) {
$hand[$i] = array_shift($deck);
}
}
|
このスクリプトを使用して、特定のカード・セットを処理するための最善の方法を見つけ出してみてください。
Hangman は基本的に英単語を推測するゲームです。ある長さの単語を指定されたら、制限回数以内で文字を推測していきます。その単語の中に含まれるある文字を正しく推測できると、その文字が該当するすべての部分に文字が入ります。設定された回数以上 (通常は 6 回) 間違えると負けです。原始的な Hangman ゲームを作成するためには、単語リストから開始する必要があります。とりあえず、単純な配列を作成しましょう。
リスト 16. 単語リストを作成する
$words = array (
"giants",
"triangle",
"particle",
"birdhouse",
"minimum",
"flood"
);
|
先ほど説明した手法を使うと、これらの単語を外部単語リストとしてのテキスト・ファイルに移動し、そのファイルから単語をインポートして使うことができます。
単語リストを用意できたらランダムに 1 つの単語を選択し、それぞれの文字に対して空白を表示し、推測を開始します。推測が行われるごとに、推測が当たったか外れたかを追跡する必要があります。あまり手間をかけずにこれを行うために、推測用の配列を単純にシリアライズし、推測が行われるたびにそれらの配列を渡します。もし、ゲームをする人がページのソースを見て「ズル」をするのを防ぎたい場合には、もう少しセキュアなことをする必要があります。
文字を保持する配列と推測の当たり外れを保持する配列を作成します。推測が当たった場合、配列にはキーとして文字を、値としてピリオドを入力します。
リスト 17. 文字を保持する配列と推測の当たり外れを保持する配列を作成する
$letters = array('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o',
'p','q','r','s','t','u','v','w','x','y','z');
$right = array_fill_keys($letters, '.');
$wrong = array();
|
今度は、推測を評価し、そしてゲームの進行に合わせて単語を表示するための、ちょっとしたコードが必要です。
リスト 18. 推測を評価し、ゲームの進行に合わせた表示をする
if (stristr($word, $guess)) {
$show = '';
$right[$guess] = $guess;
$wordletters = str_split($word);
foreach ($wordletters as $letter) {
$show .= $right[$letter];
}
} else {
$show = '';
$wrong[$guess] = $guess;
if (count($wrong) == 6) {
$show = $word;
} else {
foreach ($wordletters as $letter) {
$show .= $right[$letter];
}
}
}
|
ソース・アーカイブを見ると、推測の配列をシリアライズする方法と、推測を行うたびに配列を渡す方法を理解できるはずです。
クロスワード・パズルを解こうとするときには、まずい形とわかっていても、C で始まり T で終わる 5 文字の単語を見つけなければならない、という事態に陥ることがあるものです。Hangman 用に作成したものと同じ単語リストを使うと、あるパターンに一致する単語を容易に見つけることができます。まず、単語を渡す方法を設定します。簡単にするために、足りない文字をピリオドで置き換えます ($guess = "c...t";)。正規表現はピリオドを 1 つの文字として扱うので、単語リストを調べて一致するものを探すのは容易です。
リスト 19. 単語リストを調べる
foreach ($words as $word) {
if (preg_match("/^" . $_POST['guess'] . "$/",$word)) {
echo $word . "<br />\n";
}
}
|
単語リストの質と推測の精度にもよりますが、このスクリプトによって、条件に一致する妥当な単語リストが得られるはずです。ただし皆さんは、「a five-letter word that means 'to not play by the rules' (「ルールに従ってプレイするのではないこと」を意味する 5 文字の単語)」として「chest」と「cheat」のどちらが適切かを自分で判断する必要があります。
Mad Libs は単語で遊ぶゲームです。プレイヤーが、あらかじめ用意された短い話のなかでキーとなるいくつかの単語を同じタイプの別の単語で置き換えることで、おかしな話を新たに作成します。例えば次の文章を例に考えます。「I was walking in the park when I found a lake. I jumped in and swallowed too much water. I had to go to the hospital. (私は公園を歩いていて池を見つけました。私は飛び込み、大量の水を飲んでしまいました。私は病院に行かなければなりませんでした。)」まず、さまざまな単語をその単語のタイプを表す別の言葉 (トークン) に置き換え、そのトークンの先頭と末尾にアンダースコアーを付け、偶然に文字列が一致してしまうのを防ぎます。
リスト 20. さまざまな単語をその単語のタイプを表す別の言葉 (トークン) で置き換える
$text = "I was _VERB_ing in the _PLACE_ when I found a _NOUN_. I _VERB_ed in, and _VERB_ed too much _NOUN_. I had to go to the _PLACE_."; |
次に、基本的な単語リストを 2、3 個作成します。この例では、あまり凝ったものにはしません。
リスト 21. 基本的な単語リストを作成する
$verbs = array('pump', 'jump', 'walk', 'swallow', 'crawl', 'wail', 'roll');
$places = array('park', 'hospital', 'arctic', 'ocean', 'grocery', 'basement',
'attic', 'sewer');
$nouns = array('water', 'lake', 'spit', 'foot', 'worm',
'dirt', 'river', 'wankel rotary engine');
|
今度はテキストの評価を繰り返し、必要に応じてトークンを置き換えます。
リスト 22. テキストを評価する
while (preg_match("/(_VERB_)|(_PLACE_)|(_NOUN_)/", $text, $matches)) {
switch ($matches[0]) {
case '_VERB_' :
shuffle($verbs);
$text = preg_replace($matches[0], current($verbs), $text, 1);
break;
case '_PLACE_' :
shuffle($places);
$text = preg_replace($matches[0], current($places), $text, 1);
break;
case '_NOUN_' :
shuffle($nouns);
$text = preg_replace($matches[0], current($nouns), $text, 1);
break;
}
}
echo $text;
|
当然ですが、これは非常に単純で原始的な例にすぎません。単語リストが厳密であればあるほど、そしてベースとなるテキストに時間をかければかけるほど、結果はもっと優れたものになります。ここまでの段階で、テキスト・ファイルを使って名前リストと基本的な単語リストを既に作成しました。同じ原理を使うことによって、タイプごとに分けた単語リストを作成することができ、またそうしたリストを使うことで、もっと変化に富んだ Mad Libs を作成することができます。
ロトで正しい数字を 6 つ選ぶということは、少なくとも統計的にはまず起こり得ません。それでも、多くの人は相変わらずロトで遊ぶためにお金を払います。そして皆さんが数字好きであれば、数字の傾向を見るのも楽しいかもしれません。では、当たりの数字を追跡し、選択された回数が最も少ない 6 つの数字をリストの中に提供するスクリプトを作成してみましょう。
(免責事項: これを読んでも皆さんがロトで当てるのに役立つわけではありません。これは単に楽しみのためのものですので、ロトを買うのにお金を使いすぎないようにしてください。)
当たったロトの数字をテキスト・ファイルに保存します。個々の数字をカンマで区切り、それぞれの数字セットをそのセット専用の行に置きます。ファイルの内容を取得し、改行で分割し、カンマで各行を区切ると、リスト 23 のようなものが得られます。
リスト 23. 当たったロトの数字をテキスト・ファイルに保存する
$picks = array(
array('6', '10', '18', '21', '34', '40'),
array('2', '8', '13', '22', '30', '39'),
array('3', '9', '14', '25', '31', '35'),
array('11', '12', '16', '24', '36', '37'),
array('4', '7', '17', '26', '32', '33')
);
|
当然ですが、これは当たりの統計のベースに使うファイルとしては不十分ですが、これは出発点であり、原理を説明するためには十分です。
選択範囲を保持するためのベースとなる配列を設定します。例えば 1 から 40 のなかから数字を選択する場合には $numbers = array_fill(1,40,0); のように記述します。それから選択した数字を順に調べ、該当する適切な値をインクリメントします。
リスト 24. 選択した数字を順に調べる
foreach ($picks as $pick) {
foreach ($pick as $number) {
$numbers[$number]++;
}
}
|
最後に、値に従って数字をソートします。こうすることによって、選択された回数が最も少ない数字が配列の先頭に置かれるようになります。
リスト 25. 値に従って数字をソートする
asort($numbers);
$pick = array_slice($numbers,0,6,true);
echo implode(',', array_keys($pick));
|
この当たり数字のリストを含むテキスト・ファイルに対して、実際にロトで当たった数字を定期的に追加していくと、選択される数字の長期的な傾向を調べることができます。ある数字がどの程度頻繁に登場するかを見てみると面白いでしょう。
この記事では、ゲームに関するエクスペリエンスを PHP を使ってリッチにするための、即座に使用できるリソースを紹介しました。この「PHP で作成する 30 種類のゲーム・スクリプト」シリーズの第 2 回では、今回紹介したコードを元に、さらに大きなメリットのある、もっと複雑なスクリプトを作成します。
| 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|---|---|---|
| 10 PHP scripts | os-php-gamescripts1-php10games1.zip | 377KB | HTTP |
学ぶために
- XAMPP のページを訪れてください。XAMPP は容易にインストールできる Apache のディストリビューションであり、MySQL、PHP、Perl が含まれています。
- この記事で紹介したすべてのスクリプトの実際の例を調べてみてください
- U.S. Census Bureau は名前のリストのソースとして非常に便利です。
- Paranoia に関する情報を入手してください。
- Five-card draw の基本的なルールを学んでください。
- 手軽な単語リストがここにあります。ManyThings.org にはさらに多くの単語リストがあります。
- Mad Libs の公式サイトを訪れてください。
- Linux で Apache V2 と PHP V4.x を一緒に動作させるための方法を解説した記事、Apache V2 and PHP Installation を読んでください (PHP V5.x も使うかもしれません)。
- 「Connecting PHP applications to Apache Derby」は、Windows に PHP をインストールして構成する方法を解説しています (一部のステップは Linux にも応用することができます)。
- 「Learning PHP, Part 1」を読み、PHP の基本を学んでください。次に「Part 2」を読み、Web にアクセスできない場所から DOM と SAX を使ってファイルをアップロードする方法を学んでください。そして「第 3 回」ではワークフロー・アプリケーションを完成させます。
- PHP Manual を調べ、PHP のデータ・オブジェクトとその機能について学んでください。
- PHP.net には PHP 開発者のためのリソースが集まっています。
- 「Recommended PHP reading list」を調べてみてください。
- developerWorks には他にも PHP に関する資料が豊富に用意されています。
- IBM developerWorks の PHP project resources を利用して PHP のスキルを磨いてください。
- developerWorks podcasts では、ソフトウェア開発者のための興味深いインタビューや議論を聞くことができます。
- PHP でデータベースを使うのであれば、Zend Core for IBM を調べてみてください。これはシームレスでそのまま使用でき、インストールも容易な、IBM DB2 V9 をサポートする PHP の開発環境であり実動環境です。
- developerWorks の Technical events and webcasts で最新情報を入手してください。
- IBM オープンソース開発者にとって関心のある、世界中で今後開催される会議や業界展示会、ウェブキャスト、その他のイベントについて調べてみてください。
- developerWorks の Open source ゾーンをご覧ください。オープンソース技術を使った開発や、IBM 製品でオープンソース技術を使用するためのハウ・ツー情報やツール、プロジェクトの更新情報など、豊富な情報が用意されています。
- IBM とオープンソース技術、そして製品機能を調べ、学ぶために、無料の developerWorks On demand demos をご覧ください。
製品や技術を入手するために
- 皆さんの次期オープンソース開発プロジェクトを IBM ソフトウェアの試用版を使って革新してください。ダウンロード、あるいは DVD で入手することができます。
- IBM 製品の評価版をダウンロードし、DB2® や Lotus®、Rational®、Tivoli®、WebSphere® などが提供するアプリケーション開発ツールやミドルウェア製品をお試しください。
議論するために
- developerWorks blogs から developerWorks のコミュニティーに加わってください。
- developerWorks の PHP Forum: Developing PHP applications with IBM Information Management products (DB2, IDS) に加わってください。