Skip to content

Instantly share code, notes, and snippets.

@grom358
Created August 21, 2012 06:47
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save grom358/3412768 to your computer and use it in GitHub Desktop.
Save grom358/3412768 to your computer and use it in GitHub Desktop.
Dominion Simulator for the base set
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach
if ( !Array.prototype.forEach ) {
Array.prototype.forEach = function(fn, scope) {
for(var i = 0, len = this.length; i < len; ++i) {
fn.call(scope || this, this[i], i, this);
}
}
}
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/indexOf
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
"use strict";
if (this == null) {
throw new TypeError();
}
var t = Object(this);
var len = t.length >>> 0;
if (len === 0) {
return -1;
}
var n = 0;
if (arguments.length > 0) {
n = Number(arguments[1]);
if (n != n) { // shortcut for verifying if it's NaN
n = 0;
} else if (n != 0 && n != Infinity && n != -Infinity) {
n = (n > 0 || -1) * Math.floor(Math.abs(n));
}
}
if (n >= len) {
return -1;
}
var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
for (; k < len; k++) {
if (k in t && t[k] === searchElement) {
return k;
}
}
return -1;
}
}
Array.prototype.shuffle = function() {
var tmp, current, top = this.length;
if (top) {
while(--top) {
current = Math.floor(Math.random() * (top + 1));
tmp = this[current];
this[current] = this[top];
this[top] = tmp;
}
}
return this;
};
Array.prototype.fill = function(num, value) {
for (var i = 0; i < num; ++i) {
this.push(value);
}
return this;
};
Array.prototype.find = function(fun) {
for (var i = 0, len = this.length; i < len; ++i) {
if (fun(this[i]) {
return this[i];
}
}
return null;
};
/**
* Move the contents of this array to another array
*/
Array.prototype.moveTo = function(dest) {
dest.push.apply(dest, this); // Move this array to dest
this.length = 0; // Clear this array
};
/**
* Helper used by Dominion engine. Represents player in the game.
*/
function DominionPlayer(dominion, nick) {
this.dominion = dominion;
this.nick = nick;
this.client = null; // The attached client
// Player starts with 7 copper and 3 estate
this.deck = [].fill(7, 'Copper').fill(3, 'Estate').shuffle();
// Setup for first turn
this.turnNo = 0;
this.hand = [];
this.playArea = [];
this.discard = [];
this.cleanUp();
}
/**
* Put the card into play
*/
DominionPlayer.prototype.play = function(cardName) {
var handIndex = this.hand.indexOf(cardName);
if (handIndex >= 0) {
var card = dominionCards[cardName];
if (card && card.isPlayable) {
if (card.isAction && this.buyPhase) {
// TODO dominion.notify('error', 'Can not play action in buy phase');
return;
}
if (card.isTreasure) {
this.buyPhase = true;
}
this.hand.splice(handIndex, 1);
this.playArea.push(cardName);
// TODO dominion.notify('play', this, cardName)
card.onPlay(this.dominion);
}
}
};
/**
* Perform clean-up
*/
DominionPlayer.prototype.cleanUp = function() {
this.playArea.moveTo(this.discard);
this.hand.moveTo(this.discard);
this.drawCards(5);
this.actions = 1;
this.buys = 1;
this.coins = 0;
this.buyPhase = false;
++this.turnNo;
};
/**
* Called at end of game to score and victory card contents
*/
DominionPlayer.prototype.endGame = function() {
this.playArea.moveTo(this.discard);
this.hand.moveTo(this.discard);
// TODO Filter out victory cards from deck
};
/**
* Draw card from deck. Handles reshuffle if required. Returns null if no card
* can be drawn.
*/
DominionPlayer.prototype.draw = function() {
if (this.deck.length === 0) {
if (this.discard.length === 0) {
// No cards left to draw
return null;
}
// Shuffle discard into deck
this.deck = this.discard.shuffle();
this.discard = [];
// TODO dominion.notify('reshuffle', this);
}
return this.deck.pop();
};
/**
* Handles drawing multiple cards. Stops drawing if run out of cards to draw.
*/
DominionPlayer.prototype.multiDraw = function(num, callback) {
num = num || 1;
for (var i = 0; i < num; i++) {
var cardName = this.draw();
if (cardName !== null) {
callback(cardName);
} else {
break;
}
}
};
/**
* +X Card(s) - must draw X more Cards immediately
*/
DominionPlayer.prototype.drawCards = function(num) {
var self = this;
this.multiDraw(num, function(cardName) {
self.hand.push(cardName);
});
};
/**
* +X Action(s) - : can play X more Actions in Action phase
*/
DominionPlayer.prototype.addActions = function(num) {
num = num || 1;
this.actions += num;
};
/**
* +X Buy(s) - can buy X more cards in the Buy phase
*/
DominionPlayer.prototype.addBuys = function(num) {
num = num || 1;
this.buys += num;
};
/**
* +X coin(s) - can spend X more coins this turn
*/
DominionPlayer.prototype.addCoins = function(num) {
num = num || 1;
this.coins += num;
};
/**
* Gain - take a card and put it in your Discard pile
*/
DominionPlayer.prototype.gain = function(cardName, dest) {
dest = dest || this.discard;
if (this.dominion.supply[cardName] > 0) {
dest.push(cardName);
}
};
DominionPlayer.prototype.gainUpto = function(maxCost, dest) {
dest = dest || this.discard;
// TODO: Ask player to choose card to gain
// dest.push(cardName);
};
/**
* Discard - put cards face-up in your Discard pile
*/
DominionPlayer.prototype.discard = function(num) {
// TODO : Request client for cards to discard
};
/**
* Reduce player hand to certain number of cards
*/
DominionPlayer.prototype.maxHandSize = function(num) {
if (this.hand.length > num) {
var requiredDiscards = this.hand.length - num;
this.discard(requiredDiscards);
}
};
/**
* Controls a single game of dominion. Implements all the rules and provides
* hooks for the implementation of cards.
*/
function Dominion(playerNames, kingdomCards) {
var self = this;
var supply = {
'Copper': 60,
'Silver': 40,
'Gold': 30,
'Estate': 8,
'Duchy': 8,
'Province': 8,
'Curse': 10
};
if (playerNames.length > 2) {
supply.Estate = 12;
supply.Duchy = 12;
supply.Province = 12;
supply.curse += (playerNames.length - 2) * 10;
}
kingdomCards.forEach(function(cardName) {
supply[cardName] = 10;
});
this.supply = supply;
this.trash = [];
var players = [];
playerNames.forEach(function(playerName) {
players.push(new DominionPlayer(self, playerName));
});
this.players = players;
this.player = this.players[0]; // Current player
this.clients = [];
}
/**
* Attach the client
*/
Dominion.prototype.attach = function(client) {
var self = this;
// If client is for player, associate it with player so we can request
// them todo stuff
var player = this.players.find(function(player) {
return player.nick === client.nick;
});
if (player !== null) {
player.client = client;
}
this.clients.push(client);
};
/**
* Play game of dominion
*/
Dominion.prototype.run = function() {
while (!this.isGameOver()) {
this.actionPhase();
this.buyPhase();
this.cleanUp();
// End of turn
this.players.push(this.players.shift()); // Move player to end of turn order
}
// Calculate victory points of each player
};
/**
* Action phase
*/
Dominion.prototype.actionPhase = function() {
// TODO
};
/**
* Buy phase
*/
Dominion.prototype.buyPhase = function() {
// TODO
};
/**
* Cleanup phase
*/
Dominion.prototype.cleanUp = function() {
this.player.cleanUp();
};
/**
* The game ends at the end of any player’s turn when either:
* 1) the Supply pile of Province cards is empty or
* 2) any 3 Supply piles are empty
*/
Dominion.prototype.isGameOver = function() {
var suppy = this.supply;
if (supply.Province === 0) {
return true;
}
var emptyPiles = 0;
for (var cardName in supply) {
if (supply[cardName] === 0) {
emptyPiles++;
}
}
return emptyPiles >= 3;
};
/**
* Perform an action on each other player
*/
Dominion.prototype.eachOtherPlayer = function(callback) {
for (var i = 1, len = this.players; i < len; ++i) {
var player = this.players[i];
callback(player);
}
};
/**
* Perform an action on each player
*/
Dominion.prototype.eachPlayer = function(callback) {
this.players.forEach(callback);
};
/**
* Database of Dominion Cards
*/
var dominionCards = {
'Adventurer': {
type: 'Action',
cost: 6,
rules: 'Reveal cards from your deck until you reveal 2 Treasure cards. Put those Treasure cards in your hand and discard the other revealed cards.',
onPlay: function(dominion) {
// TODO
}
},
'Bureaucrat': {
type: ['Action', 'Attack'],
cost: 4,
rules: 'Gain a silver card; put it on top of your deck. Each other player reveals a Victory card from his hand and puts it on his deck (or reveals a hand with no Victory cards).',
onPlay: function(dominion) {
dominion.player.gain('Silver', dominion.player.deck);
dominion.eachOtherPlayer(function(player) {
// TODO
});
}
},
'Cellar': {
type: 'Action',
cost: 2,
rules: '+1 Action, Discard any number of cards. +1 Card per card discarded.',
onPlay: function(dominion) {
dominion.player.addActions(1);
// TODO
}
},
'Chancellor': {
type: 'Action',
cost: 3,
rules: '+2 Coins, You may immediately put your deck into your discard pile.',
onPlay: function(dominion) {
dominion.player.addCoins(2);
// TODO
}
},
'Chapel': {
type: 'Action',
cost: 2,
rules: 'Trash up to 4 cards from your hand.',
onPlay: function(dominion) {
// TODO
}
},
'Copper': {
type: 'Treasure',
coin: 1,
cost: 0
},
'Council Room': {
type: 'Action',
cost: 5,
rules: '+4 Cards, +1 Buy, Each other player draws a card.',
onPlay: function(dominion) {
dominion.player.drawCards(4);
dominion.player.addBuys(1);
dominion.eachOtherPlayer(function(player) {
player.drawCards(1);
});
}
},
'Curse': {
type: 'Victory',
cost: 0,
victory: -1
},
'Duchy' : {
type: 'Victory',
cost: 5,
victory: 3
},
'Estate': {
type: 'Victory',
cost: 2,
victory: 1
},
'Feast': {
type: 'Action',
cost: 4,
rules: 'Trash this card. Gain a card costing up to 5 Coins.',
onPlay: function(dominion) {
var playArea = dominion.player.playArea;
// NOTE: The Feast may have already been trashed. For example,
// when you Throne Room a Feast
if (playArea.length > 0) {
var cardName = playArea[playArea.length - 1];
if (cardName === 'Feast') {
playArea.pop();
dominion.trash.push('Feast');
}
}
dominion.player.gainUpto(5);
}
},
'Festival': {
type: 'Action',
cost: 5,
rules: '+2 Actions, +1 Buy, +2 Coins.',
onPlay: function(dominion) {
dominion.player.addActions(2);
dominion.player.addBuys(1);
dominion.player.addCoins(2);
}
},
'Gardens': {
type: 'Victory',
cost: 4,
rules: 'Worth 1 Victory for every 10 cards in your deck (rounded down).',
onVictory: function(dominion) {
return parseInt(dominion.player.deck.length / 10);
}
},
'Gold' : {
type: 'Treasure',
coin: 3,
cost: 6
},
'Laboratory': {
type: 'Action',
cost: 5,
rules: '+2 Cards, +1 Action.',
onPlay: function(dominion) {
dominion.player.drawCards(2);
dominion.player.addActions(1);
}
},
'Library': {
type: 'Action',
cost: 5,
rules: 'Draw until you have 7 cards in hand. You may set aside any Action cards drawn this way, as you draw them; discard the set aside cards after you finish drawing.',
onPlay: function(dominion) {
// TODO
}
},
'Market': {
type: 'Action',
cost: 5,
rules: '+1 Card, +1 Action, +1 Buy, +1 Coin.',
onPlay: function(dominion) {
dominion.player.drawCards(1);
dominion.player.addActions(1);
dominion.player.addBuys(1);
dominion.player.addCoins(1);
}
},
'Militia': {
type: ['Action', 'Attack'],
cost: 4,
rules: '+2 Coins, Each other player discards down to 3 cards in his hand.',
onPlay: function(dominion) {
dominion.player.addCoins(2);
dominion.eachOtherPlayer(function(player) {
player.maxHandSize(3);
});
}
},
'Mine': {
type: 'Action',
cost: 5,
rules: 'Trash a Treasure card from your hand. Gain a Treasure card costing up to 3 Coins more; put it into your hand.',
onPlay: function(dominion) {
// TODO
// 1) Filter treasure cards from hand
// 2) Ask player which card to trash
// 3) Allow them to gain treasure costing upto 3 coins more
// dominion.player.gain(cardName, dominion.player.hand)
}
},
'Moat': {
type: ['Action', 'Reaction'],
cost: 2,
rules: '+2 Cards, When another player plays an Attack card, you may reveal this from your hand. If you do, you are unaffected by that Attack.',
onPlay: function(dominion) {
dominion.player.drawCards(2);
},
onAttack: function(dominion) {
// TODO: Implement reaction
}
},
'Moneylender': {
type: 'Action',
cost: 4,
rules: 'Trash a Copper from your hand. If you do, +3 Coins.',
onPlay: function(dominion) {
// TODO
// if (dominion.player.trash('Copper')) {
// dominion.player.addCoins(3);
// }
}
},
'Province': {
type: 'Victory',
cost: 8,
victory: 8
},
'Remodel': {
type: 'Action',
cost: 4,
rules: 'Trash a card from your hand. Gain a card costing up to 2 Coins more than the trashed card.',
onPlay: function(dominion) {
// TODO
// var card = dominion.player.trash();
// if (card !== null) {
// dominion.player.gainUpto(card.cost + 2);
// }
}
},
'Silver': {
type: 'Treasure',
coin: 2,
cost: 3
},
'Smithy': {
type: 'Action',
cost: 4,
rules: '+3 Cards.',
onPlay: function(dominion) {
dominion.player.drawCards(3);
}
},
'Spy': {
type: ['Action', 'Attack'],
cost: 4,
rules: '+1 Card, +1 Action, Each player (including you) reveals the top card of his deck and either discards it or puts it back, your choice.',
onPlay: function(dominion) {
dominion.player.drawCards(1);
dominion.player.addActions(1);
dominion.eachPlayer(function(player) {
var revealed = player.reveal(1);
// TODO: Ask player to keep or discard
});
}
},
'Thief': {
type: ['Action', 'Attack'],
cost: 4,
rules: 'Each other player reveals the top 2 cards of his deck. If they revealed any Treasure cards, they trash one of them that you choose. You may gain any or all of these trashed cards. They discard the other revealed cards.',
onPlay: function(dominion) {
dominion.eachOtherPlayer(function(player) {
var revealed = player.reveal(2);
// TODO
// 1) Filter to treasure card
// 2a) If one treasure, trash it
// 2b) if two treasure, ask player to pick one, trash it
// 3) discard the non treasure cards
});
// TODO 4) Ask player to pick trashed cards they wish to gain
}
},
'Throne Room': {
type: 'Action',
cost: 4,
rules: 'Choose an Action card in your hand. Play it twice.',
onPlay: function(dominion) {
// TODO
// 1) Filter to action cards
// 2) Ask player to pick an action
// var card = dominionCards[cardName];
// card.onPlay(dominion);
// card.onPlay(dominion);
}
},
'Village': {
type: 'Action',
cost: 3,
rules: '+1 Card, +2 Actions.',
onPlay: function(dominion) {
dominion.player.drawCards(1);
dominion.player.addActions(2);
}
},
'Witch': {
type: ['Action', 'Attack'],
cost: 5,
rules: '+2 Cards, Each other player gains a Curse card.',
onPlay: function(dominion) {
dominion.player.drawCards(2);
dominion.eachOtherPlayer(function(player) {
player.gain('Curse');
});
}
},
'Woodcutter': {
type: 'Action',
cost: 3,
rules: '+1 Buy, +2 Coins.',
onPlay: function(dominion) {
dominion.player.addBuys(1);
dominion.player.addCoins(2);
}
},
'Workshop': {
type: 'Action',
cost: 3,
rules: 'Gain a card costing up to 4 Coins.',
onPlay: function(dominion) {
dominion.gainUpto(4);
}
}
};
var randomizerCards = [];
var game;
(function() {
for (var cardName in dominionCards) {
var card = dominionCards[cardName];
if (Array.isArray(card.type)) {
card.type.forEach(function(typeName) {
var typeKey = 'is' + typeName;
card[typeKey] = true;
});
} else {
var typeKey = 'is' + card.type;
card[typeKey] = true;
}
if (card.isAction || card.isTreasure) {
card.isPlayable = true;
}
// Create on play action for basic treasure cards
if (card.isTreasure && !card.onPlay) {
card.onPlay = function(dominion) {
dominion.player.addCoins(card.coin);
};
}
randomizerCards.push(cardName);
}
// Test
var firstGameSet = [
'Cellar', 'Market', 'Militia', 'Mine', 'Moat',
'Remodel', 'Smithy', 'Village', 'Woodcutter', 'Workshop'
];
game = new Dominion(['grom'], firstGameSet);
game.player.play('Copper');
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment