☕ JAVA NOTEBOOK

🎮 ミニゲーム

練習問題の定番ゲーム11個を難易度順に。初級はランダムと if、中級は配列・文字列と判定の工夫、上級は二次元配列です。

ミニゲーム

遊びたいゲームを選ぶと、そのゲームだけが表示されます。

初級

使うのは「ランダム」と「if(条件分岐)」だけ。まずここから。

🔢 数当てゲーム

初級

コンピュータが 1〜100 の中から決めた数字を当てる、練習問題の定番です。「もっと大きい/小さい」のヒントが頼りです。

数字を入れて「当てる」を押してください。
挑戦回数:0 回
仕組みとコードの説明(はじめての人向け)
const answer = Math.floor(Math.random() * 100) + 1;

if (guess === answer) {
  // 当たり
} else if (guess < answer) {
  // もっと大きい
} else {
  // もっと小さい
}

どうやって動いているか

  1. ゲームが始まると、コンピュータはまず「答え」をこっそり決めます。1〜100の中から、サイコロを振るように適当に1つ選んで、名前つきの箱にしまっておきます。この「名前つきの箱」のことを、プログラムでは変数と呼びます。答えはあなたには見えません。
  2. あなたが数字を入れてボタンを押すと、コンピュータは「箱の中の答え」と「あなたの数字」を比べます。
  3. 同じなら当たり。あなたの数字の方が小さければ「もっと大きい」、大きければ「もっと小さい」と教えてくれます。
  4. 当たるまで、これをくり返します。「挑戦回数」は、別の箱に1ずつ足して数えています。

コードを、ふだんの言葉で読むと

  • 1行目は、答えを決める行です。「0〜99のサイコロ」を作って1を足すので、答えは1〜100になります。
  • if は「もし〜なら」という意味で、「答えとまったく同じなら」という条件です。
  • else if は「それ以外で、もし〜なら」。ここでは「答えより小さいとき」です。
  • else は「それ以外は全部」。残りは「大きい」しかないので、ここで受け止めます。

なぜ、こう書くのか

  • 答えをランダム(適当な数字)にする理由:いつも同じ数字だと、2回目から答えが分かってしまい、ゲームにならないからです。
  • 条件を3つに分ける理由:比べた結果は「同じ・小さい・大きい」の3通りしかありません。起こる場合を全部、漏れなく分けるのがプログラムの基本の書き方です。
  • 答えではなく方向だけ教える理由:答えそのものは教えず、方向だけ教えるからゲームになります。ヒントのたびに候補がしぼれるので、真ん中の数字から聞いていくのがコツです。

✊ じゃんけん

初級

手を選ぶと、コンピュータがランダムで手を出します。勝ち・負け・あいこの数を数えます。

手を選んでください。
勝ち 0 ・ 負け 0 ・ あいこ 0
仕組みとコードの説明(はじめての人向け)
// 手を数字で表す:0=グー、1=チョキ、2=パー
const cpu = Math.floor(Math.random() * 3);
const diff = (cpu - player + 3) % 3;

if (diff === 0) { /* あいこ */ }
else if (diff === 1) { /* 勝ち */ }
else { /* 負け */ }

どうやって動いているか

  1. あなたがボタンを押すと、コンピュータもその場で手を決めます。決め方はサイコロと同じで、0・1・2のどれか1つを適当に選びます。
  2. 実はコンピュータの中では、グー・チョキ・パーを言葉ではなく数字で覚えています(0=グー、1=チョキ、2=パー)。
  3. 2つの数字を引き算して、その結果で「勝ち・負け・あいこ」を判定します。
  4. 結果を表示して、勝ち・負け・あいこの箱に、それぞれ数を1つ足します。

コードを、ふだんの言葉で読むと

  • cpu の行は、コンピュータの手を0〜2のサイコロで決めています。
  • diff の行は、「コンピュータの数字 − あなたの数字」をして、% 3(3で割った余り)で結果を0・1・2のどれかに直しています。
  • 最後の3行は、diff が0ならあいこ、1なら勝ち、それ以外(2)なら負け、という決め方です。

