A console Battleship game. The player places ships on a grid, the two grids are kept as separate models, and every placement and shooting rule is enforced by the grid classes rather than by the console code.
Written for the Advanced Programming Techniques course. The exercise was about splitting a small game into classes that each own one responsibility, and testing them.
Board
├── OwnGrid ships, placement rules, incoming shots
└── OpponentGrid what the player knows about the enemy grid
OwnGrid std::vector<Ship>, std::map<int,int> remaining per length,
std::set<GridPosition> already shot at
Ship length and the positions it covers
Shot a target position and its Impact: NONE, HIT, SUNKEN
GridPosition a column letter and a row number, comparable so it can go in a set
ConsoleView draws both grids and reads user input
Board is only the data model. It holds the two grids and knows nothing about output.
ConsoleView is the only class that uses std::cout and std::cin, so the game logic
can be tested without a console.
Two grids, not one. OwnGrid knows where the ships really are.
OpponentGrid only knows what the player has found out by shooting. Keeping them as
separate types makes it impossible to accidentally read information the player should
not have, which a single grid with a visibility flag would allow.
GridPosition is ordered on purpose. It defines a comparison so it can be a key in
std::set and std::map. OwnGrid keeps a std::set<GridPosition> m_shotAt of every
position already fired at, which is what stops the same hit from being counted twice and
what makes it possible to decide whether a ship is completely destroyed.
Placement rules live in OwnGrid::placeShip. It checks the grid limits, the overlap
with ships already placed, and how many ships of that length are still allowed, using
std::map<int,int> m_remainingShips. The caller only gets a bool back, so no rule can
be bypassed from outside.
One return value carries the whole result of a shot. takeBlow returns
Shot::Impact, which is NONE, HIT or SUNKEN. The caller does not have to ask any
follow up question to know what happened.
src/test.cpp holds a hand written test suite with an assertTrue helper, grouped per
class: GridPosition, Ship, OwnGrid, Board, ConsoleView, then the placement and
shooting exercises. The groups are switched on with codeSnippet1 to codeSnippet3, and
main.cpp switches between testMode and playMode with a #define, so the same
project either runs the tests or plays the game.
| File | Contents |
|---|---|
src/GridPosition.* |
Column letter and row number, parsing, comparison |
src/Ship.*, src/Shot.* |
A ship and its positions, a shot and its impact |
src/OwnGrid.* |
Ship placement rules and incoming shots |
src/OpponentGrid.* |
What is known about the enemy grid |
src/Board.* |
Holds both grids |
src/ConsoleView.* |
Drawing and user input |
src/test.cpp |
Test suite |
src/main.cpp |
Entry point, test mode or play mode |
Needs a C++14 compiler.
g++ -std=c++14 src/*.cpp -o battleship
Developed in Eclipse CDT, so the workspace files are not in this repository. The test
functions live in test.cpp and are called from main.cpp, so all sources compile
together. Switch between the two modes with the testMode and playMode defines at the
top of main.cpp.
Advanced Programming Techniques, M.Sc. Electrical Engineering and Information Technology, Hochschule Darmstadt, winter semester 2025/26.