A javascript-based take on the card game War. Implemented as a library to be used in other applications. A sample application is included as a console-based node application,
A demo site can be found here.
- Node 6+
- babel-cli (npm install -g babel-cli);
npm run build && npm run app
A Vagrantfile is provided so that the code can be built and tested in a consistent environment (Ubuntu 14.04). Install Vagrant and Virtualbox, then simply run 'vagrant up' in the vm/ directory.
cd vm/
vagrant up
vagrant ssh
cd /code
npm install
npm run build && npm run app
A gulpfile is provided that will handle watch for file changes and handle transpiling, concatenation, and unit testing.
deck = new Deck(4, 13);
deck.shuffle();
game = new WarGame(deck, 4);
let gameOutcome;
do {
gameOutcome = game.playRound();
} while (!gameOutcome.gameOver)
Note: The algorithm for giving cards back to the winner has a tendency to create games that go on forever -- it's worthwhile to add a check to kill the game after a few thousand rounds, like below:
deck = new Deck(4, 13);
deck.shuffle();
game = new WarGame(deck, 4);
let gameOutcome;
let roundCount = 0;
do {
gameOutcome = game.playRound();
if (++roundCount > 5000) { break; }
} while (!gameOutcome.gameOver)
printResult(gameOutcome);
if (roundCount === 5001) {
console.log('The players got bored and moved on to something else');
} else {
console.log(`Winner: ${gameOutcome.winner.name} with ${gameOutcome.winner.score} cards`);
}
-
Game ends when a player has amassed all cards.
-
When two (or more) players go to war, each player draws 2 cards, the latter of which is used to resolve (or continue) the war. If a player runs out of cards during a war (by drawing their last card as the first card of the war), they lose, as the winner is determined by the maximum card rank of the longest draw of the round.
-
At the end of a round, the winner collects the cards and places them on the bottom of his/her discard pile in the order they were revealed by each player, starting with the first. E.g. In 4-player game where Player 1 goes to war with Player 4 and wins, the cards would be added to the bottom of Player 1's deck in the following order:
1. Player 1 Card 1
2. Player 1 Card 2
3. Player 1 Card 3 <-- the winning card
4. Player 2 Card 1
5. Player 3 Card 1
6. Player 4 Card 1
7. Player 4 Card 2
8. Player 4 Card 3
Tweaking this rule can have drastic effects on the round count of games. As currently implemented, 70%+ games go longer than 5000 rounds (unless there's a bug)
const game = new WarGame(Deck deck, int numPlayers);
The current deck. This will contain any cards that couldn't be dealt in the beginning of the game without having players start with different numbers of cards.
Deck deck = game.deck
Gets the 1-indexed player in the game. (Which is to say that the first player is 'Player 1', not 'Player 0')
Player player1 = game.getPlayer(1);
Advances the game by a single round, including resolving any wars.
RoundResult result = game.playRound();
The key difference is that the game never ends. At the end of the round, all cards are placed in a global discard pile. When a player runs out of cards, the discard pile is added to the deck, shuffled, and the cards are distributed evenly between all players in the game.
The api for EndlessWarGame includes everything from WarGame (It actually inherits from WarGame and overrides some of the methods). It also exposes a property for the discard pile.
const deck = new Deck(4, 13);
deck.shuffle();
const game = new EndlessWarGame(deck, 2);
const outcomes = [];
for (let i = 0; i < 1000000; ++i) {
outcomes.push(game.playRound());
}
console.log(`Wars: ${outcomes.filter(o => o.war).length}`);
console.log(`Player 1 Wins: ${outcomes.filter(o => o.winner && o.winner.name === 'Player 1').length}`);
console.log(`Player 2 Wins: ${outcomes.filter(o => o.winner && o.winner.name === 'Player 2').length}`);
Deck discardPile = game.discardPile
Summarizes the round.
[int][Card] draws;
A two-dimensional array containing the cards played by each player in the round, with the most recent card being at the highest index.
draws = [
[card, card, card], // Player 1's Cards
[card], // Player 2's Cards
[card], // Player 3's Cards
[card, card, card] // Player 4's Cards
]
bool war
True if a war occurred during the round, false otherwise.
Player winner
The Player object corresponding to the winner of the round. Can be null if players run out of cards in a war at the same time.
bool gameOver
In standard war, this means that one player has amassed all the cards and won the game. Not used in EndlessWarGame.
Represents a player and their current stack.
const player = new Player(playerName)
int cardCount
The current size of the player's stack.
Same as cardCount.
string name
The player's name.
Places a Card on the top of the player's stack.
/// 2-2, 3-3
let card = new Card(1, 1);
player.addCard(card);
// 2-2, 3-3, 1-1
Places a Card on the bottom of the player's stack.
// 2-2 3-3
let card = new Card(1, 1);
player.addCard(card);
// 1-1 2-2 3-3
Removes a card from the top of the player's stack.
// 2-2 3-3
let card = player.revealCard();
// card = 3-3
// stack = 2-2
Represents a deck of Cards.
const deck = new Deck(numberOfSuits, numberOfRanks)
The number of unique suits in the deck. The suits are strictly increasing between 1 and numSuits. Naming the suits (e.g. Clubs, Hearts, etc..) is left to the consumer.
int suits = deck.numSuits;
The number of ranked cards per suit. The ranks are strictly increasing between 1 and numRanks, inclusive.
int ranks = deck.numRanks;
The number of cards on the deck at this moment.
int cSize = deck.currentSize
The initial size of the deck (number of cards at deck creation).
int iSize = deck.initialSize
Removes a card from the top of the deck.
Card card = deck.deal();
Adds the card to the top of the deck.
const card = new Card(2,2).;
deck.add(card);
Randomly re-arranges the order of the cards that are currently in the deck.
const deck = new Deck(1, 3) //[1, 2, 3]
deck.shuffle(); //[?, ?, ?]
Represents a single card.
The deck will handle the creation of cards in a sensible order with respect to suits and ranks. However the Card class can be used to add additional cards. Maybe your war game needs a nuke? Add a card with a rank of 1000 to the deck. Guaranteed to end a war immediately, at least until someone makes a bigger nuke ;).
const card = new Card(int suit, int rank);
The suit of the current card.
int suit = card.suit;
The rank of the current card.
int rank = card.rank;
MIT, see LICENSE