@static {
// As this is going to be a live home-page sample, let anyone create
create { return true; }
invent { return true; }
// As this will spawn on demand, let's clean up when the viewer goes away
delete_on_close = true;
}
// What is the state of a square
enum SquareState { Open, X, O }
// who are the two players
public principal playerX;
public principal playerO;
// who is the current player
public principal current;
// how many wins per player
public int wins_X;
public int wins_O;
// how many stalemates
public int stalemates;
// personalized data for the connected player:
// show the player their role, a signal if it is their turn, and their wins
bubble your_role = playerX == @who ? "X" : (playerO == @who ? "O" : "Observer");
bubble your_turn = current == @who;
bubble your_wins = playerX == @who ? wins_X : (playerO == @who ? wins_O : 0);
// a record of the data in the square
record Square {
public int id;
public int x;
public int y;
public SquareState state;
}
// the collection of all squares
table<Square> _squares;
// show the board to all players
public formula board = iterate _squares;
// for visualization, we break the squares into rows
public formula row1 = iterate _squares where y == 0;
public formula row2 = iterate _squares where y == 1;
public formula row3 = iterate _squares where y == 2;
// when the document is created, initialize the squares and zero out the totals
@construct {
for (int y = 0; y < 3; y++) {
for (int x = 0; x < 3; x++) {
_squares <- { x:x, y:y, state: SquareState::Open };
}
}
wins_X = 0;
wins_O = 0;
stalemates = 0;
}
// when a player connects, assign them to either the X or O role. If there are more than two players, then they can observe.
@connected {
if (playerX == @no_one) {
playerX = @who;
if (playerO != @no_one) {
transition #initiate;
}
} else if (playerO == @no_one) {
playerO = @who;
if (playerX != @no_one) {
transition #initiate;
}
}
return true;
}
// open a channel for players to select a move
message Play { int id; }
channel<Play> play;
// the game is afoot
#initiate {
current = playerX;
transition #turn;
}
// test if the placed square produced a winning combination
procedure test_placed_for_victory(SquareState placed) -> bool {
for (int k = 0; k < 3; k++) {
// vertical lines
if ( (iterate _squares where x == k && state == placed).size() == 3) {
return true;
}
// horizontal lines
if ( (iterate _squares where y == k && state == placed).size() == 3) {
return true;
}
}
// diagonals
if ( (iterate _squares where y == x && state == placed).size() == 3 || (iterate _squares where y == 2 - x && state == placed).size() == 3 ) {
return true;
}
return false;
}
#turn {
// find the open spaces
list<Square> open = iterate _squares where state == SquareState::Open;
if (open.size() == 0) {
stalemates++;
transition #end;
return;
}
// ask the current play to choose an open space
if (play.decide(current, @convert<Play>(open)).await() as pick) {
// assign the open space to the player
let placed = playerX == current ? SquareState::X : SquareState::O;;
(iterate _squares where id == pick.id).state = placed;
if (test_placed_for_victory(placed)) {
if (playerX == current) {
wins_X++;
} else {
wins_O++;
}
transition #end;
} else {
transition #turn;
}
current = playerX == current ? playerO : playerX;
}
}
#end {
(iterate _squares).state = SquareState::Open;
transition #turn;
}