+1 (315) 557-6473 

Program To Build a Line of Action Board Game Program Using Java Programming Language Assignment Solution.


Instructions

Objective
Write a java assignment program to build a line of action board game program using programming language.

Requirements and Specifications

Lines of Action is a board game invented by Claude Soucie. It is played on a checkerboard with ordinary checkers pieces. The two players take turns, each moving a piece, and possibly capturing an opposing piece. The goal of the game is to get all of one’s pieces into one group of pieces that are connected. Two pieces are connected if they are adjacent horizontally, vertically, or diagonally. Initially, the pieces are arranged as shown in Figure 1. Play alternates between Black and White, with Black moving first. Each move consists of moving a piece of your colour horizontally, vertically, or diagonally onto an empty square or onto a square occupied by an opposing piece, which is then removed from the board. A piece may jump over friendly pieces (without disturbing them), but may not cross enemy pieces, except one that it captures. A piece must move a number of squares that is exactly equal to the total number of pieces (black and white) on the line along which it chooses to move (the line of action). This line contains both the squares behind and in front of the piece that moves, as well as the square the piece is on. A piece may not move off the board, onto another piece of its colour, or over an opposing piece.
Source Code
GAME
/* Skeleton Copyright (C) 2015, 2020 Paul N. Hilfinger and the Regents of the
 * University of California. All rights reserved. */
package loa;
import java.io.PrintStream;
import java.util.Random;
import java.util.Scanner;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import static loa.Piece.*;
import static loa.Move.mv;
import static loa.Square.*;
import static loa.Main.*;
import static loa.Utils.*;
/** Represents one game of Lines of Action.
 * @author Name */
