「Java」プログラミング入門 ゲーム作りで学ぶ基礎から応用までJavaで作る三目並べゲーム【前編】

初心者がプログラミングを楽しみながら学ぶには、ゲームを題材にすることが有効だ。「Java」を使った「三目並べゲーム」の開発を通じて、Javaプログラミングを学ぼう。

2024年12月27日 08時30分 公開
[Cameron McKenzieTechTarget]

関連キーワード

Java | プログラミング


 プログラミングの学習はもどかしさを感じることもある。学習にゲームを取り入れて、気分を軽くしてみよう。取り入れる方法の一つとして、ゲームをプログラミングすることがある。プログラミングの教材としてよく使われるゲームには以下がある。

  • 数当てゲーム
  • じゃんけん
  • スネークゲーム
    • プレイヤーが成長する線(スネーク)を操作し、障害物や自身の体に衝突しないようにしながら、食べ物を食べてスコアを増やすことを目的としたゲーム。
  • スペースインベーダー
  • チェッカー
  • チェス

 本連載は、プログラミング言語・開発環境「Java」で「三目並べゲーム」を作ることを目指す。三目並べゲームのプログラミングは、難易度で言えば中間当たりに位置する。Javaで数当てゲームやじゃんけんゲームを作れる人ならば、難易度は上がるものの三目並べゲームも作れるはずだ。以下では、具体的なサンプルコードを示しながら、ステップに沿ってJavaで三目並べゲームを完成させる方法を解説する。

Javaで三目並べゲームを作ってみよう

ステップ1.「TicTacToe」クラスを作成する

 最初に、プログラムのエントリーポイント(実行開始点)を作ろう。そのためには、まず「main」メソッド(処理)を持つ「TicTacToe」というクラスを作成する。クラスとは、メソッドとデータ(フィールド)をまとめた「オブジェクト」の設計図だ。

/* Javaでの三目並べ実装例 */
public class TicTacToe {
 
  public static void main(String[] args) 
  {
  
  }
 
}

 これから示すソースコードは、TicTacToeクラスのmainメソッド内、またはTicTacToeクラスの一部として宣言されたメソッド内に記述することになる。

ステップ2.必要な変数の宣言

 三目並べのゲーム盤にはマスが9個ある。このゲームでは、選択されていないマスそれぞれの番号を表示し、どのマスをマークするのかをプレイヤーに尋ねる。以下に示す配列(同じ型の一連の変数群)「board[]」の各要素は、ゲーム盤にある1つのマスを表す。

char[] board = { '1','2','3',
                 '4','5','6',
                 '7','8','9'  };

 プログラムでは、何個のマスが選択済みなのか、現在どちらのプレイヤーのターンなのかといった情報も継続的に記録する必要がある。9マス全てが選択され、勝者がいない場合は引き分けになる。以下の「numberOfSquaresPlayed」は選択されたマスの数(0〜9)、「whoseTurnItIs」は現在ターンを迎えているプレイヤーの記号(1人目が「o」、2人目が「x」)を表す変数だ。

var numberOfSquaresPlayed = 0;
var whoseTurnItIs = 'x';

ステップ3.三目並べのゲーム盤を描画

 プレイヤーがマスを選択するたびに、プログラムはゲーム盤を描写する。今回のゲームはコマンドベースのインタフェースで動くシンプルなゲームなので、ゲーム盤の表示は画面のようになり、それほど複雑ではない。

画面 画面 三目並べのゲーム盤

 三目並べのゲーム盤を生成してCLIの画面描画するロジックを、以下のように「printTheBoard()」というメソッドに入力しよう。

private static void printTheBoard(char[] board) 
{
  System.out.println( board[0] + " | " +  board[1] + " | " + board[2]);
  System.out.println( " - + - + - " );
  System.out.println( board[3] + " | " +  board[4] + " | " + board[5]);
  System.out.println( " - + - + - " );
  System.out.println( board[6] + " | " +  board[7] + " | " + board[8]);
}

 ゲーム盤を最初に描画すると、以下のように数字のみが表示される。

1 | 2 | 3
- + - + -
4 | 5 | 6
- + - + -
7 | 8 | 9

 ゲームが進行すると、xとoがボードに記入される。三目並べのJava対戦が終了すると以下のようになる。

x | o | x
- + - + -
x | 5 | o
- + - + -
x | o | 9

ステップ4.プレイヤーに入力させる

 次に、numberOfSquaresPlayedが9未満の時は、ゲーム盤の現在の状況をプレイヤーに示してマスを選ばせるロジックを追加する。1行目の「while」は、9マス全てが埋まるまでゲームが続くようにするためのループだ。printTheBoard()で現在のゲーム盤を表示した後、プレイヤーに入力を促す。プレイヤーの入力を受け付ける変数「scanner」は、Javaの「Scanner」クラスのオブジェクト(クラスの実体)として定義する。その後、プレイヤーが選択したマスに、プレイヤーに対応する記号を記録する。

