Due Wed Nov 6, 10:00 am (electronically).
Maximum Points: 100
signature GAME = sig datatype player = Player | Opponent type board val initialBoard : board val moves : player -> board -> board list (* no moves means player loses *) val evaluate : player -> board -> real (* approximate, between -1 and 1 *) val toString : board -> string end;and the signature for playing the game :
signature PLAY = sig structure Game : GAME (* parameter *) val play : int -> Game.player -> Game.board -> (Game.board option * real) end;Take these signatures and their implementation as basis for this assignment. The
type board
represents the internal
representation of the board which is used to perform the evaluation of
the current situation. For the purpose of this assignment we consider
only evaluation which prunes the search tree based on the value of
sibling nodes.
The representation of board
is not suitable for an
interactive version of the game. In such a version we would like the
display module to maintain its own representation of the current board.
This suggests distinguishing between the notion of move and the
notion of board. Boards remain a specific datatype to the
search engine which evaluates a situation, moves represent the decisions
of the player. It is exactly this kind of information which must be
passed to the display module.
GAME
and PLAY
where boards and moves are different types with appropriate comments.
State the rationale behind your design decisions.
RANDOM
for a random number generator
based on the code in Paulson, pages 108-109,
198-199. The seed for a random number generator should be optional and
be derived from the current time otherwise (see structure
Time
in the context browser of MLWorks). Your signature
should encapsulate the state of the random number generator and protect
against "tampering".
structure Random :> RANDOM
.
functor Connect4 () :> GAME
so that the computer selects randomly between moves of equal value.
Note: this should use only one instance of a random number generator
initialized from the current time
GAME
from problem 1 by requiring a substructure satisfying
(* separate representation for interface *) (* this works with effects *) structure DisplayBoard : sig exception IllegalMove type match (* encapsulates current state *) val init : unit -> match (* initialize starting position *) val quit : match -> unit (* finish match *) val applyMove : match -> player -> move -> unit (* apply a move *) (* raises IllegalMove *) val showBoard : match -> unit (* show the current position *) datatype action = Move of move | Quit val showAction : match -> action -> unit (* show a move or "quit" *) val readAction : match -> unit -> action (* read a move or "quit" *) endNote that this signature contains free reference to types
player
and move
, which must appear
earlier in the revised GAME
signature, but not
board
, which is hidden from the display module. Note
also, that match
must have state to represent the
displayed situation.
Connect4
.
The position should be represented by an array and printed in a
simple-minded but recognizable fashion. readAction
should
read one line from the listener (TextIO.stdIn
) and parse
its beginning as an integer with the column to move on. If the move is
illegal, the user should be prompted again. If the user types
quit
instead of an integer, the returned action should be
Quit
. Note that the function
quit
does not actually need to do anything, although it
would, if the interface was implemented in X, for example.
GAME
and
PLAY
signatures)
signature INTERACTIVE_PLAY = sig val playHuman : int -> unit (* search depth, human moves first *) val playComputer : int -> unit (* search depth, computer moves first *) end; functor InteractivePlay (structure Play : PLAY) :> INTERACTIVE_PLAY = struct ... end;The functions
playHuman
and playComputer
should detect when a game is over (i.e. the player whose turn it is
cannot move), declare a winner, and exit. So there are three
possible ways a game can be finished: the "quit" action from the
user, the human wins, or the computer wins.
Connect4<userid> :> PLAY_CONNECT4
satisfying signature
signature PLAY_CONNECT4 = sig type match val init : unit -> match val move : match -> int -> unit val play : match -> int option val name : string endWe will use this to conduct the tournament among all compiling entries. Here:
init () | initializes a new match. |
move match i | your opponent plays on column i. You may assume the move is legal (our tournament director checks this) and you must update your internal state to reflect that change in state. |
play match | return SOME(i) for a move on column i or NONE (which means you have lost or resign). |
name | your userid |
Your program loses a match if it raises an uncaught exception, attempts an illegal move, if the cumulative CPU time spent by your program for a match exceeds 300 seconds, or (the usual condition) it cannot make a legal move.
The tournament is played in single elimination format, with two matches (with either program making the initial move) between randomly paired programs in each round. In case of a tie the program which used less CPU time overall advances to the next round. (See structure Timer in the context browser of MLWorks)
In each match, since there can be at most 49 moves, there can be no ties.
Feel free to improve your search program or evaluation function as you see fit. We suggest that you write a functor of the form
functor Connect4userid (structure Play : PLAY where type Game.move = int) :> PLAY_CONNECT4which provides the needed interface to the structures you already implemented.
/afs/andrew/scs/cs/15-212-X/studentdir/<your andrew id>/ass5/ass5.sml for problems 1, 2, and 3 and /afs/andrew/scs/cs/15-212-X/studentdir/<your andrew id>/ass5/tournament.sml for problem 4.
Be sure to use these names and check that tournament.sml is self-contained and compiles to help us in the organization of the tournament.