【PHP】ゲーム STEINS;GATE 電脳言語のオルダーソンループ プログラミング【解答例】
piazaというプログラマ向け学習等のサービスを行っているサイトがあります。
過去に何回か、このように解答を記事にした事があります。
最近見たら、新しいものが何個か追加されていたので、遊んでみました。
概要
ゲームのタイトルは
です。
ADVのような形式となっています。
シナリオを選ぶとプログラミング問題が出題され、正解のコードを書くと先へ進める
といった遊び方となっています。
シュタインズゲートというゲーム(アニメの方が有名)があるのですが、その拡張シナリオのような形をとっています。
このページでは、私が書いたPHPでの解答例を出していきたいとます。
色々なコードを見ると勉強になるので、私以外の人のPHPの勉強になれば幸いです。
コードを書くにあたって考慮した点も、一緒に記述しています。
正規表現のエントリーポイント
概要
- 問題:2つの単語が改行区切りで入力されるので「;」区切りで出力する。
解答例
<?php
$w = [];
$w[] = trim(fgets(STDIN));
$w[] = trim(fgets(STDIN));
echo implode(';', $w);
?>
「単語を区切り文字に使って結合させる」という処理は、実際のプログラミングでも割と使います。
例えば、カンマ区切り(CSV)などがそうですね。
スタンダードなやり方としては、配列に入れてimplodeで結合するという処理となります。
こうしておくと、入力が増えたときもOKですし、「単語が1つしかない場合は区切りが必要ない」といった例外にも対処できます。
性能解析のタイプセーフ
概要
- 問題:経験値はに必要な戦闘回数を出力する。
解答例
<?php
fscanf(STDIN, '%s %s', $c, $e);
$t = 0;
for ($i = 0; $i < $c; $i ++) {
$t += trim(fgets(STDIN));
}
printf("%d\n", ceil($t / $e));
?>
難しくありませんね。
言語による切り上げの処理を覚えているかどうかだけだと思います。
例外処理のタブーサーチ
概要
- 問題:格闘ゲームのコンボのコマンド入力を復元する。
解答例
<?php
fscanf(STDIN, '%s %s %s', $n, $m, $l);
$d = [];
for ($i = 1; $i <= $n; $i ++) {
$d[trim(fgets(STDIN))] = $i;
}
$p = [];
for ($i = 1; $i <= $l; $i ++) {
$p[] = explode(' ', trim(fgets(STDIN)));
}
for ($i = 1; $i < $l; $i ++) {
$diff = [];
foreach ($p[$i] as $j => $v) {
$diff[] = $v - $p[$i - 1][$j];
}
echo $d[implode(' ', $diff)] . "\n";
}
?>
問題文がなんだかややこしい書き方をしていますが、内容はそんなに難しくはないと思います。
問題文における例では、「-8 7 6」が1、「-5 0 -1」が2、「3 6 -9」が3となっています。
最終的な出力は”コマンド”になりますので、1 or 2 or 3をどうにかして導き出すことになります。
ということは、あくまで「-8 7 6」の部分は1という出力を導き出すキーになりますので、最初のループで配列$dのキーに「-8 7 6」のようなパラメータの変化量をそのままの形で設定し、値の方に1のような出力となるコマンドを設定しています。
二番目のループは、配列$pに入力の時系列を入れます。
三番目のループは、まず時系列の差分を$diffとして格納します。
$diffをimplodeを使い空白区切りでくっつけて、それをキーとして$dを出力すればコマンドが復元できます。
進化戦略のプロシージャ
概要
- 問題:ダンジョン部屋移動(複数人)ができるかどうかを出力する。
解答例
<?php
function mv($x) {
global $room;
if ($room[$x] < 0) {
echo "Yes\n";
exit;
}
$to = $room[$x];
if ($room[$to] !== 0) {
$room[$x] = -2;
mv($to);
}
$room[$x] = 0;
$room[$to] = -1;
}
fscanf(STDIN, '%s %s', $n, $m);
$room = array_fill(1, $n, 0);
for ($i = 1; $i <= $m; $i ++) {
fscanf(STDIN, '%s %s', $s_i, $t_i);
$room[$s_i] = ($s_i === $t_i) ? -1 : $t_i;
}
for ($i = 1; $i <= $m; $i ++) {
if ($room[$i] > 0) {
mv($i);
}
}
echo "No\n";
?>
難しそうに見えて簡単です。
「ある部屋に入っている人が移動できるか」を調べるためには、「その人が移動する先の部屋の人も移動できるか」といったように再帰的に処理されると考えられるため、再帰する関数mvを作りました。
関数mvは部屋移動処理のための関数です。後ほど説明します。
配列$roomは部屋です。array_fillで部屋数分を0で埋めます。部屋配列$roomのindexは1~$nまでです。
最初のループで$roomに移動先の配列indexを入れておきます。
$room[index]の値の意味は
1以上:移動先index
0:空室
-1:移動済
-2:移動中
とします。
二番目のループで、空室でなかったらmv関数を呼びます。
mv関数を部屋数分処理できれば、最終行でNoを出力し終わります。
mv関数は、$room[$x]の部屋が「0:空室」かどうか調べます。空室ならば移動できるからです。
$room[$x]が0より下(移動中の人がいる or 移動済の人がいる)ならば移動できないため、ただちにYesを出力してexitでプログラム自体を終了します。
空室でないなら、その部屋の人が移動できるかの判定のため、さらにmv($to)で再帰的に調べます。
移動中は$room[$x]を「-2:移動中」としておかないと無限ループになる可能性があります。
移動後は移動先を「-1:移動済」にして、移動元は「0:空室」とします。
ちなみに、罠として「滞在している部屋と移動先の部屋が同じ場合」があり、これはOKなので考慮する必要があります。上記のコードは入力を$roomに入れる際に既に移動済として処理しています。
私は、この処理を修正時に消してしまい、いつまでたったも「コードは合ってる気がするが正解にならない」ループに陥りまして、もう一回問題文を読んで気づいたということがありましたので、軽い気持ちでやるのも良くないなと思いました。
まとめ
久しぶりにやったら面白かったのですが、勘を取り戻すのに時間がかかりました。
定期的にpaizaみたいなのやった方がいいかもしんないですね。