なぜ、こう書くのか

  • 手を数字にする理由:コンピュータは「グーはチョキに勝つ」という言葉のルールを、そのままでは扱えません。数字に置き換えると、引き算という計算だけで勝敗が決められるようになります。
  • この並び(0→1→2)にする理由:グー(0)はチョキ(1)に勝ち、チョキ(1)はパー(2)に勝ち、パー(2)はグー(0)に勝つ、と「次の数字に勝つ」並びになっているからです。だから引き算で判定できます。
  • %(余り)を使う理由:パー(2)とグー(0)のように、並びが一周して戻る場合があるからです。時計の針が12の次に1へ戻るのと同じで、余りを使うと一周した場合も同じ計算で処理できます。+3 しているのは、引き算がマイナスにならないようにするためです。

🗺️ 宝探し

初級

5×5のマスの中に、宝物が1つだけ隠れています。マスを開くと、宝物までの距離(縦の差+横の差)が数字で出ます。数字が小さいほど近くです。

マスをタップして探してください。
挑戦回数:0 回
仕組みとコードの説明(はじめての人向け)
const SIZE = 5;
const treasureX = random(SIZE);
const treasureY = random(SIZE);

function random(n) {
  return Math.floor(Math.random() * n);
}

function distance(x, y) {
  return Math.abs(x - treasureX)
       + Math.abs(y - treasureY);
}

if (distance(x, y) === 0) {
  // 宝物を見つけた
} else {
  // 距離をヒントとして表示する
}

どうやって動いているか

  1. 最初に、コンピュータは宝物の「住所」を決めます。住所は「横に何番目か」と「縦に何番目か」の2つの数字です。サイコロで決めて、隠しておきます。
  2. あなたがマスを開くと、コンピュータは「開けたマスの住所」と「宝物の住所」を比べます。
  3. 横に何マス離れているか、縦に何マス離れているかを別々に数えて、足したものが「距離」です。たとえば横に2、縦に1離れていれば、距離は3です。
  4. 距離が0、つまり住所がまったく同じなら当たりです。それ以外なら、距離の数字だけを見せます(場所そのものは教えません)。

コードを、ふだんの言葉で読むと

  • treasureX と treasureY は、宝物の住所(横の番号と縦の番号)をしまう箱です。
  • distance の部分は、横の差と縦の差を足しています。Math.abs は「マイナスをなくす」という命令で、左に3つ離れていても右に3つ離れていても、離れ具合は同じ「3」として数えるためです。
  • if (distance === 0) は、「距離が0、つまり同じマスなら見つけた」という判定です。

なぜ、こう書くのか

  • 場所を2つの数字にする理由:マス目は、横と縦の番号があれば1マスを特定できるからです。将棋盤や地図と同じ考え方です。
  • 距離を「横の差+縦の差」にする理由:マス目の世界では、ななめには進めず、横と縦にしか動けないからです。実際に歩く歩数とだいたい同じになります。
  • マスごとに違う処理を書かない理由:25個のマス全部に、「開けたら距離を計算する」という同じ決まりを使い回しています。決まりが1つなら、直すときも1か所で済みます。

🎰 スロットマシン

初級

「回す」を押すと数字がぐるぐる回り、左から順番に止まります。3つ同じ数字がそろえば大当たりです。

?
?
?
「回す」を押してください。
0 回 / 大当たり 0 回
仕組みとコードの説明(はじめての人向け)
const reels = [0, 0, 0];
for (let i = 0; i < 3; i++) {
  reels[i] = Math.floor(Math.random() * 10);
}

// 回っている間は、60ミリ秒ごとに適当な数字を表示します
setInterval(showRandom, 60);

// 左から順に止めます(600 → 1100 → 1600ミリ秒後)
setTimeout(stopReel, 600);   // 1つ目
setTimeout(stopReel, 1100);  // 2つ目
setTimeout(stopReel, 1600);  // 3つ目

if (reels[0] === reels[1] && reels[1] === reels[2]) {
  // 3つそろった(大当たり)
}

どうやって動いているか

  1. 「回す」を押すと、コンピュータはまず「止まったときに出る数字」を0〜9から3つ決めて、箱の並び(配列)にしまいます。
  2. 画面の数字は、目まぐるしく切り替わりながら回ります。左から順に、少しずつ間を空けて止まっていき、止まる瞬間に、決めておいた数字に置き換わります。
  3. 全部止まったら、3つの数字を比べて、全部同じなら大当たり、2つだけ同じなら「おしい」です。
  4. 回した回数と大当たりの回数は、別の箱で数えています。

