import java.util.List;
import java.util.Stack;
import java.util.Collection;
import java.util.LinkedList;
import java.util.ArrayList;
import java.util.Collections;
/**************/
/* INTERFACES */
/**************/
interface Token
{
Character getSymbol();
}
interface PlaceableToken extends Token
{
int getIndex();
int getRow();
int getColumn();
boolean placeAt(int i);
boolean belongsOn(Grid grid);
boolean isPlacedOn(Grid grid);
}
interface ErasableToken extends PlaceableToken
{
boolean erase();
}
interface NonErasableMoveableToken extends PlaceableToken
{
boolean moveTo(int i);
}
interface MoveableToken extends ErasableToken, NonErasableMoveableToken
{
}
interface Grid
{
final int OFF_GRID = -1;
int size();
int rowCount();
int columnCount();
boolean isGridIndex(int i);
int toIndex(int r, int c);
}
interface ImmutableTokenGrid extends Grid
{
Token get(int i);
}
interface MutableTokenGrid extends ImmutableTokenGrid
{
boolean place(PlaceableToken token);
boolean erase(int i, ErasableToken token);
}
interface Player
{
boolean placeTokenAt(int i);
boolean eraseTokenAt(int i);
boolean moveToken(int from, int to);
}
interface Game
{
public void play();
}
/*****************/
/* ERROR HANDLER */
/*****************/
class Error
{
public static boolean handle(String text)
{
System.out.println(text);
return false;
}
}
/**********/
/* TOKENS */
/**********/
class Tokens
{
public static Token NO_TOKEN = new SimpleToken('-');
public static boolean isNoToken(Token token)
{
return token == Tokens.NO_TOKEN;
}
}
class SimpleToken implements Token
{
private final Character symbol;
public SimpleToken(Character c)
{
this.symbol = c;
}
public final Character getSymbol()
{
return symbol;
}
public String toString()
{
return symbol.toString();
}
}
class FixedGridPlaceableToken extends SimpleToken
implements PlaceableToken
{
protected final TokenGrid grid;
protected int index;
public FixedGridPlaceableToken(Character symbol, TokenGrid grid)
{
super(symbol);
this.grid = grid;
this.index = Grid.OFF_GRID;
}
public final int getIndex()
{
return this.index;
}
public final int getRow()
{
return this.index / grid.columnCount();
}
public final int getColumn()
{
return this.index % grid.columnCount();
}
public final boolean isPlaced()
{
return this.grid.isGridIndex(this.index);
}
public final boolean isPlacedOn(Grid grid)
{
return grid != null && this.grid == grid && this.isPlaced();
}
public final boolean belongsOn(Grid grid)
{
return grid != null && this.grid == grid;
}
public final boolean placeAt(int i)
{
if (this.isPlaced())
return Error.handle("placeAt: already placed");
if (!grid.isGridIndex(i))
return Error.handle("placeAt: invalid index " + i);
if (!Tokens.isNoToken(grid.get(i)))
return Error.handle("placeAt: other token at " + i);
this.index = i;
if (!grid.place(this))
this.index = Grid.OFF_GRID;
return this.index != Grid.OFF_GRID;
}
}
final class FixedGridMovableToken extends FixedGridPlaceableToken
implements MoveableToken
{
public FixedGridMovableToken(Character symbol, TokenGrid grid)
{
super(symbol, grid);
}
public final boolean erase()
{
if (!this.isPlaced())
return Error.handle("erase: token has not been placed");
final int eraseFrom = this.index;
this.index = Grid.OFF_GRID;
return this.grid.erase(eraseFrom, this);
}
public final boolean moveTo(int i)
{
if (this.isPlaced() && !this.erase())
return Error.handle("moveTo: was placed but not erased");
return placeAt(i);
}
}
/*********/
/* GRIDS */
/*********/
class FixedSizeRectangularGrid implements Grid
{
private final int rowCount, columnCount, size;
protected FixedSizeRectangularGrid(int rowCount, int columnCount)
{
this.rowCount = rowCount;
this.columnCount = columnCount;
this.size = rowCount * columnCount;
}
public final int size()
{
return size;
}
public final int rowCount()
{
return rowCount;
}
public final int columnCount()
{
return columnCount;
}
public final boolean isGridIndex(int i)
{
return i >= 0 && i < size;
}
public final int toIndex(int r, int c)
{
final int i = r * columnCount() + c;
return isGridIndex(i) ? i : Grid.OFF_GRID;
}
}
final class TokenGrid extends FixedSizeRectangularGrid
implements MutableTokenGrid
{
private final char FIRST_COL_HEADER = 'A';
private final int FIRST_ROW_NUMBER = 1;
private final Token[] tokens;
private final String heading;
public TokenGrid(int rowCount, int columnCount, String heading)
{
super(rowCount, columnCount);
this.heading = heading;
final int n = this.size();
tokens = new Token[n];
for (int i = 0; i < n; i++)
tokens[i] = Tokens.NO_TOKEN;
}
public final Token get(int i)
{
return tokens[i];
}
public final boolean place(PlaceableToken token)
{
if (Tokens.isNoToken(token) || !token.isPlacedOn(this))
return Error.handle("place: no token or token not on grid");
final int i = token.getIndex();
if (!Tokens.isNoToken(this.get(i)))
return Error.handle("place: other token already in place " + i);
tokens[i] = token;
return true;
}
public final boolean erase(int i, ErasableToken token)
{
if (Tokens.isNoToken(token) || token.isPlacedOn(this))
return Error.handle("erase: no token or token is not erased");
if (this.get(i) != token)
return Error.handle("erase: tokens do not match");
tokens[i] = Tokens.NO_TOKEN;
return true;
}
public String toString()
{
int c;
Character ch;
StringBuilder sb = new StringBuilder();
sb.append(this.heading + "\n\n ");
for (c = 0, ch=FIRST_COL_HEADER; c < this.columnCount(); c++)
{
sb.append(ch.toString() + ' ');
ch = (char)(ch + 1);
}
int i = 0;
for (Token token : tokens)
{
if (0 == i++ % this.columnCount())
{
sb.append("\n");
int rowNum = 1 + (i / this.columnCount());
sb.append(rowNum);
sb.append(rowNum < 10 ? " " : " ");
}
sb.append(token.toString() + ' ');
}
sb.append("\n");
return sb.toString();
}
}
/***********/
/* PLAYERS */
/***********/
class TokenHolderMover implements Player
{
private final Character symbol;
private final ImmutableTokenGrid grid;
private final Stack<MoveableToken> tokenStack = new Stack<MoveableToken>();
public TokenHolderMover(int n, Character symbol, TokenGrid grid)
{
this.grid = grid;
this.symbol = symbol;
while (n-- > 0)
tokenStack.push(new FixedGridMovableToken(symbol, grid));
}
public final boolean placeTokenAt(int i)
{
if (!hasToken())
return Error.handle("placeTokenAt: out of tokens");
return getLooseToken().placeAt(i);
}
public final boolean eraseTokenAt(int i)
{
if (!grid.isGridIndex(i))
return Error.handle("eraseTokenAt: invalid index " + i);
Token token = grid.get(i);
if (Tokens.isNoToken(token))
return Error.handle("eraseTokenAt: no token to erase");
if (!((ErasableToken)token).erase())
return Error.handle("eraseTokenAt: failed to erase");
tokenStack.push((MoveableToken)token);
return true;
}
public final boolean moveToken(int from, int to)
{
if (!grid.isGridIndex(from) || !grid.isGridIndex(to))
return Error.handle("moveToken: invalid from or to index");
Token token = grid.get(from);
if (Tokens.isNoToken(token))
return Error.handle("moveToken: no token to move");
return ((MoveableToken)token).moveTo(to);
}
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append(this.symbol);
return sb.toString();
}
private final boolean hasToken()
{
return !tokenStack.empty();
}
private final MoveableToken getLooseToken()
{
return tokenStack.pop();
}
}
/*************/
/* OFFICIALS */
/*************/
final class Official
{
private final TokenGrid grid;
private final int M, N, K, upper, lower = 0;
private final Character VOID = Tokens.NO_TOKEN.getSymbol();
public Official(int k, TokenGrid grid)
{
this.grid = grid;
upper = grid.size();
M = grid.columnCount();
N = grid.rowCount();
K = k;
}
private int row(int i) { return i / M; }
private int col(int i) { return i % M; }
private Character charAt(int i) { return grid.get(i).getSymbol(); }
public boolean bingo(int i)
{
return bingoEW(i) || bingoNS(i) || bingoNWSE(i) || bingoNESW(i);
}
public boolean bingoEW(int index)
{
final int i = index, row = row(index);
final Character c = charAt(i);
if (c == VOID ) return false;
int d, j, count = 1;
for (d = 1, j = i - d;
j >= lower && count <= K && c == charAt(j) && row(j) == row;
d++, j = i - d)
count++;
for (d = 1, j = i + d;
j < upper && count <= K && c == charAt(j) && row(j) == row;
d++, j = i + d)
count++;
return count >= K;
}
public boolean bingoNS(int index)
{
final int i = index, col = col(i);
final Character c = charAt(i);
if (c == VOID ) return false;
int d, j, count = 1;
for (d = 1, j = i - (d * M);
j >= lower && count <= K && c == charAt(j) && col(j) == col;
d++, j = i - (d * M))
count++;
for (d = 1, j = i + (d * M);
j < upper && count <= K && c == charAt(j) && col(j) == col;
d++, j = i + (d * M))
count++;
return count >= K;
}
public boolean bingoNWSE(int index)
{
final int i = index, row = row(i), col = col(i);
final Character c = charAt(i);
if (c == VOID ) return false;
int d, j, count = 1;
for (d = 1, j = i - (d * M) - d;
j >= lower && count <= K && c == charAt(j) &&
row(j) == row - d && col(j) == col - d;
d++, j = i - (d * M) - d)
count++;
for (d = 1, j = i + (d * M) + d;
j < upper && count <= K && c == charAt(j) &&
row(j) == row + d && col(j) == col + d;
d++, j = i + (d * M) + d)
count++;
return count >= K;
}
public boolean bingoNESW(int index)
{
final int i = index, row = row(i), col = col(i);
final Character c = charAt(i);
if (c == VOID ) return false;
int d, j, count = 1;
for (d = 1, j = i - (d * M) + d;
j >= lower && count <= K && c == charAt(j) &&
row(j) == row - d && col(j) == col + d;
d++, j = i - (d * M) + d)
count++;
for (d = 1, j = i + (d * M) - d;
j < upper && count <= K && c == charAt(j) &&
row(j) == row + d && col(j) == col - d;
d++, j = i + (d * M) - d)
count++;
return count >= K;
}
}
/***************/
/* TIC-TAC-TOE */
/***************/
// https://www.webofstories.com/play/donald.knuth/22
class TicTacToeGame implements Game
{
private final int N = 3, M = 3, K = 3;
private final int MAX_TURNS = N * M;
private final TokenGrid grid = new TokenGrid(N, M, "Tic-Tac-Toe");
private final Official official = new Official(K, grid);
private final Player pX = new TokenHolderMover(5, 'X', grid);
private final Player pO = new TokenHolderMover(4, 'O', grid);
private final List<Integer> plannedMoves = new ArrayList<Integer>(MAX_TURNS);
private final List<Integer> actualMoves = new ArrayList<Integer>(MAX_TURNS);
private Player pCurrent = pX;
public TicTacToeGame(List<Integer> moves)
{
for (Integer i : moves)
{
plannedMoves.add(i);
if (plannedMoves.size() == MAX_TURNS)
break;
}
}
public TicTacToeGame()
{
for (int i = 0; i < 9; i++)
plannedMoves.add(i);
Collections.shuffle(plannedMoves);
}
private void output(String message)
{
System.out.println(grid);
System.out.println(message);
System.out.print("Moves: ");
System.out.println(actualMoves);
}
public void play()
{
int turnsTaken = 0;
for (Integer i : plannedMoves)
{
if (!pCurrent.placeTokenAt(i))
{
output(pCurrent.toString() + " attempted a bad move.");
return;
}
actualMoves.add(i);
if (official.bingo(i))
{
output(pCurrent.toString() + " won!");
return;
}
pCurrent = pCurrent == pX ? pO : pX;
turnsTaken++;
}
if (turnsTaken == MAX_TURNS)
output("Cats game.");
else
output("Game is in progress.");
}
}
/*************/
/* CONNECT-4 */
/*************/
// https://en.wikipedia.org/wiki/Connect_Four
class ConnectFourGame implements Game
{
// This class is left as an exercise.
/* The parameter named moves in the constructor below
* should contain up to N x M integers, each of which
* should be greater than or equal to zero and less
* than M (duplicates are allowed) representing a
* series of columns into which players' moves are to
* be made.
*/
public ConnectFourGame(List<Integer> moves)
{
}
public ConnectFourGame()
{
}
public void play()
{
}
}
/**********************/
/* PLAY AN M,N,K-GAME */
/**********************/
// https://en.wikipedia.org/wiki/M,n,k-game
public class Play_An_MNK_Game
{
public static void main(String[] args)
{
final String TIC_TAC_TOE = "Tic-Tac-Toe";
final String CONNECT_FOUR = "Connect-4";
final int n = args.length;
if (n == 0 || (args[0].equals(TIC_TAC_TOE) && n == 1))
{
(new TicTacToeGame()).play();
return;
}
else if (args[0].equals(CONNECT_FOUR) && n == 1)
{
(new ConnectFourGame()).play();
return;
}
else if (n > 1)
{
final List<Integer> moves = new ArrayList<Integer>();
for (int i = 1; i < n; i++)
moves.add(Integer.parseInt(args[i]));
if (args[0].equals(TIC_TAC_TOE))
{
(new TicTacToeGame(moves)).play();
return;
}
if (args[0].equals(CONNECT_FOUR))
{
(new ConnectFourGame(moves)).play();
return;
}
}
System.out.println("Sorry, I don't know how to play " + args[0] + ".");
}
}