Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding game TwixT #1120

Merged
merged 9 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions docs/games.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ Status | Game
🟢 | [Tiny Bridge](#tiny-bridge)
🟢 | [Tiny Hanabi](#tiny-hanabi)
🟢 | [Trade Comm](#trade-comm)
🔶 | [TwixT](#twixt)
🔶 | [Ultimate Tic-Tac-Toe](#ultimate-tic-tac-toe)
🔶 | [Weighted Voting Games](#weighted-voting-games)
🟢 | [Y](#y)
Expand Down Expand Up @@ -891,6 +892,16 @@ Status | Game
* 2 players.
* A simple emergent communication game based on trading.

### TwixT

* Players place pegs and links on a 24x24 square to connect a line between opposite sides.
* pegs and links on a grid.
* Modern game.
* Deterministic.
* Perfect information.
* 2 players.
* [Wikipedia](https://en.wikipedia.org/wiki/TwixT)

### Ultimate Tic-Tac-Toe

* Players try and form a pattern in local boards and a meta-board.
Expand Down
9 changes: 9 additions & 0 deletions open_spiel/games/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,11 @@ set(GAME_SOURCES
trade_comm/trade_comm.h
twenty_forty_eight/2048.cc
twenty_forty_eight/2048.h
twixt/twixt.cc
twixt/twixt.h
twixt/twixtboard.cc
twixt/twixtboard.h
twixt/twixtcell.h
ultimate_tic_tac_toe/ultimate_tic_tac_toe.h
ultimate_tic_tac_toe/ultimate_tic_tac_toe.cc
y/y.cc
Expand Down Expand Up @@ -609,6 +614,10 @@ add_executable(trade_comm_test trade_comm/trade_comm_test.cc ${OPEN_SPIEL_OBJECT
$<TARGET_OBJECTS:tests>)
add_test(trade_comm_test trade_comm_test)

add_executable(twixt_test twixt/twixt_test.cc ${OPEN_SPIEL_OBJECTS}
$<TARGET_OBJECTS:tests>)
add_test(twixt_test twixt_test)

add_executable(ultimate_tic_tac_toe_test ultimate_tic_tac_toe/ultimate_tic_tac_toe_test.cc
${OPEN_SPIEL_OBJECTS} $<TARGET_OBJECTS:tests>)
add_test(ultimate_tic_tac_toe_test ultimate_tic_tac_toe_test)
Expand Down
149 changes: 149 additions & 0 deletions open_spiel/games/twixt/twixt.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// Copyright 2019 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "open_spiel/games/twixt/twixt.h"

#include <algorithm>
#include <iostream>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include "open_spiel/spiel_utils.h"
#include "open_spiel/utils/tensor_view.h"
#include "open_spiel/games/twixt/twixtcell.h"
#include "open_spiel/games/twixt/twixtboard.h"

namespace open_spiel {
namespace twixt {
namespace {

// Facts about the game.
const GameType kGameType{
/*short_name=*/"twixt",
/*long_name=*/"TwixT",
GameType::Dynamics::kSequential,
GameType::ChanceMode::kDeterministic,
GameType::Information::kPerfectInformation,
GameType::Utility::kZeroSum,
GameType::RewardModel::kTerminal,
/*max_num_players=*/2,
/*min_num_players=*/2,
/*provides_information_state_string=*/true,
/*provides_information_state_tensor=*/false,
/*provides_observation_string=*/true,
/*provides_observation_tensor=*/true,
/*parameter_specification=*/
{{"board_size", GameParameter(kDefaultBoardSize)},
{"ansi_color_output", GameParameter(kDefaultAnsiColorOutput)}},
};

std::unique_ptr<Game> Factory(const GameParameters &params) {
return std::unique_ptr<Game>(new TwixTGame(params));
}

REGISTER_SPIEL_GAME(kGameType, Factory);

} // namespace

TwixTState::TwixTState(std::shared_ptr<const Game> game) : State(game) {
const TwixTGame &parent_game = static_cast<const TwixTGame &>(*game);
board_ = Board(parent_game.board_size(), parent_game.ansi_color_output());
}

std::string TwixTState::ActionToString(open_spiel::Player player,
Action action) const {
Position position = board_.ActionToPosition(action);
std::string s = (player == kRedPlayer) ? "x" : "o";
s += static_cast<int>('a') + position.x;
s.append(std::to_string(board_.size() - position.y));
return s;
}

void TwixTState::SetPegAndLinksOnTensor(absl::Span<float> values,
const Cell& cell, int offset, bool turn,
Position position) const {
TensorView<3> view(
values, {kNumPlanes, board_.size(), board_.size() - 2}, false);
Position tensorPosition = board_.GetTensorPosition(position, turn);

if (cell.HasLinks()) {
for (int dir = 0; dir < 4; dir++) {
if (cell.HasLink(dir)) {
// peg has link in direction dir: set 1.0 on plane 1..4 / 7..10
view[{offset + 1 + dir, tensorPosition.x, tensorPosition.y}] = 1.0;
}
}
} else {
// peg has no links: set 1.0 on plane 0 / 6
view[{offset + 0, tensorPosition.x, tensorPosition.y}] = 1.0;
}

// peg has blocked neighbors: set 1.0 on plane 5 / 11
if (cell.HasBlockedNeighborsEast()) {
view[{offset + 5, tensorPosition.x, tensorPosition.y}] = 1.0;
}
}

void TwixTState::ObservationTensor(open_spiel::Player player,
absl::Span<float> values) const {
SPIEL_CHECK_GE(player, 0);
SPIEL_CHECK_LT(player, kNumPlayers);

const int kPlaneOffset[2] = {0, kNumPlanes/2};
int size = board_.size();

// 2 x 6 planes of size boardSize x (boardSize-2):
// each plane excludes the endlines of the opponent
// plane 0/6 is for the pegs
// plane 1..4 / 7..10 is for the links NNE, ENE, ESE, SSE, resp.
// plane 5/11 is pegs that have blocked neighbors

TensorView<3> view(
values, {kNumPlanes, board_.size(), board_.size() - 2}, true);

for (int c = 0; c < size; c++) {
for (int r = 0; r < size; r++) {
Position position = {c, r};
const Cell& cell = board_.GetConstCell(position);
int color = cell.color();
if (color == kRedColor) {
// no turn
SetPegAndLinksOnTensor(values, cell, kPlaneOffset[0], false, position);
} else if (color == kBlueColor) {
// 90 degr turn
SetPegAndLinksOnTensor(values, cell, kPlaneOffset[1], true, position);
}
}
}
}

TwixTGame::TwixTGame(const GameParameters &params)
: Game(kGameType, params),
ansi_color_output_(
ParameterValue<bool>("ansi_color_output", kDefaultAnsiColorOutput)),
board_size_(ParameterValue<int>("board_size", kDefaultBoardSize)) {
if (board_size_ < kMinBoardSize || board_size_ > kMaxBoardSize) {
SpielFatalError("board_size out of range [" +
std::to_string(kMinBoardSize) + ".." +
std::to_string(kMaxBoardSize) +
"]: " + std::to_string(board_size_));
}
}

} // namespace twixt
} // namespace open_spiel
151 changes: 151 additions & 0 deletions open_spiel/games/twixt/twixt.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// Copyright 2019 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef OPEN_SPIEL_GAMES_TWIXT_TWIXT_H_
#define OPEN_SPIEL_GAMES_TWIXT_TWIXT_H_

#include <iostream>
#include <memory>
#include <string>
#include <vector>

#include "open_spiel/games/twixt/twixtboard.h"
#include "open_spiel/games/twixt/twixtcell.h"

// https://en.wikipedia.org/wiki/TwixT

namespace open_spiel {
namespace twixt {

class TwixTState : public State {
public:
explicit TwixTState(std::shared_ptr<const Game> game);

TwixTState(const TwixTState &) = default;
TwixTState &operator=(const TwixTState &) = default;

open_spiel::Player CurrentPlayer() const override { return current_player_; };

std::string ActionToString(open_spiel::Player player,
Action action) const override;

std::string ToString() const override { return board_.ToString(); };

bool IsTerminal() const override {
int result = board_.result();
return (result == kRedWin || result == kBlueWin || result == kDraw);
};

std::vector<double> Returns() const override {
double reward;
int result = board_.result();
if (result == kOpen || result == kDraw) {
return {0.0, 0.0};
} else {
reward = 1.0;
if (result == kRedWin) {
return {reward, -reward};
} else {
return {-reward, reward};
}
}
};

std::string InformationStateString(open_spiel::Player player) const override {
SPIEL_CHECK_GE(player, 0);
SPIEL_CHECK_LT(player, kNumPlayers);
return ToString();
};

std::string ObservationString(open_spiel::Player player) const override {
SPIEL_CHECK_GE(player, 0);
SPIEL_CHECK_LT(player, kNumPlayers);
return ToString();
};

void ObservationTensor(open_spiel::Player player,
absl::Span<float> values) const override;

std::unique_ptr<State> Clone() const override {
return std::unique_ptr<State>(new TwixTState(*this));
};

void UndoAction(open_spiel::Player, Action) override{};

std::vector<Action> LegalActions() const override {
if (IsTerminal())
return {};
return board_.GetLegalActions(current_player_);
};

protected:
void DoApplyAction(Action action) override {
const std::vector<Action>& v = LegalActions();
if (std::find(v.begin(), v.end(), action) == v.end()) {
SpielFatalError("Not a legal action: " + std::to_string(action));
}
board_.ApplyAction(CurrentPlayer(), action);
if (board_.result() == kOpen) {
set_current_player(1 - CurrentPlayer());
} else {
set_current_player(kTerminalPlayerId);
}
};

private:
Player current_player_ = kRedPlayer;
Board board_;
void set_current_player(Player player) { current_player_ = player; }
void SetPegAndLinksOnTensor(absl::Span<float>, const Cell&, int, bool,
Position) const;
};

class TwixTGame : public Game {
public:
explicit TwixTGame(const GameParameters &params);

std::unique_ptr<State> NewInitialState() const override {
return std::unique_ptr<State>(new TwixTState(shared_from_this()));
};

int NumDistinctActions() const override {
return board_size_ * board_size_;
};

int NumPlayers() const override { return kNumPlayers; };
double MinUtility() const override { return -1.0; };
absl::optional<double> UtilitySum() const override { return 0.0; };
double MaxUtility() const override { return 1.0; };

std::vector<int> ObservationTensorShape() const override {
static std::vector<int> shape{kNumPlanes, board_size_, board_size_ - 2};
return shape;
}

int MaxGameLength() const {
// square - 4 corners + swap move
return board_size_ * board_size_ - 4 + 1;
}
bool ansi_color_output() const { return ansi_color_output_; }
int board_size() const { return board_size_; }

private:
bool ansi_color_output_;
int board_size_;
};

} // namespace twixt
} // namespace open_spiel

#endif // OPEN_SPIEL_GAMES_TWIXT_TWIXT_H_
Loading