Skip to content

Commit 020e920

Browse files
authored
add GameStoreInterface to enable golf persistence (#239)
* add GameStoreInterface to enable golf persistence - GameStoreInterface currently has InMemoryGameStore implementation for use in tests - escapist backed GameStore coming in future change - update to c++20 - remove local state from golf::GameManager - add db name to client metadata in escapist c++ client - clean up game_manager.h - add new golf rpc proto (incomplete) TODO: - add escapist backed GameStore - escapist server should read clientContext metadata for db name (ideally via Authentication header?) - InMemoryGameStore unit tests so we don't have to test via GameManager * clang-format * incredible
1 parent cdbe1ff commit 020e920

19 files changed

+522
-153
lines changed

.bazelrc

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ common --enable_bzlmod=true
66
common --disk_cache=~/bzlcache
77
common --repository_cache=~/.bzl_repo_cache
88

9-
common --cxxopt='-std=c++17'
10-
common --host_cxxopt='-std=c++17'
9+
common --cxxopt='-std=c++20'
10+
common --host_cxxopt='-std=c++20'
11+
# common --action_env=BAZEL_CXXOPTS="-std=c++20"
1112

1213
build:debug -c dbg
1314

MODULE.bazel

+1-7
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,7 @@ module(
33
version = "1.0",
44
)
55

6-
bazel_dep(name = "toolchains_llvm", version = "1.0.0")
7-
git_override(
8-
module_name = "toolchains_llvm",
9-
commit = "01132cfdae7d7187a885cf79d5a3ac1ed8a02e5a",
10-
remote = "https://github.com/bazel-contrib/toolchains_llvm",
11-
)
12-
6+
bazel_dep(name = "toolchains_llvm", version = "1.1.2")
137
bazel_dep(name = "rules_python", version = "0.34.0")
148
bazel_dep(name = "protobuf", version = "27.2", repo_name = "com_google_protobuf")
159
bazel_dep(name = "grpc", version = "1.65.0", repo_name = "com_github_grpc_grpc")

MODULE.bazel.lock

+5-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cpp/cards/golf/BUILD.bazel

+42-6
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,52 @@ cc_library(
22
name = "golf",
33
srcs = [
44
"game_manager.cc",
5-
"game_state.cc",
6-
"player.cc",
75
],
86
hdrs = [
97
"game_manager.h",
10-
"game_state.h",
118
"golf.h",
12-
"player.h",
139
],
1410
visibility = ["//visibility:public"],
11+
deps = [
12+
":game_state",
13+
":game_store",
14+
":player",
15+
"//cpp/cards",
16+
"@com_google_absl//absl/status:statusor",
17+
],
18+
)
19+
20+
cc_library(
21+
name = "game_store",
22+
srcs = ["game_store.cc"],
23+
hdrs = ["game_store.h"],
24+
visibility = ["//visibility:public"],
25+
deps = [
26+
":game_state",
27+
":player",
28+
"//cpp/cards",
29+
"@com_google_absl//absl/status",
30+
"@com_google_absl//absl/status:statusor",
31+
],
32+
)
33+
34+
cc_library(
35+
name = "game_state",
36+
srcs = ["game_state.cc"],
37+
hdrs = ["game_state.h"],
38+
visibility = ["//visibility:public"],
39+
deps = [
40+
":player",
41+
"//cpp/cards",
42+
"@com_google_absl//absl/status:statusor",
43+
],
44+
)
45+
46+
cc_library(
47+
name = "player",
48+
srcs = ["player.cc"],
49+
hdrs = ["player.h"],
50+
visibility = ["//visibility:public"],
1551
deps = [
1652
"//cpp/cards",
1753
"@com_google_absl//absl/status:statusor",
@@ -23,7 +59,7 @@ cc_test(
2359
size = "small",
2460
srcs = ["player_test.cc"],
2561
deps = [
26-
":golf",
62+
":player",
2763
"@googletest//:gtest_main",
2864
],
2965
)
@@ -33,7 +69,7 @@ cc_test(
3369
size = "small",
3470
srcs = ["game_state_test.cc"],
3571
deps = [
36-
":golf",
72+
":game_state",
3773
"@googletest//:gtest_main",
3874
],
3975
)

cpp/cards/golf/game_manager.cc

+84-54
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ namespace golf {
1414
using namespace cards;
1515

1616
using absl::InvalidArgumentError;
17+
using absl::Status;
1718
using absl::StatusOr;
1819

1920
using std::deque;
@@ -23,21 +24,28 @@ using std::vector;
2324
static const std::string allowedChars =
2425
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";
2526

26-
StatusOr<string> GameManager::registerUser(const string& username) {
27-
if (username.size() < 4 || username.size() > 15) {
27+
auto validate_user_id(const string& user_id) -> Status {
28+
if (user_id.size() < 4 || user_id.size() > 15) {
2829
return InvalidArgumentError("username length must be between 4 and 15 chars");
2930
}
3031

31-
if (username.find_first_not_of(allowedChars) != string::npos) {
32+
if (user_id.find_first_not_of(allowedChars) != string::npos) {
3233
return InvalidArgumentError("only alphanumeric, underscore, or dash allowed in username");
3334
}
35+
return absl::OkStatus();
36+
}
3437

35-
if (usersOnline.find(username) != usersOnline.end()) {
36-
return InvalidArgumentError("username taken");
38+
StatusOr<string> GameManager::registerUser(const string& user_id) {
39+
auto validate_status = validate_user_id(user_id);
40+
if (!validate_status.ok()) {
41+
return validate_status;
3742
}
3843

39-
usersOnline.insert(username);
40-
return username;
44+
auto save_status = game_store_->AddUser(user_id);
45+
if (save_status.ok()) {
46+
return user_id;
47+
}
48+
return save_status;
4149
}
4250

4351
deque<Card> GameManager::shuffleNewDeck() {
@@ -84,23 +92,21 @@ std::string GameManager::generateRandomAlphanumericString(std::size_t len) const
8492
std::optional<std::string> GameManager::generateUnusedRandomId() const {
8593
for (int i = 0; i < 10; i++) {
8694
auto attempt = generateRandomAlphanumericString(12);
87-
if (gamesById.find(attempt) == gamesById.end()) {
95+
auto existing_game = game_store_->ReadGame(attempt);
96+
if (!existing_game.ok()) {
8897
return attempt;
8998
}
9099
}
91100
return {};
92101
}
93102

94-
// TODO: generate random game id
95-
// TODO: support multiple decks
96-
StatusOr<GameStatePtr> GameManager::newGame(const string& username, int numberOfPlayers) {
97-
if (usersOnline.find(username) == usersOnline.end()) {
98-
return InvalidArgumentError("unregistered username");
99-
}
100-
if (gameIdsByUser.find(username) != gameIdsByUser.end()) {
101-
return InvalidArgumentError("already in game");
103+
// TODO: support multiple decks for many players?
104+
StatusOr<GameStatePtr> GameManager::newGame(const string& user_id, int number_of_players) {
105+
if (!game_store_->UserExists(user_id)) {
106+
return InvalidArgumentError("unknown user");
102107
}
103-
if (numberOfPlayers < 2 || numberOfPlayers > 5) {
108+
109+
if (number_of_players < 2 || number_of_players > 5) {
104110
return InvalidArgumentError("2 to 5 players");
105111
}
106112

@@ -112,22 +118,22 @@ StatusOr<GameStatePtr> GameManager::newGame(const string& username, int numberOf
112118
deque<Card> mutableDrawPile = shuffleNewDeck();
113119

114120
vector<Card> allDealt{};
115-
for (int i = 0; i < numberOfPlayers * 4; i++) {
121+
for (int i = 0; i < number_of_players * 4; i++) {
116122
allDealt.push_back(mutableDrawPile.back());
117123
mutableDrawPile.pop_back();
118124
}
119125

120126
vector<Player> mutablePlayers;
121127

122128
// two up, two down
123-
int halfway = numberOfPlayers * 2;
124-
for (int i = 0; i < numberOfPlayers; i++) {
129+
int halfway = number_of_players * 2;
130+
for (int i = 0; i < number_of_players; i++) {
125131
auto& tl = allDealt.at(2 * i);
126132
auto& tr = allDealt.at(2 * i + 1);
127133
auto& bl = allDealt.at(2 * i + halfway);
128134
auto& br = allDealt.at(2 * i + halfway + 1);
129135
if (i == 0) {
130-
mutablePlayers.emplace_back(username, tl, tr, bl, br);
136+
mutablePlayers.emplace_back(user_id, tl, tr, bl, br);
131137
} else {
132138
mutablePlayers.emplace_back(tl, tr, bl, br);
133139
}
@@ -141,29 +147,22 @@ StatusOr<GameStatePtr> GameManager::newGame(const string& username, int numberOf
141147
const deque<Card> drawPile = std::move(mutableDrawPile);
142148
const deque<Card> discardPile = std::move(mutableDiscardPile);
143149

144-
auto emplaceWorked = gamesById.emplace(
145-
gameId,
146-
std::make_shared<GameState>(GameState{drawPile, discardPile, players, false, 0, -1, gameId}));
147-
if (!emplaceWorked.second) {
148-
return InvalidArgumentError("could not generate unused game id");
149-
}
150-
151-
usersByGame.insert(std::make_pair(gameId, std::unordered_set<string>{username}));
152-
gameIdsByUser[username] = gameId;
153-
return gamesById.at(gameId);
150+
auto game_state =
151+
std::make_shared<GameState>(GameState{drawPile, discardPile, players, false, 0, -1, gameId});
152+
return game_store_->NewGame(game_state);
154153
}
155154

156-
StatusOr<GameStatePtr> GameManager::joinGame(const string& gameId, const string& username) {
157-
if (usersOnline.find(username) == usersOnline.end()) {
155+
StatusOr<GameStatePtr> GameManager::joinGame(const string& game_id, const string& user_id) {
156+
if (!game_store_->UserExists(user_id)) {
158157
return InvalidArgumentError("unregistered username");
159158
}
160159

161-
auto gameIter = gamesById.find(gameId);
162-
if (gameIter == gamesById.end()) {
160+
auto game_read_status = game_store_->ReadGame(game_id);
161+
if (!game_read_status.ok()) {
163162
return InvalidArgumentError("unknown game id");
164163
}
165164

166-
auto oldGameState = gameIter->second;
165+
auto oldGameState = *game_read_status;
167166

168167
if (oldGameState->allPlayersPresent()) {
169168
return InvalidArgumentError("no spots available");
@@ -177,28 +176,21 @@ StatusOr<GameStatePtr> GameManager::joinGame(const string& gameId, const string&
177176
updatedPlayers.push_back(p);
178177
} else {
179178
// safe because we know player is not already claimed
180-
updatedPlayers.emplace_back(*p.claimHand(username));
179+
updatedPlayers.emplace_back(*p.claimHand(user_id));
181180
playerAdded = true;
182181
}
183182
}
184183

185-
gamesById.erase(gameId);
186-
gamesById.emplace(gameId, std::make_shared<GameState>(oldGameState->withPlayers(updatedPlayers)));
187-
usersByGame.at(gameId).insert(username);
188-
gameIdsByUser[username] = gameId;
189-
190-
return gamesById.at(gameId);
184+
auto updated_game = std::make_shared<GameState>(oldGameState->withPlayers(updatedPlayers));
185+
return game_store_->UpdateGame(updated_game);
191186
}
192187

193-
StatusOr<GameStatePtr> GameManager::getGameStateForUser(const string& username) const {
194-
if (usersOnline.find(username) == usersOnline.end()) {
195-
return InvalidArgumentError("unregistered username");
196-
}
197-
if (gameIdsByUser.find(username) == gameIdsByUser.end()) {
198-
return InvalidArgumentError("user not in game");
188+
StatusOr<GameStatePtr> GameManager::getGameStateForUser(const string& user_id) const {
189+
if (!game_store_->UserExists(user_id)) {
190+
return InvalidArgumentError("unknown user");
199191
}
200192

201-
return gamesById.at(gameIdsByUser.at(username));
193+
return game_store_->ReadGameByUserId(user_id);
202194
}
203195

204196
StatusOr<GameStatePtr> GameManager::updateGameState(StatusOr<GameState> updateResult,
@@ -207,10 +199,8 @@ StatusOr<GameStatePtr> GameManager::updateGameState(StatusOr<GameState> updateRe
207199
return InvalidArgumentError(updateResult.status().message());
208200
}
209201

210-
gamesById.erase(gameId);
211-
gamesById.emplace(gameId, std::make_shared<GameState>(*updateResult));
212-
213-
return gamesById.at(gameId);
202+
auto game_state = std::make_shared<GameState>(*updateResult);
203+
return game_store_->UpdateGame(game_state);
214204
}
215205

216206
StatusOr<GameStatePtr> GameManager::peekAtDrawPile(const string& username) {
@@ -273,4 +263,44 @@ StatusOr<GameStatePtr> GameManager::knock(const string& username) {
273263
return updateGameState(game->knock(playerIndex), game->getGameId());
274264
}
275265

266+
std::unordered_set<string> GameManager::getUsersOnline() const {
267+
auto read_users_status = game_store_->GetUsers();
268+
if (!read_users_status.ok()) {
269+
return {}; // TODO: bubble status to caller
270+
}
271+
return *read_users_status;
272+
}
273+
274+
std::unordered_set<GameStatePtr> GameManager::getGames() const {
275+
return game_store_->ReadAllGames();
276+
}
277+
278+
std::unordered_map<string, string> GameManager::getGameIdsByUserId() const {
279+
auto games_result = game_store_->ReadAllGames();
280+
std::unordered_map<string, string> game_ids_by_user{};
281+
for (auto g : games_result) {
282+
auto game_id = g->getGameId();
283+
for (auto p : g->getPlayers()) {
284+
if (p.isPresent() && p.getName().has_value()) {
285+
game_ids_by_user[p.getName().value()] = game_id;
286+
}
287+
}
288+
}
289+
return game_ids_by_user;
290+
}
291+
292+
std::unordered_set<string> GameManager::getUsersByGameId(const string& game_id) const {
293+
auto game_maybe = game_store_->ReadGame(game_id);
294+
if (!game_maybe.ok()) {
295+
return {};
296+
}
297+
std::unordered_set<string> users{};
298+
for (auto p : (*game_maybe)->getPlayers()) {
299+
if (p.isPresent() && p.getName().has_value()) {
300+
users.insert(p.getName().value());
301+
}
302+
}
303+
return users;
304+
}
305+
276306
} // namespace golf

0 commit comments

Comments
 (0)