- Production Server - https://play.anti.run/lobby
- Local Dev Server - http://localhost:7380/lobby
cd packages/lobby
yarn install
yarn start
cd packages/game
yarn install
yarn start
- Install rust - https://www.rust-lang.org/tools/install
- Run the server -
cargo run --bin turbo-hearts
The backend loads a config.json
file from its working directory for configuration. A
configuration suitable for local development is checked into the repo. Changes to the default
configuration are necessary to deploy the backend on a public server.
The db_path
is the path to the sqlite database relative to where the server is started. A
database will be created automatically if none exists.
The external_uri
parameter is url to the backend (or to the proxy if deployed behind a proxy).
This needs to match one of the authorized redirect URIs for your client credentials, minus the
trailing /redirect
path.
The fusion
, github
, and google
sections contain client_id
and client_secret
parameters
for OAuth 2.0 client credentials.
The port
is the port the backend should serve from.
Takes a set of user ids and returns a corresponding set of users.
Request:
{
"ids": [
"38009247-c85b-4ca1-8e59-cf626ea565f7",
"d33b08ca-4d34-44f8-8643-cbf7fce5a91c"
]
}
Response:
{
"users": [{
"id": "38009247-c85b-4ca1-8e59-cf626ea565f7",
"name": "carrino"
}, {
"id": "d33b08ca-4d34-44f8-8643-cbf7fce5a91c",
"name": "twilson"
}]
}
Returns an html page for the game lobby displaying the live refreshing set of current set of players in the lobby and proposed games that have not yet started. Games can be created, joined, and left from this page.
Returns a text/event-stream
of events in the lobby. The following events can be returned in the
lobby event stream.
Whenever a client subscribes to the lobby, a join_lobby
message is sent to every other subscriber
in the lobby.
{
"type": "join_lobby",
"user_id": "38009247-c85b-4ca1-8e59-cf626ea565f7"
}
Whenever a client subscribes to the lobby, a lobby_state
message is sent to that subscriber
containing the list of all active subscribers, recent chat messages, and all incomplete games.
{
"type": "lobby_state",
"subscribers": ["d33b08ca-4d34-44f8-8643-cbf7fce5a91c", "38009247-c85b-4ca1-8e59-cf626ea565f7"],
"chat": [{
"timestamp": 1583168443628,
"user_id": "17b5876b-30f7-460f-beb4-a54cc114dcf2",
"message": "Anyone here?"
}, {
"timestamp": 1583168445328,
"user_id": "17b5876b-30f7-460f-beb4-a54cc114dcf2",
"message": "I'll be around for half an hour"
}],
"games": {
"8c9e2ff7-dcf3-49be-86f0-315f469840bc": {
"players": [{
"player": {
"type": "human",
"user_id": "17b5876b-30f7-460f-beb4-a54cc114dcf2"
},
"rules": "blind",
"seat": null
}],
"seed": {
"type": "redacted"
},
"created_time": 1583168449628,
"created_by": "17b5876b-30f7-460f-beb4-a54cc114dcf2",
"last_updated_time": 1583168449628,
"last_updated_by": "17b5876b-30f7-460f-beb4-a54cc114dcf2",
"started_time": null
}
}
}
Whenever a new game is created, a new_game
message is sent to all active subscribers containg the
id of the game and the id of the player who created the game.
{
"type": "new_game",
"game_id": "8c9e2ff7-dcf3-49be-86f0-315f469840bc",
"player": {
"player": {
"type": "human",
"user_id": "17b5876b-30f7-460f-beb4-a54cc114dcf2"
},
"rules": "blind",
"seat": "east"
},
"seed": {
"type": "redacted"
}
}
Whenever a player joins an existing game, a join_game
message is sent to all active subscribers
containing the id of the game and the player who joined the game.
{
"type": "join_game",
"game_id": "8c9e2ff7-dcf3-49be-86f0-315f469840bc",
"player": {
"player": {
"type": "bot",
"user_id": "d33b08ca-4d34-44f8-8643-cbf7fce5a91c",
"strategy": "duck"
},
"rules": "classic",
"seat": null
}
}
Whenever a game is started, a start_game
message is sent to all active subscribers
containing the id of the game.
{
"type": "start_game",
"game_id": "8c9e2ff7-dcf3-49be-86f0-315f469840bc",
"north": {
"type": "human",
"user_id": "11f26260-092a-4a56-bc07-24719c551503"
},
"east": {
"type": "human",
"user_id": "1b90134b-4e6f-4b20-a18b-2a1a7ccc8d1d"
},
"south": {
"type": "bot",
"user_id": "3c72e8ac-ae38-4306-a78b-acdc4369eaf0",
"strategy": "random"
},
"west": {
"type": "human",
"user_id": "a35b9169-cdf9-4912-967a-445f5502f7d7"
}
}
Whenever a player leaves an existing game, a leave_game
message is sent to all active subscribers
containing the id of the game and the id of the player who left.
{
"type": "leave_game",
"game_id": "8c9e2ff7-dcf3-49be-86f0-315f469840bc",
"player": {
"type": "human",
"user_id": "d33b08ca-4d34-44f8-8643-cbf7fce5a91c"
}
}
When the last play is made in a game a finish_game
event is sent to all subscribers.
{
"type": "finish_game",
"game_id": "8c9e2ff7-dcf3-49be-86f0-315f469840bc"
}
When a chat message is sent to the lobby a chat
event is sent to all subscribers.
{
"type": "chat",
"user_id": "38009247-c85b-4ca1-8e59-cf626ea565f7",
"message": "Anyone here?"
}
Whenever a player disconnects from the lobby every stream, a leave_lobby
message is sent to all
other active subscribers.
{
"type": "leave_lobby",
"user_id": "38009247-c85b-4ca1-8e59-cf626ea565f7"
}
Create a new game with the proposed charging rules and return its id. The actual charging rules will be selected randomly from the proposed rules of all players once the game has four players. The seed is an optional string that if present will be used to seed the rng that determines what hands are dealt. This can be used to replay a previous game, or to play the same hands simultaneously with more than four players.
Request:
{
"rules": "blind",
"seat": "north",
"seed": "my custom game seed"
}
Response:
"8c9e2ff7-dcf3-49be-86f0-315f469840bc"
Join an existing game, propose charging rules, and optionally declare a preferred seat. The actual charging rules will be selected randomly from the proposed rules of all players once the game starts. If there are conflicting preferred seats, one player requesting a seat will get the seat and the other players requesting the same seat will be assigned a random seat.
Request:
{
"game_id": "8c9e2ff7-dcf3-49be-86f0-315f469840bc",
"rules": "chain",
"seat": null
}
Start a game. Games must have at least four players to start. If more than four players joined the game, a random subset of four players will be chosen to play in the game.
Request:
{
"game_id": "8c9e2ff7-dcf3-49be-86f0-315f469840bc"
}
Leave a game that hasn't started yet.
Request:
{
"game_id": "8c9e2ff7-dcf3-49be-86f0-315f469840bc"
}
Add a bot to an existing game and propose charging rules. Returns the name of the bot.
Request:
{
"game_id": "8c9e2ff7-dcf3-49be-86f0-315f469840bc",
"rules": "chain",
"strategy": "random"
}
Response:
"71b4acdd-51e2-4e7d-9f29-76d511db9060"
Remove a player from an unstarted game.
Request:
{
"game_id": "8c9e2ff7-dcf3-49be-86f0-315f469840bc",
"user_id": "71b4acdd-51e2-4e7d-9f29-76d511db9060"
}
Send a chat message to all subscribers in the lobby.
Request:
{
"message": "Anyone here?"
}
Returns an html page for a game displaying the live refreshing game state.
Returns a text/event-stream
of events in the game. The event stream will immediately return all
events that have occurred so far when it is connected. The event stream will be closed shortly
after the game is complete. Players not in the game can subscribe to the game as well to get an
unredacted stream of all events.
The following events can be returned in the game event stream.
When a client subscribes to the game stream, all pre-existing events are immediately streamed back.
Immediately after that, an end_replay
event is sent to that client to indicate that the client
has caught up to the latest pre-existing event in the stream. This event also contains the set of
users currently subscribed to the game.
{
"type": "end_replay",
"subscribers": [
"3935516d-1782-49fe-afa7-1a00264798d9",
"e99185e6-b73e-4365-aeb8-644b71394ca8",
"0a86489a-0746-46bc-b8fe-dee4b8ea546f"
]
}
When a user subscribes to a game, a join_game
event is sent to all subscribers.
{
"type": "join_game",
"user_id": "723fc477-75d3-4fef-a672-20ac6e54bdba"
}
When a user unsubscribes from a game, a leave_game
event is sent to all subscribers.
{
"type": "leave_game",
"user_id": "723fc477-75d3-4fef-a672-20ac6e54bdba"
}
When a chat message is sent to a game a chat
event is sent to all subscribers.
{
"type": "chat",
"user_id": "723fc477-75d3-4fef-a672-20ac6e54bdba",
"message": "carrino, it's your play"
}
When a game starts, a sit
event is be sent indicating where each player is sitting and what the
charging rules are. The charging rules will be the rules proposed by the player in the north
seat. If the game was initially created with a chosen seed, that seed will also be returned.
Otherwise a redacted seed will be returned.
{
"type": "sit",
"north": {"type": "human", "user_id": "723fc477-75d3-4fef-a672-20ac6e54bdba"},
"east": {"type": "human", "user_id": "56da6b82-ff02-4c53-be8f-773dc2931df0"},
"south": {"type": "human", "user_id": "c34f709e-97b4-4bce-946e-fafe0005276b"},
"west": {"type": "bot", "user_id": "723fc477-75d3-4fef-a672-20ac6e54bdba", "algorithm": "random"},
"rules": "blind",
"seed": {
"type": "redacted"
}
}
When a new hand starts, a deal
event is sent indicating which cards were dealt to which players.
Players in the game will receive a redacted event containing only their cards.
{
"type": "deal",
"north": ["AS", "JS", "3S", "2S", "4H", "QC", "TC", "5C", "KD", "QD", "JD", "9D", "7D"],
"east": ["QS", "TS", "6S", "5S", "4S", "AH", "JH", "TH", "9H", "8H", "2C", "AD", "3D"],
"south": ["7S", "KH", "QH", "6H", "5H", "AC", "9C", "7C", "6C", "3C", "TD", "4D", "2D"],
"west": ["KS", "9S", "8S", "7H", "3H", "2H", "KC", "JC", "8C", "4C", "8D", "6D", "5D"],
"pass": "across"
}
A start_passing
event is sent when the passing phase of a hand begins.
{
"type": "start_passing"
}
A pass_status
event is sent when passing begins and after each pass is sent indicating which
players are done passing.
{
"type": "pass_status",
"north_done": false,
"east_done": true,
"south_done": false,
"west_done": false
}
When a player makes a pass, a send_pass
event is sent indicating who sent the pass and what cards
were passed. Players in the game other than the sender will receive a redacted event without the
actual cards passed.
{
"type": "send_pass",
"from": "south",
"cards": ["QH", "AC", "TD"]
}
When a player receives a pass, a recv_pass
event is sent indicating who received the pass and
what cards they received. Players in the game other than the receiver will receive a redacted event
without the actual cards passed.
{
"type": "recv_pass",
"to": "west",
"cards": ["QH", "AC", "TD"]
}
A start_charging
event is sent when a charging phase of a hand begins.
{
"type": "start_charging"
}
A charge_status
event is sent when charging begins and after each charge is made. It indicates
who is the next player to charge (only for non-free charging rules) and each player who still needs
to make a charge eventually to finish charging.
{
"type": "charge_status",
"next_charger": "east",
"north_done": true,
"east_done": false,
"south_done": true,
"west_done": false
}
When a charge is made (including an empty charge), a charge
event is sent indicating who made the
charge and what cards they charged. If the charging rules use blind charges, players in the game
other than the charger will receive a blind_charge
event instead.
{
"type": "charge",
"seat": "east",
"cards": ["QS", "AH"]
}
When a blind variant of the charging rules has been chosen and a charge is made (including an empty
charge), a blind_charge
event will be sent to other players in the game (the charger and any
spectators will receive a charge
event) indicating who made the charge and how many cards they
charged.
{
"type": "blind_charge",
"seat": "north",
"count": 1
}
When a blind variant of the charging rules has been chosen and a round of charging completes, a
reveal_charges
event will be sent indicating what charges were made.
{
"type": "reveal_charges",
"north": ["JD"],
"east": [],
"south": ["QS", "TC"],
"west": []
}
When a play is made, a play
event will be sent indicating who made the play, and what card they
played.
{
"type": "play",
"seat": "west",
"card": "8D"
}
A play_status
event is sent whenever it is someone's turn to play a card. The event indicates who
is next to play and what cards in their hand are legal to play. Players in the game other than the
next player will receive a redacted event without the legal plays.
{
"type": "play_status",
"next_player": "north",
"legal_plays": ["AS", "8S", "4S", "3S"]
}
When a new trick starts, a start_trick
event will be sent indicating which player makes the lead.
{
"type": "start_trick",
"leader": "north"
}
When a trick is completed, an end_trick
event will be sent indicating which player makes won the
trick.
{
"type": "end_trick",
"winner": "west"
}
When a claim is made, a claim
event will be sent indicating who made the claim and the current
contents of their hand.
{
"type": "claim",
"seat": "west",
"hand": ["KS", "9S", "3H", "2H", "KC", "4C", "8D", "5D"]
}
When a player accepts a claim, an accept_claim
event will be sent indicating whose claim was
accepted and who accepted the claim.
{
"type": "accept_claim",
"claimer": "west",
"acceptor": "south"
}
When a player rejects a claim, a reject_claim
event will be sent indicating whose claim was
rejected and who rejected the claim.
{
"type": "reject_claim",
"claimer": "west",
"rejector": "north"
}
A hand_complete
event is sent after the last trick in a hand ends and includes the scores for all
players in the hand.
{
"type": "hand_complete",
"north_score": 5,
"east_score": 4,
"south_score": -32,
"west_score": 26
}
A game_complete
event is sent after all four hands are complete. The only events that can occur
after this event are chat events. This event will contain the unredacted seed that was used to
generate the deals in the game. This seed can be used as a chosen seed in a future game to replay
the same hands.
{
"type": "game_complete",
"seed": {
"type": "random",
"value": "a3839486-ce09-44fe-baa3-35115b93389f"
}
}
Pass cards.
Request:
{
"game_id": "8c9e2ff7-dcf3-49be-86f0-315f469840bc",
"cards": ["QH", "AC", "TD"]
}
Charge some cards. With chain style charging rules, all players must make a final empty charge to complete the charging phase of a hand. Otherwise, all players other than the last to make a non-empty charge must make a final empty charge to complete the charging phase.
Request:
{
"game_id": "8c9e2ff7-dcf3-49be-86f0-315f469840bc",
"cards": ["QS", "AH"]
}
Play a card.
Request:
{
"game_id": "8c9e2ff7-dcf3-49be-86f0-315f469840bc",
"card": "8D"
}
Claim that you will win all the remaining tricks. By claiming, the contents of your hand is revealed to all players. If all other players accept your claim, the hand will end, otherwise play will continue.
Claims proceed in parallel with normal gameplay. Other players may ignore the claim request and continue playing normally.
Request:
{
"game_id": "8c9e2ff7-dcf3-49be-86f0-315f469840bc"
}
Accept the claim made by another player. If all other players accept the claim, the hand will end, otherwise play will continue.
Request:
{
"game_id": "8c9e2ff7-dcf3-49be-86f0-315f469840bc",
"claimer": "west"
}
Reject the claim made by another player. Play will continue as if the claim never occurred (though all players will know the hand of the player who claimed).
Request:
{
"game_id": "8c9e2ff7-dcf3-49be-86f0-315f469840bc",
"claimer": "west"
}
Send a chat message to all subscribers watching a game.
Request:
{
"game_id": "8c9e2ff7-dcf3-49be-86f0-315f469840bc",
"message": "carrino, it's your play"
}
Load summary information about recent games suitable for constructing a leaderboard or game summary table. Only completed games between four human players are included.
An optional game_id
query parameter can be passed to restrict the output only to games completed
before the given game. This is useful for paging over games in multiple calls.
An optional page_size
query parameter can be passed to specify how many games should be included.
The page size defaults to 100.
Load an unredacted timeline of events for the given hand. This endpoint can only be called for hands after they have been completed.