while (numberOfSquaresPlayed < 9) 
{
  printTheBoard(board);
  System.out.printf("記号%sのプレイヤーの番ターンです。マスを選んでください:", whoseTurnItIs);
  var scanner = new java.util.Scanner(System.in);
  var input = scanner.nextInt();
  board[input-1] = whoseTurnItIs;
}

 注意点として、配列のインデックスは0から始まる点がある。プレイヤーが入力するのは1〜9の数値のうち1つだが、配列のインデックスとして使用するには、その数値から1を引かなければならない。例えばプレイヤーが5番のマスを選択した場合、対応するマスの配列におけるインデックスは4になる。

ステップ5.勝者判定

 プレイヤーがマスを選択するごとに、1つの列がそろってゲームの勝敗が決まったかどうかをチェックする。三目並べで勝つには、以下の8通りの方法がある。

  • 横1列がそろうパターン×3種類
  • 縦1列がそろうパターン×3種類
  • 斜めがそろうパターン×2種類

 この勝利パターンを判定するために、3つのマスの状態をチェックする「if」文を8回記述するソースコードは、複雑で読みづらい。例えば1段目の列がそろっているかどうかをチェックする条件文は次のようになる。ゲームで勝ったかどうかを判断するためには、こうしたチェックが8回必要になってしまう。

if ((board[0] == whoseTurnItIs) && 
    (board[1] == whoseTurnItIs) && 
    (board[2] == whoseTurnItIs)) 
{
   System.out.println("あなたの勝ちです! おめでとう!");
   break;
}

 そこで別の選択肢として、文字を表すための「char」型を整数型として扱うJavaの仕様を使い、勝敗判定のロジックを実装してみよう。

整数型のcharとは

 Javaのchar型は、実際には16bitの符号なし整数型だ。 「int」「double」「float」といった、数値型と同じような数学的操作を適用できる。

 これを三目並べの勝敗判定に使うには、特定の行にある3つのマス内の文字に相当する文字コードを加算して、その結果がxを3回掛けた内容と等しいかどうかを確認する。もし等しければ、その行にはxが3つそろっていることになる。2人目のプレイヤーの番ターンの時は、同じ処理をoの文字について実行する。

 以下は、charの整数型としての性質を利用して、特定の行、列、斜めのラインで1つの記号がそろっているかどうかを判別するロジックだ。

if (  (board[0] + board[1] + board[2] == (whoseTurnItIs * 3)) // 1行目
   || (board[3] + board[4] + board[5] == (whoseTurnItIs * 3)) // 2行目
   || (board[6] + board[7] + board[8] == (whoseTurnItIs * 3)) // 3行目
   || (board[0] + board[3] + board[6] == (whoseTurnItIs * 3)) // 1列目
   || (board[1] + board[4] + board[7] == (whoseTurnItIs * 3)) // 2列目
   || (board[2] + board[5] + board[8] == (whoseTurnItIs * 3)) // 3列目
   || (board[0] + board[4] + board[8] == (whoseTurnItIs * 3)) // 左上から右下への斜めライン
   || (board[2] + board[4] + board[6] == (whoseTurnItIs * 3)) // 右上から左下への斜めライン
) 
{
  printTheBoard(board);
  System.out.println("あなたの勝ちです! おめでとう!");
  break;
} else {
  numberOfSquaresPlayed++;
  whoseTurnItIs = (whoseTurnItIs == 'x') ? 'o' : 'x';
}

ステップ6.交代

 勝敗判定で、1つの記号がラインを埋めていることが確認できた場合、そのプレイヤーをたたえてwhileループから脱出する。

printTheBoard(board);
System.out.println("あなたの勝ちです! おめでとう!");
break;

 1つの記号がラインを埋めていない場合は、記入済みのマス数を数えるカウンターnumberOfSquaresPlayedを1増やして、プレイヤーを交代する。以下は「三項演算子」を使用して、プレイヤーを交代するロジックを実装した例だ。現在のプレイヤーがxの場合はoのターンに、oの場合はxのターンになる。

numberOfSquaresPlayed++;
whoseTurnItIs = (whoseTurnItIs == 'x') ? 'o' : 'x';

 三項演算子は、条件文の結果に基づいて異なる数値を返す演算子だ。「条件式 ? 真の場合の値 : 偽の場合の値」という構文で利用し、if文を簡略化するのに役立つ。上記の三項演算子の用例をif文で記述すると、次のようになる。

