【PHP】プログラミングゲーム「エンジニアが死滅シタ世界」をやってみた【解答例】
piazaというプログラマ向け学習等のサービスを行っているサイトがあります。
そのコンテンツの一部としてプログラミング学習が出来るゲームがありまして、過去何回か記事に書いた事があります。
過去の記事の一つ→【paiza】プログラミングでアイドルをプロデュースPHP編
今回新たにゲームが追加されたそうなので、遊んでみます。
概要
ゲームのタイトルは
エンジニアが死滅シタ世界 ~アンドロイドとふたりぼっちで生きろ~
です。
プログラミング問題が出題され、コードを書いて解答する
感じになります。
プログラミングが好きな人なら面白いのではないかと思います。
百聞は一見に如かずですので、ぜひサイトに行ってみてください。(宣伝ではない)
せっかくなので
このページでは、私が記述したPHPでの解答例を出していきたいとます。
いろんなコードを見ると勉強になるので、私以外の人のPHPの勉強になればという思いです。
きっと「皆さんが自分で書いたコード」とは違うと思いますし…
コードを書くにあたって考慮した点も、一緒に記述しています。
荒れ果てた警察署
概要
- ランク:D
- アイテム:ショート
- 問題:二つの数字から計算して答えを出す
解答例
<?php
echo array_sum(explode(' ', trim(fgets(STDIN)))) % 10;
?>
二つの数字を足して10で割った余りを出力します。自分で足すのもアレなので配列にして勝手に計算させます。
こうしておくと、数字が増えていってもコードがそのままで対応できます。
荒れ果てたオフィス
概要
- ランク:D
- アイテム:たれ目
- 問題:重要度が3のタイトルを出力する
解答例
<?php
for ($i = trim(fgets(STDIN)); $i > 0; $i --) {
fscanf(STDIN, '%s %d', $s, $e);
$o[$e][] = $s;
}
echo implode("\n", $o[3]);
?>
重要度ごとに配列に格納します。
最後に3のものを出力しています。
配列の後ろに追加する[]は非常によく使います。
配列の初期化をしていませんが、本来はちゃんと初期化しましょう。
アンドロイドの生産工場
概要
- ランク:D
- アイテム:ロング
- 問題:文字列の奇数文字を取り出す
解答例
<?php
echo implode(array_filter(str_split(trim(fgets(STDIN))), function ($k) {
return $k % 2 == 0;
}, ARRAY_FILTER_USE_KEY));
?>
別に文字列を一文字ずつ飛ばして出力していってもいいのですが、つまらないのでやりません。
str_splitで入力された文字列を、一文字ずつバラバラの配列に変換します。
array_filterは関数の戻り値がtrueの条件で配列をフィルタリングしてくれます。ARRAY_FILTER_USE_KEYを使う事で、関数に値ではなくキー値を渡すことができます。
$k文字目が偶数の場合trueにします。配列なので1文字目(奇数)は0(偶数)から始まります。
implodeで文字列に戻します。
錆びついた電波塔
概要
- ランク:D
- アイテム:ニーソックス
- 問題:任意の電波強度以上の回数を出力する
解答例
<?php
fgets(STDIN);
echo count(array_filter(explode(' ', trim(fgets(STDIN))), function ($d) {
return $d > 5;
}));
?>
またarray_filterを使います。
今回は素直に値で判断します。
普通にcount取ればよいです。
お金が引き出せない銀行
概要
- ランク:D
- アイテム:探索服
- 問題:お金を引き出し残高を判定する
解答例
<?php
$a = trim(fgets(STDIN)) - trim(fgets(STDIN));
echo $a < 0 ? 'error' : $a;
?>
何も言う事はないくらいこのままです。
考慮点は、マイナス演算子の左右どちらが先になるか、くらいですかね。
荒れ果てたショップ
概要
- ランク:C
- アイテム:街
- 問題:入力された数字をN桁で0埋め
解答例1 - すぐ思いつく版
<?php
fscanf(STDIN, '%d %d %d', $n, $a, $b);
for ($i = $a; $i <= $b; $i ++) {
printf("%0${n}d\n", $i);
}
?>
0埋めは通常printfで行いますので、そのまま使います。
printfのフォーマット文字列の部分は、ダブルクォーテーションを使うと変数をそのまま埋め込めるため、そうしています。
本来ならば、$nと記述したいところですが、後ろにdがあるため「%nd」という変数だと扱われてエラーとなります。
そのため、変数の区切りを明確にする{}を使用して囲みます。
$aから$bは常識的なループです。
解答例2 - range版
<?php
fscanf(STDIN, '%d %d %d', $n, $a, $b);
foreach (range($a, $b) as $i) {
printf("%0${n}d\n", $i);
}
?>
ループの仕方が違います。rangeで最初に配列を作成しループさせています。
range部分を任意の配列に置き換えることが出来るため、柔軟性が高くなります。
解答例3 - array_map版
<?php
fscanf(STDIN, '%d %d %d', $n, $a, $b);
$f = "%0${n}d";
echo implode("\n", array_map(function($i) use ($f) {
return sprintf($f, $i);
}, range($a, $b)));
?>
もはや、行けるところまで行った感があります。
rangeで配列になったので、出力値も配列にしましょうという事で、array_mapを使用します。
array_mapは配列の要素分、関数を呼び出し、関数の戻り値を配列にしてくれます。
その時に無名関数を使うわけですが、sprintfのフォーマットがスコープ内にないため、useを使いグローバルスコープから持ってきます。JavaScript等では何も書かなくても外の変数を参照することができますが、PHPではuseで明示して持ってくる必要があります。
機械の総合病院
概要
- ランク:C
- アイテム:ニットセーター
- 問題:ユーザーが設定するパスワードのチェック
解答例
<?php
$t = trim(fgets(STDIN));
echo (preg_match('/(.)\1\1/', $t) == 0
&& preg_match('/[a-zA-Z]+/', $t)
&& preg_match('/\d+/', $t)
&& strlen($t) >= 6) ? 'Valid' : 'Invalid';
?>
ユーザーから入力された値のチェックは、正規表現で行うことが多いです。
今回のチェックでの特殊な条件は「同じ文字を3つ以上連続で使用することはできない」です。
逆に言うと3つ以上の連続を検出しなかった(preg_matchの戻り値が0)ならばOKです。
本来はpreg_matchの戻り値にはFALSEもあるため===を使います。
正規表現
'/(.)\1\1/'
の意味ですが、任意の文字を表す「.」を()で囲みキャプチャしています。それを「\1」で使用することで同じ文字を表しています。
学べない学校
概要
- ランク:C
- アイテム:おさげ
- 問題:じゃんけんの勝った数、負けた数を出力
解答例
<?php
$j = ['gc'=>1,'pg'=>1,'cp'=>1,'cg'=>2,'gp'=>2,'pc'=>2,'gg'=>0,'pp'=>0,'cc'=>0];
$r = [0, 0, 0];
for ($i = trim(fgets(STDIN)); $i > 0; $i --) {
$r[$j[str_replace([' ', "\n"], '', fgets(STDIN))]] ++;
}
printf("%d\n%d", $r[1], $r[2]);
?>
このような処理の場合、どれだけ最初にパターンを用意するかでロジックの簡易さが変わります。
上記のような、あらかじめ全パターンが少なく、特殊な条件がない場合、そのまま最初にデータ化しておけば条件分岐が必要なくなります。
データ部さえ差し替えれば良くなるため、後から例えば
「あいこの時も負けにしない?」
のように気軽に言われた場合の変更も、容易にできるようになります。
次回
続き→【PHP】プログラミングゲーム「エンジニアが死滅シタ世界」をやってみた その2