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 4 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
![](_static/green_circ10.png "green circle") | [Tiny Bridge](#tiny-bridge)
![](_static/green_circ10.png "green circle") | [Tiny Hanabi](#tiny-hanabi)
![](_static/green_circ10.png "green circle") | [Trade Comm](#trade-comm)
<font color="orange"><b>~</b></font> | [TwixT](#twixt)
<font color="orange"><b>~</b></font> | [Ultimate Tic-Tac-Toe](#ultimate-tic-tac-toe)
![](_static/green_circ10.png "green circle") | [Y](#y)

Expand Down Expand Up @@ -876,6 +877,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
167 changes: 167 additions & 0 deletions open_spiel/games/twixt/twixt.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// 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 <algorithm>
#include <iostream>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include "open_spiel/spiel_utils.h"
#include "open_spiel/games/twixt/twixt.h"
#include "open_spiel/games/twixt/twixtboard.h"
#include "open_spiel/utils/tensor_view.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)},
{"discount", GameParameter(kDefaultDiscount)}},
};

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);
mBoard = Board(parent_game.getBoardSize(), parent_game.getAnsiColorOutput());
}

std::string TwixTState::ActionToString(open_spiel::Player player,
Action action) const {
Move move = mBoard.actionToMove(player, action);
std::string s = (player == kRedPlayer) ? "x" : "o";
s += static_cast<int>('a') + move.first;
s.append(std::to_string(mBoard.getSize() - move.second));
return s;
}

void TwixTState::setPegAndLinksOnTensor(absl::Span<float> values,
const Cell *pCell, int offset, int turn,
Move move) const {
// we flip col/row here for better output in playthrough file
TensorView<3> view(
values, {kNumPlanes, mBoard.getSize(), mBoard.getSize() - 2}, false);
Move tensorMove = mBoard.getTensorMove(move, turn);

if (!pCell->hasLinks()) {
// peg has no links -> use plane 0
view[{0 + offset, tensorMove.second, tensorMove.first}] = 1.0;
} else {
// peg has links -> use plane 1
view[{1 + offset, tensorMove.second, tensorMove.first}] = 1.0;
}

if (pCell->hasBlockedNeighbors()) {
// peg has blocked neighbors on plane 1 -> use also plane 2
view[{2 + offset, tensorMove.second, tensorMove.first}] = 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 kOpponentPlaneOffset = 3;
const int kCurPlayerPlaneOffset = 0;
int size = mBoard.getSize();

// 6 planes of size boardSize x (boardSize-2):
// each plane excludes the endlines of the opponent
// planes 0 (3) are for the unlinked pegs of the current (opponent) player
// planes 1 (4) are for the linked pegs of the current (opponent) player
// planes 2 (5) are for the blocked pegs on plane 1 (4)

// here we initialize Tensor with zeros for each state
TensorView<3> view(
values, {kNumPlanes, mBoard.getSize(), mBoard.getSize() - 2}, true);

for (int c = 0; c < size; c++) {
for (int r = 0; r < size; r++) {
Move move = {c, r};
const Cell *pCell = mBoard.getConstCell(move);
int color = pCell->getColor();
if (player == kRedPlayer) {
if (color == kRedColor) {
// no turn
setPegAndLinksOnTensor(values, pCell, kCurPlayerPlaneOffset, 0, move);
} else if (color == kBlueColor) {
// 90 degr turn (blue player sits left side of red player)
setPegAndLinksOnTensor(values, pCell, kOpponentPlaneOffset, 90, move);
}
} else if (player == kBluePlayer) {
if (color == kBlueColor) {
// 90 degr turn
setPegAndLinksOnTensor(values, pCell, kCurPlayerPlaneOffset, 90,
move);
} else if (color == kRedColor) {
// 90+90 degr turn (red player sits left of blue player)
// setPegAndLinksOnTensor(values, pCell, 5, size-c-2, size-r-1);
setPegAndLinksOnTensor(values, pCell, kOpponentPlaneOffset, 180,
move);
}
}
}
}
}

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

if (mDiscount <= kMinDiscount || mDiscount > kMaxDiscount) {
SpielFatalError("discount out of range [" + std::to_string(kMinDiscount) +
" < discount <= " + std::to_string(kMaxDiscount) +
"]: " + std::to_string(mDiscount) + "; ");
}
}

} // namespace twixt
} // namespace open_spiel
153 changes: 153 additions & 0 deletions open_spiel/games/twixt/twixt.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// 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 mCurrentPlayer; };

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

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

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

std::vector<double> Returns() const override {
double reward;
int result = mBoard.getResult();
if (result == kOpen || result == kDraw) {
return {0.0, 0.0};
} else {
reward = pow(mDiscount, mBoard.getMoveCounter());
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 mBoard.getLegalActions(CurrentPlayer());
};

protected:
void DoApplyAction(Action move) override {
mBoard.applyAction(CurrentPlayer(), move);
if (mBoard.getResult() == kOpen) {
setCurrentPlayer(1 - CurrentPlayer());
} else {
setCurrentPlayer(kTerminalPlayerId);
}
};

private:
int mCurrentPlayer = kRedPlayer;
Board mBoard;
double mDiscount = kDefaultDiscount;

void setCurrentPlayer(int player) { mCurrentPlayer = player; }
void setPegAndLinksOnTensor(absl::Span<float>, const Cell *, int, int,
Move) 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 mBoardSize * (mBoardSize - 2);
};

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, mBoardSize, mBoardSize - 2};
return shape;
}

int MaxGameLength() const {
// square - 4 corners + swap move
return mBoardSize * mBoardSize - 4 + 1;
}
bool getAnsiColorOutput() const { return mAnsiColorOutput; }
bool getUnicodeOutput() const { return mUnicodeOutput; }
int getBoardSize() const { return mBoardSize; }
double getDiscount() const { return mDiscount; }

private:
bool mAnsiColorOutput;
bool mUnicodeOutput;
int mBoardSize;
double mDiscount;
};

} // namespace twixt
} // namespace open_spiel

#endif // OPEN_SPIEL_GAMES_TWIXT_TWIXT_H_
Loading