class Game {
    /** Number of milliseconds in 1 second. */
    static final int MILLISEC = 1000;
    /** Name of help text resource. */
    static final String HELP_FILE = "loa/HelpText.txt";
    /** Controller for one or more games of LOA, using
     * MANUALPLAYERTEMPLATE as an exemplar for manual players
     * (see the Player.create method) and AUTOPLAYERTEMPLATE
     * as an exemplar for automated players. Reports
     * board changes to VIEW at appropriate points. Uses REPORTER
     * to report moves, wins, and errors to user. If LOGFILE is
     * non-null, copies all commands to it. If STRICT, exits the
     * program with non-zero code on receiving an erroneous move from a
     * player. */
    Game(View view, PrintStream logFile, Reporter reporter,
         Player manualPlayerTemplate, Player autoPlayerTemplate,
         boolean strict) {
        _view = view;
        _playing = false;
        _logFile = logFile;
        _input = new Scanner(System.in);
        _autoPlayerTemplate = autoPlayerTemplate;
        _manualPlayerTemplate = manualPlayerTemplate;
        _nonplayer = manualPlayerTemplate.create(EMP, this);
        _white = _autoPlayerTemplate.create(WP, this);
        _black = _manualPlayerTemplate.create(BP, this);
        _reporter = reporter;
        _strict = strict;
    }
    /** Return the current board. */
    Board getBoard() {
        return _board;
    }
    /** Quit the game. */
    private void quit() {
        System.exit(0);
    }
    /** Return a move or command from the standard input, after prompting if
     * PROMPT. */
    String readLine(boolean prompt) {
        if (prompt) {
            prompt();
        }
        if (_input.hasNextLine()) {
            return _input.nextLine().trim();
        } else {
            return null;
        }
    }
    /** Print a prompt for a move. */
    private void prompt() {
        if (_playing) {
            System.out.print(_board.turn().abbrev().charAt(0));
        } else {
            System.out.print("-");
        }
        System.out.print("> ");
        System.out.flush();
    }
    /** Describes a command with up to three arguments. */
    private static final Pattern COMMAND_PATN =
        Pattern.compile("(#|\\S+)\\s*(\\S*)\\s*(\\S*)\\s*(\\S*).*");
    /** Process the command on LINE. */
    private void processCommand(String line) {
        line = line.trim();
        if (line.length() == 0) {
            return;
        }
        if (_logFile != null) {
            _logFile.println(line);
            _logFile.flush();
        }
        Matcher command = COMMAND_PATN.matcher(line);
        if (command.matches()) {
            switch (command.group(1).toLowerCase()) {
            case "#":
                break;
            case "new":
                _board.clear();
                _playing = true;
                break;
            case "dump":
                System.out.printf("%s%n", _board);
                break;
            case "manual":
                manualCommand(command.group(2).toLowerCase());
                break;
            case "auto":
                autoCommand(command.group(2).toLowerCase());
                break;
            case "quit":
                quit();
                break;
            case "seed":
                seedCommand(command.group(2));
                break;
            case "set":
                setCommand(command.group(2), command.group(3).toLowerCase(),
                           command.group(4).toLowerCase());
                break;
            case "limit":
                limitCommand(command.group(2));
                break;
            case "?": case "help":
                help();
                break;
            default:
                if (!processMove(line)) {
                    error("unknown command: %s%n", line);
                }
                break;
            }
        }
    }
    /** Return true iff white is a manual player. */
    boolean manualWhite() {
        return _white.isManual();
    }
    /** Return true iff black is a manual player. */
    boolean manualBlack() {
        return _black.isManual();
    }
    /** Report error by calling reportError(FORMAT, ARGS) on my reporter. */
    void reportError(String format, Object... args) {
        _reporter.reportError(format, args);
    }
    /** Report note by calling reportNote(FORMAT, ARGS) on my reporter. */
    void reportNote(String format, Object... args) {
        _reporter.reportNote(format, args);
    }
    /** Report move by calling reportMove(MOVE) on my reporter. */
    void reportMove(Move move) {
        _reporter.reportMove(move);
    }
    /** Set player PLAYER ("white" or "black") to be a manual player. */
    private void manualCommand(String player) {
        switch (player) {
        case "white":
            _white = _manualPlayerTemplate.create(WP, this);
            break;
        case "black":
            _black = _manualPlayerTemplate.create(BP, this);
            break;
        default:
            error("unknown player: %s%n", player);
        }
    }
    /** Set player PLAYER ("white" or "black") to be an automated player. */
    private void autoCommand(String player) {
        switch (player) {
        case "white":
            _white = _autoPlayerTemplate.create(WP, this);
            break;
        case "black":
            _black = _autoPlayerTemplate.create(BP, this);
            break;
        default:
            error("unknown player: %s%n", player);
        }
    }
    /** Seed random-number generator with SEED (as a long). */
    private void seedCommand(String seed) {
        try {
            _randomSource.setSeed(Long.parseLong(seed));
        } catch (NumberFormatException excp) {
            error("Invalid number: %s%n", seed);
        }
    }
    /** Set square S to CONTENT ('black', 'white', or '-'), and next player
        to move to NEXTPLAYER: 'black' or 'white'. */
    private void setCommand(String S, String content, String nextPlayer) {
        try {
            Piece p = Piece.playerValueOf(content);
            Piece next = Piece.playerValueOf(nextPlayer);
            if (next == EMP) {
                error("invalid next player: -");
            } else {
                _board.set(sq(S), p, next);
            }
        } catch (IllegalArgumentException excp) {
            error("invalid arguments to set: set %s %s %s%n", S, content,
                  nextPlayer);
        }
    }
    /** Set the corrent move limit according to the numeral in LIMIT. LIMIT
     * must be a valid numeral that is greater than the current number of
     * moves by either player in the current game. */
    private void limitCommand(String limit) {
        try {
            _board.setMoveLimit(Integer.parseInt(limit));
        } catch (NumberFormatException excp) {
            throw new IllegalArgumentException("badly formed numeral");
        }
    }
    /** Perform the move designated by LINE, if a valid move. Return
     * true iff LINE has the syntax of a move. */
    private boolean processMove(String line) {
        Move move = mv(line);
        if (move == null) {
            return false;
        } else if (!_playing) {
            error("no game in progress%n");
        } else if (!_board.isLegal(move)) {
            error("illegal move: %s%n", line);
        } else {
            _board.makeMove(move);
        }
        return true;
    }
    /** Play this game, printing any results. */
    public void play() {
        _board = new Board();
        _playing = true;
        while (true) {
            try {
                String next;
                _view.update(this);
                if (_board.gameOver() && _playing) {
                    announceWinner();
                    _playing = false;
                }
                if (_playing) {
                    switch (_board.turn()) {
                    case WP:
                        next = _white.getMove();
                        break;
                    case BP:
                        next = _black.getMove();
                        break;
                    default:
                        throw new Error("Unreachable statement");
                    }
                } else {
                    next = _nonplayer.getMove();
                }
                if (next == null) {
                    return;
                } else {
                    processCommand(next);
                }
            } catch (IllegalArgumentException excp) {
                System.err.printf("Error: %s%n", excp.getMessage());
            }
        }
    }
    /** Print an announcement of the winner. Requires that the game has been
     * won. */
    private void announceWinner() {
        switch (_board.winner()) {
        case BP:
            _reporter.reportNote("Black wins.");
            break;
        case WP:
            _reporter.reportNote("White wins.");
            break;
        default:
            _reporter.reportNote("Tie game.");
            break;
        }
    }
    /** Return an integer r, 0 <= r < N, randomly chosen from a
     * uniform distribution using the current random source. */
    int randInt(int n) {
        return _randomSource.nextInt(n);
    }
    /** Print a help message. */
    void help() {
        Main.printResource(HELP_FILE);
    }
    /** The official game board. */
    private Board _board;
    /** The current templates for manual and automated players. */
    private Player _autoPlayerTemplate, _manualPlayerTemplate;
    /** Player of white pieces. */
    private Player _white;
    /** Player of black pieces. */
    private Player _black;
    /** A player to handle commands when game not started. Does not
     * actually generate moves. */
    private Player _nonplayer;
    /** A source of pseudo-random numbers, primed to deliver the same
     * sequence in any Game with the same seed value. */
    private Random _randomSource = new Random();
    /** True if actually playing (game started and not stopped or finished).
     */
    private boolean _playing;
    /** The object that is displaying the current game. */
    private View _view;
    /** Log file, or null if absent. */
    private PrintStream _logFile;
    /** Input source. */
    private Scanner _input;
    /** Reporter for messages and errors. */
    private Reporter _reporter;
    /** If true, command errors cause termination with error exit
     * code. */
    private boolean _strict;
}
BOARD
/* Skeleton Copyright (C) 2015, 2020 Paul N. Hilfinger and the Regents of the
 * University of California. All rights reserved. */
