Checking The Win ConditionΒΆ

Lastly, we need to check if the game has ended and declare a winner if so.

A game can end in one of two ways:

  • A player has three in a row (row, column, or diagonal) and wins
  • All positions on the board have been filled and no player fulfills the winning condition, resulting in a draw

To check for wins, we need to iterate over the rows, columns, and diagonals and see if all tokens belong to the same player:

# check a row, column, or diagonal ($delta specifies the direction)
DEF check_group $start_position $delta:
  DEBUG CONCAT CONCAT "check_group: " STR $start_position STR $delta;
  ASSIGN 'x_ct' 0;
  ASSIGN 'o_ct' 0;
  ASSIGN 'k' 0;
  ASSIGN 'pos' $start_position;
  WHILE LESS $k 3:
    ASSIGN 'token' GET_BY_POSITION GET_CONTAINER CONCAT STR GET_POSITION_ELT $pos 0 STR GET_POSITION_ELT $pos 1 0;
    ASSIGN 'token_type' NULL;
    IF NOT IS_NULL $token:
      ASSIGN 'token_type' TOKEN_GET_PROP $token 'token_type';
    END_IF
    DEBUG $token_type;
    DEBUG $token;
    IF EQUAL $token_type 'x':
      ASSIGN 'x_ct' PLUS $x_ct 1;
    END_IF
    IF EQUAL $token_type 'o':
      ASSIGN 'o_ct' PLUS $o_ct 1;
    END_IF
    ASSIGN 'k' PLUS $k 1;
    ASSIGN 'pos' TUPLE2
      PLUS GET_POSITION_ELT $pos 0 GET_POSITION_ELT $delta 0
      PLUS GET_POSITION_ELT $pos 1 GET_POSITION_ELT $delta 1;
  END_WHILE
  DEBUG STR TUPLE2 $x_ct $o_ct;
  IF EQUAL $x_ct 3:
    RETURN 'x';
  END_IF
  IF EQUAL $o_ct 3:
    RETURN 'o';
  END_IF
  RETURN NULL;
END_DEF

DEF get_winner:
  DEBUG "computing winner";
  ASSIGN 'i' 0;
  WHILE LESS $i 3:
    # check row
    ASSIGN 'tmp' check_group TUPLE2 $i 0 TUPLE2 0 1;
    IF NOT IS_NULL $tmp:
      RETURN $tmp;
    END_IF
    # check column
    ASSIGN 'tmp' check_group TUPLE2 0 $i TUPLE2 1 0;
    IF NOT IS_NULL $tmp:
      RETURN $tmp;
    END_IF
    ASSIGN 'i' PLUS $i 1;
  END_WHILE
  # check diagonals
  ASSIGN 'tmp' check_group TUPLE2 0 0 TUPLE2 1 1;
  IF NOT IS_NULL $tmp:
    RETURN $tmp;
  END_IF
  ASSIGN 'tmp' check_group TUPLE2 0 2 TUPLE2 1 -1;
  IF NOT IS_NULL $tmp:
    RETURN $tmp;
  END_IF
END_DEF

To check if all positions have been filled and combine this result with the preceding:

DEF all_tokens_placed:
  ASSIGN 'i' 0;
  WHILE LESS $i 3:
    ASSIGN 'j' 0;
    WHILE LESS $j 3:
      IF EQUAL SIZE GET_CONTAINER CONCAT STR $i STR $j 0:
        RETURN FALSE;
      END_IF
      ASSIGN 'j' PLUS $j 1;
    END_WHILE
    ASSIGN 'i' PLUS $i 1;
  END_WHILE
  RETURN TRUE;
END_DEF

DEF get_winner:
  DEBUG "computing winner";
  ASSIGN 'i' 0;
  WHILE LESS $i 3:
    ASSIGN 'tmp' check_group TUPLE2 $i 0 TUPLE2 0 1;
    IF NOT IS_NULL $tmp:
      RETURN $tmp;
    END_IF
    ASSIGN 'tmp' check_group TUPLE2 0 $i TUPLE2 1 0;
    IF NOT IS_NULL $tmp:
      RETURN $tmp;
    END_IF
    ASSIGN 'i' PLUS $i 1;
  END_WHILE
  ASSIGN 'tmp' check_group TUPLE2 0 0 TUPLE2 1 1;
  IF NOT IS_NULL $tmp:
    RETURN $tmp;
  END_IF
  ASSIGN 'tmp' check_group TUPLE2 0 2 TUPLE2 1 -1;
  IF NOT IS_NULL $tmp:
    RETURN $tmp;
  END_IF
END_DEF

DEF check_game_end:
  ASSIGN 'winner' get_winner;
  IF OR
    all_tokens_placed
    NOT IS_NULL $winner
  :
    SET_PROP "game_over" TRUE;
    IF IS_NULL $winner:
      SET_PROP 'winner' 'DRAW';
      RETURN NULL;
    END_IF
    SET_PROP 'winner' $winner;
    RETURN NULL;
  END_IF
