じゃんけんの勝敗判定

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

じゃんけんの勝敗判定 とは

複数の if を組み合わせて、3 すくみの勝敗を判定するロジックを書こう。本レッスンでは、じゃんけんの勝敗判定 の基本から実際の使いどころまでを整理し、現場で迷わず使える形に落とし込みます。

じゃんけんで学ぶ複雑な条件分岐

ここまでで if 文と else 文を使った 2 択の分岐を見てきました。「a > 0 なら正、そうでなければ負」のように、truefalse の片方に従って処理を切り替えるパターンです。しかし現実のプログラムでは、もっと 3 通り以上の結果 を扱いたい場面が山のように出てきます。たとえばユーザーのランクを gold silver bronze の 3 段階で振り分けたい、テストの点数を A B C D の 4 段階で評価したい、といったケースです。

そんなときの最初の練習台にぴったりなのが、誰もが知っている じゃんけん です。手はグー・チョキ・パーの 3 種類、結果は勝ち・負け・引き分けの 3 種類。条件分岐の練習問題として完璧な題材です。

プログラミングの世界では、じゃんけんのような 三すくみ (rock-paper-scissors) は条件分岐の登竜門として有名です。Java だけでなく Python JavaScript どの言語の教材にも必ず登場します。

このレッスンでは、Solution.janken(int me, int opponent) メソッドを完成させて、私の手と相手の手から勝敗の文字列を返します。手は 0 がグー、1 がチョキ、2 がパーで、戻り値は "win" "lose" "draw" のいずれかです。

ルールの整理

まずはルールを Java のコードに落とし込みやすい形でまとめます。じゃんけんの勝敗関係は以下の通りです。

  • グー (0) はチョキ (1) に勝つ
  • チョキ (1) はパー (2) に勝つ
  • パー (2) はグー (0) に勝つ
  • 同じ手 (me == opponent) なら引き分け

3 つの勝ちパターンと 1 つの引き分けパターン、合わせて 4 種類のケースを if で書き分けるのが基本のアプローチです。

「全部のパターンを列挙する」というのは、最初の頃は遠回りに感じるかもしれません。でも条件分岐に慣れていないうちは、スマートに書こうとして条件を取りこぼす ほうがずっと危険です。まずは愚直に全部書きましょう。

判定フローを図で確認する

判定の流れを Mermaid で図にすると、頭の中の整理がぐっと楽になります。

diagram (will load when visible)

上から順に判定していけば、いつかは必ずどれかの return に当たります。図にしておくと「漏れがないか」「順番は妥当か」を確認しやすくなります。

引き分けを最初に判定するパターン

まずは最もシンプルな書き方から見ましょう。引き分けは me == opponent という 1 行で判定できるので、これを 最初に チェックするのがコツです。

Java

public class Solution { public static String janken(int me, int opponent) { if (me == opponent) { return "draw"; } if (me == 0 && opponent == 1) { return "win"; } if (me == 1 && opponent == 2) { return "win"; } if (me == 2 && opponent == 0) { return "win"; } return "lose"; } }

引き分けを先に片付けてしまえば、残りは「勝ち 3 パターン」と「それ以外は負け」の判定に集中できます。各 if の中で return してしまうので else を書く必要すらありません。これは Java で頻出する 早期 return (early return) のスタイルです。

早期 return は読みやすさの強い味方です。ネストが深くならず、各分岐の条件が独立して見えるので、後からコードを読み返したときに「ここで何を判定しているか」が一目でわかります。

勝ちパターンを全部列挙する書き方

別解として、勝ちパターンを || で 1 つの式にまとめる書き方もあります。

Java

public class Solution { public static String janken(int me, int opponent) { if (me == opponent) { return "draw"; } boolean win = (me == 0 && opponent == 1) || (me == 1 && opponent == 2) || (me == 2 && opponent == 0); if (win) { return "win"; } return "lose"; } }

&& (かつ) と || (または) の使い分けがポイントです。1 つの勝ちパターンの中では me == 0 && opponent == 1 のように 両方が同時に成り立つ 必要があるので &&、3 つの勝ちパターンのどれかに当てはまればよいので || で繋ぐ、という構造です。

この書き方の利点は、勝ち条件全体が boolean win という名前付きの変数に入るので、後から「勝ち条件をテストしたい」「ログに残したい」といった拡張がしやすいことです。

(me + 1) % 3 == opponent でスマートに書く

上級者向けの別解として、剰余演算子 % を使った 1 行解法もあります。じゃんけんの勝ちパターンには 隠れた規則性 があります。

  • グー (0) はチョキ (1) に勝つ — 0 + 1 = 1
  • チョキ (1) はパー (2) に勝つ — 1 + 1 = 2
  • パー (2) はグー (0) に勝つ — 2 + 1 = 3、ただし 3 % 3 = 0

