LoginSignup
1
1

More than 5 years have passed since last update.

ぼくのかんがえたさいきょうのおせろ【思考ルーチン編2】

Last updated at Posted at 2019-02-18

1.はじめに

 前回の記事に書いた通りGitHubに挑戦していたのですが、Google Chromeの翻訳機能を駆使してもいまいち使い方が分からないまま、時間だけが過ぎていく今日この頃でした。そこで、今回からはGitHubと同じような機能をもち、日本語もサポートしてくれているbitbucketを活用していきます。また、これまでのJavaソースはEclipseを用いて作成していたので、Eclipseをbitbucketと連携させながらコーディングしていきます。すまないGitHub。

 で、オセロの石を置く場所を自動で探索する思考ルーチンを実装していこうと思った訳ですが、少しコーディングから離れていた間に、これまで自分がどのような実装をしていたのか、さっぱり分からなくなってしまいました。思考ルーチン編1の記事を投稿したときにコメントをいただいた通り、OthelloBoardクラスの行数がとんでもなく多くなってしまい(460行!)、変数やメソッドの繋がりが極めて複雑になったからでした。そのため、まずはOthelloBoardクラスをいくつかのクラスに分割し、どのクラスがどの変数・メソッドを参照しているのかがもう少し分かりやすくなるように書き直すことから始めていきます。

 しかし、もはや書いた私ですら良く分からなくなってしまっているOthelloBoardクラス。ちょっと別のクラスに変数を移したり、ちょっとコードを書き替えただけで、Eclipseが大量のエラーメッセージを出しやがります。やばい、何個エラーを潰してもちっとも終わる気配がない……!
OthelloBoardクラス「ボーっとしてるなよ、坊主。気張れよ。あとたった何万回ぐらいだ。」

 ……クラス設計の重要性が身に沁みた週末でした。

2.新しいクラス設計

 ということで、OthelloBoardクラス内の変数やメソッドを次の各クラスに分散させてみました。

  • オセロ盤のサイズ(一辺に置くことのできる石の数)やプレイヤー側の石の色など、ゲーム開始時に設定を行うためのConfigクラス。
  • 石の配置状況を管理するBoardクラス。
  • コマンドプロンプト上にオセロ盤や黒石、白石の数を表示するPrintクラス。
  • 指定されたマスに石を置いたとき、相手の石をひっくり返せるかどうか判定するFlipクラス。
  • (主に敵側が)石を置くべきマスを判定する(戦略を練る)ためのStrategyクラス。
  • (主にプレイヤー側が)石を置きたいマスを入力(選択)するためのPlayerクラス。
  • ターンの経過や終了処理については、従来通りOthelloBoardクラスに残しておきます。

 初めから色々書き替えようとすると収拾がつかなくなる(なった)ので、とりあえずこれだけにしておきます。

Configクラス(とDiscStateクラス)

 オセロを開始する際、コマンドプロンプトからオセロ盤のサイズやプレイヤー側の石の色を入力させるクラスです。今後、オセロ開始時に入力させる項目を追加する際は、このクラスに実装していく予定です。将来的には、敵側の思考ルーチンの難易度とかを選ばせるようにしたいです。

Configクラス(クリックすると開きます)
import java.util.Scanner;

public class Config {
    private final int size;             // オセロ盤の一辺(8, 10, 12, 14, 16)
    private final char playerColor;     // プレイヤーの石の色
    private final char otherColor;      // 相手の石の色

    public Config() {
        this.size = this.askBoardSize();
        this.playerColor = this.askPlayerColor();
        if (this.playerColor == DiscState.BLACK) {
            this.otherColor = DiscState.WHITE;
        } else {
            this.otherColor = DiscState.BLACK;
        }
    }

    public int getSize() {
        return this.size;
    }
    public char getPlayerColor() {
        return this.playerColor;
    }
    public char getOtherColor() {
        return this.otherColor;
    }

