Created
September 16, 2013 05:22
-
-
Save aghull/6576943 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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