Pages

Play Tic-Tac-Toe

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] + ".");
    }
}