package loa;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Formatter;
import java.util.List;
import java.util.regex.Pattern;
import static loa.Piece.*;
import static loa.Square.*;
/** Represents the state of a game of Lines of Action.
 * @author Name
 */
class Board {
    /** Default number of moves for each side that results in a draw. */
    static final int DEFAULT_MOVE_LIMIT = 60;
    /** Pattern describing a valid square designator (cr). */
    static final Pattern ROW_COL = Pattern.compile("^[a-h][1-8]$");
    /** A Board whose initial contents are taken from INITIALCONTENTS
     * and in which the player playing TURN is to move. The resulting
     * Board has
     * get(col, row) == INITIALCONTENTS[row][col]
     * Assumes that PLAYER is not null and INITIALCONTENTS is 8x8.
     *
     * CAUTION: The natural written notation for arrays initializers puts
     * the BOTTOM row of INITIALCONTENTS at the top.
     */
    Board(Piece[][] initialContents, Piece turn) {
        initialize(initialContents, turn);
    }
    /** A new board in the standard initial position. */
    Board() {
        this(INITIAL_PIECES, BP);
    }
    /** A Board whose initial contents and state are copied from
     * BOARD. */
    Board(Board board) {
        this();
        copyFrom(board);
    }
    /** Set my state to CONTENTS with SIDE to move. */
    void initialize(Piece[][] contents, Piece side) {
        assert contents.length == BOARD_SIZE;
        for (int row = 0; row < BOARD_SIZE; row++) {
            assert contents[row].length == BOARD_SIZE;
            for (int col = 0; col < BOARD_SIZE; col++) {
                Square sq = Square.sq(col, row);
                set(sq, contents[row][col]);
            }
        }
        _turn = side;
        _moveLimit = DEFAULT_MOVE_LIMIT;
    }
    /** Set me to the initial configuration. */
    void clear() {
        initialize(INITIAL_PIECES, BP);
    }
    /** Set my state to a copy of BOARD. */
    void copyFrom(Board board) {
        if (board == this) {
            return;
        }
        System.arraycopy(board._board, 0, _board, 0, BOARD_SIZE * BOARD_SIZE);
        _moves.clear();
        _moves.addAll(board._moves);
        _turn = board._turn;
        _moveLimit = board._moveLimit;
        _winnerKnown = board._winnerKnown;
        _winner = board._winner;
        _subsetsInitialized = board._subsetsInitialized;
        _whiteRegionSizes.clear();
        _whiteRegionSizes.addAll(board._whiteRegionSizes);
        _blackRegionSizes.clear();
        _blackRegionSizes.addAll(board._blackRegionSizes);
    }
    /** Return the contents of the square at SQ. */
    Piece get(Square sq) {
        return _board[sq.index()];
    }
    /** Set the square at SQ to V and set the side that is to move next
     * to NEXT, if NEXT is not null. */
    void set(Square sq, Piece v, Piece next) {
        _board[sq.index()] = v;
        if (next != null) {
            _turn = next;
        }
    }
    /** Set the square at SQ to V, without modifying the side that
     * moves next. */
    void set(Square sq, Piece v) {
        set(sq, v, null);
    }
    /** Set limit on number of moves by each side that results in a tie to
     * LIMIT, where 2 * LIMIT > movesMade(). */
    void setMoveLimit(int limit) {
        if (2 * limit <= movesMade()) {
            throw new IllegalArgumentException("move limit too small");
        }
        _moveLimit = 2 * limit;
    }
    /** Assuming isLegal(MOVE), make MOVE. This function assumes that
     * MOVE.isCapture() will return false. If it saves the move for
     * later retraction, makeMove itself uses MOVE.captureMove() to produce
     * the capturing move. */
    void makeMove(Move move) {
        assert isLegal(move);
        Square from = move.getFrom();
        set(from, EMP);
        Square to = move.getTo();
        boolean isCapture = get(to) == _turn.opposite();
        set(to, _turn, _turn.opposite());
        _moves.add(isCapture ? move.captureMove() : move);
        _subsetsInitialized = false;
        _winner = winner();
    }
    /** Retract (unmake) one move, returning to the state immediately before
     * that move. Requires that movesMade () > 0. */
    void retract() {
        assert movesMade() > 0;
        Move move = _moves.remove(movesMade() - 1);
        _turn = _turn.opposite();
        Square from = move.getFrom();
        set(from, _turn, null);
        Square to = move.getTo();
        set(to, move.isCapture() ? _turn.opposite() : EMP);
        _subsetsInitialized = false;
        _winnerKnown = false;
        _winner = winner();
    }
    /** Return the Piece representing who is next to move. */
    Piece turn() {
        return _turn;
    }
    /** Return true iff FROM - TO is a legal move for the player currently on
     * move. */
    boolean isLegal(Square from, Square to) {
        if (blocked(from, to)) {
            return false;
        }
        if (_turn == get(to)) {
            return false;
        }
        int direction = from.direction(to);
        int distance = from.distance(to);
        int piecesFound = 1;
        piecesFound += countPiecesInDirection(from, direction);
        int oppositeDirection = direction >= 4 ? direction - 4 : direction + 4;
        piecesFound += countPiecesInDirection(from, oppositeDirection);
        return piecesFound == distance;
    }
    /** Return true iff MOVE is legal for the player currently on move.
     * The isCapture() property is ignored. */
    boolean isLegal(Move move) {
        return isLegal(move.getFrom(), move.getTo());
    }
    /** Return a sequence of all legal moves from this position. */
    List legalMoves() {
        List legalMoves = new ArrayList<>();
        for (Square from : ALL_SQUARES) {
            if (get(from) == _turn) {
                for (int dir = 0; dir < 8; dir++) {
                    for (int step = 1; step < 8; step++) {
                        Square dest = from.moveDest(dir, step);
                        if (dest == null) {
                            break;
                        }
                        if (isLegal(from, dest)) {
                            if (get(dest) != _turn) {
                                boolean isCapture =
                                        get(dest) == _turn.opposite();
                                legalMoves.add(Move.mv(from, dest, isCapture));
                            }
                            break;
                        }
                    }
                }
            }
        }
        return legalMoves;
    }
    /** Return true iff the game is over (either player has all his
     * pieces continguous or there is a tie). */
    boolean gameOver() {
        return winner() != null;
    }
    /** Return true iff SIDE's pieces are continguous. */
    boolean piecesContiguous(Piece side) {
        return getRegionSizes(side).size() == 1;
    }
    /** Return the winning side, if any. If the game is not over, result is
     * null. If the game has ended in a tie, returns EMP. */
    Piece winner() {
        if (!_winnerKnown) {
            _winner = null;
            if (piecesContiguous(WP) || piecesContiguous(BP)) {
                if (piecesContiguous(WP) != piecesContiguous(BP)) {
                    _winner = piecesContiguous(WP) ? WP : BP;
                    _winnerKnown = true;
                } else {
                    _winner = _turn.opposite();
                    _winnerKnown = true;
                }
            } else if (movesMade() >= _moveLimit) {
                _winner = EMP;
                _winnerKnown = true;
            }
        }
        return _winner;
    }
    /** Return the total number of moves that have been made (and not
     * retracted). Each valid call to makeMove with a normal move increases
     * this number by 1. */
    int movesMade() {
        return _moves.size();
    }
    @Override
    public boolean equals(Object obj) {
        Board b = (Board) obj;
        return Arrays.deepEquals(_board, b._board) && _turn == b._turn;
    }
    @Override
    public int hashCode() {
        return Arrays.deepHashCode(_board) * 2 + _turn.hashCode();
    }
    @Override
    public String toString() {
        Formatter out = new Formatter();
        out.format("===\n");
        for (int r = BOARD_SIZE - 1; r >= 0; r -= 1) {
            out.format(" ");
            for (int c = 0; c < BOARD_SIZE; c += 1) {
                String phantomSpace = c < BOARD_SIZE - 1 ? " " : "";
                out.format("%s" + phantomSpace, get(sq(c, r)).abbrev());
            }
            out.format("\n");
        }
        out.format("Next move: %s\n===", turn().fullName());
        return out.toString();
    }
    /** Return true if a move from FROM to TO is blocked by an opposing
     * piece or by a friendly piece on the target square. */
    private boolean blocked(Square from, Square to) {
        assert from.isValidMove(to);
        int direction = from.direction(to);
        int distance = from.distance(to);
        for (int step = 1; step < distance; step++) {
            Square dest = from.moveDest(direction, step);
            assert dest != null;
            if (get(dest) == _turn.opposite()) {
                return true;
            }
        }
        return false;
    }
    /** Return the size of the as-yet unvisited cluster of squares
     * containing P at and adjacent to SQ. VISITED indicates squares that
     * have already been processed or are in different clusters. Update
     * VISITED to reflect squares counted. */
    private int numContig(Square sq, boolean[][] visited, Piece p) {
        visited[sq.row()][sq.col()] = true;
        int clusterSize = 1;
        for (Square square : sq.adjacent()) {
            int row = square.row();
            int col = square.col();
            if (!visited[row][col] && get(square) == p) {
                clusterSize += numContig(square, visited, p);
            }
        }
        return clusterSize;
    }
    /** Set the values of _whiteRegionSizes and _blackRegionSizes. */
    private void computeRegions() {
        if (_subsetsInitialized) {
            return;
        }
        _whiteRegionSizes.clear();
        _blackRegionSizes.clear();
        boolean[][] visited = new boolean[BOARD_SIZE][BOARD_SIZE];
        for (Square square : ALL_SQUARES) {
            Piece piece = get(square);
            if (piece != EMP && !visited[square.row()][square.col()]) {
                int clusterSize = numContig(square, visited, piece);
                if (piece == WP) {
                    _whiteRegionSizes.add(clusterSize);
                } else {
                    _blackRegionSizes.add(clusterSize);
                }
            }
        }
        _whiteRegionSizes.sort(Collections.reverseOrder());
        _blackRegionSizes.sort(Collections.reverseOrder());
        _subsetsInitialized = true;
    }
    /** Return the sizes of all the regions in the current union-find
     * structure for side S. */
    List getRegionSizes(Piece s) {
        computeRegions();
        if (s == WP) {
            return _whiteRegionSizes;
        } else {
            return _blackRegionSizes;
        }
    }
    /** Return number of pieces on moving in DIRECTION from FROM. */
    private int countPiecesInDirection(Square from, int direction) {
        int piecesFound = 0;
        for (int step = 1; ; step++) {
            Square dest = from.moveDest(direction, step);
            if (dest == null) {
                break;
            }
            if (get(dest) != EMP) {
                piecesFound++;
            }
        }
        return piecesFound;
    }
    /** The standard initial configuration for Lines of Action (bottom row
     * first). */
    static final Piece[][] INITIAL_PIECES = {
        { EMP, BP, BP, BP, BP, BP, BP, EMP },
        { WP, EMP, EMP, EMP, EMP, EMP, EMP, WP },
        { WP, EMP, EMP, EMP, EMP, EMP, EMP, WP },
        { WP, EMP, EMP, EMP, EMP, EMP, EMP, WP },
        { WP, EMP, EMP, EMP, EMP, EMP, EMP, WP },
        { WP, EMP, EMP, EMP, EMP, EMP, EMP, WP },
        { WP, EMP, EMP, EMP, EMP, EMP, EMP, WP },
        { EMP, BP, BP, BP, BP, BP, BP, EMP }
    };
    /** Current contents of the board. Square S is at _board[S.index()]. */
    private final Piece[] _board = new Piece[BOARD_SIZE * BOARD_SIZE];
    /** List of all unretracted moves on this board, in order. */
    private final ArrayList _moves = new ArrayList<>();
    /** Current side on move. */
    private Piece _turn;
    /** Limit on number of moves before tie is declared. */
    private int _moveLimit;
    /** True iff the value of _winner is known to be valid. */
    private boolean _winnerKnown;
    /** Cached value of the winner (BP, WP, EMP (for tie), or null (game still
     * in progress). Use only if _winnerKnown. */
    private Piece _winner;
    /** True iff subsets computation is up-to-date. */
    private boolean _subsetsInitialized;
    /** List of the sizes of continguous clusters of pieces, by color. */
    private final ArrayList
        _whiteRegionSizes = new ArrayList<>(),
        _blackRegionSizes = new ArrayList<>();
}
MACHINE PLAYER
/* Skeleton Copyright (C) 2015, 2020 Paul N. Hilfinger and the Regents of the
 * University of California. All rights reserved. */