コードを、ふだんの言葉で読むと

  • reels は、数字を入れる箱3つ分です。reels[0] が1つ目、reels[1] が2つ目、reels[2] が3つ目です。番号が0から始まるのが配列の約束です。
  • for は「くり返し」の命令です。「i を0、1、2と変えながら、同じ処理を3回する」という意味です。
  • && は「〜で、かつ〜」という意味です。「1つ目と2つ目が同じで、かつ2つ目と3つ目が同じ」なら、3つ全部が同じだと言えます。
  • setInterval は「○ミリ秒ごとにくり返す」setTimeout は「○ミリ秒後に1回だけ実行する」という、時間の予約の命令です。止まる時間を左から少しずつ遅らせる(600→1100→1600)ことで、順番に止まって見えます。ミリ秒は1秒の1000分の1の単位です。

なぜ、こう書くのか

  • 箱を3つ並べる(配列にする)理由:数字が3つあるものを、バラバラの変数3つで持つより、並びで持つ方が「i 番目」と数えて処理できて、くり返し(for)と相性がいいからです。
  • 大当たりがなかなか出ない理由:3つそろう確率は 10×10×10 の組み合わせのうち10通り、つまり100回に1回くらいです。お店のスロットがなかなか当たらないのと同じ仕組みです。

➕ 計算ドリル

初級

足し算・引き算・掛け算の問題が10問出ます。数字と記号は毎回ランダムです。

? + ? = ?
1問目(10問中)
仕組みとコードの説明(はじめての人向け)
let a = Math.floor(Math.random() * 9) + 1;
let b = Math.floor(Math.random() * 9) + 1;
let op = ['+', '−', '×'][Math.floor(Math.random() * 3)];

let answer;
if (op === '+') {
  answer = a + b;
} else if (op === '−') {
  answer = a - b;
} else {
  answer = a * b;
}

どうやって動いているか

  1. コンピュータは、2つの数字と、計算の記号(+・−・×)をランダムに決めて、問題を表示します。
  2. 答えは、問題を作った時点でコンピュータが先に計算して、箱にしまってあります。
  3. あなたが答えを入れると、箱の中の答えと比べて、正解かどうかを教えます。
  4. 10問終わると、正解の数を表示します。

コードを、ふだんの言葉で読むと

  • 1〜2行目は「1〜9の数字を適当に決める」行です。数当てゲームの「答えを決める」と同じ書き方です。
  • 3行目は、記号の並び ['+', '−', '×'] の中から、ランダムに1つ選んでいます。並びの中から1つ選ぶのも、配列の使い方です。
  • if / else if / else は数当てゲームと同じ「3つの場合分け」です。記号によって、計算のしかたを変えています。

なぜ、こう書くのか

  • 問題をランダムにする理由:問題を全部書いておく必要がなく、数字の組み合わせの数だけ問題が作れるからです。
  • 引き算で大きい方を前にする理由:「3 − 7」のような答えがマイナスになる問題を避けるためです。a より b が大きければ、2つの数字を入れ替えてから問題にします。
  • 答えを先に計算しておく理由:あなたの答えと比べるには、正しい答えを持っている必要があるからです。問題を作る側が先に答えを用意するのが、ドリルやテストの基本の形です。

中級

配列や文字列、くり返しを使って、判定の工夫が増えます。考え方の型は上級にもつながります。

⭕ 三目並べ(○×ゲーム)

中級

あなたは ○、コンピュータは × です。縦・横・斜めのどれかで先に3つ並べた方が勝ちです。

あなたの番です。マスをタップしてください。
仕組みとコードの説明(はじめての人向け)
// 勝ちになる並びは8通りだけ。先に全部書いておきます
const LINES = [
  [0,1,2],[3,4,5],[6,7,8],   // 横
  [0,3,6],[1,4,7],[2,5,8],   // 縦
  [0,4,8],[2,4,6]            // 斜め
];