if (whoseTurnItIs == 'x') 
{
  whoseTurnItIs = 'o';
} else {
  whoseTurnItIs = 'x';
}

 新人の開発者だった当時、私はずっと三項演算子の難解な構文が嫌いだった。Java開発者としての経験を積んだ今でも好きにはなれないが、以前よりも使いこなせていると感じる。

ステップ7.引き分けの処理

 9ターン繰り返しても勝者が決まらない場合は、whileループを脱出する。今回は、引き分けを宣言して両者をたたえるようにした。

if (numberOfSquaresPlayed == 9) 
{
  printTheBoard(board);
  System.out.println("引き分け! お疲れさまでした。");
}

ステップ8.プログラムを実行する

 全てのソースコードを記入できたら変更を保存し、プログラムを実行する。入力した内容が正しければ、プログラムは問題なく実行される。

 ここまでに紹介したソースコードによる三目並べゲームのJavaプログラムは以下の通りだ。なお1行目の「package com.mcnz.vector;」は、このプロジェクトの構造を定義す「パッケージ宣言」だ。ここではプロジェクトを手掛ける組織の種類、組織名、プロジェクト名を示している。

package com.mcnz.vector;
 
public class TicTacTest {
 
  public static void main(String[] args) 
  {
    char[] board = { '1', '2', '3', 
                     '4', '5', '6', 
                     '7', '8', '9' };
 
    var numberOfSquaresPlayed = 0;
    var whoseTurnItIs = 'x';
    while (numberOfSquaresPlayed < 9) 
    {
      printTheBoard(board);
      System.out.printf("記号%sのプレイヤーの番ターンです。マスを選んでください:", whoseTurnItIs);
      var scanner = new java.util.Scanner(System.in);
      var input = scanner.nextInt();
      board[input - 1] = whoseTurnItIs;
 
      if (  (board[0] + board[1] + board[2] == (whoseTurnItIs * 3)) // 1行目
         || (board[3] + board[4] + board[5] == (whoseTurnItIs * 3)) // 2行目
         || (board[6] + board[7] + board[8] == (whoseTurnItIs * 3)) // 3行目
         || (board[0] + board[3] + board[6] == (whoseTurnItIs * 3)) // 1列目
         || (board[1] + board[4] + board[7] == (whoseTurnItIs * 3)) // 2列目
         || (board[2] + board[5] + board[8] == (whoseTurnItIs * 3)) // 3列目
         || (board[0] + board[4] + board[8] == (whoseTurnItIs * 3)) // 左上から右下への斜めライン
         || (board[2] + board[4] + board[6] == (whoseTurnItIs * 3)) // 右上から左下への斜めライン
      ) 
      {
         printTheBoard(board);
         System.out.println("あなたの勝ちです! おめでとう!");
         break;
      } else {
         numberOfSquaresPlayed++;
         whoseTurnItIs = (whoseTurnItIs == 'x') ? 'o' : 'x';
      }
    }
  }
 
  private static void printTheBoard(char[] board) 
  {
    System.out.printf("%n %s | %s | %s %n", board[0], board[1], board[2]);
    System.out.println(" - + - + - ");
    System.out.printf(" %s | %s | %s %n", board[3], board[4], board[5]);
    System.out.println(" - + - + - "); 
    System.out.printf(" %s | %s | %s %n%n", board[6], board[7], board[8]);
  }
}

 次回は、さらなる学習のために三目並べゲームの改良案を紹介する。

TechTarget発 エンジニア虎の巻

米国TechTargetの豊富な記事の中から、開発のノウハウや技術知識など、ITエンジニアの問題解決に役立つ情報を厳選してお届けします。

Copyright © ITmedia, Inc. All Rights Reserved.

From Informa TechTarget

お知らせ
米国TechTarget Inc.とInforma Techデジタル事業が業務提携したことが発表されました。TechTargetジャパンは従来どおり、アイティメディア(株)が運営を継続します。これからも日本企業のIT選定に役立つ情報を提供してまいります。

ITmedia マーケティング新着記事

news009.jpg

AI・ARで「探索」 人より商品とつながるSNSの行く末――2025年のSNS大予測(Pinterest編)
ビジュアル探索プラットフォームとしての独自の道を進み続けるPinterestはもはやSNSでは...

news055.png

「お年玉はキャッシュレスでもらいたい」が初の3割越え あげる側の意向は?
インテージが2025年のお年玉に関する調査結果を発表した。

news062.jpg

ポータルサイトの集客UPに欠かせないSEO対策 一般的なWebサイトと何が違う?
今回は、ポータルサイトのSEO対策に取り組みたいあなたへ、具体的な方法をわかりやすく解...