Index: experimental/hex/hex_12.cpp |
diff --git a/experimental/hex/hex_12.cpp b/experimental/hex/hex_12.cpp |
deleted file mode 100644 |
index 184551dde230a357f28c39997550061787ac1cf1..0000000000000000000000000000000000000000 |
--- a/experimental/hex/hex_12.cpp |
+++ /dev/null |
@@ -1,617 +0,0 @@ |
-// Hex Maniac v1.2 |
-// Copyright Cameron Browne |
-// 5/10/2000 |
-// |
-// Point-pairing algorithm with blind row. |
-// |
-// Feel free to experiment with and develop this code, but please |
-// acknowledge its contribution to any derived work. |
-// |
-// Compiles with the Microsoft C/C++ compiler (12.00.8168) and linker |
-// (6.00.8168) but should be portable. Use the following options: |
-// cl -GX hex_12.cpp |
-// |
-// Guaranteed bug free. Any errors must have been introduced by |
-// subsequent developers :) |
- |
-#include <iostream> |
- |
-using std::cout; |
-using std::cin; |
-using std::endl; |
- |
- |
-////////////////////////////////////////////////////////////////////////////// |
- |
-const int MIN_N = 3; // Smallest board size |
-const int MAX_N = 26; // Largest board size (limit of alphanumeric notation) |
- |
-static enum State // board point or move state |
-{ |
- EMPTY = 0, // empty board position |
- VERT = 1, // computer player (vertical = j) |
- HORZ = 2, // human player (horizontal = i) |
- NUM_STATES |
-}; |
- |
-static char* StateNames[NUM_STATES] = |
-{ |
- "Empty", |
- "Vertical", |
- "Horizontal", |
-}; |
- |
-struct Coord |
-{ |
- Coord(int i_=-1, int j_=-1) : i(i_), j(j_) {} |
- |
- int i; |
- int j; |
-}; |
- |
-// Hexagonal neighbours of X: |
-// >-< |
-// >-< 0 >-< | 5 | 0 |
-// < 5 >-< 1 > ---+---+--- |
-// >-< X >-< == 4 | X | 1 |
-// < 4 >-< 2 > ---+---+--- |
-// >-< 3 >-< 3 | 2 | |
-// >-< |
-static const Coord nbor[6] = |
-{ |
- Coord(1,-1), Coord(1,0), Coord(0,1), |
- Coord(-1,1), Coord(-1,0), Coord(0,-1) |
-}; |
- |
-// Blind row defense templates |
-// |
-// Key: |
-// h = existing HORZ piece |
-// v = existing VERT piece |
-// . = empty position |
-// x = existing piece (VERT or HORZ) |
-// - = don't care |
-// H = last HORZ move |
-// V = VERT's best reply |
-// r = reentrant block on furthest connected piece (or adjacent if none) |
-// |
-// Note: more general situations should go last. |
-// |
-const int NUM_DEFENSE_TEMPLATES = 25; |
-static const char* HEX_DefenseTemplate[NUM_DEFENSE_TEMPLATES][2] = |
-{ |
- { "H V - -", "- - - -" }, // acute blind corner |
- { "- - . H", "- - V -" }, // obtuse blind corner |
- { "- V H v", "- - - -" }, |
- { "- v H V", "- - - -" }, |
- { "- V H .", "h . . h" }, |
- { "- . H V", "h . . h" }, |
- { "v h H -", "V . h -" }, |
- { "- H h v", "h . V -" }, |
- { "- V H -", "h . h -" }, |
- { "- - H V", "- h . h" }, |
- { "- V H -", "- v - -" }, |
- { "- H V -", "- v - -" }, |
- { "- V H -", "- - h -" }, |
- { "- H V -", "h - - -" }, |
- { ". H h -", "V - - -" }, |
- { "- h H .", "- - V -" }, |
- { "- V H -", "h . - -" }, |
- { "- H V -", "- . h -" }, |
- { "- h H h", "- v V v" }, |
- { "- h H h", "v V v -" }, |
- { "- h H v", "- v V -" }, |
- { "- v H h", "- V v -" }, |
- { "- H r -", "h - - -" }, |
- { "- r H -", "- - h -" }, |
- { "- . H .", "- V . -" }, // reentrant block below (general case) |
-}; |
- |
- |
-/////////////////////////////////////////////////////////////////////////////// |
- |
-class HEX_Game |
-{ |
-public: |
- HEX_Game(int n_=11) : N(n_), SwapOption(true) {} |
- ~HEX_Game() {} |
- |
- bool Play(void); |
-private: |
- bool DoMove(State who); |
- bool GetUserMove(Coord& move) const; |
- bool GameWon(State who); |
- bool GameWon(State who, Coord const& from); |
- void RandomEmptyPosition(Coord& posn) const; |
- void ShowBoard(void) const; |
- bool IsValid(Coord const& posn) const; |
- |
- void BestOpening(Coord& move) const; |
- bool GoodSwap(Coord const& move) const; |
- bool BestComputerMove(Coord& move) const; |
- bool DefendBlindRow(Coord& move) const; |
- void SpareMove(Coord& move) const; |
- |
-private: |
- State Board[MAX_N][MAX_N]; // the board |
- int N; // current board size NxN |
- int NumMoves; // number of moves played |
- Coord Last; // opponent's last move |
- bool SwapOption; // whether the swap option is enabled |
- bool HasSwapped; // whether a player has swapped already |
- State WhoStarted; // who played first |
- bool Visit[MAX_N][MAX_N]; // for rough working |
-}; |
- |
- |
-/////////////////////////////////////////////////////////////////////////////// |
- |
-// Alternates moves between players until the game is won |
-// |
-// Returns: whether user choose to continue playing |
-// |
-bool HEX_Game::Play(void) |
-{ |
- memset(Board, 0, sizeof(Board)); |
- NumMoves = 0; |
- HasSwapped = false; |
- |
- // Determine who starts |
- cout << "\nDo you want to start? [y/n/q]: "; |
- char ch('n'); |
- cin >> ch; |
- |
- if (tolower(ch) == 'q') |
- exit(1); |
- |
- WhoStarted = (tolower(ch) == 'y') ? HORZ : VERT; |
- |
- if (WhoStarted == HORZ) |
- ShowBoard(); |
- |
- // Play a game |
- State who(WhoStarted); |
- while (!DoMove(who)) |
- who = State((NumMoves + WhoStarted + 1) % 2 + 1); |
- |
- cout << "\nGame won by " << StateNames[who] << "!" << endl; |
- cout << "\nPlay again on " << N << 'x' << N << "? [y/n]: "; |
- cin >> ch; |
- |
- return (tolower(ch) == 'y'); |
-} |
- |
-// Determines and plays a move for the specfied player |
-// |
-// Returns: whether this move wins the game |
-// |
-bool HEX_Game::DoMove(State who) |
-{ |
- Coord move; |
- bool did_swap = (who==HORZ) ? GetUserMove(move) : BestComputerMove(move); |
- if (did_swap) |
- { |
- // 'who' chose to swap |
- State opp = (who == VERT) ? HORZ : VERT; |
- Board[Last.j][Last.i] = EMPTY; // undo opening move |
- Board[Last.i][Last.j] = who; // redo its reflection |
- ShowBoard(); |
- cout << endl << "Swap! " << StateNames[who][0] << " takes "; |
- cout << char('A' + Last.j) << 1 + Last.i << "." << endl; |
- |
- WhoStarted = who; |
- HasSwapped = true; |
- } |
- else |
- { |
- // Make the move |
- Board[move.j][move.i] = who; |
- NumMoves++; |
- Last = move; |
- |
- // Show the result |
- ShowBoard(); |
- cout << endl << StateNames[who][0] << " plays at "; |
- cout << char('A' + move.i) << 1 + move.j << "." << endl; |
- } |
- |
- return GameWon(who); |
-} |
- |
-// Reads user's move from the keyboard (HORZ) |
-// |
-// Returns: whether user chose to swap |
-// |
-bool HEX_Game::GetUserMove(Coord& move) const |
-{ |
- char str[4]; |
- if (SwapOption && !HasSwapped && NumMoves==1) |
- { |
- char ch('y'); |
- cout << endl << "Swap opening move? "; |
- if (GoodSwap(Last)) |
- cout << "(I would) [y/n/q]: "; |
- else |
- cout << "(I wouldn't) [y/n/q]: "; |
- cin >> ch; |
- |
- if (tolower(ch) == 'q') |
- exit(2); |
- |
- if (tolower(ch) == 'y') |
- return true; // Swap |
- } |
- |
- do { |
- cout << endl << "Enter move [eg f6/q]: "; |
- cin >> str; |
- |
- if (str[0] == 'q') |
- exit(3); |
- |
- move.i = tolower(str[0]) - 'a'; |
- move.j = atoi(&str[1]) - 1; |
- } while (!IsValid(move) || Board[move.j][move.i]); |
- |
- return false; |
-} |
- |
-// Returns: whether the game has been won by the specified player |
-// |
-bool HEX_Game::GameWon(State who) |
-{ |
- memset(Visit, 0, sizeof(Visit)); |
- if (who == VERT) |
- { |
- for (int i = 0; i < N; i++) |
- if (Board[0][i]==VERT && !Visit[0][i] && GameWon(VERT, Coord(i,0))) |
- return true; |
- } |
- else |
- { |
- for (int j = 0; j < N; j++) |
- if (Board[j][0]==HORZ && !Visit[j][0] && GameWon(HORZ, Coord(0,j))) |
- return true; |
- } |
- return false; |
-} |
- |
-// Recursively examines neighbours of potential winning chains |
-// |
-// Returns: whether the game has been won by the specified player |
-// |
-bool HEX_Game::GameWon(State who, Coord const& from) |
-{ |
- if (who==HORZ && from.i==N-1 || who==VERT && from.j==N-1) |
- return true; |
- |
- Visit[from.j][from.i] = true; // this coord has been visited |
- |
- for (int n = 0; n < 6; n++) |
- { |
- // Check neighbours for continuation of chain |
- Coord to(from.i + nbor[n].i, from.j + nbor[n].j); |
- if |
- ( |
- IsValid(to) && Board[to.j][to.i]==who |
- && |
- !Visit[to.j][to.i] && GameWon(who, to) |
- ) |
- return true; |
- } |
- return false; |
-} |
- |
-// Draws board in ASCII text format |
-// |
-void HEX_Game::ShowBoard(void) const |
-{ |
- // Show alphabetical (HORZ) labels |
- cout << endl << " "; |
- for (int i = 0; i < N; i++) |
- cout << char('A' + i) << ' '; |
- cout << endl; |
- |
- for (int j = 0; j < N; j++) |
- { |
- // Show numeric (VERT) labels |
- for (int s = 0; s < j; s++) |
- cout << ' '; |
- if (j < 9) |
- cout << ' '; |
- cout << j + 1; |
- |
- // Show state of each board position along this row |
- for (int i = 0; i < N; i++) |
- if (Board[j][i] == VERT) |
- cout << " V"; |
- else if (Board[j][i] == HORZ) |
- cout << " H"; |
- else |
- cout << " ."; |
- cout << endl; |
- } |
-} |
- |
-// Returns: whether posn is within the bounds of the board |
-// |
-bool |
-HEX_Game::IsValid(Coord const& posn) const |
-{ |
- return (posn.i >= 0 && posn.i < N && posn.j >= 0 && posn.j < N); |
-} |
- |
- |
-/////////////////////////////////////////////////////////////////////////////// |
-// Strategic operations |
- |
-// Determines computer's best opening move (VERT) |
-// |
-void HEX_Game::BestOpening(Coord& move) const |
-{ |
- move = Coord(N - 1, 0); // obtuse corner in blind row |
-} |
- |
-// Returns: whether this opening move is worth swapping |
-// |
-bool |
-HEX_Game::GoodSwap(Coord const& move) const |
-{ |
- return (move.i == N - move.j - 1); // swap along short diagonal |
-} |
- |
-// Determines best move for computer (VERT) |
-// |
-// Returns: true on swap, else false |
-// |
-bool |
-HEX_Game::BestComputerMove(Coord& move) const |
-{ |
- if (NumMoves == 0) |
- { |
- BestOpening(move); // opening move |
- } |
- else if (NumMoves == 1 && SwapOption && !HasSwapped && GoodSwap(Last)) |
- { |
- return true; // swap opponent's opening move |
- } |
- else if (Last.j == 0) |
- { |
- DefendBlindRow(move); // defend the blind row |
- } |
- else |
- { |
- if (Last.i < N - Last.j) // determine dual point on left |
- move = Coord(N - Last.j, N - Last.i - 1); |
- else // determine dual point on right |
- move = Coord(N - Last.j - 1, N - Last.i); |
- } |
- |
- if (!IsValid(move) || Board[move.j][move.i]) |
- SpareMove(move); // spare move - do some damage! |
- |
- return false; |
-} |
- |
-// Determines best move along the blind row (row 0) |
-// |
-// Returns: |
-// Whether appropriate defending move was found. |
-// |
-bool HEX_Game::DefendBlindRow(Coord& move) const |
-{ |
- move = Coord(-1, -1); // should already be initialised but make sure |
- |
- for (int d = 0; d < NUM_DEFENSE_TEMPLATES; d++) |
- { |
- // Find root position H on top row |
- for (int root = 0; root < 4; root++) |
- if (HEX_DefenseTemplate[d][0][root * 2] == 'H') |
- break; |
- if |
- ( |
- root>=4 // no root found |
- || |
- root==0 && !(Last.i==0 && Last.j==0) // acute corner no good |
- || |
- root==3 && !(Last.i==N-1 && Last.j==0) // obtuse corner no good |
- ) |
- continue; |
- |
- // Check whether rest of template matches board position |
- bool valid = true; |
- |
- int reentrant = 0; |
- |
- for (int j = 0; j < 2 && valid; j++) |
- for (int c = 0; c < 4 && valid; c++) |
- { |
- int i = Last.i - root + c; |
- char ch = HEX_DefenseTemplate[d][j][c * 2]; |
- |
- if (ch == 'V' && !Board[j][i]) |
- move = Coord(i, j); |
- else if (ch == 'V' && Board[j][i]) |
- valid = false; |
- else if (ch == 'h' && Board[j][i] != HORZ) |
- valid = false; |
- else if (ch == 'v' && Board[j][i] != VERT) |
- valid = false; |
- else if (ch == 'x' && !Board[j][i]) |
- valid = false; |
- else if (ch == '.' && Board[j][i]) |
- valid = false; |
- else if (ch == 'r') |
- { |
- if (Board[j][i] == VERT) |
- valid = false; |
- else |
- { |
- move = Coord(Last); |
- reentrant = (i < root) ? -1 : 1; // is reentrant move |
- } |
- } |
- } |
- |
- if (!valid) |
- continue; // invalid template |
- |
- if (move.i == -1 || move.j == -1) |
- continue; // no valid reply found |
- |
- if (reentrant) |
- { |
- if (reentrant > 0) |
- { |
- // Find reentrant block above |
- while (move.i < N - 1) |
- { |
- if (!Board[0][move.i]) |
- return true; // adjacent block above |
- |
- if (!Board[1][move.i]) |
- { |
- move = Coord(move.i, 1); |
- return true; // reentrant block above |
- } |
- move.i++; |
- } |
- } |
- else |
- { |
- // Find reentrant block below |
- while (move.i >= 0) |
- { |
- if (!Board[0][move.i]) |
- return true; // adjacent block above |
- |
- if (!Board[1][move.i - 1]) |
- { |
- move = Coord(move.i - 1, 1); |
- return true; // reentrant block above |
- } |
- move.i--; |
- } |
- } |
- return false; // no reentrant block found |
- } |
- return true; // good move found! |
- } |
- return false; |
-} |
- |
- |
-// Determines best spare move. There will ALWAYS be a spare move somewhere. |
-// |
-void HEX_Game::SpareMove(Coord& move) const |
-{ |
- // Look for urgent moves on the blind row |
- move.j = 0; |
- for (move.i = 0; move.i < N; move.i++) |
- if |
- ( |
- !Board[0][move.i] |
- && |
- ( |
- move.i < N - 1 && Board[0][move.i + 1] == HORZ |
- && |
- ( |
- Board[1][move.i] == VERT |
- || |
- move.i > 0 && !Board[1][move.i] && Board[1][move.i-1]==HORZ |
- ) |
- || |
- move.i > 0 && Board[0][move.i - 1] == HORZ |
- && |
- ( |
- Board[1][move.i - 1] == VERT |
- || |
- !Board[1][move.i] && Board[1][move.i + 1] == HORZ |
- ) |
- ) |
- ) |
- return; |
- |
- // If top obtuse corner is empty, take it |
- if (!Board[0][N - 1]) |
- { |
- move = Coord(N - 1, 0); |
- return; |
- } |
- |
- // Block any reentrant point on row 1 |
- move.j = 1; |
- for (move.i = 0; move.i < N - 1; move.i++) // skip move.i == N - 1 |
- if |
- ( |
- !Board[1][move.i] |
- && |
- ( |
- Board[0][move.i] == HORZ && Board[0][move.i + 1] != HORZ |
- || |
- Board[0][move.i + 1] == HORZ && Board[0][move.i] != HORZ |
- ) |
- ) |
- return; |
- |
- // Block any point adjacent to White on row 0 |
- move.j = 0; |
- for (move.i = 0; move.i < N; move.i++) |
- if |
- ( |
- !Board[0][move.i] |
- && |
- ( |
- move.i > 0 && Board[0][move.i - 1] == HORZ |
- || |
- move.i < N - 1 && Board[0][move.i + 1] == HORZ |
- ) |
- ) |
- return; |
- |
- // Take any point an empty point away from Black on row 0 |
- move.j = 0; |
- for (move.i = 0; move.i < N; move.i++) |
- if |
- ( |
- !Board[0][move.i] |
- && |
- ( |
- move.i > 1 && !Board[0][move.i-1] && Board[0][move.i-2]==VERT |
- || |
- move.i < N-2 && !Board[0][move.i+1] && Board[0][move.i+2]==VERT |
- ) |
- ) |
- return; |
- |
- // Take any empty point from top obtuse corner down (must be at least one) |
- for (move.j = 0; move.j < N; move.j++) |
- for (move.i = N-1; move.i >= 0; move.i--) |
- if (!Board[move.j][move.i]) |
- return; |
- |
- move = Coord(-1, -1); // bad result |
-} |
- |
- |
-/////////////////////////////////////////////////////////////////////////////// |
- |
-void main(int argc, char* argv[]) |
-{ |
- cout << "Hex Maniac v1.2" << endl; |
- cout << "Cameron Browne 5/10/2000" << endl << endl; |
- cout << "You are horizontal (H), computer is vertical (V)" << endl; |
- |
- int n = 11; |
- if (argc == 2) |
- { |
- n = atoi(argv[1]); |
- if (n < MIN_N) |
- n = MIN_N; |
- else if (n > MAX_N) |
- n = MAX_N; |
- } |
- |
- HEX_Game game(n); |
- while (game.Play()); |
-} |