Hearts
Many of the bugs have been fixed, this is from an old version.
@static {
// anyone can create
create { return true; }
invent { return true; }
}
// we define the suit of a card
enum Suit {
Clubs:1,
Hearts:2,
Spades:3,
Diamonds:4,
}
// the rank of a card
enum Rank {
Two:2,
Three:3,
Four:4,
Five:5,
Six:6,
Seven:7,
Eight:8,
Nine:9,
Ten:10,
Jack:11,
Queen:12,
King:13,
Ace:14,
}
// where can a card be
enum Place {
Deck:1,
Hand:2,
InPlay:3,
Taken:4
}
// model the card and its location and ownership
record Card {
public int id;
public Suit suit;
public Rank rank;
private principal owner;
private int ordering;
private Place place;
private auto points = suit == Suit::Hearts ? 1 : (suit == Suit::Spades && rank==Rank::Queen ? 13 : 0);
// define a policy as to who can see the card
policy p {
// if it is in hand on in the pot, then only the owner of the card can see it
// the rules of hearts have cards face down
if (place == Place::Hand || place == Place::Taken) {
return @who == owner;
}
// if it is in the pot or in play, then anyone can see it
if (place==Place::InPlay) {
return true;
}
// otherwise, it is in the deck and thus not visible
return false;
}
method reset() {
ordering = Random.genInt();
owner = @no_one;
place = Place::Hand;
}
require p;
}
// the entire deck of cards
table<Card> deck;
// show the player hand (and let the privacy policy filter out by person)
bubble hand = iterate deck where place == Place::Hand where owner == @who order by id asc;
// show all cards in the pot (this would be a different way of defining hand)
bubble my_take = iterate deck where place == Place::Taken && owner == @who;
// no real constructor
message Empty {}
principal owner;
record Player {
public int id;
public principal link;
public int points;
viewer_is<link> int play_order;
}
table<Player> players;
@connected {
if ((iterate players where link==@who).size() > 0) {
return true;
}
if (players.size() < 4) {
players <- {
link:@who,
play_order: players.size(),
points:0
};
if (players.size() == 4) {
transition #setup;
}
return true;
}
return false;
}
// everyone in the game
public auto people = iterate players order by play_order;
// the players by their ordering
public auto players_ordered = iterate players order by play_order;
// are we actually playing the game?
public bool playing = false;
// how setup the game state
#setup {
// build the deck
foreach (s in Suit::*) {
foreach (r in Rank::*) {
deck <- {rank:r, suit:s, place:Place::Deck};
}
}
// normalize the players from 0 to 3
int normativeOrder = 0;
(iterate players order by play_order asc).play_order = normativeOrder++;
// shuffle and distribute the cards
transition #shuffle_and_distribute;
}
enum PassingMode { Across:0, ToLeft:1, ToRight:2, None:3 }
public PassingMode passing_mode;
#shuffle_and_distribute {
// it may be useful to allow methods on a record, fuck
(iterate deck).reset();
// distribute cards to players
Player[] op = (iterate players order by play_order).toArray();
for (int k = 0; k < 4; k++) {
if (op[k] as player) {
(iterate deck where owner == @no_one order by ordering limit 13).owner = player.link;
}
}
transition #pass;
}
message CardDecision {
int id;
}
channel<CardDecision[]> pass_channel;
// this is wanky, need arrays at a top level that are finite to help...
principal player1;
principal player2;
principal player3;
principal player4;
principal current;
#pass {
if (passing_mode == PassingMode::None) {
transition #start_play;
return;
}
// this is wanky as fuck, and I don't like it. We have this fundamental problem of what if there are not enough players, then how does this fail...
// we should consider a @fatal keyword to signal that a game is just fucked
Player[] op = (iterate players order by play_order).toArray();
if (op[0] as player) {
player1 = player.link;
}
if (op[1] as player) {
player2 = player.link;
}
if (op[2] as player) {
player3 = player.link;
}
if (op[3] as player) {
player4 = player.link;
}
// what does an await on no_one mean, it means the whole thing is fucked
// we really need a future array since this has some awkward stuff
future<maybe<CardDecision[]>> pass1 = pass_channel.choose(player1, @convert<CardDecision>(iterate deck where owner==player1), 3);
future<maybe<CardDecision[]>> pass2 = pass_channel.choose(player2, @convert<CardDecision>(iterate deck where owner==player2), 3);
future<maybe<CardDecision[]>> pass3 = pass_channel.choose(player3, @convert<CardDecision>(iterate deck where owner==player3), 3);
future<maybe<CardDecision[]>> pass4 = pass_channel.choose(player4, @convert<CardDecision>(iterate deck where owner==player4), 3);
// the reason we do the futures above and then await them below like this is so all players can pass at the same time.
// the problem at hand is that the await will consume, so non-awaited futures will cause the client to sit dumbly... this can be fixed easily I think
// by having the make_future<> check the stream and pre-drain the queue and allow the await to short-circuit with the provide option
if (pass1.await() as decision1) {
if (pass2.await() as decision2) {
if (pass3.await() as decision3) {
if (pass4.await() as decision4) {
if (passing_mode == PassingMode::ToRight) {
foreach (dec in decision1) {
(iterate deck where id == dec.id).owner = player2;
}
foreach (dec in decision2) {
(iterate deck where id == dec.id).owner = player3;
}
foreach (dec in decision3) {
(iterate deck where id == dec.id).owner = player4;
}
foreach (dec in decision4) {
(iterate deck where id == dec.id).owner = player1;
}
} else if (passing_mode == PassingMode::ToLeft) {
foreach (dec in decision1) {
(iterate deck where id == dec.id).owner = player4;
}
foreach (dec in decision2) {
(iterate deck where id == dec.id).owner = player1;
}
foreach (dec in decision3) {
(iterate deck where id == dec.id).owner = player2;
}
foreach (dec in decision4) {
(iterate deck where id == dec.id).owner = player3;
}
} else if (passing_mode == PassingMode::Across) {
foreach (dec in decision1) {
(iterate deck where id == dec.id).owner = player3;
}
foreach (dec in decision2) {
(iterate deck where id == dec.id).owner = player4;
}
foreach (dec in decision3) {
(iterate deck where id == dec.id).owner = player1;
}
foreach (dec in decision4) {
(iterate deck where id == dec.id).owner = player2;
}
}
}}}}
transition #start_play;
}
public int played = 0;
public Suit suit_in_play;
public bool points_played = false;
public auto in_play = iterate deck where place == Place::InPlay order by rank desc;
#start_play {
// no cards hae been played
played = 0;
points_played = false;
// assign a player to current
current = player1;
if ( (iterate deck where rank == Rank::Two && suit == Suit::Clubs)[0] as two_clubs) {
current = two_clubs.owner;
} // otherwise, @fatal
transition #play;
}
channel<CardDecision> single_play;
// how to attribute this to a person
public principal last_winner;
#play {
list<Card> choices = iterate deck where owner == current && place == Place::Hand && rank == Rank::Two && suit == Suit::Clubs;
if (choices.size() == 0) {
choices = iterate deck where owner==current && place == Place::Hand && (
played == 0 && (points_played || points == 0) ||
played > 0 && suit_in_play == suit
);
}
if (choices.size() == 0) { // anything in hand
choices = iterate deck where owner==current && place == Place::Hand;
}
if (choices.size() == 0) {
transition #score;
return;
}
future<maybe<CardDecision>> playX = single_play.decide(current, @convert<CardDecision>(choices));
if (playX.await() as dec) {
if ((iterate deck where id == dec.id)[0] as cardPlayed) {
cardPlayed.place = Place::InPlay;
if (cardPlayed.points > 0) {
points_played = true;
}
if (played == 0) {
suit_in_play = cardPlayed.suit;
}
}
}
// if the number of cards played is less than 4, then next player; otherwise, decide winner of pot and award points
// TODO: need finite arrays and cyclic integers
if (current == player1) {
current = player2;
} else if (current == player2) {
current = player3;
} else if (current == player3) {
current = player4;
} else if (current == player4) {
current = player1;
}
if (played == 3) {
if ( (iterate deck where place == Place::InPlay && suit == suit_in_play order by rank desc limit 1)[0] as winner) {
(iterate deck where place == Place::InPlay).owner = winner.owner;
last_winner = winner.owner;
}
(iterate deck where place == Place::InPlay).place = Place::Taken;
played = 0;
current = last_winner;
if( (iterate deck where owner == current && place == Place::Hand).size() == 0) {
transition #score;
return;
}
} else {
played++;
}
transition #play;
}
public int points_awarded = 0;
#score {
// award points
foreach(p in iterate players) {
int local_points = 0;
foreach(c in iterate deck where owner == p.link && place == Place::Taken) {
local_points += c.points;
}
if (local_points == 26) {
foreach(p2 in iterate players where link != p.link) {
p2.points += 26;
points_awarded += 26;
}
} else {
p.points += local_points;
points_awarded += local_points;
}
}
passing_mode = passing_mode.next();
transition #shuffle_and_distribute;
}