Give the details of the battleship game java.
I wanted to make a simple console game in order to practice OOP. I would really appreciate a review that looks at readability, maintenance, and best practices.
What annoys me a little bit with this code is I don't use interfaces, abstract classes, or inheritance, but I couldn't find a good use case for them here.
Board.java
package com.tn.board;
import com.tn.constants.Constants;
import com.tn.ship.Ship;
import com.tn.utils.Position;
import com.tn.utils.Utils;
import java.awt.Point;
import java.util.Scanner;
public class Board {
private static final Ship[] ships;
private char[][] board;
/**
* Initialise ships (once).
*
*/
static {
ships = new Ship[]{
new Ship("Carrier", Constants.CARRIER_SIZE),
new Ship("Battleship", Constants.BATTLESHIP_SIZE),
new Ship("Cruiser", Constants.CRUISER_SIZE),
new Ship("Submarine", Constants.SUBMARINE_SIZE),
new Ship("Destroyer", Constants.DESTROYER_SIZE)
};
}
/**
* Constructor
*/
public Board() {
board = new char[Constants.BOARD_SIZE][Constants.BOARD_SIZE];
for(int i = 0; i < Constants> for(int j = 0; j < Constants> board[i][j] = Constants.BOARD_ICON;
}
}
placeShipsOnBoard();
}
/**
* Target ship ship.
*
* @param point the point
* @return ship
*/
public Ship targetShip(Point point) {
boolean isHit = false;
Ship hitShip = null;
for(int i = 0; i < ships>
Ship ship = ships[i];
if(ship.getPosition() != null) {
if(Utils.isPointBetween(point, ship.getPosition())) {
isHit = true;
hitShip = ship;
break;
}
}
}
final char result = isHit ? Constants.SHIP_IS_HIT_ICON : Constants.SHOT_MISSED_ICON;
updateShipOnBoard(point, result);
printBoard();
return (isHit) ? hitShip : null;
}
/**
* Place ships on board.
*/
private void placeShipsOnBoard() {
System.out.printf("%nAlright - Time to place out your ships%n%n");
Scanner s = new Scanner(System.in);
for(int i = 0; i < ships>
Ship ship = ships[i];
boolean isShipPlacementLegal = false;
System.out.printf("%nEnter position of %s (length %d): ", ship.getName(), ship.getSize());
while(!isShipPlacementLegal) {
try {
Point from = new Point(s.nextInt(), s.nextInt());
Point to = new Point(s.nextInt(), s.nextInt());
while(ship.getSize() != Utils.distanceBetweenPoints(from, to)) {
System.out.printf("The ship currently being placed on the board is of length: %d. Change your coordinates and try again",
ship.getSize());
from = new Point(s.nextInt(), s.nextInt());
to = new Point(s.nextInt(), s.nextInt());
}
Position position = new Position(from, to);
if(!isPositionOccupied(position)) {
drawShipOnBoard(position);
ship.setPosition(position);
isShipPlacementLegal = true;
} else {
System.out.println("A ship in that position already exists - try again");
}
} catch(IndexOutOfBoundsException e) {
System.out.println("Invalid coordinates - Outside board");
}
}
}
}
private void updateShipOnBoard(Point point, final char result) {
int x = (int) point.getX() - 1;
int y = (int) point.getY() - 1;
board[y][x] = result;
}
/**
*
* @param position
* @return
*/
private boolean isPositionOccupied(Position position) {
boolean isOccupied = false;
Point from = position.getFrom();
Point to = position.getTo();
outer:
for(int i = (int) from.getY() - 1; i < to>
for(int j = (int) from.getX() - 1; j < to>
if(board[i][j] == Constants.SHIP_ICON) {
isOccupied = true;
break outer;
}
}
}
return isOccupied;
}
/**
*
* @param position
*/
private void drawShipOnBoard(Position position) {
Point from = position.getFrom();
Point to = position.getTo();
for(int i = (int) from.getY() - 1; i < to>
for(int j = (int) from.getX() - 1; j < to>
board[i][j] = Constants.SHIP_ICON;
}
}
printBoard();
}
/**
* Print board.
*/
private void printBoard() {
System.out.print("t");
for(int i = 0; i < Constants>
System.out.print(Constants.BOARD_LETTERS[i] + "t");
}
System.out.println();
for(int i = 0; i < Constants>
System.out.print((i+1) + "t");
for(int j = 0; j < Constants>
System.out.print(board[i][j] + "t");
}
System.out.println();
}
}
}
Constants.java
package com.tn.constants;
public class Constants {
private Constants() {}
public static final int PLAYER_LIVES = 17; //sum of all the ships
public static final int CARRIER_SIZE = 5;
public static final int BATTLESHIP_SIZE = 4;
public static final int CRUISER_SIZE = 3;
public static final int SUBMARINE_SIZE = 3;
public static final int DESTROYER_SIZE = 2;
public static final char SHIP_ICON = 'X';
public static final char BOARD_ICON = '-';
public static final char SHIP_IS_HIT_ICON = 'O';
public static final char SHOT_MISSED_ICON = 'M';
public static final char[] BOARD_LETTERS = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'};
public static final int BOARD_SIZE = 10;
}
Player.java
package com.tn.player;
import com.tn.board.Board;
import com.tn.constants.Constants;
import com.tn.ship.Ship;
import java.awt.Point;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class Player {
private int id;
private int lives;
private Board board;
private Map
private Scanner scanner;
/**
* Instantiates a new Player.
*
* @param id the id
*/
public Player(int id) {
System.out.printf("%n=== Setting up everything for Player %s ====", id);
this.id = id;
this.lives = Constants.PLAYER_LIVES;
this.board = new Board();
this.targetHistory = new HashMap<>();
this.scanner = new Scanner(System.in);
}
/**
* Get id.
*
* @return the id
*/
public int getId() {
return id;
}
/**
* Get lives.
*
* @return the lives
*/
public int getLives() {
return lives;
}
/**
* Decrement live by one.
*/
public void decrementLiveByOne() {
lives--;
}
/**
* Turn to play.
*
* @param opponent the opponent
*/
public void turnToPlay(Player opponent) {
System.out.printf("%n%nPlayer %d, Choose coordinates you want to hit (x y) ", id);
Point point = new Point(scanner.nextInt(), scanner.nextInt());
while(targetHistory.get(point) != null) {
System.out.print("This position has already been tried");
point = new Point(scanner.nextInt(), scanner.nextInt());
}
attack(point, opponent);
}
/**
* Attack
*
* @param point
* @param opponent
*/
private void attack(Point point, Player opponent) {
Ship ship = opponent.board.targetShip(point);
boolean isShipHit = (ship != null) ? true : false;
if(isShipHit) {
ship.shipWasHit();
opponent.decrementLiveByOne();
}
targetHistory.put(point, isShipHit);
System.out.printf("Player %d, targets (%d, %d)",
id,
(int)point.getX(),
(int)point.getY());
System.out.println("...and " + ((isShipHit) ? "HITS!" : "misses..."));
}
}
Ship.java
package com.tn.ship;
import com.tn.utils.Position;
}
Player.java
package com.tn.player;
import com.tn.board.Board;
import com.tn.constants.Constants;
import com.tn.ship.Ship;
import java.awt.Point;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class Player {
private int id;
private int lives;
private Board board;
private Map
private Scanner scanner;
/**
* Instantiates a new Player.
*
* @param id the id
*/
public Player(int id) {
System.out.printf("%n=== Setting up everything for Player %s ====", id);
this.id = id;
this.lives = Constants.PLAYER_LIVES;
this.board = new Board();
this.targetHistory = new HashMap<>();
this.scanner = new Scanner(System.in);
}
/**
* Get id.
*
* @return the id
*/
public int getId() {
return id;
}
/**
* Get lives.
*
* @return the lives
*/
public int getLives() {
return lives;
}
/**
* Decrement live by one.
*/
public void decrementLiveByOne() {
lives--;
}
/**
* Turn to play.
*
* @param opponent the opponent
*/
public void turnToPlay(Player opponent) {
System.out.printf("%n%nPlayer %d, Choose coordinates you want to hit (x y) ", id);
Point point = new Point(scanner.nextInt(), scanner.nextInt());
while(targetHistory.get(point) != null) {
System.out.print("This position has already been tried");
point = new Point(scanner.nextInt(), scanner.nextInt());
}
attack(point, opponent);
}
/**
* Attack
*
* @param point
* @param opponent
*/
private void attack(Point point, Player opponent) {
Ship ship = opponent.board.targetShip(point);
boolean isShipHit = (ship != null) ? true : false;
if(isShipHit) {
ship.shipWasHit();
opponent.decrementLiveByOne();
}
targetHistory.put(point, isShipHit);
System.out.printf("Player %d, targets (%d, %d)",
id,
(int)point.getX(),
(int)point.getY());
System.out.println("...and " + ((isShipHit) ? "HITS!" : "misses..."));
}
}
Ship.java
package com.tn.ship;
import com.tn.utils.Position;
public class Ship {
private String name;
private int size;
private int livesLeft;
private boolean isSunk;
private Position position;
public Ship(String name, int size) {
this.name = name;
this.size = size;
this.livesLeft = size;
this.isSunk = false;
}
public String getName() {
return name;
}
public int getSize() {
return size;
}
public int getLivesLeft() {
return livesLeft;
}
public boolean isSunk() {
return isSunk;
}
public void setSunk(boolean sunk) {
isSunk = sunk;
}
public Position getPosition() {
return position;
}
public void setPosition(Position position) {
this.position = position;
}
public void shipWasHit() {
if(livesLeft == 0) {
isSunk = true;
System.out.println("You sunk the " + name);
return;
}
livesLeft--;
}
}
Position.java
package com.tn.utils;
import com.tn.constants.Constants;
import java.awt.Point;
public class Position {
private Point from;
private Point to;
/**
* Instantiates a new Position.
*
* @param from the from
* @param to the to
*/
public Position(Point from, Point to) {
if(from.getX() > Constants.BOARD_SIZE || from.getX() < 0> || from.getY() > Constants.BOARD_SIZE || from.getY() < 0> || to.getX() > Constants.BOARD_SIZE || to.getX() < 0> || to.getY() > Constants.BOARD_SIZE || to.getY() < 0> throw new ArrayIndexOutOfBoundsException();
}
this.from = from;
this.to = to;
}
/**
* Gets from.
*
* @return the from
*/
public Point getFrom() {
return from;
}
/**
* Get to.
*
* @return the to
*/
public Point getTo() {
return to;
}
}
Utils.java
package com.tn.utils;
import java.awt.Point;
public class Utils {
private Utils() {
}
/**
* Distance between points doubles.
*
* @param from the from
* @param to the to
* @return the double
*/
public static double distanceBetweenPoints(Point from, Point to) {
double x1 = from.getX();
double y1 = from.getY();
double x2 = to.getX();
double y2 = to.getY();
return Math.sqrt(Math.pow(x1-x2, 2) + Math.pow(y1-y2, 2)) + 1;
}
/**
* Is the point between booleans.
*
* @param point the point
* @param position the position
* @return the boolean
*/
public static boolean isPointBetween(Point point, Position position) {
Point from = position.getFrom();
Point to = position.getTo();
return from.getY() <= point.getY()
&& to.getY() >= point.getY()
&& from.getX() <= point.getX()
&& to.getX() >= point.getX();
}
}
Game.java
package com.tn.game;
import com.tn.player.Player;
public class Game {
private Player[] players;
/**
* Instantiates a new Game.
*/
public Game() {
players = new Player[]{
new Player(1),
new Player(2)
};
}
/**
* Start.
*/
public void start() {
int i = 0;
int j = 1;
int size = players.length;
Player player = null;
while(players[0].getLives() > 0 && players[1].getLives() > 0) {
players[i++ % size].turnToPlay(players[j++ % size]);
player = (players[0].getLives() < players>
players[1] :
players[0];
}
System.out.printf("Congrats Player %d, you won!",player.getId());
}
}
Main.java
package .tn;
import com.tn.game.Game;
public class Main {
public static void main(String[] args) {
Game game = new Game();
game.start();
}
}
Regarding the battleship game java, it looks to me that you're using exceptions to somehow perform flow control. Exceptions are not control flow mechanisms.
public Position(Point from, Point to) { if (from.getX() > Constants.BOARD_SIZE || from.getX() < 0>
|| from.getY() > Constants.BOARD_SIZE || from.getY() < 0> Constants.BOARD_SIZE || to.getX() < 0> Constants.BOARD_SIZE || to.getY() < 0 xss=removed xss=removed>
And then
} catch (IndexOutOfBoundsException e) {
System.out.println("Invalid coordinates - Outside board");
}
I think you should validate that the given coordinates are inside the board before trying to create the Position. This is user input and is perfectly reasonable to do it. You're already validating that ship.getSize() != Utils.distanceBetweenPoints(from, to). You would even be doing that in the Board object itself, instead of then having Position checking for Constants.BOARD_SIZE.