つまり「自分の手に +1 した値 (3 で割った余り) が相手の手と等しい」なら自分の勝ちです。コードにすると次の通りです。

Java

public class Solution { public static String janken(int me, int opponent) { if (me == opponent) { return "draw"; } if ((me + 1) % 3 == opponent) { return "win"; } return "lose"; } }

たった 3 つの if で全パターンを判定できるエレガントな書き方です。ただし、可読性は人によって賛否があります。チームのコードレビューでは「式の意味がパッと読めないからコメントを付けて」と指摘されることも多いので、用法用量を守って使ってください。

「短いコード = 良いコード」ではありません。Java の現場では 読み手の負担 が最優先されることが多く、愚直な if の列挙のほうが好まれるケースも珍しくありません。

よくある間違い

条件分岐の練習で初心者が踏むワナを 3 つ紹介します。

  • 条件の網羅性が足りない — 勝ちパターンを 2 つしか書かずに残り 1 つを忘れる、引き分けの判定を書き忘れる、といったケースです。テストケースを増やしてあげると、こういった抜けが必ず炙り出されます。先ほどの Mermaid 図のように、すべての分岐を絵にしてから書くと抜けが減ります
  • ||&& の使い間違いme == 0 || opponent == 1 のように、本来 && で繋ぐべき条件を || で書いてしまうミスです。||どちらか片方でも true ならパスしてしまうので、「自分がグー」というだけで勝ち扱いになるなど、判定が壊滅的に狂います。「両方とも成り立つ必要があるなら &&、どれか 1 つでも成り立てばよいなら ||」と毎回声に出して確かめてください
  • 引き分けの検出忘れ — 勝ちパターンだけ書いて、勝ちでなかったらすべて "lose" を返してしまうと、janken(1, 1) のような同じ手のときも "lose" になります。me == opponent をどこかで必ず判定する必要があります

もうひとつ、地味だけど頻発するのが 戻り値の文字列のタイポ です。"win" "lose" "draw" という綴りは英語が苦手な人ほど "Win" "loose" "drow" のように打ち間違えがちです。Java の文字列比較は大文字小文字を区別するので、テストの expected と 1 文字でも違えば fail します。

エラーメッセージで expected: win, actual: Win のような出力が出てきたら、まずは綴りを疑ってみましょう。コピー&ペーストできる場面ではなるべく手打ちせず、貼り付けるのも 1 つの安全策です。

やってみよう

それでは右側のエディタで課題に挑戦しましょう。書き方は自由ですが、以下の手順がオススメです。

  1. まず if (me == opponent) で引き分けを判定する
  2. 続いて勝ちの 3 パターン (0 vs 11 vs 22 vs 0) を if で順番に判定する
  3. どれにも当てはまらなければ最後に return "lose"; で締める

慣れてきたら、(me + 1) % 3 == opponent を使った 1 行解法に書き換えてみたり、|| で勝ちパターンを 1 つにまとめてみたり、リファクタリングの練習にしてみてください。同じ動きを 3 通りで書けるようになると、Java の条件分岐がぐっと自分のものになります。

また、Solution.janken(int me, int opponent) の引数を変えて、janken(0, 0) (引き分け)、janken(2, 1) (負け)、janken(1, 2) (勝ち) などを手元で予想 → 実行してみると理解が深まります。条件分岐は手を動かして体に染み込ませる単元です。Android アプリでも Web アプリでも Minecraft Mod でも、書くプログラムの大半に条件分岐が登場します。ここでしっかり練習しておきましょう。

次のレッスン

次は FizzBuzz の判定部分 で、複数の if を組み合わせて、3 すくみの勝敗を判定するロジックを書こう を学びます。

事前確認 — 進む前に次の 3 つができることを確認しましょう。

  1. じゃんけん の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  3. 練習問題やクイズで間違えた箇所を読み直して理解した

理解度チェック (30 秒)

Q. じゃんけん とは何か、1 文で説明してください。

この章のポイント

A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。

関連レッスン

要件

  1. クラス名は Solution、メソッド名は janken、引数は int me, int opponent の 2 つにすること
  2. 戻り値の型は String で、勝ちなら "win"、負けなら "lose"、引き分けなら "draw" を返すこと
  3. ルールは グー (0) > チョキ (1) > パー (2) > グー (0) の三すくみで、同じ手なら引き分け

入出力例

test-cases.txt

janken(0, 1)"win" janken(1, 2)"win" janken(2, 0)"win" janken(0, 2)"lose" janken(1, 0)"lose" janken(2, 1)"lose" janken(0, 0)"draw" janken(1, 1)"draw"

ヒント

main.java
main.java
学習モード

メモ

じゃんけんの勝敗判定

⌘S で保存