package loa;
import static loa.Piece.*;
/**
 * An automated Player.
 *
 * @author Mane
 */
class MachinePlayer extends Player {
    /**
     * A position-score magnitude indicating a win (for white if positive,
     * black if negative).
     */
    private static final int WINNING_VALUE = Integer.MAX_VALUE - 20;
    /**
     * A magnitude greater than a normal value.
     */
    private static final int INFTY = Integer.MAX_VALUE;
    /**
     * A new MachinePlayer with no piece or controller (intended to produce
     * a template).
     */
    MachinePlayer() {
        this(null, null);
    }
    /**
     * A MachinePlayer that plays the SIDE pieces in GAME.
     */
    MachinePlayer(Piece side, Game game) {
        super(side, game);
    }
    @Override
    String getMove() {
        Move choice;
        assert side() == getGame().getBoard().turn();
        choice = searchForMove();
        getGame().reportMove(choice);
        return choice.toString();
    }
    @Override
    Player create(Piece piece, Game game) {
        return new MachinePlayer(piece, game);
    }
    @Override
    boolean isManual() {
        return false;
    }
    /**
     * Return a move after searching the game tree to DEPTH>0 moves
     * from the current position. Assumes the game is not over.
     */
    private Move searchForMove() {
        Board work = new Board(getBoard());
        assert side() == work.turn();
        _foundMove = null;
        if (side() == WP) {
            findMove(work, chooseDepth(), true, 1, -INFTY, INFTY);
        } else {
            findMove(work, chooseDepth(), true, -1, -INFTY, INFTY);
        }
        return _foundMove;
    }
    /**
     * Find a move from position BOARD and return its value, recording
     * the move found in _foundMove iff SAVEMOVE. The move
     * should have maximal value or have value > BETA if SENSE==1,
     * and minimal value or value < ALPHA if SENSE==-1. Searches up to
     * DEPTH levels. Searching at level 0 simply returns a static estimate
     * of the board value and does not set _foundMove. If the game is over
     * on BOARD, does not set _foundMove.
     */
    private int findMove(Board board, int depth, boolean saveMove,
                         int sense, int alpha, int beta) {
        if (board.gameOver()) {
            if (board.winner() == EMP) {
                return 0;
            }
            return board.winner() == WP ? WINNING_VALUE : -WINNING_VALUE;
        }
        if (depth == 1) {
            return board.getRegionSizes(BP).size()
                    - board.getRegionSizes(WP).size();
        }
        Move chosenMove = null;
        int record = sense > 0 ? Integer.MIN_VALUE : Integer.MAX_VALUE;
        for (Move move : board.legalMoves()) {
            board.makeMove(move);
            int value = findMove(board, depth - 1,
                    false, sense > 0 ? -1 : 1,
                    alpha, beta);
            boolean cut = false;
            if ((sense > 0 && value >= record)
                    || (sense < 0 && value <= record)) {
                chosenMove = move;
                record = value;
                if (sense > 0) {
                    alpha = Math.max(alpha, value);
                } else {
                    beta = Math.min(beta, value);
                }
                if (beta <= alpha) {
                    cut = true;
                }
            }
            board.retract();
            if (cut) {
                break;
            }
        }
        if (saveMove) {
            _foundMove = chosenMove;
        }
        return record;
    }
    /**
     * Return a search depth for the current position.
     */
    private int chooseDepth() {
        return 7;
    }
    /**
     * Used to convey moves discovered by findMove.
     */
    private Move _foundMove;
}
HUMAN PLAYER
/* Skeleton Copyright (C) 2015, 2020 Paul N. Hilfinger and the Regents of the
 * University of California. All rights reserved. */