function win(board, p) {
  return LINES.some(line =>
    line.every(i => board[i] === p));
}

どうやって動いているか

  1. 盤は、9個の箱が一列に並んだものとして管理しています。左上から0〜8の番号がついていて、真ん中は4番です。あなたが置くと、その番号の箱に「○」が入ります。
  2. 置くたびに、コンピュータは「勝ちの並び」になっていないか調べます。
  3. コンピュータの番では、決まった順番でマスを探します。まず自分が勝てるマス、次に、あなたが次で勝ちそうなマスをふさぎ、それでもなければ真ん中、最後は空いているマスから適当に選びます。
  4. 勝ち負けが決まったら盤を止めて、それ以上置けないようにします。

コードを、ふだんの言葉で読むと

  • LINES は、勝ちになる並び8通り(横3つ、縦3つ、斜め2つ)を、最初に全部書き並べたものです。
  • win の部分は、「8通りのうち、どれか1つでも、3マス全部が同じ記号で埋まっているか」を調べています。some は「どれか1つでも」、every は「全部」という意味の命令です。

なぜ、こう書くのか

  • 勝ちの並びを先に全部書く理由:三目並べの勝ち方は8通りしかありません。「どう調べるか」を工夫するより、答えを全部書いて照らし合わせる方が、簡単で間違いがありません。
  • 盤を一列の箱にする理由:3×3の盤でも、番号を0〜8と一列にすれば十分に表せるからです。管理が簡単になります(五目並べのように大きくなければ、この方法で足ります)。
  • コンピュータに「探す順番」を決めておく理由:コンピュータは自分で考えているわけではありません。「勝てるマス→止めるマス→真ん中」という順番を人が先に決めておくと、その通りに動くだけで、考えているように見えます。

🔢 ヒット&ブロー

中級

コンピュータが決めた、重ならない3つの数字を当てます。ヒット=数字も場所も合っている、ブロー=数字は合っているが場所が違う、というヒントが出ます。

3桁の数字(同じ数字は2回使えません)を入れてください。
挑戦回数:0 回
仕組みとコードの説明(はじめての人向け)
function judge(guess, secret) {
  let hit = 0, blow = 0;
  for (let i = 0; i < 3; i++) {
    for (let j = 0; j < 3; j++) {
      if (guess[i] === secret[j]) {
        if (i === j) hit++;
        else blow++;
      }
    }
  }
  return [hit, blow];
}

どうやって動いているか

  1. コンピュータは、重ならない数字を3つ選んで、箱の並び(配列)にしまいます。
  2. あなたが3つの数字を入れると、答えと1つずつ、全部の組み合わせで比べます。
  3. 数字が合っていて、場所(何番目か)も同じならヒット。数字は合っているのに場所が違うならブローです。
  4. ヒントと今までの予想は一覧に残るので、それを見ながら候補をしぼっていきます。

コードを、ふだんの言葉で読むと

  • guess[i] は「あなたの予想の i 番目」secret[j] は「答えの j 番目」という意味です。
  • for の中に for が入っています(二重のくり返し)。あなたの数字1つに対して、答えの数字全部と比べるので、比べる回数は 3×3=9回です。
  • i === j は「場所も同じ」という意味です。数字が同じで場所も同じならヒット、場所が違えばブローと分けています。

なぜ、こう書くのか

  • 二重のくり返しを使う理由:「片方の全部」と「もう片方の全部」を比べるときの、決まった書き方だからです。成績表と名簿を照らし合わせるような作業は、だいたいこの形になります。
  • 数字を重ねない理由:同じ数字が2つあると、「どっちと合ったのか」があいまいになり、ヒントが分かりにくくなるからです。

🃏 ブラックジャック

中級

カードの合計を21に近づけるゲームです。21を超えると負け(バースト)。A(エース)は11にも1にもなります。相手は合計が17以上になるまで引き続けます。

相手のカード
合計:?
あなたのカード
合計:0
「ゲーム開始」を押してください。
仕組みとコードの説明(はじめての人向け)
function total(hand) {
  let sum = 0;   // 合計
  let aces = 0;  // エースの数
  for (const card of hand) {
    sum += (card === 1) ? 11 : card;
    if (card === 1) aces++;
  }
  while (sum > 21 && aces > 0) {
    sum -= 10;  // エースを 11 ではなく 1 として数え直す
    aces--;
  }
  return sum;
}

