Unocab
Unocab is an UNO game engine. Usage is simple:
// On creation of a Game, an UNO deck of 108 cards is
// shuffled and assigned to `game.state.cache.deck`.
const game = new Game({
// Whether to stack +2s. If this is enabled, and
// say three +2s are played one after the other,
// the person to draw next will pick up 6 cards.
// This option is enabled by default.
stackPlusTwos: true,
// The initial seed determines all the random events
// of the game. Hence if you create two games with
// the same initial seed and the same number of players
// join the game at the same time, the shuffled deck and
// the hands of the players will be the same.
initialSeed: "unocab rocks!",
// Explained in the next README section.
shorthandMode: false,
});
// At the start of the game, the engine also plays the
// first card of the deck which is not a +4 or color
// switcher. After this point, players continue the
// game based on the type and color of this card.
// Whenever new players join, a hand of 7 cards is
// assigned to each of them. A hand is formed by
// drawing the top card of the deck 7 times. This
// is random, since the deck has been shuffled.
const [p1, p2] = ["player1", "player2"];
game.join(p1, p2);
// let's assume the first card was a Red 4, and p1 has
// a Red 6 and a +4.
game.play(p1, { type: "six", color: "red" });
game.draw(p2);
game.pass(p2);
game.play(p1, { type: "plus-four" });
game.switchColor(p1, "blue");
game.callBluff(p2);
// ... more moves
const end = game.hasEnded();
if (end) console.log(end.loser + " lost the game!");
Full vs Shorthand mode
During creation of a Game
, you can optionally pass in shorthandMode: true
. In shorthand
mode, the engine only keeps as much information about the game as is needed for it to
function. Otherwise, and by default, all information about the game is kept in the events
array (game.state.events
). This allows you to have a history of all the happenings in that
particular game, and also allows you to jump to an arbitrary point in the game. For example,
to undo a move, you could use game.jumpToEventIndex(-2)
. This
function is not usable in shorthand mode, and the events array will only keep 5 events at most.
TLDR: Use shorthand mode if you don't want to use jumpToEventIndex
or keep the entire game history.
Serialising and Deserialising
A game can be serialised into JSON using game.serialise()
, and cloned using game.clone()
.
A game can be deserialised using new Game({ serialisedState: gameJson })
.
However, when deserialising a game, arguments like shorthandMode
or stackPlusTwos
are not
allowed, since they were already set when the game was being initiated.
Error Reporting
If a player tries to perform an invalid move, such as drawing twice in a row or playing a card
that is not allowed, an UnocabError
will be thrown. The message provides an adequate description
of what the problem was, but you can also access information pertaining to the error using the code
and data
fields in an UnocabError
. Please have a look at errors.ts
for the variations of error codes and the data they provide.
Types
Unocab provides Zod types for most of the objects it uses in case you're building something like a REST API and want to verify the structure. Please glance over the types.ts
file to figure out what Zod types are provided.
GameEvent
A game event is an object with a type
that denotes what kind of event it is (e.g. draw
or card_played
), and additional info about that particular event, such as the card
property in a card_played
event.
It looks like { type: "card_played", by: "player-one", card: { type: "six", color: "red" } }
.
NOTE: In full mode (aka not shorthand mode), the events array will also contain events with type game_info
. These events are not a result of player's actions, but are added by the engine to denote certain happenings. For example, when a player calls bluff, a game_info
event will be added afterwards to signify whether the bluff call succeeded or failed. This can be helpful for something like implementation of animations when re-playing the game.
For more information about what events exist, please glance over the types.ts
file.
Fetching valid events
To obtain a list of "options" for the active player in a game, use game.validEvents(playerId)
.
This will return an array of GameEvent
s which the player is allowed to act on. These events can
directly be used like game.processNewEvent(event)
, which acts as an alternative to functions like
game.draw
or game.pass
, which internally use processNewEvent
as well.
Demo implementations
Please refer to impls/cli-uno.ts
for a cli-based game with an AI.
There's also impls/autoplay.ts
, which is an AI vs AI game.
The "AI" in this case is just a series of if conditions, ofcourse! :D