    // オセロ盤のサイズが決まるまで入力を受け付ける
    private int askBoardSize() {
        while (true) {
            System.out.println("\nオセロ盤の一辺の長さを決めてください。");
            System.out.print("[6, 8, 10, 12, 14, 16 のいずれか]:");
            Scanner sc = new Scanner(System.in);
            String line = sc.nextLine();
            if ("6".equals(line) || "8".equals(line) || "10".equals(line) || "12".equals(line) ||
                    "14".equals(line) || "16".equals(line)) {
                System.out.println("オセロ盤の一辺の長さは" + line + "です。");
                return Integer.parseInt(line);
            }
            System.out.println("入力が間違っています。");
        }
    }

    // プレイヤーの石の色が決まるまで入力を受け付ける
    private char askPlayerColor() {
        while (true) {
            System.out.println("\nあなたの石を決めてください。");
            System.out.println("[b (黒), w (白) のいずれか]:");
            Scanner sc = new Scanner(System.in);
            String line = sc.nextLine();
            if ("b".equals(line)) {
                System.out.println("あなたの石は黒です。");
                return DiscState.BLACK;
            } else if ("w".equals(line)) {
                System.out.println("あなたの石は白です。");
                return DiscState.WHITE;
            }
            System.out.println("入力が間違っています。");
        }
    }
}

 なお、以前のコードでは石の色を表す'B', 'W'、石を置いていないことを表す'N'を直接書いていましたが、これらはマジックナンバーと呼ばれよろしくない存在です(万が一、別の文字を割り当てることになったとき、コード全体で書き換える必要が出てくるため、エラーやバグの温床になりやすい)。そこで、石の色や配置の状況を表すDiscStateクラスを用意し、それぞれ文字定数BLACK、WHITE、NONEを割り当てました。

DiscStateクラス(クリックすると開きます)
// オセロ盤の石の色や配置の状態を表すための文字定数を管理する
public final class DiscState {
    public static final char BLACK = 'B';       // 黒石が置かれている
    public static final char WHITE = 'W';       // 白石が置かれている
    public static final char NONE = 'N';        // 石が置かれていない

    private DiscState() {
    }
}

 やっと、staticやfinalの使い所が実感できてきた気がしています。

Boardクラス

 オセロ盤の各マスに石が置かれているか、置かれている場合は黒石か白石かといった状態を管理するクラスです。また、ゲーム開始時におけるオセロ盤の初期化(中央4マスだけに石を置く)や、石の配置状況が更新された際は必ず黒石、白石の個数の更新もします。特に2つ目の理由により、石の配置状況の更新はputDiscメソッドとflipAllDiscsメソッドのみとし、両者のメソッド内で個数の更新を同時に行うよう実装しました。なお、Configクラスからオセロ盤のサイズを受け取る想定です。
 初めにオセロプログラムを作り始めたときは、石の位置を表す座標をint型変数x, yで表していたのですが、途中から座標を表すクラスCoordinatesを自作して利用していました。しかし今回、int型の座標を表すクラスがJavaのライブラリにあること(Pointクラス)を知ったので、今後はそちらを利用していきます。

Boardクラス(クリックすると開きます)
import java.awt.Point;
import java.util.ArrayList;

public class Board {
    private final int size;                 // オセロ盤の一辺(8, 10, 12, 14, 16)
    private char[][] squares;               // 各マスの石の有無、石がある場合は色を表す
    private int blackCounter;               // 黒石の個数
    private int whiteCounter;               // 白石の個数

    public Board(int size) {
        this.size = size;
        this.squares = new char[this.size][this.size];
        this.blackCounter = 0;
        this.whiteCounter = 0;
    }

    public int getSize() {
        return this.size;
    }
    public char[][] getSquares() {
        return this.squares;
    }
    public char getSquareState(Point p) {
        return this.squares[p.y][p.x];
    }
    public int getCounter(char color) {
        if (color == DiscState.BLACK) {
            return this.blackCounter;
        } else if (color == DiscState.WHITE) {
            return this.whiteCounter;
        } else {
            return this.size*this.size - this.blackCounter - this.whiteCounter;
        }
    }

    // オセロ盤をゲーム開始時の状態にする
    public void initializeBoard() {
        for (int y = 0; y < this.size; y ++) {
            for (int x = 0; x < this.size; x ++) {
                squares[y][x] = 'N';
            }
        }
        // 中央4マスだけに石を置く
        this.putDisc(DiscState.BLACK, new Point(this.size/2 - 1, this.size/2 - 1));
        this.putDisc(DiscState.BLACK, new Point(this.size/2, this.size/2));
        this.putDisc(DiscState.WHITE, new Point(this.size/2, this.size/2 - 1));
        this.putDisc(DiscState.WHITE, new Point(this.size/2 - 1, this.size/2));
    }

