package tictactoe; import java.util.Scanner; import java.util.ArrayList; import java.util.Random; /** * 三目並べ */ public class Tictactoe { static Board board; // 盤 static int comLevel[] = {-1, -1}; /* コンピュータのレベル -1 : 人間が打つ 0 : 合法手から完全ランダム 1 : 一手勝ちがあればそこに打ち、無ければランダム 2 : 一手勝ちがあればそこに打ち、無ければ二手負けしない手からランダム 3 : 指定した手数先読みする */ public static void main (String args[]) { setComLevel(); board = new Board(); board.show(); while (true) { // ○番 if (comLevel[0] == -1) board.player(); else board.com (comLevel[0]); board.show(); if (board.doesWin()) { // ライン達成による○勝ち System.out.println ("○の勝ちです"); break; } else if (board.resign) { // 投了による×勝ち System.out.println ("×の勝ちです"); break; } else if (board.emptySquares == 0) { System.out.println ("引き分けです"); break; } board.nextTurn(); // ×番 if (comLevel[1] == -1) board.player(); else board.com (comLevel[1]); board.show(); if (board.doesWin()) { // ライン達成による×勝ち System.out.println ("×の勝ちです"); break; } else if (board.resign) { // 投了による○勝ち System.out.println ("○の勝ちです"); break; } else if (board.emptySquares == 0) { // 引き分けになるのは9手目なので、この判定がtrueになることは無い System.out.println ("引き分けです"); break; } board.nextTurn(); } } /** * COMのレベルを決定する */ static void setComLevel() { Scanner keyBoardScanner = new Scanner(System.in); System.out.print ("○はCOMが持ちますか? (Y/N) "); String inputString = keyBoardScanner.next(); if (inputString.equals ("Y") || inputString.equals ("y")) { while (true) { // 適切な値が選択されるまでループ System.out.print ("COMのレベルは? (0~9) "); inputString = keyBoardScanner.next(); try { comLevel[0] = Integer.parseInt (inputString); } catch (NumberFormatException e) { // 整数値以外が入力された場合 System.out.println ("0~9を入力してください"); continue; } if (comLevel[0] <0 || 9 maxLineValue) maxLineValue = lineValue; if (lineValue < minLineValue) minLineValue = lineValue; lineValue = board[4]+board[5]+board[6]; if (lineValue > maxLineValue) maxLineValue = lineValue; if (lineValue < minLineValue) minLineValue = lineValue; lineValue = board[7]+board[8]+board[9]; if (lineValue > maxLineValue) maxLineValue = lineValue; if (lineValue < minLineValue) minLineValue = lineValue; lineValue = board[1]+board[4]+board[7]; if (lineValue > maxLineValue) maxLineValue = lineValue; if (lineValue < minLineValue) minLineValue = lineValue; lineValue = board[2]+board[5]+board[8]; if (lineValue > maxLineValue) maxLineValue = lineValue; if (lineValue < minLineValue) minLineValue = lineValue; lineValue = board[3]+board[6]+board[9]; if (lineValue > maxLineValue) maxLineValue = lineValue; if (lineValue < minLineValue) minLineValue = lineValue; lineValue = board[1]+board[5]+board[9]; if (lineValue > maxLineValue) maxLineValue = lineValue; if (lineValue < minLineValue) minLineValue = lineValue; lineValue = board[3]+board[5]+board[7]; if (lineValue > maxLineValue) maxLineValue = lineValue; if (lineValue < minLineValue) minLineValue = lineValue; } /** * 局面の評価値を計算する * 先読みは行わず、現在の盤面のみで評価値を決定する * この部分を変更することによりCOMの戦略が変わる * @return int : 盤面の評価値 */ private int computeValue() { computeLineValue(); if (maxLineValue == 3) // ○が3つ並んでいる場合 value = Integer.MAX_VALUE; // ○勝ち else if (minLineValue == -3) // ×が3つ並んでいる場合 value = Integer.MIN_VALUE; // ×勝ち else if ((maxLineValue == 2) && (turn == NOUGHT)) // ○リーチかつ○番の場合 value = Integer.MAX_VALUE; // 次の手で○勝ち else if ((minLineValue == -2) && (turn == CROSS)) // ×リーチかつ×番の場合 value = Integer.MIN_VALUE; // 次の手で×勝ち else if (emptySquares == 0) // 勝敗が決しないまま升が全て埋まったとき value = 0; // 引き分け else { value = 0; for (int i=1; i<=9; ++i) // 各升に付加した評価値を合計する value += board[i] * SQUAREVALUE[i]; if (maxLineValue == 2) value += 2; // ○リーチなら評価値を上げる if (minLineValue == -2) value -= 2; // ×リーチなら評価値を下げる } return value; } /** * 局面の評価値を計算する * 指定した手数先まで再帰的に先読みし、min-max法で評価値を決定する * @param int depth : 先読みする手数 * @return int : 盤面の評価値 */ private int computeValue (int depth) { if (depth > emptySquares) depth = emptySquares; // 手数の上限は空升数なので、先読み手数もそれに合わせる if (depth == 0) // 先読み手数が0の場合は先読みせずに現在の盤面の評価値を返す return computeValue(); int nextValue = 0; // 次の手を打った後の盤面の評価値 /* 着手可能手のリストを作成する */ ArrayList nextBoardList = new ArrayList(); Board nextBoard; /* 着手可能手を打った後の盤面を生成し、評価値で昇順になるようにリストに挿入する */ for (int i=1; i<=9; ++i) { // 各升に対して着手可能手の生成を行う if (board[i] == EMPTY) { // 空升なら着手可能 boolean adden = false; nextBoard = nextBoard (i); // その升に打った後の盤面を生成 nextValue = nextBoard.computeValue(); // 評価値(先読み無し)を計算 for (int j=0; j=0; --i) { // 評価値の高そうな手から順に探す nextBoard = nextBoardList.get(i); nextValue = nextBoard.computeValue (depth-1); // 1手進めて再帰 if (value < nextValue) value = nextValue; if (value == Integer.MAX_VALUE) // 必勝手発見 break; // これ以上の評価値計算は不要なのでループから脱出 } } } else { // ×番の場合 nextBoard = nextBoardList.get(0); // 最も評価値の低い手を取り出す if (nextBoard.value == Integer.MIN_VALUE) { // 必勝手発見 value = Integer.MIN_VALUE; } else if (nextBoard.value == Integer.MAX_VALUE) { // どの手を打っても負け確定 value = Integer.MAX_VALUE; } else { while (true) { if (nextBoardList.get(nextBoardList.size()-1).value == Integer.MAX_VALUE) { nextBoardList.remove(nextBoardList.size()-1); // 負け確定の手は取り除く } else break; } /* 各有効手に対してそれを着手後の盤面に対して再帰的に評価値を求める */ /* 着手後の盤面の評価値のうち、最も評価値の低いものを現在の盤面の評価値とする */ nextValue = Integer.MAX_VALUE; for (int i=0; i nextValue) value = nextValue; if (value == Integer.MIN_VALUE) // 必勝手発見 break; // これ以上の評価値計算は不要なのでループから脱出 } } } return value; } /** * 人間が一手打つ */ void player () { int place; // 打つ位置 Scanner keyBoardScanner = new Scanner(System.in); if (turn == NOUGHT) System.out.println ("○番です"); else System.out.println ("×番です"); while (true) { // 適切な位置が選択されるまでループ System.out.print ("打つ位置(1-9)を選んでください(0=投了): "); String inputString = keyBoardScanner.next(); try { place = Integer.parseInt (inputString); } catch (NumberFormatException e) { // 整数値以外が入力された場合 System.out.println ("1~9を入力してください"); continue; } if (place <0 || 9 nextBoardList = new ArrayList(); Board nextBoard; /* 着手可能手を打った後の盤面を生成し、評価値で昇順になるようにリストに挿入する */ for (int i=1; i<=9; ++i) { // 各升に対して着手可能手の生成を行う if (board[i] == EMPTY) { // 空升なら着手可能 boolean adden = false; nextBoard = nextBoard (i); // その升に打った後の盤面を生成 int nextValue = nextBoard.computeValue(); // 評価値(先読み無し)を計算 for (int j=0; jr2) r=r1; else r=r2; // 一応ランダムだが、評価値の高い手が選ばれる確率を高くする nextBoard = nextBoardList.get (r); } } else { // ×番の場合 nextBoard = nextBoardList.get(0); // 最も評価値の低い手を取り出す if (nextBoard.value == Integer.MIN_VALUE) { // 必勝手発見 } else { r1 = rnd.nextInt (nextBoardList.size()); r2 = rnd.nextInt (nextBoardList.size()); if (r1r2) r=r1; else r=r2; // 一応ランダムだが、評価値の高い手が選ばれる確率を高くする nextBoard = nextBoardList.get (r); } } else { // ×番の場合 nextBoard = nextBoardList.get(0); // 最も評価値の低い手を取り出す if (nextBoard.value == Integer.MIN_VALUE) { // 必勝手発見 } else if (nextBoard.value == Integer.MAX_VALUE) { // どの手を打っても負け確定 } else { while (true) { if (nextBoardList.get(nextBoardList.size()-1).value == Integer.MAX_VALUE) { nextBoardList.remove(nextBoardList.size()-1); // 負け確定の手は取り除く } else break; } r1 = rnd.nextInt (nextBoardList.size()); r2 = rnd.nextInt (nextBoardList.size()); if (r1 " + nextBoard.value); for (int j=0; j nextValue) { nextBoardList.remove (i); nextBoardList.add (j, nextBoard); break; } } } nextBoard = nextBoardList.get(nextBoardList.size()-1); // 最も評価値の高い手を取り出す for (int i=nextBoardList.size()-2; i>=0; --i) // 最も評価値と高い手と同じ評価値の手の個数を数える if (nextBoardList.get(i).value == nextBoard.value) ++bestPlaces; else break; r = rnd.nextInt (bestPlaces); nextBoard = nextBoardList.get(nextBoardList.size()-r-1); // 最も評価値の低い手からランダムに1手選択する } } } else { // ×番の場合 nextBoard = nextBoardList.get(0); // 最も評価値の高い手を取り出す if (nextBoard.value == Integer.MIN_VALUE) { // 必勝手発見 } else if (nextBoard.value == Integer.MAX_VALUE) { // どの手を打っても負け確定 } else { while (true) { if (nextBoardList.get(nextBoardList.size()-1).value == Integer.MAX_VALUE) { nextBoardList.remove(nextBoardList.size()-1); // 負け確定の手は取り除く } else break; } if (nextBoardList.size() == 1) { // 負け確定以外の手が1つしか無い場合 nextBoard = nextBoardList.get(0); } else { for (int i=0; i " + nextBoard.value); for (int j=0; j nextValue) { nextBoardList.remove (i); nextBoardList.add (j, nextBoard); break; } } } nextBoard = nextBoardList.get(0); // 最も評価値の低い手を取り出す for (int i=1; i