ジャンケンの判定
NBUゆるゆるかれんだー Advent Calendar 2022
https://adventar.org/calendars/7785
23日目の穴が空いていたので埋めます。
前振り
年末、介護施設に10ヶ月入所していた父が車椅子から離れて生活できそうだという事で実家に戻ってくる。そこで、部屋の片付けと家具のレイアウト変更が必要と母に呼ばれて手伝いに岡崎に来ている。スマホとルービックキューブだけ持って。
家の中のアチコチや道路までの階段に手すりが追加されていた。すっかり筋肉が落ちてしまったが何とか掴まり立ちして伝い歩きができるらしい。
ベットや机を移動したりエアコンが不調と言うのでフィルターを外したりしてた。明日はテレビの移動とアンテナの配線をやろう。
今はコタツに寝転んでスマホからこの記事を書いているところ。Google Siteはタッチパネルからの操作に不具合があってスマホからはとても書きにくい。
本題
プログラムの条件判定の練習にジャンケンの判定を書くのは手頃で良い題材ですね。
私はC言語を講義で担当したことも普段使うこともないのですが、今回はC言語で書きます。
理由は短く書けそうだから。
スマホからコードを書くので、記号1つ入力するにも大変苦戦します。全角の”が入力されたり。半角の"と区別つかないですよね。短いコードは正義です。
なるべく短いコード書く競技?プログラミングにコードゴルフといものがあります。今回のコードはそれです。
問題
標準入力に文字列が1行、次の書式で入力される。
A B
1文字目のAの位置にはG C Pのどれか1文字
2文字目は空白文字
3文字目のBの位置にはG C Pのどれか1文字。
この入力に対してW L Dのどれか1文字を出力するプログラムを作成せよ。
出力する文字の条件はG C PをジャンケンのグーチョキパーとみてAの勝ちをW 負けをL 引分をD とする。
問題ここまで。
C言語を学んだ事があるなら
ifで条件分岐 / 条件論理 / 文字の読み込みと出力 scanfとかprintf
などが頭に浮かぶことでしょう。私もそうです。
大体こんな感じかな?if 勝ち else if 負け else 引分 など。
ある程度の頭に完成イメージまで行く人もいると思います。
思い浮かびましたか?
では、下に例を載せますのでなるべく短いコードを書いてから比べてみましょう。
例まで 5
例まで 4
例まで 3
例まで 2
例まで 1
例です。※bugアリ。下の方に修正版を追記。より短くした。
解説
ideone はブラウザから様々なプログラム言語を試せるサイトです。今回の様にちょっとコードを試すのに便利です。私は言語の文法の確認に使ったりしてます。
入力を書き換えてその他のケースでも判定に問題が無いことを確かめてみてください。
本来は改行コードが無駄なので改行を削って1行で書くのですが今回は書きやすさの点で改行しています。
#include やint mainなど決まり事はコンパイラーに任せて省略。warningは出ます。
char x,y; scanf("%c %c",&x,&y);と書いていたけどバイト列で書けば書式を無くしてgetsで代用できるし文字数が減る。ただしgetsはバッファオーバラン攻撃の温床なので非推奨です。
printf("%c",とか1文字の出力に文字数的に贅沢なのでputcharに変更。
"WLLDWWL"は勝敗判定表です。文字列は配列へのポインタなので[インデックスの式]で判定結果にアクセスできますね。
G C P はasciiコードで71 67 80(10進数)なので
A-Bを計算すると
80-67= 13
67-80 = -13
80-71 = 9
71-67 = 4
引分 = 0
マイナスのインデックスは困るので 13+ しておいて、A-Bの結果を
0~26に収めます。
AとBを1ビット右シフトして下位2ビットを&3で取り出すと
G C Pは 3 1 0 に圧縮できます。
これで3+A-Bの結果は0~6に収まるので対応する位置にWLDを並べて完成。
演算子の優先順位に苦戦。&が低くて助かります。
Bug対応版
大晦日に朝風呂に入っていて気がついたのでそのまま浴槽からスマホで検証して直しました。
ついでに気になっていたcharバッファの代わりにintで行けるのではも試していけたのでより短くできた。
解説
結果は0~6なので3bitですから式の最後の&3は&7でした。
char x[4];の代わりにint yを使います。3文字の入力なので24bitでintで足りますね。
ただしgetsの格納順が下位バイトからになっていたのでアクセスに要注意です。
つまり1文字目は下位8bitで3文字目は下位から数えて24~17bitです。
/2の操作と合わせて>>17で済むのはお得ですが()で優先順位を上げる必要があります。
小技としてmainの引数でint yを宣言して変数宣言の末尾の;を使わなくてよいようにしています。
コマンドライン引数用のint c, char*argvは使わないし都合が良いですね。
追記
式で3+しなくてもよい。&7でマイナスにはならない。
しかしテーブルが1つ長くなって差し引き1文字短縮。
判定テーブルを"WLD"のようにコンパクトにする試みを掲載。逆に式が複雑になるのでコード短縮にはつながらなかった。