    // オセロ盤の指定された座標に石を置き、同時に石の個数を更新する
    public void putDisc(char color, Point p) {
        this.squares[p.y][p.x] = color;
        if (color == DiscState.BLACK) {
            this.blackCounter ++;
        } else if (color == DiscState.WHITE) {
            this.whiteCounter ++;
        }
    }

    // オセロ盤の指定された座標の石をひっくり返し、同時に石の個数を更新する
    public void flipAllDiscs(ArrayList<Point> discs) {
        for (Point disc : discs) {
            this.flipDisc(disc);
        }
    }

    // オセロ盤の指定された座標の石をひっくり返し、同時に石の個数を更新する
    private void flipDisc(Point p) {
        if (this.squares[p.y][p.x] == DiscState.BLACK) {
            this.squares[p.y][p.x] = DiscState.WHITE;
            this.blackCounter --;
            this.whiteCounter ++;
        } else if (this.squares[p.y][p.x] == DiscState.WHITE) {
            this.squares[p.y][p.x] = DiscState.BLACK;
            this.blackCounter ++;
            this.whiteCounter --;
        }
    }
}

Printクラス

 コマンドプロンプト上に、罫線を駆使してオセロ盤を表示します。Boardクラスのメンバ変数・配列を受け取ります。

Printクラス(クリックすると開きます)
import java.awt.Point;
import java.util.ArrayList;

public class Print {
    private Board board;                                        // オセロ盤の状態
    private final String alphabets = "abcdefghijklmnop";        // 横方向の座標を示すアルファベット

    public Print(Board board) {
        this.board = board;
    }

    // オセロ盤をコンソール上に表示する
    public void printBoard() {
        this.printBoardAlphabetLine();                          // アルファベット行
        this.printBoardOtherLine("┏", "┳", "┓");                  // 上端
        for (int y = 0; y < this.board.getSize() - 1; y ++) {
            this.printBoardDiscLine(y);                         // 石を表示する行
            this.printBoardOtherLine("┣", "╋", "┫");          // 行間の枠
        }
        this.printBoardDiscLine(this.board.getSize() - 1);      // 石を表示する行
        this.printBoardOtherLine("┗", "┻", "┛");              // 下端
    }

    // プレイヤーと相手の石の数を表示する
    public void printDiscNumber(char playerColor) {
        if (playerColor == DiscState.BLACK) {
            System.out.print("あなた = " + this.board.getCounter(DiscState.BLACK) + "  ");
            System.out.println("相手 = " + this.board.getCounter(DiscState.WHITE));
        } else if (playerColor == DiscState.WHITE) {
            System.out.print("あなた = " + this.board.getCounter(DiscState.WHITE) + "  ");
            System.out.println("相手 = " + this.board.getCounter(DiscState.BLACK));
        }
    }

    // ひっくり返した石の座標をすべて表示する
    public void printAllFlippedDiscs(ArrayList<Point> discs) {
        System.out.println("次の石をひっくり返しました。");
        int count = 0;
        for (Point disc : discs) {
            System.out.print(alphabets.substring(disc.x, disc.x + 1) + (disc.y + 1) + " ");
            count ++;
            if (count == 8) {
                System.out.println("");
                count = 0;
            }
        }
        System.out.println("");
    }

    // オセロ盤の列を示すアルファベットを表示する
    private void printBoardAlphabetLine() {
        String buf = "  ";
        for (int x = 0; x < this.board.getSize(); x ++) {
            buf += "   " + this.alphabets.charAt(x);
        }
        System.out.println(buf);
    }

    // オセロ盤の石がある行を1行分表示する
    private void printBoardDiscLine(int y) {
        String buf = String.format("%2d┃", y+1);
        for (int x = 0; x < this.board.getSize(); x ++) {
            if (this.board.getSquareState(new Point(x, y)) == DiscState.BLACK) {
                buf += "●┃";
            } else if (this.board.getSquareState(new Point(x, y)) == DiscState.WHITE) {
                buf += "○┃";
            } else {
                buf += " ┃";
            }
        }
        System.out.println(buf);
    }