// 相手は、合計が17未満なら引き続けます
while (total(dealer) < 17) {
  dealer.push(drawCard());
}

どうやって動いているか

  1. ゲームが始まると、あなたと相手にカードが2枚ずつ配られます。相手の2枚目は裏向きで、合計も見えません。
  2. 「ヒット」を押すとカードが1枚増えます。21を超えたら、その場で負けです。
  3. 「スタンド」で止めると、相手の番になります。相手は「合計が17以上になるまで引く」という決まりだけで動きます。
  4. 両方の合計を比べて、21に近い方の勝ちです。

コードを、ふだんの言葉で読むと

  • total は「カードの並びを受け取って、合計を返す」処理です。エースはまず11として足します。
  • while は「〜の間、くり返す」という命令です。「合計が21を超えていて、まだエースが残っている間」は、合計から10を引いて、エースを1として数え直します。
  • 相手の動きも while です。「合計が17より小さい間、カードを引く」という1つの決まりで書けます。

なぜ、こう書くのか

  • 合計の計算を1つの処理にまとめる理由:あなたの合計、相手の合計、エースの数え直しなど、同じ計算が何度も出てくるからです。1か所にまとめると、間違えたときも直すのは1か所で済みます。
  • 相手に「17」という決まりだけ持たせる理由:コンピュータは考えていません。「17未満なら引く」という単純な決まりでも、相手らしく動いて見えます。三目並べの「探す順番」と同じ発想です。

🔤 ハングマン

中級

隠れた英単語(IT・パソコンの言葉)の文字を、1つずつ当てていくゲームです。ミスは6回までです。

_ _ _ _
文字を選んでください。
あと 6 回ミスできます
仕組みとコードの説明(はじめての人向け)
const word = 'JAVA';
let guessed = {};  // 選んだ文字を入れておく

let disp = '';
for (let i = 0; i < word.length; i++) {
  const ch = word.charAt(i);  // i 番目の文字
  disp += guessed[ch] ? ch : '_';
}

どうやって動いているか

  1. コンピュータは、単語の一覧から1つをランダムに選びます。最初は全部の文字が「_」で隠れています。
  2. あなたが文字のボタンを押すと、その文字が単語の中にあるか調べます。
  3. あれば、その文字の場所全部が開きます。なければ、ミスの残り回数が1つ減ります。
  4. 全部開けば勝ち、ミスが6回になると負けで、答えの単語が表示されます。

コードを、ふだんの言葉で読むと

  • word.length は「文字の数」です。JAVA なら 4 になります。
  • word.charAt(i) は「i 番目の文字を取り出す」という意味です。番号は0から始まるので、charAt(0) は 'J' です。
  • guessed[ch] ? ch : '_' は「その文字を選んだことがあれば文字を、まだなら _ を」という意味の短い場合分けです。
  • 表示は、単語の文字を1つずつ見て、「開くか隠すか」を決めながら組み立てています。

なぜ、こう書くのか

  • 選んだ文字を記録しておく理由:画面の表示は、この記録から毎回作り直せます。表示と記録を分けておくと、「新しい単語」でも同じ作り方が使えます。
  • 文字列も「並び」として扱う理由:文字列は、文字が並んだものと考えることができます。配列で学んだ「i 番目を取り出す」考え方が、そのまま使えます。

上級

二次元配列と「方向を数える」判定、コンピュータの読みが入ります。迷路は二次元配列の地図を、ボタンで移動します。

⚫ 五目並べ

上級

あなたは黒石です。空いているマスをタップすると石を置けます。コンピュータが白石を置くので、先に縦・横・斜めのどれかで5つ並べた方が勝ちです。

あなたの番です。マスをタップしてください。
仕組みとコードの説明(はじめての人向け)
// 0=空き、1=黒、2=白。盤は二次元配列です
board[y][x] = 1;

// 縦・横・斜めの4方向をまとめて表します
const dirs = [
  [1, 0], [0, 1], [1, 1], [1, -1]
];

function isWin(x, y, player) {
  return dirs.some(function (d) {
    return countLine(x, y, d[0], d[1], player) >= 5;
  });
}