package loa;
/** A Player that prompts for moves and reads them from its Game.
 * @author Name
 */
class HumanPlayer extends Player {
    /** A new HumanPlayer with no piece or controller (intended to produce
     * a template). */
    HumanPlayer() {
        this(null, null);
    }
    /** A HumanPlayer that plays the SIDE pieces in GAME. It uses
     * GAME.getMove() as a source of moves. */
    HumanPlayer(Piece side, Game game) {
        super(side, game);
    }
    @Override
    String getMove() {
        return getGame().readLine(false);
    }
    @Override
    Player create(Piece piece, Game game) {
        return new HumanPlayer(piece, game);
    }
    @Override
    boolean isManual() {
        return true;
    }
}
MOVE
/* Skeleton Copyright (C) 2015, 2020 Paul N. Hilfinger and the Regents of the
 * University of California. All rights reserved. */
package loa;
import static loa.Board.*;
import static loa.Piece.*;
import static loa.Square.*;
/** A move in Lines of Action. A move denotes a "from" and "to" square.
 * It may also be used to indicate whether a piece is captured as a result
 * of the move (this is relevant when Moves are used to record moves,
 * allowing these moves to be undone.)
 * @author P. N. Hilfinger */
final class Move {
    /* Implementation note: We create moves by means of static "factory
     * methods" all named mv, which in turn use the single (private)
     * constructor. There is a unique Move for each combination of arguments.
     * As a result the default equality operation (same as ==) will
     * work. */
    /** Return a move denoted S. When CAPTURE is true, inicates a move
     * that results in a capture. Returns null if S is not a
     * valid move. */
    static Move mv(String s, boolean capture) {
        s = s.trim();
        if (s.matches("[a-h][1-8]-[a-h][1-8]\\b.*")) {
            return mv(sq(s.substring(0, 2)), sq(s.substring(3, 5)),
                      capture);
        } else {
            return null;
        }
    }
    /** Return a move denoted MOVE with isCapture() false. */
    static Move mv(String move) {
        return mv(move, false);
    }
    /** Return a move from FROM to TO. If CAPTURE, indicates a move that
     * captures a piece. Returns null if FROM-TO is not a valid move. */
    static Move mv(Square from, Square to, boolean capture) {
        if (from == null || to == null) {
            return null;
        }
        return _moves[from.index()][to.index()][capture ? 1 : 0];
    }
    /** Return a move from FROM to TO with isCapture() false. */
    static Move mv(Square from, Square to) {
        return mv(from, to, false);
    }
    /** Return the Square moved from. */
    Square getFrom() {
        return _from;
    }
    /** Return the Square moved to. */
    Square getTo() {
        return _to;
    }
    /** Return true if this Move is a capture. */
    boolean isCapture() {
        return _capture;
    }
    /** Return the same Move as this, with isCapture() true. */
    Move captureMove() {
        return _captureMove;
    }
    /** Return the length of this move (number of squares moved). */
    int length() {
        return _from.distance(_to);
    }
    @Override
    public String toString() {
        return String.format("%s-%s", getFrom(), getTo());
    }
    /** Construct a Move from FROM to TO, capturing iff CAPTURE. */
    private Move(Square from, Square to, boolean capture) {
        assert from.isValidMove(to);
        _from = from; _to = to;
        _capture = capture;
        _captureMove = _capture ? this : new Move(from, to, true);
    }
    /** Starting and destination Squares. */
    private final Square _from, _to;
    /** True iff this Move records a capture. */
    private final boolean _capture;
    /** When this is not a capture move, the Move with the same getFrom()
     * and getTo() as this, but with isCapture() true. */
    private final Move _captureMove;
    /** The set of all possible Moves, indexed by row and column of
     * start, row and column of destination, and whether Move denotes
     * a capture. */
    private static Move[][][] _moves = new Move[NUM_SQUARES][NUM_SQUARES][2];
    static {
        for (int c = 0; c < BOARD_SIZE; c += 1) {
            for (int r = 0; r < BOARD_SIZE; r += 1) {
                Square from = sq(c, r);
                int fromi = from.index();
                for (int dir = 0; dir < 8; dir += 1) {
                    for (Square to = from.moveDest(dir, 1); to != null;
                         to = to.moveDest(dir, 1)) {
                        int toi = to.index();
                        _moves[fromi][toi][0] = new Move(from, to, false);
                        _moves[fromi][toi][1]
                            = _moves[fromi][toi][0]._captureMove;
                    }
                }
            }
        }
    }
}
SQUARE
/* Skeleton Copyright (C) 2015, 2020 Paul N. Hilfinger and the Regents of the
 * University of California. All rights reserved. */