    // オセロ盤の枠を表す罫線を1行分表示する
    private void printBoardOtherLine(String left, String middle, String right) {
        String buf = "  " + left;
        for (int x = 0; x < this.board.getSize() - 1; x ++) {
            buf += "━" + middle;
        }
        System.out.println(buf + "━" + right);
    }
}

Flipクラス

 指定されたマスに石を置いたと仮定し、相手の石をひっくり返せるかどうかを判定します。Boardクラスのメンバ変数・配列と、次のターンでどこにどの色の石を置くかどうかの情報を受け取ります。
 相手の石をひっくり返せるかどうかの判定については、以前のコードから大分軽量化を試みました。特に、縦・横・斜めの8方向にひっくり返せる石を探す部分については、まず各方向に1マスずつ進むためのベクトルとしてDirectionsクラスを用意しました。

Directionsクラス(クリックすると開きます)
import java.awt.Point;
import java.util.ArrayList;

public class Directions {
    public static final ArrayList<Point> directions;

    static {
        directions = new ArrayList<Point>();
        directions.add(new Point(1, 0));        //   0度
        directions.add(new Point(1, 1));        //  45度
        directions.add(new Point(0, 1));        //  90度
        directions.add(new Point(-1, 1));       // 135度
        directions.add(new Point(-1, 0));       // 180度
        directions.add(new Point(-1, -1));      // 225度
        directions.add(new Point(0, -1));       // 270度
        directions.add(new Point(1, -1));       // 315度
    }
}

 Flipクラスでは、指定されたマスに石を置くと相手の石をひっくり返せるかどうかを調べるisAvailableSquareメソッドと、ひっくり返せる石の座標の一覧を取得するgetAllFlippedDiscsメソッドを用意しています。また、Directionsクラスを活用し、同じような処理は新規メソッドに分離したため、前回のものより大分すっきりしたように思います。

Flipクラス(クリックすると開きます)
import java.awt.Point;
import java.util.ArrayList;

public class Flip {
    private Board board;                    // オセロ盤の状態
    private char nextColor;                 // 置こうとしている石の色
    private Point nextMove;                 // 石を置こうとしているマス

    public Flip(Board board, char nextColor, Point nextMove) {
        this.board = board;
        this.nextColor = nextColor;
        this.nextMove = nextMove;
    }

    // 指定されたマスが相手の石をひっくり返せるマスかどうか判定する
    public boolean isAvailableSquare() {
        // すでに石が置かれていないかどうか調べる
        if (!this.isEmptySquare(this.nextMove)) {
            return false;
        }
        // 各方向にひっくり返せる石があるかどうか調べる
        for (Point direction : Directions.directions) {
            if (this.searchFlippedDiscs(this.nextMove, direction).size() > 0) {
                // ひっくり返せる石があった場合
                return true;
            }
        }
        // どの方向にもひっくり返せる石がなかった場合
        return false;
    }

    // ひっくり返される石の座標の一覧を返す
    public ArrayList<Point> getAllFlippedDiscs() {
        ArrayList<Point> allFlippedDiscs = new ArrayList<Point>();
        for (Point direction : Directions.directions) {
            allFlippedDiscs.addAll(this.searchFlippedDiscs(this.nextMove, direction));
        }
        return allFlippedDiscs;
    }

    // 指定されたマスに石がないかどうか判定する
    private boolean isEmptySquare(Point square) {
        if (this.board.getSquareState(square) == DiscState.NONE) {
            return true;
        } else {
            return false;
        }
    }

    // 縦・横・斜めのうち1方向に対し、ひっくり返せる石の座標の一覧を取得する
    // 相手の石が連続する間は一覧に仮登録し続け、その直後に自分の石がきたらその一覧を返す
    // しかし、隣にマスがない(盤の外)または石がない場合は、一覧を全消去して返す(=ひっくり返せない)
    private ArrayList<Point> searchFlippedDiscs(Point square, Point direction) {
        Point currentSquare = new Point(square);
        ArrayList<Point> flippedDiscs = new ArrayList<Point>();

        while(true) {
            // 隣のマスの座標を求める
            Point nextSquare = this.getNextSquare(currentSquare, direction);
            // 隣のマスの状況によりループを抜ける場合
            if (!this.isSquareInRange(nextSquare)) {
                // 隣にマスがない場合
                flippedDiscs.clear();
                break;
            } else if (board.getSquareState(nextSquare) == DiscState.NONE) {
                // 隣のマスに石がない場合
                flippedDiscs.clear();
                break;
            } else if (board.getSquareState(nextSquare) == this.nextColor) {
                // 隣のマスに自分の石がある場合
                break;
            }
            // 隣のマスに相手の石がある場合は、さらに隣のマスに進む
            flippedDiscs.add(nextSquare);
            currentSquare.setLocation(nextSquare);
        }
        return flippedDiscs;
    }

    // 指定された向きに関して隣のマスの座標を求める
    private Point getNextSquare(Point currentSquare, Point direction) {
        Point nextSquare = new Point(currentSquare.x, currentSquare.y);
        nextSquare.translate(direction.x, direction.y);
        return nextSquare;
    }

    // 指定されたマスがオセロ盤の中にあるかどうか調べる
    private boolean isSquareInRange(Point square) {
        if (0 <= square.x && square.x < this.board.getSize() &&
            0 <= square.y && square.y < this.board.getSize()) {
            return true;
        } else {
            return false;
        }
    }
}

 なお、Flipクラスはあくまでひっくり返せるかどうかを判定するだけであって、実際にひっくり返す処理を行うのはBoardクラス内のflipAllDiscsメソッドです。その意味では、Flipクラスは本当はCheckFlipクラスとでも名付けた方が良かったかも知れません。

Strategyクラス

 石を置けるマスがあるかどうか判定し、ある場合はどのマスに石を置くかを選ぶクラスです。Boardクラスのメンバ変数・配列を受け取ります。
 今後検討している処理(主にログ)のため、以前のコードより変更しています。まず、次に石を置くべきマスの候補に対し、何らかの評価値を割り当てたり、何らかの条件を満たすとフラグを立てたりできるよう、Pointクラスを継承させたCandidate(候補)クラスを作成しました。今は、そのマスに石を置くとひっくり返せる石の座標を保持させていますが、たとえば角に配置できるかどうかや、ここまでのターン数に応じて石を少なく取ったり多く取ったりさせるなどの処理を追加する予定です。

Candidateクラス(クリックすると開きます)
import java.awt.Point;
import java.util.ArrayList;

public class Candidate extends Point {
    private ArrayList<Point> allFlippedDiscs;

    public Candidate(Point point) {
        super(point);
    }
    public Candidate(Point point, ArrayList<Point> allFlippedDiscs) {
        super(point);
        this.allFlippedDiscs = allFlippedDiscs;
    }
    public void setAllFlippedDiscs(ArrayList<Point> allFlippedDiscs) {
        this.allFlippedDiscs = allFlippedDiscs;
    }
    public ArrayList<Point> getAllFlippedDiscs() {
        return this.allFlippedDiscs;
    }
}

 続いて、Strategyクラスです。

Strategyクラス(クリックすると開きます)
import java.awt.Point;
import java.util.ArrayList;
import java.util.Random;

public class Strategy {
    private Config config;                      // 初期設定
    private Board board;                        // オセロ盤の状態
    private char nextColor;                     // 置こうとしている石の色
    ArrayList<Candidate> candidates;            // 石を置ける(=相手の石をひっくり返せる)マスの一覧

    public Strategy(Config config, Board board, char nextColor) {
        this.config = config;
        this.board = board;
        this.nextColor = nextColor;
        this.candidates = new ArrayList<Candidate>();
    }

    // 次に石を置けるマスがあるかどうかを判定する
    public boolean hasCandidates() {
        this.searchCandidates();
        if (this.candidates.size() > 0) {
            return true;
        } else {
            return false;
        }
    }


    // 次に石を置くべきマスを1つ選ぶ
    public Candidate getNextMove() {
        return this.getNextMoveRandom();
    }

    // 次に石を置くべきマスをランダムに1つ選ぶ
    private Candidate getNextMoveRandom() {
        return this.candidates.get(new Random().nextInt(this.candidates.size()));
    }