END_DEF

We’ll also need to actually invoke the check_game_end function from the receiver, so:

RECEIVE place_token $position:
  ...
  IF EQUAL SIZE $target 0:
    APPEND $target POP $token_source 0;
    ROTATE_PLAY_ORDER;
    # ADD THIS vvv
    check_game_end;
    ACCEPT;
  END_IF
  ...
END_RECEIVE

Putting it all together, the complete ruleset looks like this:

DEF set_up:
  INSTANTIATE_TOKEN 12 "x" 5 "x_tokens";
  INSTANTIATE_TOKEN 12 "o" 4 "o_tokens";
END_DEF

DEF all_tokens_placed:
  ASSIGN 'i' 0;
  WHILE LESS $i 3:
    ASSIGN 'j' 0;
    WHILE LESS $j 3:
      IF EQUAL SIZE GET_CONTAINER CONCAT STR $i STR $j 0:
        RETURN FALSE;
      END_IF
      ASSIGN 'j' PLUS $j 1;
    END_WHILE
    ASSIGN 'i' PLUS $i 1;
  END_WHILE
  RETURN TRUE;
END_DEF

DEF check_group $start_position $delta:
  DEBUG CONCAT CONCAT "check_group: " STR $start_position STR $delta;
  ASSIGN 'x_ct' 0;
  ASSIGN 'o_ct' 0;
  ASSIGN 'k' 0;
  ASSIGN 'pos' $start_position;
  WHILE LESS $k 3:
    ASSIGN 'token' GET_BY_POSITION GET_CONTAINER CONCAT STR GET_POSITION_ELT $pos 0 STR GET_POSITION_ELT $pos 1 0;
    ASSIGN 'token_type' NULL;
    IF NOT IS_NULL $token:
      ASSIGN 'token_type' TOKEN_GET_PROP $token 'token_type';
    END_IF
    DEBUG $token_type;
    DEBUG $token;
    IF EQUAL $token_type 'x':
      ASSIGN 'x_ct' PLUS $x_ct 1;
    END_IF
    IF EQUAL $token_type 'o':
      ASSIGN 'o_ct' PLUS $o_ct 1;
    END_IF
    ASSIGN 'k' PLUS $k 1;
    ASSIGN 'pos' TUPLE2
      PLUS GET_POSITION_ELT $pos 0 GET_POSITION_ELT $delta 0
      PLUS GET_POSITION_ELT $pos 1 GET_POSITION_ELT $delta 1;
  END_WHILE
  DEBUG STR TUPLE2 $x_ct $o_ct;
  IF EQUAL $x_ct 3:
    RETURN 'x';
  END_IF
  IF EQUAL $o_ct 3:
    RETURN 'o';
  END_IF
  RETURN NULL;
END_DEF

DEF get_winner:
  DEBUG "computing winner";
  ASSIGN 'i' 0;
  WHILE LESS $i 3:
    ASSIGN 'tmp' check_group TUPLE2 $i 0 TUPLE2 0 1;
    IF NOT IS_NULL $tmp:
      RETURN $tmp;
    END_IF
    ASSIGN 'tmp' check_group TUPLE2 0 $i TUPLE2 1 0;
    IF NOT IS_NULL $tmp:
      RETURN $tmp;
    END_IF
    ASSIGN 'i' PLUS $i 1;
  END_WHILE
  ASSIGN 'tmp' check_group TUPLE2 0 0 TUPLE2 1 1;
  IF NOT IS_NULL $tmp:
    RETURN $tmp;
  END_IF
  ASSIGN 'tmp' check_group TUPLE2 0 2 TUPLE2 1 -1;
  IF NOT IS_NULL $tmp:
    RETURN $tmp;
  END_IF
END_DEF

DEF check_game_end:
  ASSIGN 'winner' get_winner;
  IF OR
    all_tokens_placed
    NOT IS_NULL $winner
  :
    SET_PROP "game_over" TRUE;
    IF IS_NULL $winner:
      SET_PROP 'winner' 'DRAW';
      RETURN NULL;
    END_IF
    SET_PROP 'winner' $winner;
    RETURN NULL;
  END_IF
END_DEF

RECEIVE place_token $position:
  IF NOT EQUAL $PLAYER_NAME $CURRENT_PLAYER:
    REJECT "Not your turn";
  END_IF
  ASSIGN 'target' GET_CONTAINER $position;
  ASSIGN 'token_source' GET_CONTAINER CONCAT $CURRENT_PLAYER "_tokens";
  IF EQUAL SIZE $target 0:
    APPEND $target POP $token_source 0;
    ROTATE_PLAY_ORDER;
    check_game_end;
    ACCEPT;
  END_IF
  REJECT "Can't play there (non-empty)";
END_RECEIVE