-
Notifications
You must be signed in to change notification settings - Fork 895
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
Add new game: Checkers #861
Conversation
Thanks! Seems like the checkers test is causing a seg fault. This usually means there's a bad memory access (out of bounds indexing into an array). To find it, I recommend building in debug mode (compiler flags -O0 and possibly -g as well) and run the test using valgrind: https://stackoverflow.com/questions/5134891/how-do-i-use-valgrind-to-find-memory-leaks. |
Setting the |
Thanks. Let me look into it! |
Hi @lanctot, something I've noticed in clobber. In this line, shouldn't the rank of the TensorView always be 3 instead of kCellStates? Maybe I'm missing something. |
We used |
Thanks for fixing. I have not looked at the code yet. Curious: how did you handle the case of multiple captures? |
I spread it across multiple turns to keep it simple. If a player makes a capture and the same piece has another capture move possible, then the turn will not go to the opponent. The player will get another turn with only the remaining capture move available (all capture moves are anyways mandatory). But since you've mentioned it. I've noticed an edge case I've missed for multiple jumps. I'll update the code, add test cases and push again. |
My doubt was, to do this wouldn't you have replaced Suppose if I were to add another dimension to the Tensor, a boolean to indicate whether the cell is on the boundary or not. Wouldn't that increase the rank from 3 to 4, although Maybe I've got this jumbled up and should go through tensor_view.h |
Sorry -- you are indeed correct. I had forgotten the use of TensorView, but it refers to the number of dimensions and should not be confused with the cell states. (It just happens to be 3 in this case, so it works.) This should read TensorView<3> in clobber. (Do you want to submit a separate PR? If not, very easy to change on our side). Yeah, using Tensorview does make encoding the observation tensors easier and less error-prone. See the one in breakthrough for example: https://github.com/deepmind/open_spiel/blob/0ffd9584c8be560f4e0f659b3ad93933d9078b48/open_spiel/games/breakthrough.cc#L326 |
I can create a separate PR. P.S. I was checking the contributor graph and noticed 1 commit against my name, even though when I click on it I can see several more. Curious to know if I'm missing any git configurations to have caused this discrepancy. |
Hmm, yeah.. I see that but I'm not really sure what the cause is. I think our import/export tool (copybara) squashes all the commits from one PR into a single commit when it gets imported. But there has to be more to it than that, because you also updated games.md in a separate PR. Let's see what it looks like after this next PR and Checkers. Could also be how the views get summarized/aggregated on the github side. |
|
||
// Implementation of the board game Checkers. | ||
// https://en.wikipedia.org/wiki/Checkers | ||
// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed your implementation has rows and columns as paramters. We usually list them all here (see headers of other games)
// | ||
// Some notes about this implementation: | ||
// - Drawing: | ||
// Game is drawn if no pieces have been removed in 40 moves |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
40 Seems a bit arbitrary-- is this a commonly chosen value for this game? If so maybe mention that in the comments here?
open_spiel/games/checkers.h
Outdated
|
||
Player current_player_ = 0; // Player zero (White, 'o') goes first. | ||
Player outcome_ = kInvalidPlayer; | ||
int multiple_jump_piece_ = 0; // Piece in the board who can do multiple jump. Represented by row * rows_ + column |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you say a bit more in the comments here about how this variable is used or why it's needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This variable is used to indicate that the last moved piece can make an additional jump (multiple jumps). In that case LegalActions is filtered to only represent moves available for this piece.
I'm not entirely happy with the implementation since I had to use a global variable, if you have other thoughts, do share them
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok great. But, what I meant was: could you elaborate on that in the comment here.
Also, can we please clarify what version of the rules you are using (at the top of the header file)? I wasn't aware of forced multi-captures, e.g. I don't see anything about it mentioned on https://en.wikipedia.org/wiki/Checkers. Maybe this is a common convention used in computer Checkers? If so, please clarify that in the documentation at the top of the file. Also e.g. the choice of 40 moves without capture, had not heard of it.. but maybe this is part of a specific rule set or checkers convention?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's mentioned in the Wiki link that
When capturing an opponent's piece is possible, capturing is mandatory in most official rules.
That's why I added those. About the 40 move rule, I could find some mentions of it online
http://www.flyordie.com/games/help/checkers/en/games_rules_checkers.html
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for submitting this game! It's a great addition!
Feel free to push back on some of the more design type comments/suggestions.
It's also worth taking a look at the Google C++ style guide. You followed most of it, but in particular the 80 char line length limit needs a closer look.
open_spiel/games/checkers.h
Outdated
namespace open_spiel { | ||
namespace checkers { | ||
|
||
inline constexpr int kNumPlayers = 2; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Marking these as inline seems redundant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's true. Removed it.
open_spiel/games/checkers.h
Outdated
|
||
Player current_player_ = 0; // Player zero (White, 'o') goes first. | ||
Player outcome_ = kInvalidPlayer; | ||
int multiple_jump_piece_ = 0; // Piece in the board who can do multiple jump. Represented by row * rows_ + column |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We've got an 80 char line limit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have fixed these
open_spiel/games/checkers.h
Outdated
|
||
Player current_player_ = 0; // Player zero (White, 'o') goes first. | ||
Player outcome_ = kInvalidPlayer; | ||
int multiple_jump_piece_ = 0; // Piece in the board who can do multiple jump. Represented by row * rows_ + column |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there only one piece that can jump multiple times?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. This variable is used to identify a piece with a multiple jump possibility that has completed it's first jump. Hence there'll be only one such piece.
open_spiel/games/checkers.cc
Outdated
@@ -0,0 +1,546 @@ | |||
// Copyright 2019 DeepMind Technologies Limited |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2022
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
// Index 1: Direction is diagonally up-right. | ||
// Index 2: Direction is diagonally down-right. | ||
// Index 3: Direction is diagonally down-left. | ||
constexpr std::array<int, kNumDirections> kDirRowOffsets = {{-1, -1, 1, 1}}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it worth having this as
std:array<std::array<int, 2>, kNumDirection>
that includes both row and column?
Is it even worth having a Point/Location type struct with x/y or row/column values, possibly with a + operator.
Here are two that I wrote:
https://github.com/deepmind/open_spiel/blob/master/open_spiel/games/havannah.h#L67
https://github.com/deepmind/open_spiel/blob/master/open_spiel/games/quoridor.h#L54
though they might be overcomplicated for this use case. Feel free to push back on this if you don't think it's a good idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keeping this as is improves readability IMO
open_spiel/games/checkers.cc
Outdated
const int start_column = values[1]; | ||
const int direction = values[2]; | ||
const int move_type = values[3]; | ||
const int captured_piece_type = values[4]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see, this is why the piece types are in there. Is it better to put more details in the history and undo based on that instead of including it in the action?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great suggestion. Have introduced a TurnHistoryInfo struct to do this. Now actions have reduced to 1/4th.
void BasicSerializationTest() { | ||
std::shared_ptr<const Game> game = LoadGame("checkers"); | ||
std::unique_ptr<State> state = game->NewInitialState(); | ||
std::unique_ptr<State> state2 = game->DeserializeState(state->Serialize()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems worth testing a more complicated state.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added one more test with a board after a few moves
} | ||
|
||
void BasicCheckersTests() { | ||
testing::LoadGameTest("checkers"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you test different sizes as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added it
absl::StrAppend(&result, RowLabel(rows_, r)); | ||
|
||
for (int c = 0; c < columns_; c++) { | ||
absl::StrAppend(&result, StateToString(BoardAt(r, c))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it worth distinguishing between empty black/white squares, as in one that pieces can be on vs ones that are inaccessible? Maybe with dot vs space?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't understand this correctly. Can you elaborate?
open_spiel/games/checkers.cc
Outdated
SetBoard(end_row, end_column, CellState::kEmpty); | ||
CellState captured_piece = OpponentState(PlayerToState(player)); | ||
SetBoard((start_row + end_row) / 2, (start_column + end_column) / 2, | ||
captured_piece_type == PieceType::kMan ? captured_piece : CrownState(captured_piece)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This wrapping is a bit odd.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have introduced a function here to improve readability
PiperOrigin-RevId: 462677216 Change-Id: I2fc4b0b25d1265d7ff12df6c5b50a6defebbd496
Merged in 808e54e |
No description provided.