    // 石を置ける(=相手の石をひっくり返せる)マスを探索する
    private void searchCandidates() {
        for (int y = 0; y < this.board.getSize(); y ++) {
            for (int x = 0; x < this.board.getSize(); x ++) {
                Point currentSquare = new Point(x, y);
                Flip flip = new Flip(this.board, this.nextColor, currentSquare);
                if (flip.isAvailableSquare()) {
                    this.candidates.add(new Candidate(currentSquare, flip.getAllFlippedDiscs()));
                }
            }
        }
    }
}

 前回に引き続き、敵側の思考ルーチンは石を置ける場所からランダムに1つ選ぶだけの単純なものです。次回こそ、このクラスをもっともっと充実させていく予定です。

Playerクラス

 次に石を置く場所をプレイヤーに入力させるクラスです。入力された座標のマスに他の石がないかどうか、相手の石をひっくり返せるかどうか、そもそも入力された文字列が座標として正しいかどうかを判定します。なお、クラスのネーミングは安直だったと反省しています。

Playerクラス(クリックすると開きます)
import java.awt.Point;
import java.util.Scanner;

public class Player {
    private Board board;                                    // オセロ盤の状態
    private char nextColor;                             // 置こうとしている石の色

    private Candidate nextMove;                         // 次に石を置くマス
    private Flip flip;                                      // ひっくり返せる石の情報

    private final String alphabets = "abcdefghijklmnop";    // 横方向の座標を示すアルファベット

    public Player(Board board, char nextColor) {
        this.board = board;
        this.nextColor = nextColor;
        this.nextMove = new Candidate(new Point(0, 0));
    }

    // 次に石を置く場所が決まるまで入力を受け付ける
    public Candidate askNextMove() {
        Scanner sc = new Scanner(System.in);
        while (true) {
            // 入力
            System.out.println("\n石を置く場所を決めてください。");
            System.out.print("[x座標 y座標](例 a 1):");
            String line = sc.nextLine();
            // プレイヤーの入力した座標がオセロ盤の範囲内かどうか判定する
            if (!this.checkCoordinatesRange(line)) {
                // 座標が正しくない場合、再度入力させる
                System.out.println("入力が間違っています。");
                continue;
            }
            // 石を置ける(=相手の石をひっくり返せる)マスかどうか判定する
            this.flip = new Flip(this.board, this.nextColor, this.nextMove);
            if (!this.flip.isAvailableSquare()) {
                System.out.println("そのマスに石を置くことはできません。");
                continue;
            }
            this.nextMove.setAllFlippedDiscs(this.flip.getAllFlippedDiscs());
            return this.nextMove;
        }
    }

    // プレイヤーの入力した座標がオセロ盤の範囲内かどうか判定する
    private boolean checkCoordinatesRange(String line) {
        String[] tokens = line.split(" ");
        // 1文字目のアルファベットから横の座標を読み取る
        int x = this.alphabets.indexOf(tokens[0]);
        if (tokens[0].length() != 1 || x < 0 || x >= this.board.getSize()) {
            return false;
        }
        // 残りの文字から縦の座標を読み取る
        int y;
        try {
            y = Integer.parseInt(tokens[1]);
        } catch (NumberFormatException e) {
            return false;
        }
        if (y <= 0 || y > this.board.getSize()) {
            return false;
        }

        this.nextMove.setLocation(x, y - 1);
        return true;
    }
}

OthelloBoardクラス

 オセロのターン経過などを処理するクラスです。他の様々な処理を別クラスとして分離したことにより、大分読みやすくなったと思います。もちろん、まだ手を入れていく必要はありますが……

OthelloBoardクラス(クリックすると開きます)
public class OthelloBoard {
    private Config config;                                      // 初期設定
    private Board board;                                        // オセロ盤の状態
    private int turnCountMax;                                   // ターン数の最大値(1辺*1辺-4)

    private int turnCounter;                                    // 現在ターン数
    private int skipCounter;                                    // 連続スキップ回数(2になればオセロ終了)
    private boolean isPlayerTurn;                               // 現在ターンがプレイヤーの番ならばtrue
    private char nextColor;                                 // 現在のターンがどちらの色の手番か

