Skip to content

Instantly share code, notes, and snippets.

@aghull
Created September 16, 2013 05:22
Show Gist options
  • Save aghull/6576943 to your computer and use it in GitHub Desktop.
Save aghull/6576943 to your computer and use it in GitHub Desktop.
hearts = new Game();
var cards = new Pieces(['2C',]).attributes({
suit: hearts.suitOf,
value: hearts.valueOf,
points: hearts.pointsOf
});
hearts.deck = new Space({cards: cards});
hearts.players = new Players(4).attributes({
score: 0
}).spaces({
hand: new Space({cards: cards}),
played: new Space({card: cards.single()}),
tricks: new Space({cards: cards})
});
hearts.round = 0;
hearts.win = 100;
/**
* - game.find('robber') // all pieces in the game of a type
game.robber // direct ref
* - game.find('tokens on board') // all of a type in a space
game.board.tokens // collection
* - game.find('player:2 armies in russia') // of a type and modified by a player (where an 'each' was supplied)
game.countries('russia').armies({player:2})
game.player(2).armies.within('russia')
* - game.find('suit:H card in player:1 hand') // can modify a piece and space
game.player(1).hand.cards({suit:'H'})
game.cards({suit:'H'}).within('hand', {player:1})
* - game.find('not suit:H card in my hand') // special modifiers
game.player(me).hand.cards.except({suit:'H'})
* - game.find('wheat in player:1 hand') // all pieces in a player's hand of a specific name
game.player(1).hand.cards('wheat')
game.wheat.within('hand', {player:1})
* - game.find('not my armies in my country') // INVADERS!
game.countries({player:me}).armies.except({player:me})
* - game.find('suit of card in player:1 played') // another attribute can be returned instead of name by specifying "<attribute> of"
* - game.find('quantity of suit:H card in player:1 hand') // quantity is a special attribute
* - game.find('first suit:H card in player:1 hand') // first/last/the are short-cuts to get one element out of an array
* - game.find('highest:value suit:H card in player:1 hand') // can also use custom attributes to get one element out of an array
* - game.find('presence of suit:H card in player:1 hand') // presence/absence return true/false depending on whether any items match
* - game.find('player of hand with the 2c') // 'with' is the inverse of 'in' and finds items that contain other items
*/
hearts.moves = {
play: new Move({
description: 'Play a card from your hand onto the board',
piece: function(move) { return move.player.hand.cards(); },
to: function(move) { return move.player.played; },
valid: function(move) {
return (this.led===null // im leading
|| move.card.suit()==this.led.suit() // matches led
|| move.player.hand.cards({ suit: this.led.suit() }).length==0) // or I have none
&& (this.heartsBroken // hearts already broken
|| move.card.points()==0 // this is not a point card
|| move.player.hand.cards({ points: 0 }).length==0); // player only has point cards
},
after: function(move) {
if (move.card.suit()=='H') { // in this version, only a true heart can break hearts
this.heartsBroken = true;
}
}
}),
passCards: new Move({
description: function() { return 'Pass 3 cards ' + ['to your left', 'across', 'to your right'](this.round % 4); },
pieces: function(move) { return move.player.hand.cards(); },
exactly: 3,
to: function(move) { return move.player.after(this.round % 4).hand; }
})
};
// base collection methods (players, spaces, pieces)
([number], [hash attributes]) // refine collection, auto-first if this.single
except([string name], [hash attributes]) // negation
each
map
list // (pluck)
sum(attribute)
highest, lowest
game.players/player // players coll/single
new() // number
space/s() // creates space(s)
piece/s() // creates piece(s)
<name of space> // owned spaces collection
<name of piece> // owned pieces collection
having([string name], [hash attributes]) // return player' spaces that has pieces matches by name and/or attributes
owning([string name], [hash attributes]) // return players that own pieces matches by name and/or attributes
// player instance, has spaces, pieces, holds pieces through spaces
spaces // collection
pieces // collection
<name of space> // owned spaces collection
<name of piece> // owned pieces collection
<name of attr> // value
after // player after this one
has([string name], [hash attributes]) // return whether player have pieces matches by name and/or attributes
owns([string name], [hash attributes]) // return whether player owns pieces matches by name and/or attributes
// spaces coll/single
new() // assigns pieces
space/s() // creates space(s)
piece/s() // assigns piece(s)
<name of space> // nested spaces coll
<name of piece> // pieces coll
having([string name], [hash attributes]) // return space that has pieces matches by name and/or attributes
// space instance
<name of space> // nested spaces coll
<name of piece> // pieces coll
has([string name], [hash attributes]) // return whether space has pieces matches by name and/or attributes
// pieces coll
new() // assigns attributes
have attributes
within([string name], [hash attributes]) // return pieces within a space by filter
// piece instance
player
space
moves have spaces
/**
* Pieces are just strings that belong to a type. Piece types must be completely distinct, even when their identity is hidden (e.g. face
* down). E.g. Catan resources below are a single 'piece' not separate pieces, since they look identical when face down. When pieces have
* distinct identities, these are listed in arrays with each element containing a piece definition. Piece names and piece type names must
* all be mutually distinct for a given game.
*
* quantity is 1 unless specified, may be 'unlimited'
*
* may be each: 'player', 'team', some other piece
*
* pieces can be accessed as an array of piece names by:
* - game.find('all on board') // all pieces on a space, 'on/in' means all pieces or spaces inside of another space
* - game.find('robber') // all pieces in the game of a type
* - game.find('tokens on board') // all of a type in a space
* - game.find('player:2 armies in russia') // of a type and modified by a player (where an 'each' was supplied)
* - game.find('suit:H card in player:1 hand') // can modify a piece and space
* - game.find('not suit:H card in my hand') // special modifiers
* - game.find('player:1 wheat in hand') // all pieces in a player's hand of a specific name
* - game.find('not my armies in my country') // INVADERS!
* - game.find('suit of card in player:1 played') // another attribute can be returned instead of name by specifying "<attribute> of"
* - game.find('quantity of suit:H card in player:1 hand') // quantity is a special attribute
* - game.find('first suit:H card in player:1 hand') // first/last/the are short-cuts to get one element out of an array
* - game.find('highest:value suit:H card in player:1 hand') // can also use custom attributes to get one element out of an array
* - game.find('presence of suit:H card in player:1 hand') // presence/absence return true/false depending on whether any items match
* - game.find('player of hand with the 2c') // 'with' is the inverse of 'in' and finds items that contain other items
*
* <expr>: [<value> of] [<adjective>,...] <node> [on|in|with [<adjective>,...] <node>]
* <node>: all | <piece> | <space>
* <adjective>: [not] ( first[:<int>] | last[:<int>] | nth:<int> | highest:<attribute> | lowest:<attribute> | my | <attribute>:<string> )
* <value>: presence | absence | quantity | sum:<attribute> | <attribute>
*
* e.g.:
* card: [ '1S', '2S', '3S', '4S', '5S', { joker: 2 } ] => equivalent to [ '1S', '2S', '3S', '4S', '5S', 'joker', 'joker' ]
* robber: 1 => name of piece is identical to name of type, just 'robber'
* resource: [ { wheat: 'unlimited' }, { brick: 'unlimited' }, { stone: 'unlimited' }, { lumber: 'unlimited' }, { wool: 'unlimited' } ]
* token: { each: 'player' } => all named 'token' but can be distinguished by player
* army: { quantity: 20, each: 'player' } => can be distinguished by player and location but otherwise indistinguishable
* token: [ { king: { each: 'player' } }, { pawn: { quantity: 4, each: 'player' } } ] => each player has 4 pawns and a king but sometimes they can look the same
*/
pieces: {
'card': ['2H', '3H']
},
/**
* Pieces and spaces automatically receive some attributes, like player. Add extra attributes for pieces here. Specify which function can
* accept the identity of the piece and will return the attribute value. This will be added as attributes on the piece elements to allow
* quick identificaion and selection.
*/
attributes: {
card: {
suit: this.suitOf,
value: this.valueOf,
points: this.pointsOf
}
},
/**
* Moves are granted and revoked to players with the game methods can, cannot and must. Each take a player number or 'all' or 'none' and
* an action or array of actions. Each returns a promise that can have its then method called with success and failure callbacks made. A
* fail method can also be called which takes only the failure callback.
*/
moves: {
play: _.extend(Typ.Moves.MovePiece, {
description: 'Play a card from your hand onto the board',
pieces: 'card in my hand',
exactly: 1,
to: 'my played',
valid: function(move) {
return (this.led()===null // im leading
|| move.find('suit of the card')==this.led() // matches led
|| this.find('absence of suit:' + this.led() + ' card in my hand')) // or I have none
&& (state.heartsBroken // hearts already broken
|| move.find('points of the card')==0 // this is not a point card
|| this.find('absence of points:0 card in my hand')); // player only has point cards
},
after: function(move) {
if (move.find('suit of the card')=='H') { // in this version, only a true heart can break hearts
state.heartsBroken = true;
}
},
}),
passCards: _.extend(Typ.Moves.MovePiece, {
description: function() { return 'Pass 3 cards ' + ['to your left', 'across', 'to your right'](state.round % 4); },
pieces: 'card in my hand',
exactly: 3,
to: function(move) {
return 'player:' + this.playerAfter(move.player, state.round % 4) + ' hand';
},
})
},
/**
* This method will be called to bootstrap your game. Do your setup, kick off your player actions, and define your turns.
*/
init: function() {
var state = this.state;
this.players.setup(4);
this.set({
trump: 'H',
round: 0,
score: [0,0,0,0],
tricks: [0,0,0,0],
win: 100
});
this.when('start', function() {
become('deal');
});
this.when('deal', function() {
this.move('card', 'deck');
this.shuffle('deck');
this.players.each(function(player) {
this.move('first:13 card', 'my hand');
});
state.lead = 0;
state.heartsBroken = false;
this.become(state.round%4==0 ? 'lead' : 'pass cards');
});
this.when('pass cards', function() {
return this.must('all', 'passCards').then(function() {
state.lead = this.find('player of the hand with 2c');
this.become('lead');
});
});
this.when('lead', function() {
state.current = state.lead;
this.become('play card');
});
this.when('play card', function() {
this.must(state.current, 'play').then(function() {
if (this.find('quantity of card in played')==4) {
this.become('win trick');
}
state.current = this.playerAfter(state.current);
this.become('play card');
});
});
this.when('win trick', function() {
var winner = this.trickWinner();
state.tricks[winner]++;
this.move('card in played', 'player:' + winner + ' tricks');
if (this.find('presence of card in hand')) {
state.lead = winner;
this.become('play trick');
} else {
this.become('end deal');
}
});
this.when('end deal', function() {
var points = this.players.each(function(player) {
return this.find('sum:points of card in my tricks');
});
if (_.max(points)==26) {
points = _.map(points, function(p) { return 26-p; }); // reverse the scores if someone took all the points
}
this.players.each(function(player) {
this.state.score[player] += points[player];
});
if (_.max(this.state.score) >= this.state.win) {
this.win(this.playerWithHighest(state.score));
} else {
this.become('deal');
}
});
},
/**
* Define these queries to set which pieces are visible to which players
*/
isVisible: function() {
return ['card in played', 'card in my hand'];
},
led: function() {
return this.find('suit of the card in player:' + state.lead + ' played');
},
trickWinner: function() {
return this.find('player of played with highest:value suit:H card')
|| this.find('player of played with highest:value suit:' + this.led() + ' card');
},
suitOf: function(card) {
return card ? card[1] : null;
},
valueOf: function(card) {
if (card===null) { return null; }
if (card[0]=='j') { return 11; }
if (card[0]=='q') { return 12; }
if (card[0]=='k') { return 13; }
if (card[0]=='a') { return 14; }
return card[0];
},
pointsOf: function(card) {
if (card=='QS') { return 13; }
if (this.suitOf(card)=='H') { return 1; }
return 0;
},
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment