mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-22 07:10:42 -08:00
Add some comments
This commit is contained in:
@@ -7,18 +7,26 @@ import java.util.Random;
|
||||
import java.util.function.Predicate;
|
||||
import java.text.NumberFormat;
|
||||
|
||||
|
||||
/* This class holds the game state and the game logic */
|
||||
public class Battle {
|
||||
|
||||
/* parameters of the game */
|
||||
private int seaSize;
|
||||
private int[] sizes;
|
||||
private int[] counts;
|
||||
|
||||
|
||||
/* The game setup - the ships and the sea */
|
||||
private ArrayList<Ship> ships;
|
||||
private Sea sea;
|
||||
|
||||
private int[] losses;
|
||||
private int hits;
|
||||
private int misses;
|
||||
/* game state counts */
|
||||
private int[] losses; // how many of each type of ship have been sunk
|
||||
private int hits; // how many hits the player has made
|
||||
private int misses; // how many misses the player has made
|
||||
|
||||
// Names of ships of each size. The game as written has ships of size 3, 4 and 5 but
|
||||
// can easily be modified. It makes no sense to have a ship of size zero though.
|
||||
private static String NAMES_BY_SIZE[] = {
|
||||
"error",
|
||||
"size1",
|
||||
@@ -27,10 +35,11 @@ public class Battle {
|
||||
"aircraft carrier",
|
||||
"size5" };
|
||||
|
||||
// Entrypoint
|
||||
public static void main(String args[]) {
|
||||
Battle game = new Battle(6,
|
||||
new int[] { 2, 3, 4 },
|
||||
new int[] { 2, 2, 2 });
|
||||
Battle game = new Battle(6, // Sea is 6 x 6 tiles
|
||||
new int[] { 2, 3, 4 }, // Ships are of sizes 2, 3, and 4
|
||||
new int[] { 2, 2, 2 }); // there are two ships of each size
|
||||
game.play();
|
||||
}
|
||||
|
||||
@@ -39,7 +48,7 @@ public class Battle {
|
||||
sizes = shipSizes;
|
||||
counts = shipCounts;
|
||||
|
||||
/* validate parameters */
|
||||
// validate parameters
|
||||
if (seaSize < 4) throw new RuntimeException("Sea Size " + seaSize + " invalid, must be at least 4");
|
||||
|
||||
for (int sz : sizes) {
|
||||
@@ -51,20 +60,25 @@ public class Battle {
|
||||
throw new RuntimeException("Ship counts must match");
|
||||
}
|
||||
|
||||
sea = new Sea(seaSize);
|
||||
ships = new ArrayList<Ship>();
|
||||
losses = new int[counts.length];
|
||||
// Initialize game state
|
||||
sea = new Sea(seaSize); // holds what ship if any occupies each tile
|
||||
ships = new ArrayList<Ship>(); // positions and states of all the ships
|
||||
losses = new int[counts.length]; // how many ships of each type have been sunk
|
||||
|
||||
// Build up the list of all the ships
|
||||
int shipNumber = 1;
|
||||
for (int type = 0; type < counts.length; ++type) {
|
||||
for (int i = 0; i < counts[i]; ++i) {
|
||||
ships.add(new Ship(shipNumber++, "Ship", sizes[type]));
|
||||
ships.add(new Ship(shipNumber++, sizes[type]));
|
||||
}
|
||||
}
|
||||
|
||||
// When we put the ships in the sea, we put the biggest ones in first, or they might
|
||||
// not fit
|
||||
ArrayList<Ship> largestFirst = new ArrayList<>(ships);
|
||||
Collections.sort(largestFirst, Comparator.comparingInt((Ship ship) -> ship.size()).reversed());
|
||||
|
||||
// place each ship into the sea
|
||||
for (Ship ship : largestFirst) {
|
||||
ship.placeRandom(sea);
|
||||
}
|
||||
@@ -79,11 +93,13 @@ public class Battle {
|
||||
System.out.println("Start game");
|
||||
Input input = new Input(seaSize);
|
||||
try {
|
||||
while (lost < ships.size()) {
|
||||
if (! input.readCoordinates()) {
|
||||
while (lost < ships.size()) { // the game continues while some ships remain unsunk
|
||||
if (! input.readCoordinates()) { // ... unless there is no more input from the user
|
||||
return;
|
||||
}
|
||||
|
||||
// The computer thinks of the sea as a grid of rows, from top to bottom.
|
||||
// However, the user will use X and Y coordinates, with Y going bottom to top
|
||||
int row = seaSize - input.y();
|
||||
int col = input.x() - 1;
|
||||
|
||||
@@ -104,6 +120,9 @@ public class Battle {
|
||||
ship.hit(col, row);
|
||||
++hits;
|
||||
System.out.println("A direct hit on ship number " + ship.id());
|
||||
|
||||
// If a ship was hit, we need to know whether it was sunk.
|
||||
// If so, tell the player and update our counts
|
||||
if (ship.isSunk()) {
|
||||
++lost;
|
||||
System.out.println("And you sunk it. Hurrah for the good guys.");
|
||||
@@ -142,6 +161,8 @@ public class Battle {
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
// This should not happen running from console, but java requires us to check for it
|
||||
System.err.println("System error.\n" + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,34 +4,41 @@ import java.util.Comparator;
|
||||
import java.util.Random;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/** A single ship, with its position and where it has been hit */
|
||||
class Ship {
|
||||
public static final int ORIENT_E=0;
|
||||
public static final int ORIENT_SE=1;
|
||||
public static final int ORIENT_S=2;
|
||||
public static final int ORIENT_SW=3;
|
||||
// These are the four directions that ships can be in
|
||||
public static final int ORIENT_E=0; // goes East from starting position
|
||||
public static final int ORIENT_SE=1; // goes SouthEast from starting position
|
||||
public static final int ORIENT_S=2; // goes South from starting position
|
||||
public static final int ORIENT_SW=3; // goes SouthWest from starting position
|
||||
|
||||
private int id;
|
||||
private int size;
|
||||
private String type;
|
||||
private boolean placed;
|
||||
private boolean sunk;
|
||||
private ArrayList<Boolean> hits;
|
||||
private int id; // ship number
|
||||
private int size; // how many tiles it occupies
|
||||
private boolean placed; // whether this ship is in the sea yet
|
||||
private boolean sunk; // whether this ship has been sunk
|
||||
private ArrayList<Boolean> hits; // which tiles of the ship have been hit
|
||||
|
||||
private int startX;
|
||||
private int startX; // starting position coordinates
|
||||
private int startY;
|
||||
private int orientX;
|
||||
private int orientX; // x and y deltas from each tile occupied to the next
|
||||
private int orientY;
|
||||
|
||||
public Ship(int i, String name, int sz) {
|
||||
id = i; type = name; size = sz;
|
||||
public Ship(int i, int sz) {
|
||||
id = i; size = sz;
|
||||
sunk = false; placed = false;
|
||||
hits = new ArrayList<>(Collections.nCopies(size, false));
|
||||
}
|
||||
|
||||
/** @returns the ship number */
|
||||
public int id() { return id; }
|
||||
/** @returns the ship size */
|
||||
public int size() { return size; }
|
||||
|
||||
/* record the ship as having been hit at the given coordinates */
|
||||
public void hit(int x, int y) {
|
||||
// need to work out how many tiles from the ship's starting position the hit is at
|
||||
// that can be worked out from the difference between the starting X coord and this one
|
||||
// unless the ship runs N-S, in which case use the Y coord instead
|
||||
int offset;
|
||||
if (orientX != 0) {
|
||||
offset = (x - startX) / orientX;
|
||||
@@ -40,11 +47,13 @@ class Ship {
|
||||
}
|
||||
hits.set(offset, true);
|
||||
|
||||
// if every tile of the ship has been hit, the ship is sunk
|
||||
sunk = hits.stream().allMatch(Predicate.isEqual(true));
|
||||
}
|
||||
|
||||
public boolean isSunk() { return sunk; }
|
||||
|
||||
// whether the ship has already been hit at the given coordinates
|
||||
public boolean wasHit(int x, int y) {
|
||||
int offset;
|
||||
if (orientX != 0) {
|
||||
@@ -55,6 +64,9 @@ class Ship {
|
||||
return hits.get(offset);
|
||||
};
|
||||
|
||||
// Place the ship in the sea.
|
||||
// choose a random starting position, and a random direction
|
||||
// if that doesn't fit, keep picking different positions and directions
|
||||
public void placeRandom(Sea s) {
|
||||
Random random = new Random();
|
||||
for (int tries = 0 ; tries < 1000 ; ++tries) {
|
||||
@@ -68,6 +80,77 @@ class Ship {
|
||||
throw new RuntimeException("Could not place any more ships");
|
||||
}
|
||||
|
||||
// Attempt to fit the ship into the sea, starting from a given position and
|
||||
// in a given direction
|
||||
// This is by far the most complicated part of the program.
|
||||
// It will start at the position provided, and attempt to occupy tiles in the
|
||||
// requested direction. If it does not fit, either because of the edge of the
|
||||
// sea, or because of ships already in place, it will try to extend the ship
|
||||
// in the opposite direction instead. If that is not possible, it fails.
|
||||
public boolean place(Sea s, int x, int y, int orient) {
|
||||
if (placed) {
|
||||
throw new RuntimeException("Program error - placed ship " + id + " twice");
|
||||
}
|
||||
switch(orient) {
|
||||
case ORIENT_E: // east is increasing X coordinate
|
||||
orientX = 1; orientY = 0;
|
||||
break;
|
||||
case ORIENT_SE: // southeast is increasing X and Y
|
||||
orientX = 1; orientY = 1;
|
||||
break;
|
||||
case ORIENT_S: // south is increasing Y
|
||||
orientX = 0; orientY = 1;
|
||||
break;
|
||||
case ORIENT_SW: // southwest is increasing Y but decreasing X
|
||||
orientX = -1; orientY = 1;
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Invalid orientation " + orient);
|
||||
}
|
||||
|
||||
if (!s.isEmpty(x, y)) return false; // starting position is occupied - placing fails
|
||||
|
||||
startX = x; startY = y;
|
||||
int tilesPlaced = 1;
|
||||
int nextX = startX;
|
||||
int nextY = startY;
|
||||
while (tilesPlaced < size) {
|
||||
if (extendShip(s, nextX, nextY, nextX + orientX, nextY + orientY)) {
|
||||
// It is clear to extend the ship forwards
|
||||
tilesPlaced += 1;
|
||||
nextX = nextX + orientX;
|
||||
nextY = nextY + orientY;
|
||||
} else {
|
||||
int backX = startX - orientX;
|
||||
int backY = startY - orientY;
|
||||
|
||||
if (extendShip(s, startX, startY, backX, backY)) {
|
||||
// We can move the ship backwards, so it can be one tile longer
|
||||
tilesPlaced +=1;
|
||||
startX = backX;
|
||||
startY = backY;
|
||||
} else {
|
||||
// Could not make it longer or move it backwards
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mark in the sea which tiles this ship occupies
|
||||
for (int i = 0; i < size; ++i) {
|
||||
int sx = startX + i * orientX;
|
||||
int sy = startY + i * orientY;
|
||||
s.set(sx, sy, id);
|
||||
}
|
||||
|
||||
placed = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check whether a ship which already occupies the "from" coordinates,
|
||||
// can also occupy the "to" coordinates.
|
||||
// They must be within the sea area, empty, and not cause the ship to cross
|
||||
// over another ship
|
||||
private boolean extendShip(Sea s, int fromX, int fromY, int toX, int toY) {
|
||||
if (!s.isEmpty(toX, toY)) return false; // no space
|
||||
if ((fromX == toX)||(fromY == toY)) return true; // horizontal or vertical
|
||||
@@ -84,59 +167,4 @@ class Ship {
|
||||
if ((corner1 == 0) || (corner1 != corner2)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean place(Sea s, int x, int y, int orient) {
|
||||
if (placed) {
|
||||
throw new RuntimeException("Program error - placed ship " + id + " twice");
|
||||
}
|
||||
switch(orient) {
|
||||
case ORIENT_E:
|
||||
orientX = 1; orientY = 0;
|
||||
break;
|
||||
case ORIENT_SE:
|
||||
orientX = 1; orientY = 1;
|
||||
break;
|
||||
case ORIENT_S:
|
||||
orientX = 0; orientY = 1;
|
||||
break;
|
||||
case ORIENT_SW:
|
||||
orientX = -1; orientY = 1;
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Invalid orientation " + orient);
|
||||
}
|
||||
|
||||
if (!s.isEmpty(x, y)) return false;
|
||||
startX = x; startY = y;
|
||||
int tilesPlaced = 1;
|
||||
int nextX = startX;
|
||||
int nextY = startY;
|
||||
while (tilesPlaced < size) {
|
||||
if (extendShip(s, nextX, nextY, nextX + orientX, nextY + orientY)) {
|
||||
tilesPlaced += 1;
|
||||
nextX = nextX + orientX;
|
||||
nextY = nextY + orientY;
|
||||
} else {
|
||||
int backX = startX - orientX;
|
||||
int backY = startY - orientY;
|
||||
|
||||
if (extendShip(s, startX, startY, backX, backY)) {
|
||||
tilesPlaced +=1;
|
||||
startX = backX;
|
||||
startY = backY;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < size; ++i) {
|
||||
int sx = startX + i * orientX;
|
||||
int sy = startY + i * orientY;
|
||||
s.set(sx, sy, id);
|
||||
}
|
||||
placed = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user