Snake/src/SnakePkg/Board.java
2018-11-06 10:35:26 +02:00

550 lines
19 KiB
Java
Executable File

package SnakePkg;
import java.lang.Math;
/**
* @class Board
* @brief The game's board representation.
*
* The board in a square collection of tiles numbered in a
* boustrophedon (zig-zag) way. A number of snakes, ladders
* and apples which called elements are placed on the board
* for each game.
*
* @author Christos Choutouridis 8997
* @email cchoutou@ece.auth.gr
*/
public class Board {
/** Constants */
/**@{ */
static final int POINTS_MAX = 20; /**< The maximum absolute number of points for each apple */
static final int POINTS_STEP = 5; /**< The difference between different apple points */
/**@} */
/** @name Constructors */
/** @{ */
/** A doing nothing default constructor
* @warining Avoid using this constructor as it requires all setters(or copy)
* and @ref createBoard() to be called after.
*/
Board () {
N = M =0;
tiles = null;
snakes = null;
ladders = null;
apples = null;
}
/**
* @brief Creates a board for game
*
* This constructor allocates the memory for the board and elements and
* creates a board by placing all required elements on the board.
*
* @param N The row for the board
* @param M The columns of the board
* @param numOfSnakes Number of snakes to place
* @param numOfLadders Number of ladders to place
* @param numOfApples Number of Apples to place
*
* @warning
* We call @ref createBoard() inside this constructor in order for
* the board to be in "playable condition". This is preferable by the author.
* A constructor should(if possible) to leave the object in a usable condition.
* In order to follow the project requirements we create this functionality in a
* separate function @ref createBoard(). We believe that if a user can make a
* mistake he eventually will do it sometime. Here, if we leave the createBoard()
* call to user we are enabling him to make it.
*/
Board (int N, int M, int numOfSnakes, int numOfLadders, int numOfApples) {
// Init the board object
setN (N); // Input checked version (may throw)
setM (M); // Input checked version (may throw)
tiles = new int[N][M];
snakes = new Snake[numOfSnakes];
ladders = new Ladder[numOfLadders];
apples = new Apple[numOfApples];
createBoard (); // Complete board preparation and make all the element memory allocations
}
/**
* @brief Copy constructor.
* We make a deep copy of B and we trust B's data to be valid.
* @param B The board we want to copy
* @note We don't use clone as long as we don't inherit Cloneable iface
* @note This requires Snake, Apple and Ladder copy constructors
*/
Board (Board B) {
N = B.getN();
M = B.getM();
tiles = new int[N][M];
snakes = new Snake[B.getSnakes().length];
ladders = new Ladder[B.getLadders().length];
apples = new Apple[B.getApples().length];
// Copy B's guts into new memory
setTiles(B.getTiles()); // primitive
copySnakes(B.getSnakes());
copyLadders(B.getLadders());
copyApples(B.getApples());
}
/** @} */
/** @name Get/Set interface */
/** @{ */
/** Get value N */
int getN () { return N; }
/** Set value N */
void setN (int N) { this.N = N; }
/** Get value M */
int getM () { return M; }
/** Set value M */
void setM (int M) { this.M = M; }
/** Get reference to tiles */
int[][] getTiles () { return tiles; }
/**
* Set tiles
* @param tiles Source of tiles to use
* @note This has to be called if the board is default constructed
*/
void setTiles (int[][] tiles) {
// Check if we need allocation
if (this.tiles == null)
this.tiles = new int[N][M];
// Assign values
for (int i =0 ; i<N ; ++i)
for (int j =0 ; j<M ; ++j)
this.tiles[i][j] = tiles[i][j];
}
/** Get reference to snakes */
Snake[] getSnakes() { return snakes; }
/**
* Set snakes
* @param snakes Reference to snakes to use
* @note This requires snakes must be allocated elsewhere.
*/
void setSnakes(Snake[] snakes) { this.snakes = snakes; }
/** Get reference to ladders */
Ladder[] getLadders() { return ladders; }
/**
* Set ladders
* @param ladders Reference to ladders to use
* @note This requires ladders must be allocated elsewhere.
*/
void setLadders(Ladder[] ladders) { this.ladders = ladders; }
/** Get reference to apples */
Apple[] getApples() { return apples; }
/**
* Set apples
* @param apples Reference to apples to use
* @note This requires apples must be allocated elsewhere.
*/
void setApples(Apple[] apples) { this.apples = apples; }
/**
* Copy snakes (deep copy)
* @param snakes Source of snakes to use
* @note Requires Snake copy constructor
* @note This has to be called if the board is default constructed
*/
void copySnakes(Snake[] snakes) {
// Check if we need allocation
if (this.snakes == null)
this.snakes = new Snake[snakes.length];
// Assign values (deep copy)
for (int i =0 ; i<this.snakes.length ; ++i)
this.snakes[i] = new Snake(snakes[i]);
}
/**
* Copy ladders (deep copy)
* @param ladders Source of ladders to use
* @note Requires Ladder copy constructor
* @note This has to be called if the board is default constructed
*/
void copyLadders (Ladder[] ladders) {
// Check if we need allocation
if (this.ladders == null)
this.ladders = new Ladder[ladders.length];
// Assign values (deep copy)
for (int i =0 ; i<this.ladders.length ; ++i)
this.ladders[i] = new Ladder(ladders[i]);
}
/**
* Copy apples (deep copy)
* @param apples Source of apples to use
* @note Requires Apple copy constructor
* @note This has to be called if the board is default constructed
*/
void copyApples (Apple[] apples) {
// Check if we need allocation
if (this.apples == null)
this.apples = new Apple[apples.length];
// Assign values (deep copy)
for (int i =0 ; i<this.apples.length ; ++i)
this.apples[i] = new Apple(apples[i]);
}
/** @} */
/** @name Exposed API members */
/** @{ */
/**
* Check if the tile is a snake head. If so return the snake's
* tails tile. If not return the same tile
* @param tile The tile to check
* @return The result tile
*/
int checkSnake (int tile) {
for (int i =0 ; i<snakes.length ; ++i) {
if (snakes[i].getHeadId() == tile)
return snakes[i].getTailId();
}
return tile;
}
/**
* Check if the tile is a ladder down step. If so return the ladder's
* up step tile. If not return the same tile.
* @note
* This also break the ladder if used
* @param tile The tile to check
* @return The result tile
*/
int checkLadder (int tile) {
for (int i =0 ; i<ladders.length ; ++i) {
if (ladders[i].getDownStepId() == tile &&
ladders[i].getBroken() == false) {
ladders[i].setBroken(true);
return ladders[i].getUpStepId();
}
}
return tile;
}
/**
* Check if the tile is an apple tile. If so eat it and return the score difference
* @param tile The tile to check
* @return The score difference
*/
int checkApple (int tile) {
int ds =0; // delta-score
for (int i =0 ; i<apples.length ; ++i) {
if (apples[i].getAppleTileId() == tile) {
// eat it
ds = apples[i].getPoints();
apples[i].setPoints(0);
}
}
return ds;
}
/**
* Create a playable board for the game.
* @warning
* This is not required to be called after construction in order to ensure board's playable
* condition. In fact this function SHOULD NOT CALLED AT ALL.
* The project requirements expect this to be public. The preferable mode would be private.
* @see Board() constructor.
*/
void createBoard () {
_tileNumbering (); // Create tile numbering
_placeSnakes (); // Place snakes
_placeApples (); // Place Apples
_placeLadders (); // place ladders
}
/**
* @brief
* make and print element boards
* This is not required in order for the board to be playable
* It just produce a stdout output for convenience.
*/
void createElementBoard () {
String[][] elementBoardSnakes = new String[N][M];
String[][] elementBoardLadders = new String[N][M];
String[][] elementBoardApples = new String[N][M];
_makeElementSnakes (elementBoardSnakes);
_makeElementLadders (elementBoardLadders);
_makeElementApples (elementBoardApples);
_printElement (elementBoardSnakes, "elementBoardSnakes");
_printElement (elementBoardLadders, "elementBoardLadders");
_printElement (elementBoardApples, "elementBoardApples");
}
/** @} */
/** @name Private api */
/**@{ */
/**
* @brief
* Create the tile numbering in a boustrophedon (zig-zag) way.
* We use a starting point the tile[0][0] and as finish point
* the tile[N-1][M-1]
*/
private void _tileNumbering () {
for (int i=0, tile =1 ; i<N ; ++i) {
if (i%2 == 0) {
// Even row, go right
for (int j=0 ; j<M ; ++j)
tiles[i][j] = tile++;
}
else {
// Odd row, go left
for (int j=M-1 ; j>=0 ; --j)
tiles[i][j] = tile++;
}
}
}
/**
* @brief
* Place the snakes on the board
* The only constrain at this point is that snake tails must be placed
* below heads and heads must be placed in separate tiles
*/
private void _placeSnakes () {
int [] head = new int [snakes.length]; // temporary place holder for heads
int [] tail = new int [snakes.length]; // temporary place holder for tails
for (int i =0, tile =0 ; i<snakes.length ; ++i) {
// Keep getting heads until they are different from the previous
do
tile = _pickRandom (M+1, M*N); // Don't use first row for heads
while (_search (head, tile) >= 0);
head[i] = tile;
tail[i] = _pickRandom (1, head[i]-head[i]%M); // Don't use heads row and up for tail
snakes[i] = new Snake(i, head[i], tail[i]); // Allocate snake
}
}
/**
* @brief
* Place apples on the board
* The constrains here are
* that apples have to lie on different tiles and not in some
* snake's head.
* @note We require we have snakes.
*/
private void _placeApples () {
int [] apple_tiles = new int [apples.length]; // temporary placeholder for apples
int [] snake_tiles = new int [snakes.length]; // array with snake head tiles
for (int i =0 ; i<snakes.length ; ++i) // Load snake head tiles
snake_tiles[i] = snakes[i].getHeadId();
for (int i =0, tile =0 ; i<apples.length ; ++i) {
// Keep getting tiles until they are different from the previous
// and not in some snake's head
do
tile = _pickRandom (1, M*N);
while ((_search (apple_tiles, tile) >= 0) ||
(_search (snake_tiles, tile) >= 0));
apple_tiles[i] = tile;
// get points
int points = _pickRandom (1, (POINTS_MAX/POINTS_STEP)) * POINTS_STEP;
boolean red = (boolean)(Math.random() >=0.5); // get color
// Allocate apple
if (red)
apples[i] = new Apple(i, tile, "red", points);
else
apples[i] = new Apple(i, tile, "black", -points);
}
}
/**
* @brief
* Place ladders on board
*
* We add constrains so each ladder's up-step tile has to be different from:
* * A snake's head tile. This ensures ladders and snakes are independent
* * A ladders's down-step. This ensure we eliminate ladder chains.
* * One other ladder's up-step. This is not critical but helps the printElement functionality
*
* We add constrains so each ladder's down-step tile has to be different from:
* * A snake's head tile. This ensures ladders and snakes are independent
* * A ladders's down-step. This is not critical but helps the printElement functionality
* * One other ladder's up-step. This ensure we eliminate ladder chains.
* @note We require we have snakes.
*/
private void _placeLadders () {
int [] up_step = new int [ladders.length]; // temporary place holder for up-steps
int [] down_step = new int [ladders.length]; // temporary place holder for down-step
int [] snake_tiles= new int [snakes.length]; // array with snake head tiles
for (int i =0 ; i<snakes.length ; ++i) // Load snake head tiles
snake_tiles[i] = snakes[i].getHeadId();
for (int i =0, tile =0 ; i<ladders.length ; ++i) {
// Keep getting up-steps until they are different from the previous ladder tiles
// and not in some snake's head
do
tile = _pickRandom (M+1, M*N); // Don't use first row for up-steps
while ((_search (up_step, tile) >= 0)
|| (_search (down_step, tile) >= 0)
|| (_search (snake_tiles, tile) >= 0));
up_step[i] = tile;
// Keep getting down-steps until they are different from the previous ladder tiles
// and not in some snake's head
do
// Don't use up-step row and up for down-step
tile = _pickRandom (1, up_step[i]-up_step[i]%M);
while ((_search (up_step, tile) >= 0)
|| (_search (down_step, tile) >= 0)
|| (_search (snake_tiles, tile) >= 0));
down_step[i] = tile;
ladders[i] = new Ladder (i, up_step[i], down_step[i]); // Allocate ladder
}
}
/**
* Make element array of snakes as required by the project
* @param elemSnakes
*/
private void _makeElementSnakes (String[][] elemSnakes) {
int [] head_tiles = new int [snakes.length]; // array with snake head tiles
int [] tail_tiles = new int [snakes.length]; // array with snake head tiles
int sn =-1;
// Load snake head tiles
for (int i =0 ; i<snakes.length ; ++i) {
head_tiles[i] = snakes[i].getHeadId();
tail_tiles[i] = snakes[i].getTailId();
}
// Search all tiles for snake heads and tails
for (int i =0; i<N ; ++i) {
for (int j =0 ; j<M ; ++j) {
if ((sn = _search (head_tiles, tiles[i][j])) >= 0)
elemSnakes[i][j] = "SH" + sn;
else if ((sn = _search (tail_tiles, tiles[i][j])) >= 0)
elemSnakes[i][j] = "ST" + sn;
else
elemSnakes[i][j] = "___";
}
}
}
/**
* Make element array of ladders as required by the project
* @param elemLadders
*/
private void _makeElementLadders (String[][] elemLadders) {
int [] up_tiles = new int [ladders.length]; // array with ladder up-step tiles
int [] down_tiles = new int [ladders.length]; // array with ladder down-step tiles
int sn =-1;
// Load ladder tiles
for (int i =0 ; i<ladders.length ; ++i) {
up_tiles[i] = ladders[i].getUpStepId();
down_tiles[i] = ladders[i].getDownStepId();
}
// Search all tiles for snake heads and tails
for (int i =0; i<N ; ++i) {
for (int j =0 ; j<M ; ++j) {
if ((sn = _search (up_tiles, tiles[i][j])) >= 0)
elemLadders[i][j] = "LU" + sn;
else if ((sn = _search (down_tiles, tiles[i][j])) >= 0)
elemLadders[i][j] = "LD" + sn;
else
elemLadders[i][j] = "___";
}
}
}
/**
* Make element array of apples as required by the project
* @param elemApples
*/
private void _makeElementApples (String[][] elemApples) {
int [] red_tiles = new int [apples.length]; // array with red apple tiles
int [] black_tiles = new int [apples.length]; // array with black apple tiles
int sn =-1;
// Load apple tiles
for (int i =0 ; i<apples.length ; ++i) {
if (apples[i].getColor() == "red")
red_tiles[i] = apples[i].getAppleTileId();
else
black_tiles[i] = apples[i].getAppleTileId();
}
// Search all tiles for snake heads and tails
for (int i =0; i<N ; ++i) {
for (int j =0 ; j<M ; ++j) {
if ((sn = _search (red_tiles, tiles[i][j])) >= 0)
elemApples[i][j] = "AR" + sn;
else if ((sn = _search (black_tiles, tiles[i][j])) >= 0)
elemApples[i][j] = "AB" + sn;
else
elemApples[i][j] = "___";
}
}
}
/**
* Print element array
* @param elem The element array to print
* @param caption The caption
* @note
* As long as we use tiles[0][0] for first tile, this method
* has to print in reverse Y-axis order. For ex:
* <pre>
* 16 15 14 13
* 09 10 11 12
* 08 07 06 05
* 01 02 03 04
* </pre>
*/
private void _printElement (String[][] elem, String caption) {
System.out.print(caption);
for (int i=N-1 ; i>=0 ; --i) {
System.out.println("");
System.out.print(" ");
for (int j =0 ; j<M ; ++j)
System.out.print(elem[i][j] + " ");
}
System.out.println("");
System.out.println("");
}
/**
* Pick a random tile in range [from..to]
* @param from The first tile to consider
* @param to The last tile to consider
* @return The random pick
*/
private int _pickRandom (int from, int to) {
return from + (int)(Math.random() * (to - from));
}
/** Search algorithm
* @param array Array to search
* @param elem Element of type T to find inside of array
* @return The status of the operation
* @arg -1 Element not found
* @arg >=0 Element found
*/
private int _search (int[] array, int elem) {
for (int i=0 ; i<array.length ; ++i)
if (elem == array[i])
return i;
return -1;
}
/**@} */
/** @name Data members */
/** @{ */
private int N; /**< Board's row count */
private int M; /**< Board's Column count */
private int[][] tiles; /**< Board's tiles */
private Snake[] snakes; /**< Board's snakes */
private Ladder[] ladders; /**< Board's ladders */
private Apple[] apples; /**< Board's apples */
/** @} */
}