dumbo
A fun little game engine.
 All Classes
board.cpp
1 /*
2  * Copyright (c) 2019. David Fridovich-Keil.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above
13  * copyright notice, this list of conditions and the following
14  * disclaimer in the documentation and/or other materials provided
15  * with the distribution.
16  *
17  * 3. Neither the name of the copyright holder nor the names of its
18  * contributors may be used to endorse or promote products derived
19  * from this software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS
22  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
25  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31  * POSSIBILITY OF SUCH DAMAGE.
32  *
33  * Please contact the author(s) of this library if you have any questions.
34  * Author: David Fridovich-Keil ( dfk@eecs.berkeley.edu )
35  */
36 
37 ///////////////////////////////////////////////////////////////////////////////
38 //
39 // Tic tac toe board.
40 //
41 ///////////////////////////////////////////////////////////////////////////////
42 
43 #include <dumbo/core/game_state.h>
44 #include <dumbo/tic_tac_toe/board.h>
45 #include <dumbo/tic_tac_toe/square.h>
46 
47 #include <glog/logging.h>
48 #include <iostream>
49 #include <random>
50 #include <tuple>
51 #include <unordered_set>
52 #include <vector>
53 
54 namespace dumbo {
55 namespace tic {
56 
57 const std::vector<Board::SquareTuple> Board::winning_sets_ = {
58  // Rows.
59  std::make_tuple(Square(0, 0, true), Square(0, 1, true), Square(0, 2, true)),
60  std::make_tuple(Square(1, 0, true), Square(1, 1, true), Square(1, 2, true)),
61  std::make_tuple(Square(2, 0, true), Square(2, 1, true), Square(2, 2, true)),
62 
63  // Columns.
64  std::make_tuple(Square(0, 0, true), Square(1, 0, true), Square(2, 0, true)),
65  std::make_tuple(Square(0, 1, true), Square(1, 1, true), Square(2, 1, true)),
66  std::make_tuple(Square(0, 2, true), Square(1, 2, true), Square(2, 2, true)),
67 
68  // Diagonals.
69  std::make_tuple(Square(0, 0, true), Square(1, 1, true), Square(2, 2, true)),
70  std::make_tuple(Square(0, 2, true), Square(1, 1, true),
71  Square(2, 0, true))};
72 
73 const std::vector<Board::SquareTuple> Board::losing_sets_ = {
74  // Rows.
75  std::make_tuple(Square(0, 0, false), Square(0, 1, false),
76  Square(0, 2, false)),
77  std::make_tuple(Square(1, 0, false), Square(1, 1, false),
78  Square(1, 2, false)),
79  std::make_tuple(Square(2, 0, false), Square(2, 1, false),
80  Square(2, 2, false)),
81 
82  // Columns.
83  std::make_tuple(Square(0, 0, false), Square(1, 0, false),
84  Square(2, 0, false)),
85  std::make_tuple(Square(0, 1, false), Square(1, 1, false),
86  Square(2, 1, false)),
87  std::make_tuple(Square(0, 2, false), Square(1, 2, false),
88  Square(2, 2, false)),
89 
90  // Diagonals.
91  std::make_tuple(Square(0, 0, false), Square(1, 1, false),
92  Square(2, 2, false)),
93  std::make_tuple(Square(0, 2, false), Square(1, 1, false),
94  Square(2, 0, false))};
95 
96 Board::Board(const std::unordered_set<Square, Square::Hasher>& occupied_squares,
97  bool my_turn)
98  : GameState<Square>(my_turn), occupied_squares_(occupied_squares) {
99  // Check to make sure all occupied squares are legal.
100  constexpr uint8_t kGridSize = 3;
101  for (const auto& sq : occupied_squares_)
102  CHECK(sq.row < kGridSize && sq.col < kGridSize);
103 
104  // Populate empty squares vector.
105  for (uint8_t ii = 0; ii < kGridSize; ii++) {
106  for (uint8_t jj = 0; jj < kGridSize; jj++) {
107  const Square sq{ii, jj, my_turn_};
108  const Square sq_other_player{ii, jj, !my_turn_};
109 
110  if (!occupied_squares_.count(sq) &&
111  !occupied_squares_.count(sq_other_player))
112  empty_squares_.emplace_back(sq);
113  }
114  }
115 
116  // Make sure this is a valid board.
117  Check();
118 }
119 
120 // Choose a random move from this game state.
121 Square Board::RandomMove() const {
122  CHECK(!empty_squares_.empty());
123  std::uniform_int_distribution<size_t> unif(0, empty_squares_.size() - 1);
124  return empty_squares_[unif(rng_)];
125 }
126 
127 // Populate the GameState that occurs when the current player plays
128 // the given move. Returns whether or not the move was legal.
129 bool Board::NextState(const Square& move, GameState* next_state) const {
130  CHECK_NOTNULL(next_state);
131  CHECK_EQ(move.my_square, my_turn_);
132 
133  // Check legality first.
134  Square move_other_player = move;
135  move_other_player.my_square = !move.my_square;
136  if (occupied_squares_.count(move) ||
137  occupied_squares_.count(move_other_player))
138  return false;
139 
140  // Update next state.
141  Board* next_board = static_cast<Board*>(next_state);
142  CHECK_NOTNULL(next_board);
143 
144  // Remember to switch who's move it is!
145  next_board->my_turn_ = !my_turn_;
146 
147  // Occupied squares are the same, plus the new move.
148  next_board->occupied_squares_.clear();
149  next_board->occupied_squares_.insert(occupied_squares_.begin(),
150  occupied_squares_.end());
151  next_board->occupied_squares_.emplace(move);
152 
153  // Empty squares are the same, except the implicit color needs to change.
154  next_board->empty_squares_.clear();
155  bool found_matching_empty = false;
156  for (const auto& empty : empty_squares_) {
157  if (empty == move) {
158  found_matching_empty = true;
159  continue;
160  }
161 
162  next_board->empty_squares_.emplace_back(empty);
163  next_board->empty_squares_.back().my_square = next_board->my_turn_;
164  }
165 
166  CHECK(found_matching_empty);
167  CHECK_EQ(next_board->empty_squares_.size(), empty_squares_.size() - 1);
168 
169  // Make sure next board is valid.
170  next_board->Check();
171 
172  return true;
173 }
174 
175 // Is this a terminal state? If so, return whether it is a win/loss/draw
176 // (encoded as 1.0/0.0/0.5).
177 bool Board::IsTerminal(double* win) const {
178  CHECK_NOTNULL(win);
179 
180  // Utility for checking if the board contains a winning/losing set.
181  auto has_set = [this](const SquareTuple& s) {
182  return this->occupied_squares_.count(std::get<0>(s)) &&
183  this->occupied_squares_.count(std::get<1>(s)) &&
184  this->occupied_squares_.count(std::get<2>(s));
185  };
186 
187  // Check all winning sets.
188  for (const auto& ws : winning_sets_) {
189  if (has_set(ws)) {
190  *win = 1.0;
191  return true;
192  }
193  }
194 
195  // Check all losing sets.
196  for (const auto& ls : losing_sets_) {
197  if (has_set(ls)) {
198  *win = 0.0;
199  return true;
200  }
201  }
202 
203  // Draw if no remaining empty squares.
204  if (empty_squares_.empty()) {
205  *win = 0.5;
206  return true;
207  }
208 
209  return false;
210 }
211 
212 // Check if same as another game state.
213 bool Board::operator==(const GameState<Square>& rhs) const {
214  const Board& rhs_board = static_cast<const Board&>(rhs);
215 
216  // Make sure every element of occupied squares match. Empty squares will then
217  // also have to match if they were constructed correctly.
218  for (const auto& sq : rhs_board.occupied_squares_) {
219  if (!occupied_squares_.count(sq)) return false;
220  }
221 
222  return true;
223 }
224 
225 // Render. By default, our moves are 'X's and humans are 'O's.
226 void Board::Render() const {
227  constexpr uint8_t kNumRows = 3;
228 
229  Square sq{0, 0, true};
230  for (uint8_t ii = 0; ii < kNumRows; ii++) {
231  sq.row = ii;
232 
233  for (uint8_t jj = 0; jj < kNumRows; jj++) {
234  sq.col = jj;
235  sq.my_square = true;
236  if (occupied_squares_.count(sq))
237  std::cout << " X |";
238  else {
239  sq.my_square = false;
240  if (occupied_squares_.count(sq))
241  std::cout << " O |";
242  else
243  std::cout << " |";
244  }
245  }
246 
247  std::cout << std::endl;
248  }
249 }
250 
251 // Check to make sure this is a valid board configuration.
252 void Board::Check() const {
253  // Check correct number of empty squares and occupied squares.
254  constexpr size_t kNumRows = 3;
255  CHECK_EQ(occupied_squares_.size() + empty_squares_.size(),
256  kNumRows * kNumRows);
257 
258  // Check to make sure every cell is either occupied or empty.
259  auto is_occupied = [this](uint8_t ii, uint8_t jj) {
260  Square sq{ii, jj, true};
261  if (this->occupied_squares_.count(sq)) return true;
262 
263  sq.my_square = false;
264  return this->occupied_squares_.count(sq) > 0;
265  }; // is_occupied
266 
267  auto is_empty = [this](uint8_t ii, uint8_t jj) {
268  const Square sq{ii, jj, this->my_turn_};
269  for (const auto& empty : this->empty_squares_) {
270  if (empty == sq) return true;
271  }
272 
273  return false;
274  }; // is_empty
275 
276  for (uint8_t ii = 0; ii < 3; ii++) {
277  for (uint8_t jj = 0; jj < 3; jj++) {
278  if (!is_occupied(ii, jj)) CHECK(is_empty(ii, jj));
279  }
280  }
281 
282  // Make sure all empty squares' turn match current turn.
283  for (const auto& empty : empty_squares_) CHECK_EQ(empty.my_square, my_turn_);
284 }
285 
286 } // namespace tic
287 } // namespace dumbo