どうやって動いているか

  1. 盤は10×10で大きいので、場所は「縦の番号」と「横の番号」の2つで管理します。マンションの部屋を「○階の○号室」で表すのと同じです。この「番号が2つある箱の並び」のことを、プログラムでは二次元配列と呼びます。
  2. あなたが石を置くと、コンピュータは、その石を中心に、縦・横・斜め(2つ)の、合わせて4つの向きを調べます。
  3. それぞれの向きに、同じ色の石が何個続いているか、両側に向かって1つずつ数えます。どれかの向きで5個以上なら勝ちです。
  4. コンピュータの番では、三目並べと同じ順番(勝てる場所→勝ちを止める場所)に加えて、「石の近く」や「真ん中の方」のように良さそうな場所に点数を付けて、一番点の高い場所に置きます。

コードを、ふだんの言葉で読むと

  • board[y][x] は、「縦y番目・横x番目のマス」という意味です。ここに0(空き)・1(黒)・2(白)の数字を入れて、盤の様子を表しています。
  • dirs は、4つの向きを「横に+1、縦に+0」のような数字の組で書いたものです。[1, 0] は横向き、[0, 1] は縦向き、[1, 1] と [1, -1] は斜めです。
  • isWin の部分は、「置いた石から両側へ同じ色を数えて、合計が5以上なら勝ち」という判定です。

なぜ、こう書くのか

  • 置いた石の周りだけ調べる理由:勝ち負けが変わるのは、今置いた石の並びだけだからです。盤全体を毎回調べ直す必要はなく、周りだけ見れば十分で、処理も簡単になります。
  • 向きを数字の組で書く理由:4つの向きに、同じ「数える処理」を使い回せるからです。向きごとに別々のプログラムを書くと4倍の量になり、間違いも増えます。
  • コンピュータが点数制な理由:五目並べはマスが100個もあって、「良い場所」が1つに決まらないからです。候補に点数を付けて一番高い場所を選ぶ、という決め方にしています(本当に強いAIではなく、簡単なルールです)。

🧭 迷路ゲーム

上級

🚶 があなた、🏁 がゴールです。下の矢印ボタンで動きます。グレーのマスは壁で、通れません。迷路は2種類からランダムに出ます。

ゴールまで進んでください。
歩数:0 歩
仕組みとコードの説明(はじめての人向け)
// 地図は文字列の並びです(1=壁、0=道)
const map = [
  '1111',
  '1001',
  '1011',
  '1111'
];

// 進みたい場所(nx, ny)が壁でなければ進めます
if (map[ny].charAt(nx) !== '1') {
  px = nx;
  py = ny;
}

// ゴールの場所に着いたらクリア
if (px === gx && py === gy) {
  // ゴール!
}

どうやって動いているか

  1. 迷路は、「壁(1)」と「道(0)」を並べた地図で表しています。あなたの場所は、横の番号 x と縦の番号 y の2つで表します(五目並べの盤と同じ考え方です)。
  2. 矢印ボタンを押すと、まず「進みたい場所」を計算します。例えば右ボタンなら、横の番号が1つ増えた場所です。
  3. その場所が壁なら進めず、そのまま。道なら、あなたの場所を新しい場所に書き換えます。
  4. ゴールの場所とあなたの場所が同じになったら、クリアです。

コードを、ふだんの言葉で読むと

  • map[ny] は「地図の ny 行目」という意味で、charAt(nx) は「その行の nx 番目の文字」です。ハングマンで使った charAt と同じです。
  • !== '1' は「壁ではない」という意味です。「壁なら止まる」ではなく「壁でなければ進む」と書くことで、進む処理を1か所にまとめています。
  • 場所を2つの数字(x と y)で持つおかげで、上下左右の移動は「どちらの数字を±1するか」だけで表せます。

なぜ、こう書くのか

  • 迷路を文字の地図にする理由:見た目そのままの形で書けるからです。地図を書き換えるだけで、別の迷路が作れます。
  • 「進めるかどうか」を先に調べる理由:プログラムでは、動く前に行き先をチェックするのが基本です。壁の中に入ってから戻すより、先に止める方が簡単で間違いがありません。