    // コンストラクタ
    public OthelloBoard() {
        System.out.println("オセロを始めます。");
        // 初期設定
        this.config = new Config();
        this.board = new Board(this.config.getSize());
        this.board.initializeBoard();
        this.turnCountMax = this.config.getSize()*this.config.getSize() - 4;
        Print print = new Print(this.board);
        print.printBoard();
        print.printDiscNumber(this.config.getPlayerColor());

        // 第1ターンのみの処理
        this.turnCounter = 1;
        this.skipCounter = 0;
        this.isPlayerTurn = this.getFirstMove();
        this.nextColor = this.getNextColor();
    }

    // オセロを開始する
    public void start() {
        // 毎ターンの処理
        while (this.turnCounter <= this.turnCountMax) {
            // ターンをスキップするかどうか判定する
            Strategy strategy = new Strategy(this.config, this.board, this.nextColor);
            if (!strategy.hasCandidates()) {
                // 現在のターンを敵側にゆずる
                System.out.println("ターンがスキップされました。");
                this.skipCounter ++;
                if (this.skipCounter == 2) {
                    System.out.println("ターンが連続でスキップされたため、オセロを終了します。");
                    break;
                }
                this.isPlayerTurn = !this.isPlayerTurn;
                this.nextColor = this.getNextColor();
                continue;
            }
            // 以下、ターンをスキップしない場合
            // 次に石を置く場所を決める
            this.skipCounter = 0;
            Candidate nextMove;
            if (this.isPlayerTurn) {
                // プレイヤーのターン
                System.out.println("\nTurn " + this.turnCounter + ":あなたのターンです。");
                Player player = new Player(this.board, this.nextColor);
                nextMove = player.askNextMove();
            } else {
                // 相手のターン
                System.out.println("\nTurn " + this.turnCounter + ":相手のターンです。");
                nextMove = strategy.getNextMove();
            }
            // ひっくり返した後の盤面を表示する
            this.board.putDisc(this.nextColor, nextMove);
            this.board.flipAllDiscs(nextMove.getAllFlippedDiscs());
            Print print = new Print(this.board);
            print.printBoard();
            print.printDiscNumber(this.config.getPlayerColor());
            print.printAllFlippedDiscs(nextMove.getAllFlippedDiscs());
            // 次ターンのための処理
            this.turnCounter ++;
            this.isPlayerTurn = !this.isPlayerTurn;
            if (this.isPlayerTurn) {
                this.nextColor = this.config.getPlayerColor();
            } else {
                this.nextColor = this.config.getOtherColor();
            }
        }
        // 勝敗の判定
        this.printResult();
    }

    // ゲームの勝敗を表示する
    private void printResult() {
        if (this.board.getCounter(DiscState.BLACK) > this.board.getCounter(DiscState.WHITE)) {
            System.out.println("黒石の勝ちです。");
        } else {
            System.out.println("白石の勝ちです。");
        }
    }

    // 現在のターンがどちらの色の手番か判定する
    private char getNextColor() {
        if (this.isPlayerTurn) {
            return this.config.getPlayerColor();
        } else {
            return this.config.getOtherColor();
        }
    }

    // 先手がどちらかを決める
    // プレイヤーが黒石ならプレイヤーが先手、白石なら相手が先手となる
    private boolean getFirstMove() {
        if (this.config.getPlayerColor() == DiscState.BLACK) {
            return true;
        } else {
            return false;
        }
    }
}

OthelloBoardTestクラス

 OthelloBoardクラスを呼び出す、ただそれだけのクラスです。

OthelloBoardTestクラス(クリックすると開きます)
public class OthelloBoardTest {
    public static void main(String args[]) {
        OthelloBoard ob = new OthelloBoard();
        ob.start();
    }
}

3.(ひとまず)完成⇒コミット

 先ほどご説明したソースコードたちを、Bitbucketにコミットしました。⇒MyOthello

 しばらくはBitbucketで色々試してみて、いつかのタイミングでGitHubにもリベンジしてみたいです。なお、eclipseとの連携のやり方はGitHubもほぼ同じみたいです。

 ここまでお読みいただき、ありがとうございました!

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1