package loa;
import java.util.regex.Pattern;
import static loa.Utils.*;
/** Represents a position on a LOA board. Positions are indexed from
 * from (0, 0) (lower-left corner) to (BOARD_SIZE - 1, BOARD_SIZE - 1)
 * (upper-right). Squares are immutable and unique: there is precisely
 * one square created for each distinct position. Clients create squares
 * using the factory method sq, not the constructor. Because there is a
 * unique Square object for each position, you can freely use the
 * cheap == operator (rather than the .equals method) to compare Squares,
 * and the program does not waste time creating the same square over
 * and over again.
 * @author P. N. Hilfinger
 */
final class Square {
    /** The total number of possible rows or columns. */
    static final int BOARD_SIZE = 8;
    /** The total number of possible squares. */
    static final int NUM_SQUARES = BOARD_SIZE * BOARD_SIZE;
    /** The regular expression for a square designation (e.g.,
     * a3). For convenience, it is in parentheses to make it a
     * group. This subpattern may be incorporated into
     * other patterns that contain square designations (such as
     * patterns for moves) using SQ.pattern(). */
    static final Pattern SQ = Pattern.compile("([a-h][1-8])");
    /** Return my row position, where 0 is the bottom row. */
    int row() {
        return _row;
    }
    /** Return my column position, where 0 is the leftmost column. */
    int col() {
        return _col;
    }
    /** Return distance (number of squares) to OTHER. */
    int distance(Square other) {
        return Math.max(Math.abs(_row - other._row),
                        Math.abs(_col - other._col));
    }
    /** Return true iff THIS - TO is a valid move. */
    boolean isValidMove(Square to) {
        return this != to
            && (_row == to._row || _col == to._col
                || _row + _col == to._row + to._col
                || _row - _col == to._row - to._col);
    }
    /** Definitions of directions. DIR[k] = (dcol, drow)
     * means that to going one step from (col, row) in direction k,
     * brings us to (col + dcol, row + drow). */
    private static final int[][] DIR = {
        { 0, 1 }, { 1, 1 }, { 1, 0 }, { 1, -1 }, { 0, -1 },
        { -1, -1 }, { -1, 0 }, { -1, 1 }
    };
    /** The increments (delta column, delta row) from one Square to the
     * next indexed by direction. */
    private static final int[]
        DC = { 0, 1, 1, 1, 0, -1, -1, -1 },
        DR = { 1, 1, 0, -1, -1, -1, 0, 1 };
    /** Mapping of (dc + 1, dr + 1) to direction, where (dc, dr) are the
     * column and row displacements of an adjacent square. */
    private static final int[][] DISP_TO_DIR = {
        { 5, 6, 7 }, { 4, -1, 0 }, { 3, 2, 1 }
    };
    /** Return the Square that is STEPS>0 squares away from me in direction
     * DIR, or null if there is no such square.
     * DIR = 0 for north, 1 for north-east, 2 for east, etc., up to
     * 7 for north-west. If DIR has another value, return null. */
    Square moveDest(int dir, int steps) {
        if (dir < 0 || dir > 7 || steps <= 0) {
            return null;
        }
        int c = col() + DC[dir] * steps,
            r = row() + DR[dir] * steps;
        if (exists(c, r)) {
            return sq(c, r);
        } else {
            return null;
        }
    }
    /** Return the direction (an int as defined in the documentation
     * for moveDest) of the move THIS-TO. */
    int direction(Square to) {
        assert isValidMove(to);
        int dc = col() > to.col() ? 0 : col() == to.col() ? 1 : 2,
            dr = row() > to.row() ? 0 : row() == to.row() ? 1 : 2;
        return DISP_TO_DIR[dc][dr];
    }
    /** Return an array of all Squares adjacent to SQ. */
    Square[] adjacent() {
        return ADJACENT[index()];
    }
    @Override
    public String toString() {
        return _str;
    }
    /** Return true iff COL ROW is a legal square. */
    static boolean exists(int col, int row) {
        /* A useful trick: since char is an unsigned type, the value of
         * (char) x < V is the same as x >= 0 && x < V. */
        return (char) row < BOARD_SIZE && (char) col < BOARD_SIZE;
    }
    /** Return the (unique) Square denoting COL ROW. */
    static Square sq(int col, int row) {
        if (!exists(row, col)) {
            error(1, "row or column out of bounds");
        }
        return SQUARES[col][row];
    }
    /** Return the (unique) Square denoting the position in POSN, in the
     * standard text format for a square (e.g. a4). Return null if POSN
     * does not denote a valid square designation. */
    static Square sq(String posn) {
        if (SQ.matcher(posn).matches()) {
            return sq(posn.charAt(0) - 'a', posn.charAt(1) - '1');
        }
        return null;
    }
    /** The Square (COL, ROW). */
    private Square(int col, int row) {
        _row = row;
        _col = col;
        _str = String.format("%c%d", (char) ('a' + _col), 1 + _row);
    }
    @Override
    public boolean equals(Object other) {
        return super.equals(other);
    }
    /** Return a unique number between 0 and NUM_SQUARES-1, inclusive,
     * for this Square. All distinct squares have distinct index values.
     */
    int index() {
        return (_row << 3) + _col;
    }
    @Override
    public int hashCode() {
        return index();
    }
    /** The cache of all created squares, by row and column. */
    private static final Square[][] SQUARES =
        new Square[BOARD_SIZE][BOARD_SIZE];
    /** A list of all Squares on a board. */
    static final Square[] ALL_SQUARES = new Square[BOARD_SIZE * BOARD_SIZE];
    static {
        for (int c = 0; c < BOARD_SIZE; c += 1) {
            for (int r = 0; r < BOARD_SIZE; r += 1) {
                Square sq = new Square(c, r);
                ALL_SQUARES[sq.index()] = SQUARES[c][r] = sq;
            }
        }
    }
    /** A mapping of Square index s.index() to arrays of Squares adjacent to
     * Square s. */
    private static final Square[][] ADJACENT = new Square[ALL_SQUARES.length][];
    static {
        for (Square sq : ALL_SQUARES) {
            int cl = Math.max(0, sq.col() - 1),
                cr = Math.min(sq.col() + 1, BOARD_SIZE - 1),
                rl = Math.max(0, sq.row() - 1),
                rr = Math.min(sq.row() + 1, BOARD_SIZE - 1);
            ADJACENT[sq.index()] =
                new Square[(cr - cl + 1) * (rr - rl + 1) - 1];
            for (int k = 0, r = rl; r <= rr; r += 1) {
                for (int c = cl; c <= cr; c += 1) {
                    if (r == sq.row() && c == sq.col()) {
                        continue;
                    }
                    ADJACENT[sq.index()][k] = sq(c, r);
                    k += 1;
                }
            }
        }
    }
    /** My row and column. */
    private final int _row, _col;
    /** My String denotation. */
    private final String _str;
}
PIECE
/* Skeleton Copyright (C) 2015, 2020 Paul N. Hilfinger and the Regents of the
 * University of California. All rights reserved. */
