My Second take on this can be found here
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;
/**
* Initialize 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.BOARD_SIZE; i++) {
for(int j = 0; j < Constants.BOARD_SIZE; j++) {
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.length; i++) {
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.length; i++) {
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.getY(); i++) {
for(int j = (int) from.getX() - 1; j < to.getX(); j++) {
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.getY(); i++) {
for(int j = (int) from.getX() - 1; j < to.getX(); j++) {
board[i][j] = Constants.SHIP_ICON;
}
}
printBoard();
}
/**
* Print board.
*/
private void printBoard() {
System.out.print("\t");
for(int i = 0; i < Constants.BOARD_SIZE; i++) {
System.out.print(Constants.BOARD_LETTERS[i] + "\t");
}
System.out.println();
for(int i = 0; i < Constants.BOARD_SIZE; i++) {
System.out.print((i+1) + "\t");
for(int j = 0; j < Constants.BOARD_SIZE; j++) {
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<Point, Boolean> targetHistory;
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);
}
/**
* Gets id.
*
* @return the id
*/
public int getId() {
return id;
}
/**
* Gets 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;
}
/**
* Gets 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 double.
*
* @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 point between boolean.
*
* @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[1].getLives()) ?
players[1] :
players[0];
}
System.out.printf("Congrats Player %d, you won!",player.getId());
}
}
Main.java
package com.tn;
import com.tn.game.Game;
public class Main {
public static void main(String[] args) {
Game game = new Game();
game.start();
}
}