8.7 KiB
Dot and Boxes
Project overview
The goal of this project is to implement a game called Dots And Boxes: two players, A and B, take turns to draw lines connecting dots on a \(m \times n\) (here \(2 \times 2\)) board.
If a player manages to close a box (a \(1 \times 1\) square surrounded by lines), the player claims the box by placing its token (A/B) in the box, and the player gets another turn. The game is finished when all boxes have been claimed, and the player with the most claimed boxes wins. Note that, even if a player claims multiple boxes in a single move, they would only get one extra move.
For example, in the following game player A won:
Your task
The file game.cpp
contains a partial implementation of the game. The main game loop is implemented in the function play_game(grid)
. This function is called from main.cpp
and gets the game grid as an argument. The game grid is already fully implemented. We describe the functionality it provides in a following section below.
Your task is extending the game implementation in file game.cpp
such that it works the way described above. We provide some functions in this file and thus suggest a certain structure – but you are completely free to do it your way. Add, remove or change anything you want. The only constraint is that you must implement the function play_game which takes the game grid as argument. You are of course free to change the body of this function in whatever way you like.
If you do decide to change the body of the play_game function be careful to not accidentally change the format of the outputs it generates. For example: If you change the output std::cout << "Game finished" << std::endl;
to std::cout << "Game Finished" << std::endl;
you will not pass some test cases. The autograder is very picky when checking whether or not your outputs are correct. You can get all the points in this exercise without changing any of the output lines in the template.
Note: More Information on the the concept of the game on Code Expert.
Solution
#include "game.h"
#include <assert.h>
#include <iostream>
// Checks whether or not the game is finished.
// A game is considered finished when all lines have been drawn.
bool game_is_finished(const Grid &grid) {
for (unsigned int j = 0; j < grid.num_rows(); ++j) {
for (unsigned int i = 0; i < grid.num_cols(); ++i) {
if (grid.field(i, j) == ' ')
return false;
}
}
return true;
}
// Draw a line in the grid starting at point (row, col) going towards the given
// direction
void draw_line(Grid &grid, unsigned int row, unsigned int col, char direction) {
switch (direction) {
case 'r':
grid.horizontal(row, col) = true;
break;
case 'l':
grid.horizontal(row, col - 1) = true;
break;
case 'd':
grid.vertical(row, col) = true;
break;
case 'u':
grid.vertical(row - 1, col) = true;
break;
default:
// We should never reach this point.
std::cout << "Invalid line direction.";
assert(false);
}
}
// Checks whether or not a box (or field) is newly completed.
// A box is newly completed if all four sides are drawn but the box is not
// claimed yet.
bool is_newly_completed_box(const Grid &grid, unsigned int row,
unsigned int col) {
bool state;
if (grid.vertical(row, col) == true && grid.horizontal(row, col) == true &&
grid.vertical(row, col + 1) && grid.horizontal(row + 1, col) == true)
state = true;
else
state = false;
return state;
}
// Play the players move.
// This function returns true if the player completed a box with their move.
// Otherwise it returns false.
bool play_move(Grid &grid, char player, unsigned int row, unsigned int col,
char direction) {
// Note: we assume the move is valid
// draw the new line
draw_line(grid, row, col, direction);
// check if the current players move completed a new box
bool completed_new_box = false;
switch (direction) {
case 'r':
// we drew a horizontal line
// check if box above the horizontal line is newly completed
// Note: box above only exists if the line is not at the top edge of
// the grid!
if (row > 0 && is_newly_completed_box(grid, row - 1, col)) {
completed_new_box = true;
grid.field(row - 1, col) = player;
}
if (row >= 0 && row < grid.num_rows() &&
is_newly_completed_box(grid, row, col)) {
completed_new_box = true;
grid.field(row, col) = player;
}
break;
case 'l':
if (row > 0 && is_newly_completed_box(grid, row - 1, col - 1)) {
completed_new_box = true;
grid.field(row - 1, col - 1) = player;
}
if (row >= 0 && row < grid.num_rows() &&
is_newly_completed_box(grid, row, col - 1)) {
completed_new_box = true;
grid.field(row, col - 1) = player;
}
break;
case 'u':
if (col > 0 && is_newly_completed_box(grid, row - 1, col - 1)) {
completed_new_box = true;
grid.field(row - 1, col - 1) = player;
}
if (col >= 0 && col < grid.num_cols() &&
is_newly_completed_box(grid, row - 1, col)) {
completed_new_box = true;
grid.field(row - 1, col) = player;
}
break;
case 'd':
if (col > 0 && is_newly_completed_box(grid, row, col - 1)) {
completed_new_box = true;
grid.field(row, col - 1) = player;
}
if (col >= 0 && col < grid.num_cols() &&
is_newly_completed_box(grid, row, col)) {
completed_new_box = true;
grid.field(row, col) = player;
}
break;
}
return completed_new_box;
}
// Computes a players score.
// A Players score is the number of fields claimed by the player.
unsigned int compute_player_score(const Grid &grid, char player) {
int score = 0;
for (unsigned int j = 0; j < grid.num_rows(); j++) {
for (unsigned int i = 0; i < grid.num_rows(); i++) {
if (grid.field(i, j) == player)
++score;
}
}
return score;
}
// Checks if the move is within the bounds of the grid,
// and the line is not already drawn
bool is_valid_move(const Grid &grid, unsigned int row, unsigned int col,
char direction) {
bool state = false;
switch (direction) {
case 'l':
if (col > 0 && row <= grid.num_rows() &&
grid.horizontal(row, col - 1) == false)
state = true;
break;
case 'r':
if (col < grid.num_cols() && row <= grid.num_rows() &&
grid.horizontal(row, col) == false)
state = true;
break;
case 'u':
if (row > 0 && col <= grid.num_cols() &&
grid.vertical(row - 1, col) == false)
state = true;
break;
case 'd':
if (row < grid.num_rows() && col <= grid.num_cols() &&
grid.vertical(row, col) == false)
state = true;
break;
default:
state = false;
break;
}
return state;
}
// Main game loop
void play_game(Grid &grid) {
// initialize player and step
char current_player = 'A';
unsigned int current_move = 1;
// print initial grid
grid.print_grid();
// initialize user input
unsigned int row, col;
char direction;
// loop while game is not finished
while (!game_is_finished(grid)) {
std::cout << "Move # " << current_move << std::endl;
// get a valid move
std::cout << "Player " << current_player << "'s move: " << std::endl;
std::cin >> row >> col >> direction;
while (!is_valid_move(grid, row, col, direction)) {
std::cout << "Invalid move!" << std::endl;
std::cin >> row >> col >> direction;
}
// play the move
bool move_completed_box =
play_move(grid, current_player, row, col, direction);
current_move += 1;
// print grid
grid.print_grid();
// change current player
switch (current_player) {
case 'A':
if (move_completed_box == true)
current_player = 'A';
else
current_player = 'B';
break;
case 'B':
if (move_completed_box == true)
current_player = 'B';
else
current_player = 'A';
break;
default:
// We should never reach this point.
std::cout << "Unknown Player";
assert(false);
}
}
// Game loop exited. The game must be finished
std::cout << "Game finished" << std::endl;
// Compute and display player scores
unsigned int score_a = compute_player_score(grid, 'A');
unsigned int score_b = compute_player_score(grid, 'B');
std::cout << "Player A: " << score_a << std::endl;
std::cout << "Player B: " << score_b << std::endl;
if (score_a == score_b) {
std::cout << "It's a draw!" << std::endl;
} else {
char winner;
if (score_a > score_b)
winner = 'A';
else
winner = 'B';
std::cout << "Player " << winner << " wins!" << std::endl;
}
}
Made by JirR02 in Switzerland 🇨🇭