package loa;
/** A Piece denotes the contents of a square, or identifies one side
 * (Black or White) of a game.
 * @author P. N. Hilfinger
 */
enum Piece {
    /** The names of the pieces. EMP indicates an empty square. The
     * arguments give names to the piece colors. */
    BP, WP, EMP;
    /** Returns the full name of this piece (black, white, or -). */
    String fullName() {
        switch (this) {
        case BP:
            return "black";
        case WP:
            return "white";
        default:
            return "-";
        }
    }
    /** Returns the one-character denotation of this piece on the standard
     * text display of a board. */
    String abbrev() {
        switch (this) {
        case BP:
            return "b";
        case WP:
            return "w";
        default:
            return "-";
        }
    }
    /** Return player (white or black piece) for which .fullName()
     * returns NAME. Also returns EMP for NAME=="". */
    static Piece playerValueOf(String name) {
        switch (name.toLowerCase()) {
        case "black":
            return BP;
        case "white":
            return WP;
        case "-": case "":
            return EMP;
        default:
            throw new IllegalArgumentException("piece name unknown");
        }
    }
    /** Returns Piece with my opposing color (null for EMP). */
    Piece opposite() {
        switch (this) {
        case BP:
            return WP;
        case WP:
            return BP;
        default:
            return null;
        }
    }
    /** The textual representation of this piece. */
    private String _fullName;
    /** The one-character abbreviation of this piece, used in printed
     * representations ot the board. */
    private String _abbrev;
}