XGod_t = cluster is create, reset, player_set, board_set, make_move, player_up, user_move, move_over, command_response, game_draw, game_win, game_is_in_progress, game_is_over, game_is_unspecified, show_hint, destroy % Overview: The XGod_t manages the entire X user interface for an % antichess game. It displays the board_t, shows moves made, % indicates the game status including time remaining, and gets % moves or command strings from the user. Games are either % over, with a corresponding win/draw state, in progress, or in % an "unspecified" state. All X errors will be handled by % signalling failure and leaving the state of the XGod % unspecified. % Typical calling orders: The specificiations for the XGod_t % procedures determines the calling sequence of these % procedures. But, to further clarify the process, the % following are the "indended" calling sequences: % % The first call is to XGod_t$create, creating a game in % unspecified state. Any XGod_t that has not ben destroyed may % be reset() to unspecified state, and calls to player_set() can % occur at any time without affecting the game state. % % In unspecified state, the following actions may be taken: % % - The "player_up" route: % = player_up % = any combination of user_move, make_move, game_is_* % = move_over % % - The "game over" route: % = game_win or game_draw % = any combination of user_move, game_is_* % = reset to get back to unspecifed state. % % An XGod_t may be destroyed in any game state. % Random equates piece_width = 64 %% Width of a piece bitmap piece_height = 64 %% Height of a piece bitmap filename_prefix = "/mit/6.170/ps5/bitmaps/sm_" %% Prefix for these bitmaps custom_bitmap_prefix = "/mit/6.170/groups/se01/bitmaps/" %% Prefix for custom bitmaps % Representation: rep = record[w: window, % Window used by XGod Xboard: Xboard_t, % X interface to board board: board_t, % Current board player_info: color_array_t[player_info_t], % Player info records Xstatus: color_array_t[Xstatus_t], % X interface to status areas player_up: color_t, % Current player (or none) winner: color_t, % Winner of the game (or none) check_mate, time_out: bool, % Reason for winning draw: bool, % Flag to indicate a drawn game Xbuttons: Xbuttons_t, % X interface to the buttons highlighted_squares: array[place_t], % List of highlighted places selected_square: sel_place_t, % Square currently selected for move Xcommand: Xcommand_t, % X interface to command area base_time: time_t, % Reference time for curr. player Xtimer: Xtimer_t, % Interface to window's timer hint: bool] % Working on a hint... sel_place_t = oneof[place: place_t, no_place: null] % Representation Invariants: % *** Note that the invariants need not apply after the % XGod_t$destroy call, since all functions require that % XGod_t$destroy has not been called on their arguments. % * No more than one of the following conditions may be true: % * player_up is not "none" % * winner is not "none" % * draw is true % * If winner is not "none", then check_mate and time_out cannot % both be true. % * Xstatus[color_t$make_black()] and % Xstatus[color_t$make_white()] must have values, which reflect % the player_up and player_info values i window w. % * player_info[color_t$make_black()] and % player_info[color_t$make_white()] must have valid values. % * Xboard represents board_t board in window w. % * base_time is the value passed into the last invocation of % player_up if rep.player_up is not "none". % * The low bound of highlighted_squares is 1. % * Xcommand is displaying the last value sent to % command_response. % * hint is false if a machine player is up. % Abstraction Function: % * All XGod_t's interface elements are in window "w". % * Xboard represents the current board state in "board", and also % the highlights associated with having selected the source of a % move (selected_square has a place). % * Board represents the current state of the game. % * Player_info is the most up-to-date player information. Note % that if a human player is up, the current time-left should be % computed using the current time - base_time instead of % accessing the player_info structure. % * The Xstatus members represent the most current player_info % structures. If a human player is up and user_move is % executing, however, then that player's time will be updated % based on base_time instead of the player_info structure. % * If player_up is not none, then it is that player's turn to % make a move. % * If winner is not none, then the given player has won the game. % If check_mate is true, then the game was won by checkmate. If % time_out is true, then the game was won by timeout. If % neither are true, then the game was won by no pieces % remaining. % * If draw is true, then the game was drawn. (By a stalemate) % * Xbuttons represents the command buttons. % * Selected_square indicates that a move selection from square % "place" is in progress, or that no move selection is in % process if "no_place". % * Xcommand represents the command area at the bottom of the % screen. % * Base_time is the time that the current player's move is timed % from. The current time remaining is the player_info's % time_left-(cur_time-base_time). % * Xtimer represents the timer status of the window w. % * The interface indicates that the computer is working on a hint % iff a player is up and the hint flag is set. % Notes on implementation: % The XGod_t handles all X interaction. It delegates the display % and operation of the buttons, status areas, and command area % to Xbuttons_t, Xstatus_t, and Xcommand_t, respectively. % XGod_t delegates the display and coordinate translation of the % board to Xboard_t, but handles the user interaction for the % board area itself. % Color XGod_color_scheme_t = record[background: color] own color_scheme: XGod_color_scheme_t := XGod_color_scheme_t${background: "gray50"} create = proc(board: board_t, white_player, black_player: player_info_t) returns (cvt) signals (invalid) % effects: Returns a new XGod_t with a copy of board and the % information from white_player and black_player. % Signals invalid if the information is bad. All % negative times are interpreted as 0.0. The XGod_t % makes a copy of the board_t and the player_info_t % structures for ailure if its own use. Signals fan % X error occurs. % Equates: extra_space = 10 %% Extra space to put between areas of the screen. % Get dimensions Xstatus_min_width, Xstatus_height: Pixel := Xstatus_t$get_size() Xboard_width, Xboard_height: Pixel := Xboard_t$get_size(piece_width, piece_height) Xbuttons_width, Xbuttons_height: Pixel := Xbuttons_t$get_size() Xcommand_min_width, Xcommand_height: Pixel := Xcommand_t$get_size() % Copy the players white_player := player_info_t$copy(white_player) black_player := player_info_t$copy(black_player) % Fix time_left to be >= 0.0 if white_player.time_left < 0.0 then white_player.time_left := 0.0 end % if if black_player.time_left < 0.0 then black_player.time_left := 0.0 end % if % Compute window width width: Pixel := Xstatus_min_width * 2 if Xboard_width + Xbuttons_width + extra_space > width then width := Xboard_width + Xbuttons_width + extra_space end % if if Xcommand_min_width > width then width := Xcommand_min_width end % if width := width + 2 * extra_space % Compute window height height: Pixel := 4 * extra_space + Xstatus_height + Xcommand_height middle_area_height: Pixel if Xbuttons_height > Xboard_height then middle_area_height := Xbuttons_height else middle_area_height := Xboard_height end % if height := height + middle_area_height % Create the window w: window := window$create("Antichess", width, height, color_scheme.background) %%%% bad_color converts to failure % Create the interface elements: % Status areas Xstatus_white: Xstatus_t := Xstatus_t$create(w, extra_space, extra_space, width/2 - extra_space-1, color_t$make_white(), white_player, false, custom_bitmap_prefix) %%%% Signals convert to failure Xstatus_black: Xstatus_t := Xstatus_t$create(w, width/2, extra_space, width/2 - extra_space-1, color_t$make_black(), black_player, false, custom_bitmap_prefix) %%%% Signals convert to failure y: Pixel := extra_space * 2 + Xstatus_height % Board dummy1, dummy2: pixel Xboard: Xboard_t Xboard, dummy1, dummy2 := Xboard_t$create(w, board, piece_width, piece_height, filename_prefix, extra_space, y) %%%% Signals convert to failure % Buttons Xbuttons: Xbuttons_t := Xbuttons_t$create(w, width - extra_space - Xbuttons_width, y) %%%% Signals convert to failure y := y + middle_area_height + extra_space % Command area Xcommand: Xcommand_t := Xcommand_t$create(w, extra_space, y, width - extra_space * 2) % Create the timer Xtimer: Xtimer_t := Xtimer_t$create(w) % Create the rep r: rep := rep${w: w, Xboard: Xboard, board: board_t$copy(board), player_info: (color_array_t[player_info_t]$ create_white_black(white_player, black_player)), Xstatus: (color_array_t[Xstatus_t]$ create_white_black(Xstatus_white, Xstatus_black)), player_up: color_t$make_none(), winner: color_t$make_none(), check_mate: false, time_out: false, draw: false, Xbuttons: Xbuttons, highlighted_squares: array[place_t]$create(1), selected_square: sel_place_t$make_no_place(nil), Xcommand: Xcommand, base_time: 0.0, Xtimer: Xtimer, hint: false} % Return the rep return (r) end create reset = proc(XGod: cvt, new_board: board_t, white_player, black_player: player_info_t) signals (invalid) % requires: The XGod has not been destroyed. % modifies: XGod % effects: Modifies the state of x to reflect the arguments, % as in create. Signals invalid if the information % is bad and leaves the state the same as before % reset was called. The XGod_t makes a copy of the % board_t and the player_info_t structures for its % own use. Negative time_left is interpreted as 0.0. % Signals failure if an X error occurs. % Copy the player_info white_player := player_info_t$copy(white_player) black_player := player_info_t$copy(black_player) % Save the player_info XGod.player_info[color_t$make_white()] := white_player XGod.player_info[color_t$make_black()] := black_player % Fix negative time_left: if (white_player.time_left < 0.0) then white_player.time_left := 0.0 end % if if (black_player.time_left < 0.0) then black_player.time_left := 0.0 end % if % Save the board XGod.board := board_t$copy(new_board) % Update the board Xboard_t$reset(XGod.Xboard, new_board) %%%% Signals convert to failure % Update the player info Xstatus_t$set_player_info(XGod.Xstatus[color_t$make_white()], white_player) %%%% Signals convert to failure Xstatus_t$set_player_info(XGod.Xstatus[color_t$make_black()], black_player) %%%% Signals convert to failure % Update the player_up status Xstatus_t$set_player_up(XGod.Xstatus[color_t$make_white()], false) %%%% Signals convert to failure Xstatus_t$set_player_up(XGod.Xstatus[color_t$make_black()], false) %%%% Signals convert to failure % Clear the command response command_response(up(XGod), "") % Finish updating the rep XGod.winner := color_t$make_none() XGod.player_up := XGod.winner XGod.draw := false XGod.time_out := false XGod.check_mate := false XGod.highlighted_squares := array[place_t]$create(0) XGod.selected_square := sel_place_t$make_no_place(nil) % Unhide the buttons Xbuttons_t$hide_buttons(XGod.Xbuttons, false) %%%% Signals convert to failure end reset player_set = proc(XGod: cvt, c: color_t, player_info: player_info_t) signals (invalid) % requires: The XGod has not been destroyed. % modifies: XGod % effects: Sets the player information for the player of % color_t c. Signals invalid if the information is % bad and leaves the state of the player unchanged. % Negative time_left is interpreted as 0.0. Leaves % game status (one of the players "up" or game over) % unchanged. Signals failure if an X error occurs. % Signal invalid if color is "none"... if (color_t$is_none(c)) then signal invalid end % if % Copy the player_info player_info := player_info_t$copy(player_info) % Update the rep XGod.player_info[c] := player_info % Fix negative time_left if (player_info.time_left < 0.0) then player_info.time_left := 0.0 end % if % Update the player info Xstatus_t$set_player_info(XGod.Xstatus[c], XGod.player_info[c]) %%%% Signals convert to failure end player_set board_set = proc(XGod: cvt, new_board: board_t) % requires: The XGod has not been destroyed. % modifies: XGod % effects: Modifies the state of x to reflect the arguments, % as in create. The XGod_t makes a copy of the % board_t for its own use. The game state is not % changed. Signals failure if an X error occurs. % Save the board XGod.board := board_t$copy(new_board) % Update the board Xboard_t$reset(XGod.Xboard, new_board) %%%% Signals convert to failure end board_set make_move = proc(XGod: cvt, m: move_t) signals (game_state) % requires: The XGod has not been destroyed. % modifies: XGod % effects: Makes the move determined by m on the XGod_t's % board_t and updates the X display. Signals % game_state if the game is not "in progress". % Signals failure if an X error occurs. % Make sure nothing is highlighted cancel_highlights(up(XGod)) % Figure out what piece is moving p_from: piece_t := XGod.board[m.from] % Make the move on the board board_t$make_move(XGod.board, m) % Indicate the move graphically Xboard_t$make_move(XGod.Xboard, m.from, m.to, p_from, XGod.board[m.to]) %%%% Signals convert to failure end make_move player_up = proc(XGod: XGod_t, c: color_t, time_left: time_left_t, base_time: time_t, hint: bool) signals (game_state) % requires: The XGod has not been destroyed. % modifies: XGod % effects: Indicates graphically that the player of color_t c % is up for play, and updates the time_left display. % Uses base_time to compute at later times the time % remaining. Signals game_state if the game state is % not "unspecified". Signals failure if an X error % occurs. If a human player is up, the hint flag % indicates that user_move will not be called, and % instead the computer will think about a suggested % move while the player is up. % Get the rep r: rep := down(XGod) if (~game_is_unspecified(XGod)) then signal game_state end % if % Assume that this isn't a hint request... r.hint := false if (~color_t$is_none(c)) then Xstatus_t$set_player_up(r.Xstatus[c], true) %%%% Signals convert to failure. if (r.player_info[c].machine) then % A machine player is up: Indicate a status message % that the computer is thinking... Xcommand_t$set_status(r.Xcommand, r.player_info[c].name || " is thinking...") %%%% Signals convert to failure Xbuttons_t$hide_buttons(r.Xbuttons, true) %%%% Signals convert to failure. elseif (hint) then % A human player is up and the machine player is going % to think about a suggested move... Xcommand_t$set_status(r.Xcommand, "Computer is thinking about hints for " || r.player_info[c].name || "...") %%%% Signals convert to failure Xbuttons_t$hide_buttons(r.Xbuttons, true) %%%% Signals convert to failure. % Indicate that a hint is in progress... r.hint := true end % if r.player_info[c].time_left := time_left Xstatus_t$set_player_info(r.Xstatus[c], r.player_info[c]) %%%% Signals convert to failure. r.player_up := c r.base_time := base_time end % if end player_up user_move = proc(XGod: XGod_t) returns (move_t) signals (command(string), timeout, game_state) % requires: The XGod has not been destroyed. % modifies: XGod % effects: Waits for and returns a move for the player for the % last XGod_t$player_up player, or signals a command % string if the user enters a command. If the game % is over, user_move will not return; it will always % signal. Signals timeout if a human player is up % and time runs out. If user_move is called again % with the same player up and still no time remains, % the timeout signal will be generated again after at % about one second since the previous return of % user_move. Signals game_state if the game state is % "unspecified". Signals failure if an X error % occurs. % Check game state if (game_is_unspecified(XGod)) then signal game_state end % if % Get the rep r: rep := down(XGod) % Update the status, if necessary... (Game status for game over is % constant and already set.) if (game_is_in_progress(XGod)) then Xcommand_t$set_status(r.Xcommand, color_t$unparse(r.player_up) || "'s turn. Please make a move, or select a command.") end % if % Leave the highlights as-is for show_hints... % Loop on events while (true) do % Make sure we're getting a timer, if needed... if game_is_in_progress(XGod) then Xtimer_t$get_timer(r.Xtimer) end % if % Get an event we: window_event := window$get_event(r.w) command: string %% Temp variable for commands % Let the buttons handle the event... begin command := Xbuttons_t$handle_event(r.Xbuttons, we) %%%% Signals (other than not_mine) convert to failure. cancel_highlights(XGod) command_response(XGod, "") r.selected_square := sel_place_t$make_no_place(nil) signal command(command) end % begin except when not_mine: end % Let the command area handle the event... begin command := Xcommand_t$handle_event(r.Xcommand, we) %%%% Signals (other than not_mine) convert to failure. cancel_highlights(XGod) command_response(XGod, "") r.selected_square := sel_place_t$make_no_place(nil) Xcommand_t$clear_typein(r.Xcommand) % Attempt to parse the command as a move... move: move_t := move_t$parse(command) except when invalid: signal command(command) end % It is a move: is it valid? if (game_is_in_progress(XGod)) then move_list: array[move_t] := board_t$moves(r.board, r.player_up) for m: move_t in array[move_t]$elements(move_list) do if m = move then return (move) end % if end % for end % if % Move not found or moves not allowed. Just return the command signal command(command) end % begin except when not_mine: end % Check for a timer event... if window_event$is_timer(we) then Xtimer_t$got_timer(r.Xtimer) if game_is_in_progress(XGod) then % Request another timer Xtimer_t$get_timer(r.Xtimer) % Update the time remaining... time: time_t := get_time(exact) time_left: time_left_t := (r.player_info[r.player_up]. time_left - (time - r.base_time)) if (time_left < 0.0) then time_left := 0.0 end % if new_player_info: player_info_t := player_info_t$copy(r.player_info[r.player_up]) new_player_info.time_left := time_left % Update the time display... Xstatus_t$set_player_info(r.Xstatus[r.player_up], new_player_info) if (time_left = 0.0) then signal timeout end % if end % if end % if % Check for a click on the board... if window_event$is_mouse(we) then me: mouse_event := window_event$value_mouse(we) if me.button = 1 cand me.pressed then % Left button click -> find the place place: place_t := Xboard_t$find_place(r.Xboard, me.x, me.y) % (Note: no_place exception handled on the "if"...) % Cancel any highlights cancel_highlights(XGod) if sel_place_t$is_place(r.selected_square) then % Place is selected: This is the "to"... % Check the square for valid move... moves: array[move_t] := board_t$moves(r.board, r.player_up) for move: move_t in array[move_t]$elements(moves) do if move.to = place cand move.from = sel_place_t$value_place(r.selected_square) then % Found the move: it's valid. % Clear the command response: command_response(XGod, "") % Clear the selected square... r.selected_square := sel_place_t$make_no_place(nil) % Return the move string return (move) end % if end % for % Must have been an invalid move: % Reset the selected square r.selected_square := sel_place_t$make_no_place(nil) end % if % No place is selected: This is the "from"... % Cancel any highlights (like from a hint...) cancel_highlights(XGod) % Check the source square... okay: bool := false moves: array[move_t] := board_t$moves(r.board, r.player_up) for move: move_t in array[move_t]$elements(moves) do if move.from = place then okay := true set_highlight(XGod, move.to, highlight_move_to) end % if end % for if okay then % Valid moves from the square... set_highlight(XGod, place, highlight_move_from) command_response(XGod, "Select the move destination or different piece to move.") r.selected_square := sel_place_t$make_place(place) else % No valid moves from the square command_response(XGod, "No valid moves from square " || place_t$unparse(place)) end % if end % if me.button = 1 except when no_place: end end % if end % while %%%% Signals convert to failure end user_move move_over = proc(XGod: XGod_t, time_left: time_left_t) signals (invalid, game_state) % requires: The XGod has not been destroyed. % modifies: XGod % effects: Updates the time left for the player that just % moved after the turn is over. Sets the game state % to "unspecified". Signals invalid if time_left is % less than zero or game_state if the game is not in % progress. Signals failure if an X error occurs. % Check the time_left if (time_left < 0.0) then time_left := 0.0 end % if % Check the game state if (~game_is_in_progress(XGod)) then signal game_state end % if % Get the rep r: rep := down(XGod) % Cancel any highlights cancel_highlights(XGod) % Get the player whose turn is over c: color_t := r.player_up % Set the player_up flag to none, i.e., unspecified game state r.player_up := color_t$make_none() % Indicate the player's turn is over Xstatus_t$set_player_up(r.Xstatus[c], false) %%%% Signals convert to failure. % Update the time r.player_info[c].time_left := time_left Xstatus_t$set_player_info(r.Xstatus[c], r.player_info[c]) %%%% Signals convert to failure. % Make sure the buttons aren't covered Xbuttons_t$hide_buttons(r.Xbuttons, false) %%%% Signals convert to failure % Flush events if the machine player's turn is over, or if the % computer is done with a hint. if r.player_info[c].machine | r.hint then Xtimer_t$got_timer(r.Xtimer) window$flush_events(r.w) end % if end move_over show_hint = proc(XGod: XGod_t, move: move_t) signals (game_state) % requires: The XGod has not been destroyed. % modifies: XGod % effects: Graphically indicates a hint move. If the game is % over, show_hint will signal game_state. Show_hint % should be followed by a call to user_move, with no % intervening XGod calls. % Check the game state if (game_is_over(XGod)) then signal game_state end % if % Remove any existing highlights cancel_highlights(XGod) % Highlight the move set_highlight(XGod, move.from, highlight_move_from) %%%% Signals convert to failure. set_highlight(XGod, move.to, highlight_move_to) %%%% Signals convert to failure. end show_hint command_response = proc(XGod: cvt, message: string) % requires: The XGod has not been destroyed. % modifies: XGod % effects: Prints a message which is a response to a command. % The message will remain until command_response is % called again. Signals failure if an X error % occurs. % Show the response Xcommand_t$set_response(XGod.Xcommand, message) %%%% Signals convert to failure. end command_response game_draw = proc(XGod: XGod_t) signals (game_state) % requires: The XGod has not been destroyed. % modifies: XGod % effects: Signals that the game is a draw. Assumes that this % happened as a result of a stalemate. Causes the % game to end. Signals game_state if the game is not % "unspecified". Signals failure if an X error % occurs. % Check the game state if (~game_is_unspecified(XGod)) then signal game_state end % if % Get the rep r: rep := down(XGod) % Set the draw flag r.draw := true % Update the status area Xcommand_t$set_status(r.Xcommand, "The game is a draw.") %%%% Signals convert to failure. end game_draw game_win = proc(XGod: XGod_t, winner: color_t, mate: bool, timeout: bool) signals (invalid, game_state) % requires: The XGod has not been destroyed. % modifies: XGod % effects: Signals that the game was won by player of color_t % c. Mate is true if the game was won by a % checkmate; timeout is true if the game was won by % the opponent running out of time; both mate and % timeout are false if the game was won by having all % of one's pieces taken. Causes the game to end. % Signals invalid if both mate and bool are true. % Signals game_state if the game state is not % "unspecified". Signals failure if an X error % occurs. % Check the parameters if (color_t$is_none(winner) cor (mate & timeout)) then signal invalid end % if % Check the game state if (~game_is_unspecified(XGod)) then signal game_state end % if % Cancel highlights cancel_highlights(XGod) % Get the rep r: rep := down(XGod) % Set the game state r.winner := winner r.check_mate := mate r.time_out := timeout % Update the status message: string if mate then % Check mate message := "being checkmated." elseif timeout then % Time out message := "opponent running out of time." else % Pieces gone message := "losing all pieces." end % if message := (r.player_info[winner].name || " (" || color_t$unparse(winner) || ") won by " || message) Xcommand_t$set_status(r.Xcommand, message) %%%% Signals convert to failure. end game_win game_is_in_progress = proc(XGod: cvt) returns (bool) % requires: The XGod has not been destroyed. % effects: Returns true if the game state is "in progress". return (~color_t$is_none(XGod.player_up)) end game_is_in_progress game_is_over = proc(XGod: cvt) returns (bool) % requires: The XGod has not been destroyed. % effects: Returns true if the game state is "over". return (~color_t$is_none(XGod.winner) | XGod.draw) end game_is_over game_is_unspecified = proc(XGod: XGod_t) returns (bool) % requires: The XGod has not been destroyed. % effects: Returns true if the game state is "unspecified" return (~game_is_over(XGod) cand ~game_is_in_progress(XGod)) end game_is_unspecified destroy = proc(XGod: cvt) % requires: The XGod has not been destroyed. % modifies: XGod % effects: Destroys the X objects associated with the XGod_t; % must be called before the program terminates. % Signals failure if an X error occurs. % Flush events window$flush_events(XGod.w) % Destroy the window window$destroy(XGod.w) end destroy % Internal procedures: cancel_highlights = proc(XGod: cvt) signals (bad_color) % requires: The XGod has not been destroyed. % modifies: XGod, w % effects: Removes the highlights on all squares made through % set_highlight. % Un-highlight the places for place: place_t in (array[place_t]$ elements(XGod.highlighted_squares)) do Xboard_t$set_piece(XGod.Xboard, place, XGod.board[place], highlight_none) %%%% Signals convert to failure end % for % Clear the list XGod.highlighted_squares := array[place_t]$create(1) end cancel_highlights set_highlight = proc(XGod: cvt, place: place_t, highlight_level: int) signals (bad_color) % requires: The XGod has not been destroyed. % modifies: XGod, w % effects: Sets the highlight level for the square at "place". % Signals bad_color if an internal color is invalid. % Set the highlight Xboard_t$set_piece(XGod.Xboard, place, XGod.board[place], highlight_level) %%%% Signals convert to failure. % Add to the list array[place_t]$addh(XGod.highlighted_squares, place) end set_highlight end XGod_t