JirR02 88e0b5ed69 Converted everything to orgmode
converted everything to orgmode and added solution to the README files
2025-03-31 08:40:43 +02:00
..
2025-03-31 08:40:43 +02:00
2025-03-31 08:40:43 +02:00

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:

/JirR02/ETH_Informatik/media/branch/master/Informatik_I/Bonus_1/pictures/dots_and_boxes.svg

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 🇨🇭