Skip to content

Commit 3ee4aef

Browse files
Giovanni OrtolaniGiovanni Ortolani
authored andcommitted
add squadro
1 parent 453ab85 commit 3ee4aef

File tree

7 files changed

+1497
-0
lines changed

7 files changed

+1497
-0
lines changed

docs/games.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ Status | Game
7575
<font color="orange"><b>~</b></font> | [Slovenian Tarok](#slovenian-tarok)
7676
<font color="orange"><b>~</b></font> | [Skat (simplified bidding)](#skat-simplified-bidding)
7777
<font color="orange"><b>~</b></font> | [Solitaire (K+)](#solitaire-k)
78+
<font color="orange"><b>~</b></font> | [Squadro](#squadro)
7879
![](_static/green_circ10.png "green circle") | [Tic-Tac-Toe](#tic-tac-toe)
7980
![](_static/green_circ10.png "green circle") | [Tiny Bridge](#tiny-bridge)
8081
![](_static/green_circ10.png "green circle") | [Tiny Hanabi](#tiny-hanabi)
@@ -777,6 +778,16 @@ Status | Game
777778
* [Wikipedia](https://en.wikipedia.org/wiki/Klondike_\(solitaire\)) and
778779
[Bjarnason et al. '07, Searching solitaire in real time](http://web.engr.oregonstate.edu/~afern/papers/solitaire.pdf)
779780

781+
### Squadro
782+
783+
* Each turn, players can move their one of their pieces on the board.
784+
* Uses tokens on a grid.
785+
* Modern game.
786+
* Deterministic.
787+
* Perfect information.
788+
* 2 players.
789+
* [bgg](https://boardgamegeek.com/boardgame/245222/squadro)
790+
780791
### Tic-Tac-Toe
781792

782793
* Players place tokens to try and form a pattern.

open_spiel/games/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ set(GAME_SOURCES
157157
skat.h
158158
solitaire.cc
159159
solitaire.h
160+
squadro.cc
161+
squadro.h
160162
stones_and_gems.cc
161163
stones_and_gems.h
162164
tarok.cc
@@ -560,6 +562,10 @@ add_executable(solitaire_test solitaire_test.cc ${OPEN_SPIEL_OBJECTS}
560562
$<TARGET_OBJECTS:tests>)
561563
add_test(solitaire_test solitaire_test)
562564

565+
add_executable(squadro_test squadro_test.cc ${OPEN_SPIEL_OBJECTS}
566+
$<TARGET_OBJECTS:tests>)
567+
add_test(squadro_test squadro_test)
568+
563569
add_executable(stones_and_gems_test stones_and_gems_test.cc
564570
${OPEN_SPIEL_OBJECTS}
565571
$<TARGET_OBJECTS:tests>)

open_spiel/games/squadro.cc

Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
// Copyright 2019 DeepMind Technologies Limited
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "open_spiel/games/squadro.h"
16+
17+
#include <algorithm>
18+
#include <memory>
19+
#include <utility>
20+
21+
#include "open_spiel/utils/tensor_view.h"
22+
23+
namespace open_spiel {
24+
namespace squadro {
25+
namespace {
26+
27+
// Facts about the game
28+
const GameType kGameType{
29+
/*short_name=*/"squadro",
30+
/*long_name=*/"Squadro",
31+
GameType::Dynamics::kSequential,
32+
GameType::ChanceMode::kDeterministic,
33+
GameType::Information::kPerfectInformation,
34+
GameType::Utility::kZeroSum,
35+
GameType::RewardModel::kTerminal,
36+
/*max_num_players=*/2,
37+
/*min_num_players=*/2,
38+
/*provides_information_state_string=*/true,
39+
/*provides_information_state_tensor=*/false,
40+
/*provides_observation_string=*/true,
41+
/*provides_observation_tensor=*/true,
42+
/*parameter_specification=*/{} // no parameters
43+
};
44+
45+
std::shared_ptr<const Game> Factory(const GameParameters& params) {
46+
return std::shared_ptr<const Game>(new SquadroGame(params));
47+
}
48+
49+
REGISTER_SPIEL_GAME(kGameType, Factory);
50+
51+
std::string PlayerToString(Player player) {
52+
switch (player) {
53+
case 0:
54+
return "P0";
55+
case 1:
56+
return "P1";
57+
default:
58+
SpielFatalError(absl::StrCat("Invalid player id ", player));
59+
}
60+
}
61+
62+
std::string CellToString(int row, int col,
63+
const std::array<std::array<Position, kRows>, kNumPlayers>& board) {
64+
if (board[0][col].position == row) {
65+
if (board[0][col].direction == TokenState::forward) {
66+
return "^";
67+
} else if (board[0][col].direction == TokenState::backward) {
68+
return "v";
69+
}
70+
}
71+
if (board[1][row].position == col) {
72+
if (board[1][row].direction == TokenState::forward) {
73+
return ">";
74+
} else if (board[1][row].direction == TokenState::backward) {
75+
return "<";
76+
}
77+
}
78+
return ".";
79+
}
80+
81+
} // namespace
82+
83+
int SquadroState::CurrentPlayer() const {
84+
if (IsTerminal()) {
85+
return kTerminalPlayerId;
86+
} else {
87+
return current_player_;
88+
}
89+
}
90+
91+
bool SquadroState::OverpassOpponent(int opponent, int player_position, Action move) {
92+
bool overpassOpponent = false;
93+
94+
while (board_[opponent][player_position].position == move + 1) {
95+
overpassOpponent = true;
96+
// Send opponent player back to default position
97+
if (board_[opponent][player_position].direction == TokenState::forward) {
98+
board_[opponent][player_position].position = 0;
99+
} else {
100+
board_[opponent][player_position].position = 6;
101+
}
102+
player_position += board_[current_player_][move + 1].direction == TokenState::forward ? 1 : -1;
103+
}
104+
board_[current_player_][move + 1].position = player_position;
105+
return overpassOpponent;
106+
}
107+
108+
void SquadroState::DoApplyAction(Action move) {
109+
int playerPosition = board_[current_player_][move + 1].position;
110+
TokenState playerDirection = board_[current_player_][move + 1].direction;
111+
int steps = playerDirection == TokenState::forward ? movements_[current_player_][move].forward : movements_[current_player_][move].backward;
112+
int other_player = 1 - current_player_;
113+
bool overpassOpponent = false;
114+
int unit_move = playerDirection == TokenState::forward ? 1 : -1;
115+
int finalPosition = playerPosition + steps;
116+
117+
do {
118+
playerPosition += unit_move;
119+
board_[current_player_][move + 1].position = playerPosition;
120+
overpassOpponent = OverpassOpponent(other_player, playerPosition, move);
121+
} while (playerPosition > 0 && playerPosition < kRows - 1 && unit_move * playerPosition < unit_move * finalPosition && !overpassOpponent);
122+
123+
if (board_[current_player_][move + 1].position == 0) {
124+
board_[current_player_][move + 1].direction = TokenState::missing; // Token removed from the board
125+
++missing_tokens_[current_player_];
126+
} else if (board_[current_player_][move + 1].position == 6) {
127+
board_[current_player_][move + 1].direction = TokenState::backward; // Invert token direction when it reaches the end of the board
128+
}
129+
130+
if (missing_tokens_[current_player_] == 4) {
131+
outcome_ = current_player_ == 0 ? Outcome::kPlayer1 : Outcome::kPlayer2;
132+
}
133+
134+
++moves_made_;
135+
if (moves_made_ >= 200) {
136+
outcome_ = Outcome::kDraw;
137+
}
138+
139+
current_player_ = other_player;
140+
}
141+
142+
std::vector<Action> SquadroState::LegalActions() const {
143+
std::vector<Action> moves;
144+
if (IsTerminal()) return moves;
145+
for (int pos = 0; pos < kNumActions; ++pos) {
146+
if (board_[current_player_][pos + 1].direction != TokenState::missing) moves.push_back(pos);
147+
}
148+
return moves;
149+
}
150+
151+
std::string SquadroState::ActionToString(Player player,
152+
Action action_id) const {
153+
return absl::StrCat(PlayerToString(player), action_id);
154+
}
155+
156+
SquadroState::SquadroState(std::shared_ptr<const Game> game)
157+
: State(game) {
158+
for (int player = 0; player <= 1; ++player) {
159+
for (int pos = 0; pos < kRows; ++pos) {
160+
if (pos == 0 || pos == 6) {
161+
// There are only 5 pieces per player. No pieces are present in the corners.
162+
board_[player][pos] = {0, TokenState::missing};
163+
} else {
164+
board_[player][pos] = {0, TokenState::forward};
165+
}
166+
}
167+
}
168+
}
169+
170+
std::string SquadroState::ToString() const {
171+
std::string str;
172+
for (int row = kRows - 1; row >= 0; --row) {
173+
for (int col = 0; col < kCols; ++col) {
174+
str.append(CellToString(row, col, board_));
175+
}
176+
str.append("\n");
177+
}
178+
str.append("C");
179+
int current_player = CurrentPlayer();
180+
if (current_player == kTerminalPlayerId) {
181+
str.append("2");
182+
} else {
183+
str.append(std::to_string(CurrentPlayer()));
184+
}
185+
return str;
186+
}
187+
188+
bool SquadroState::IsTerminal() const {
189+
return outcome_ != Outcome::kUnknown;
190+
}
191+
192+
std::vector<double> SquadroState::Returns() const {
193+
if (outcome_ == Outcome::kPlayer1) return {1.0, -1.0};
194+
if (outcome_ == Outcome::kPlayer2) return {-1.0, 1.0};
195+
return {0.0, 0.0};
196+
}
197+
198+
std::string SquadroState::InformationStateString(Player player) const {
199+
SPIEL_CHECK_GE(player, 0);
200+
SPIEL_CHECK_LT(player, num_players_);
201+
return HistoryString();
202+
}
203+
204+
std::string SquadroState::ObservationString(Player player) const {
205+
SPIEL_CHECK_GE(player, 0);
206+
SPIEL_CHECK_LT(player, num_players_);
207+
return ToString();
208+
}
209+
210+
int SquadroState::CellToInt(int row, int col) const{
211+
std::string str = CellToString(row, col, board_);
212+
return cell_state_map_.at(str);
213+
}
214+
215+
void SquadroState::ObservationTensor(Player player,
216+
absl::Span<float> values) const {
217+
SPIEL_CHECK_GE(player, 0);
218+
SPIEL_CHECK_LT(player, num_players_);
219+
220+
TensorView<2> view(values, {kCellStates, kRows*kCols}, true);
221+
for (int row = kRows - 1; row >= 0; --row) {
222+
for (int col = 0; col < kCols; ++col) {
223+
int cell = row * kCols + col;
224+
view[{CellToInt(row, col), cell}] = 1.0;
225+
}
226+
}
227+
228+
}
229+
230+
std::unique_ptr<State> SquadroState::Clone() const {
231+
return std::unique_ptr<State>(new SquadroState(*this));
232+
}
233+
234+
SquadroGame::SquadroGame(const GameParameters& params)
235+
: Game(kGameType, params) {}
236+
237+
SquadroState::SquadroState(std::shared_ptr<const Game> game,
238+
const std::string& str)
239+
: State(game) {
240+
241+
for (int player = 0; player <= 1; ++player) {
242+
for (int pos = 0; pos < kRows; ++pos) {
243+
board_[player][pos] = {0, TokenState::missing};
244+
}
245+
}
246+
247+
int xs = 0;
248+
int os = 0;
249+
int r = 6;
250+
int c = 0;
251+
for (const char ch : str) {
252+
switch (ch) {
253+
case '.':
254+
break;
255+
case '^':
256+
board_[0][c].position = r;
257+
board_[0][c].direction = TokenState::forward;
258+
break;
259+
case '>':
260+
board_[1][r].position = c;
261+
board_[1][r].direction = TokenState::forward;
262+
break;
263+
case 'v':
264+
board_[0][c].position = r;
265+
board_[0][c].direction = TokenState::backward;
266+
break;
267+
case '<':
268+
board_[1][r].position = c;
269+
board_[1][r].direction = TokenState::backward;
270+
break;
271+
case '0':
272+
current_player_ = 0;
273+
break;
274+
case '1':
275+
current_player_ = 1;
276+
break;
277+
case '2':
278+
current_player_ = kTerminalPlayerId;
279+
break;
280+
}
281+
if (ch == '.' || ch == '^' || ch == '>' || ch == 'v' || ch == '<' || ch == 'C') {
282+
++c;
283+
if (c >= kCols) {
284+
r--;
285+
c = 0;
286+
}
287+
}
288+
}
289+
SPIEL_CHECK_TRUE(r == -1 && ("Problem parsing state (incorrect rows)."));
290+
SPIEL_CHECK_TRUE(c == 1 &&
291+
("Problem parsing state (column value should be 0)"));
292+
293+
int count_p0_tokens = 0;
294+
int count_p1_tokens = 0;
295+
for (int i = 0; i < kNumActions; ++i) {
296+
count_p0_tokens += board_[0][i + 1].direction == TokenState::missing ? 0 : 1;
297+
count_p1_tokens += board_[1][i + 1].direction == TokenState::missing ? 0 : 1;
298+
}
299+
300+
if (count_p0_tokens == 1) {
301+
outcome_ = Outcome::kPlayer1;
302+
} else if (count_p1_tokens == 1) {
303+
outcome_ = Outcome::kPlayer2;
304+
}
305+
SPIEL_CHECK_FALSE(count_p0_tokens == 1 && count_p1_tokens == 1 &&
306+
("P1 and P2 cannot both have a single piece."));
307+
}
308+
309+
} // namespace squadro
310+
} // namespace open_spiel

0 commit comments

Comments
 (0)