I have been given a problem statement to create a Battleship game in java.
My working code (Spring Boot+Web) is placed here along with the problem statement. https://github.com/ankidaemon/BattleShip
This question is majorly focused on design, please help me figure out, how can I make it decoupled and apply suitable design patterns.
StartGame.java - getting called from controller
@Component
public class StartGame {
private static final Logger logger = LoggerFactory.getLogger(StartGame.class);
public String init(File inputFile) throws FileNotFoundException, InputException {
// TODO Auto-generated method stub
ArrayList<BattleShips> p1s = new ArrayList<BattleShips>();
ArrayList<BattleShips> p2s = new ArrayList<BattleShips>();
int areaWidth = 0;
int areahight = 0;
ArrayList<Coordinate> player1missiles = null;
ArrayList<Coordinate> player2missiles = null;
try{
Scanner sc = new Scanner(inputFile);
areaWidth = sc.nextInt();
if(areaWidth>9 || areaWidth<1){
raiseException("Supplied area width is invalid.",sc);
}
areahight = sc.next().toUpperCase().charAt(0) - 64;
if(areahight>25 || areahight<0){
raiseException("Supplied area height is invalid.",sc);
}
sc.nextLine();
int noOfships = sc.nextInt();
if(noOfships>areahight*areaWidth || noOfships<1){
raiseException("Supplied no of ships is invalid.",sc);
}
sc.nextLine();
for (int j = 0; j < noOfships; j++) {
char typeOfShip = sc.next().toUpperCase().charAt(0);
if(typeOfShip!='P' && typeOfShip!='Q'){
raiseException("Supplied type of ship is invalid.",sc);
}
int shipWidth = sc.nextInt();
if(shipWidth>areaWidth || shipWidth<0){
raiseException("Supplied ship width is invalid.",sc);
}
int shiphight = sc.nextInt();
if(shiphight>areahight || shiphight<0){
raiseException("Supplied ship height is invalid.",sc);
}
BattleShips ship;
for (int i = 0; i <= 1; i++) {
char[] locCharArr = sc.next().toUpperCase().toCharArray();
int[] loc = new int[2];
loc[0] = locCharArr[0] - 65;
loc[1] = locCharArr[1] - 49;
if(loc[0]>areahight || loc[0]<0 || loc[1]>areaWidth || loc[1]<0){
raiseException("Supplied ship location is invalid.",sc);
}
ship = new BattleShips(shipWidth, shiphight, typeOfShip, loc);
if (i % 2 == 0)
p1s.add(ship);
else
p2s.add(ship);
}
sc.nextLine();
}
player1missiles = returnMissileCoordinates(sc.nextLine());
player2missiles = returnMissileCoordinates(sc.nextLine());
sc.close();
}catch(InputMismatchException e){
throw new InputException("Invalid Input supplied.",ErrorCode.INVALIDINPUT);
}
BattleArea player1 = new BattleArea("player1", areaWidth, areahight, p1s);
BattleArea player2 = new BattleArea("player2", areaWidth, areahight, p2s);
player1.placeShips();
player2.placeShips();
while (!player1.isLost() && !player2.isLost()) {
for (int i = 0; i < player1missiles.size();) {
Coordinate c = player1missiles.get(i);
while (player1.fireMissile(c, player2)) {
player1missiles.remove(i);
if (i < player1missiles.size()) {
c = player1missiles.get(i);
} else
break;
}
if (player1missiles.size() > 0) {
player1missiles.remove(i);
}
break;
}
for (int j = 0; j < player2missiles.size();) {
Coordinate c = player2missiles.get(j);
while (player2.fireMissile(c, player1)) {
player2missiles.remove(j);
if (j < player2missiles.size()) {
c = player2missiles.get(j);
} else
break;
}
if (player2missiles.size() > 0) {
player2missiles.remove(j);
}
break;
}
}
if (player1.isLost()) {
logger.info("-------------------------");
logger.info("Player 2 has Won the Game");
logger.info("-------------------------");
return "Player 2 has Won the Game";
} else {
logger.info("-------------------------");
logger.info("Player 1 has Won the Game");
logger.info("-------------------------");
return "Player 1 has Won the Game";
}
}
private static ArrayList<Coordinate> returnMissileCoordinates(String nextLine) {
// TODO Auto-generated method stub
ArrayList<Coordinate> tmp = new ArrayList<Coordinate>();
String[] arr = nextLine.split("\\ ");
Coordinate tmpC;
for (String s : arr) {
char[] charArr = s.toCharArray();
tmpC = new Coordinate(charArr[1] - 49, charArr[0] - 65);
tmp.add(tmpC);
}
return tmp;
}
private void raiseException(String message, Scanner sc) throws InputException {
sc.close();
throw new InputException(message, ErrorCode.INVALIDINPUT);
}
}
BattleArea.java
public class BattleArea {
private static final Logger logger = LoggerFactory.getLogger(BattleArea.class);
private String belongsTo;
private int width,height;
private ArrayList<BattleShips> battleShips;
private Set<Coordinate> occupied=new TreeSet<Coordinate>();
private int[][] board=null;
private boolean lost=false;
public BattleArea(String belongsTo, int width, int height, ArrayList<BattleShips> battleShips) {
super();
this.belongsTo = belongsTo;
this.width = width;
this.height = height;
this.battleShips = battleShips;
this.board=new int[this.width][this.height];
}
public void placeShips(){
for(BattleShips ship:this.battleShips){
int x=ship.getLocation()[1];
int y=ship.getLocation()[0];
if(ship.getWidth()+x>this.width || ship.getHeight()+y>this.height){
logger.error("Coordinate x-"+x+" y-"+y+" for "+this.belongsTo+" is not avilable.");
throw new ProhibitedException("Ship cannot be placed in this location.",ErrorCode.OUTOFBATTLEAREA);
}else{
Coordinate c=new Coordinate(x, y);
if(occupied.contains(c)){
logger.error("Coordinate x-"+c.getX()+" y-"+c.getY()+" for "+this.belongsTo+" is already occupied.");
throw new ProhibitedException("Ship cann't be placed in this location.",ErrorCode.ALREADYOCCUPIED);
}else{
Coordinate tempC;
for(int i=x;i<ship.getWidth()+x;i++){
for(int j=y;j<ship.getHeight()+y;j++){
logger.debug("Placing at x-"+i+" y-"+j+" for "+this.belongsTo);
tempC=new Coordinate(i, j);
occupied.add(tempC);
if(ship.getTypeOfShip()=='P'){
board[i][j]=1;
}else if(ship.getTypeOfShip()=='Q'){
board[i][j]=2;
}
}
}
}
}
}
}
public boolean fireMissile(Coordinate c, BattleArea enemyBattleArea){
int x=c.getX();
int y=c.getY();
logger.info("Firing at "+enemyBattleArea.belongsTo+" x-"+x+" y-"+y+" :");
if(enemyBattleArea.board[x][y]!=0){
if(enemyBattleArea.board[x][y]==-1){
logger.debug("Already blasted!");
return false;
}
else if(enemyBattleArea.board[x][y]==1){
Coordinate temp=new Coordinate(x,y);
enemyBattleArea.occupied.remove(temp);
enemyBattleArea.board[x][y]=-1;
if(enemyBattleArea.occupied.size()==0){
enemyBattleArea.setLost(true);
}
logger.debug("Suucessfully blasted!!");
return true;
}else{
enemyBattleArea.board[x][y]=enemyBattleArea.board[x][y]-1;
logger.debug("Half life left!!");
return true;
}
}else{
logger.debug("Missed");
return false;
}
}
public boolean isLost() {
return lost;
}
public void setLost(boolean lost) {
this.lost = lost;
}
}
BattleShips.java
public class BattleShips {
private int width,height;
private char typeOfShip;
private int[] location;
public BattleShips(int width, int height, char typeOfShip, int[] loc) {
super();
this.width = width;
this.height = height;
this.typeOfShip = typeOfShip;
this.location = loc;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public char getTypeOfShip() {
return typeOfShip;
}
public int[] getLocation() {
return location;
}
}
Coordinate.java
public class Coordinate implements Comparable<Coordinate> {
private int x,y;
public Coordinate(int x, int y) {
super();
this.x = x;
this.y = y;
}
@Override
public String toString() {
return "Coordinate [x=" + x + ", y=" + y + "]";
}
@Override
public int compareTo(Coordinate o) {
// TODO Auto-generated method stub
if(this.x==o.x && this.y==o.y)
return 0;
else if(this.x<o.x && this.y<o.y)
return -1;
else
return 1;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}
Sample Input
5 E
2
Q 1 1 A1 B2
P 2 1 D4 C3
A1 B2 B2 B3
A1 B2 B3 A1 D1 E1 D4 D4 D5 D5
Rules
1. Player1 will fire first. Each player will get another chance till ( hit == successful ).
2. Battleships will be placed horizontally.
3. Type-Q ship requires 2 missiles hit to get destroyed.
4. Type-P ship requires 1 missile hit to get destroyed.
Input
First line of the input contains dimensions of battle area having width and height separated by space.
Second line will have number (B) of battleships each player has.
Then in the next line battleship type, dimensions (width and height) & positions (Y coordinate and X coordinate) for Player-1 and then for Player-2 will be given separated by space.
And then in the next line Player-1’s sequence (separated by space) of missiles target location coordinates (Y and X) will be given and then for sequence for Player-2.
Constraints:
1 <= Width of Battle area (M) <= 9
A <= Height of Battle area (N) <= Z
1 <= Number of battleships <= M * N
Type of ship = {‘P’, ‘Q’}
1 <= Width of battleship <= M
A <= Height of battleship <= N
1 <= X coordinate of ship <= M
A <= Y coordinate of ship <= N