Rule Changes
After you have finished coding the six main procedures--print-help ,
do-move , print-position , generate-moves ,
whose-turn and primitive-position and have solved the standard game, it is time to move
on to the second phase of the project: changing the rules of your chosen game. To do this, you need
to know how to use set-rule! , get-rule and add-menu-entry! to
configure game rules.
In general, please consult our two example modules,
mtttt.scm
and m1210.scm, to see how we modify the rules in a working game
module.
Manipulating the Game-Specific Menu
Gamesman defines three procedures that allow you to change the rules of your basic game and turn
it into a completely new game with a (very possibly) different strategy. The three procedures are:
get-rule -- Takes the name of a rule (a key) as its argument and returns
the value currently associated with that rule.
set-rule! -- Takes the name of a rule and a new value for that rule and
stores them together, overwriting any previous value associated with this rule. The return
value of this procedure, the word okay, is not useful. We use set-rule! strictly for its side
effect of associating a key (the name of a rule) with a new value.
add-menu-entry! -- This procedure takes two arguments, both zero-argument
procedures. The first is the display procedure (called when Gamesman prints the game-specific
menu), the second is the select procedure (called when we select this particular menu entry.
The add-menu-entry! procedure has the side effect of adding an entry to the
game-specific options menu maintained by Gamesman. This is the menu where the user chooses to
play a standard or misére game, for instance. The newly added entry is accessed by the user
typing the number assigned by gamesman.
Think of get-rule and set-rule! as accessing a table keyed by the names
of your rules. For example, to look up the value stored under the key standard-game, you'd call
get-rule like this:
STk> (get-rule 'standard-game)
What get-rule returns is based on what you last put there using set-rule! . Watch:
STk> (set-rule! 'standard-game #t)
okay
STk> (get-rule 'standard-game)
#t
STk> (set-rule! 'standard-game 'bla-bla-bla-bla)
okay
STk> (get-rule 'standard-game)
bla-bla-bla-bla
The name of the rule and what is associated with it is completely up to you. Just remember that
you must first create the rule with set-rule! before you can look up the rule with
get-rule . If you call get-rule with a rule that does not exist, you will
get the following error:
STk> (get-rule 'monkey)
*** Error:
I can't find the option monkey in (game-specific-options-table) -- GET-OPTION
You should use get-rule and set-rule! to create procedures which
- Know how to change a rule of the game, and
- Know how to display the current setting for that rule.
Both procedures should take no arguments. The module template already comes with such procedures
for the standard-game rule. They are called display-standard-misere and toggle-standard-misere and
are the same regardless of which game you're implementing. Notice that display-standard-misere serves
only to display the value of the rule, whereas toggle-standard-misere changes this value by calling
set-rule! . Feel free to modify either of them to suit your liking more. When a rule has only two
allowable settings (a standard game or a misére game), a procedure that toggles these settings is
particularly appropriate. This is especially easy to do if the value of the rule is a boolean, an
inherently binary data type. To toggle the standard-game rule toggle-standard-misere sets it to the
not of its previous value:
(set-rule! 'standard-game (not (get-rule 'standard-game)))
Most of the user-defined rules you are asked to implement are not so simple; many -- though not
all -- of them will require you to read input from the user. A simple template to follow in this case
would be:
(define (change-my-rule)
(display "Enter a new value for my-rule: ") ;; trailing space important!
(let ((value (read)))
(if (valid-my-rule-value? value)
(set-rule! my-rule value)
(format #t "Bad value for my-rule: ~A.\n" value))))
This is probably the simplest interface allowed. Feel free to make yours much more fancy. Your top
priority is, of course, to get your new rules to work; your second priority is to make changing the
rules as simple and intuitive as possible. Do not require the user to type in loads. Allow toggling
of rules where possible. Make sure that help always is available.
A rule required of all game modules is the ability to vary the starting position of the game. The
name of this rule is initial-position . Unlike the names
of other rules, you may not change this name since Gamesman must
refer to it when it starts the game. To allow the user to change the
initial position, you'll need to write functions that change and
display its current value. The function that changes this rule will
need to somehow read an initial position from the user. The
following code would have worked for Tomorrow's-Tic-Tac-Toe:
(define (change-initial)
(display "Should x or o go first? ")
(let ((first (read)))
(display "Now enter the board: ")
(set-rule! 'initial-position (se first (read)))))
The problem with this function is that it is much too trusting. What if the user enters a position
that is not valid? This functions will accept it without complaint. But playing with an invalid
initial position will cause Gamesman to crash. Your game module, on the other hand, must be bullet
proof: no matter what the user enters, it should not crash. You are responsible for error-checking
any input you get from the user: make sure it is valid before accepting it. (There are, of course,
some things that you can type that will cause STk to stop working, like Control-D and Control-K. You
are not responsible for catching these.)
When you're done writing the procedure to display the current setting of a rule and the procedure
to change it, you'll want to use add-menu-entry! to stick them into the right menu in
Gamesman so that you have access to them when playing your game. There are four menus in Gamesman
(as described in the Gamesman How To handout):
- Load Game Menu
- Game-Specific Options Menu
- Play Options Menu
- Moves Menu
The game-specific menu is the only one you will have access to. It is titled "name-of-your-game
Options". It is this menu where your rule-changing options will appear. You should not need to modify
the other menus. But if you really, really want to, let us know.
Here is what the game-specific Tic-Tac-Toe menu looks like:
Tomorrow's-Tic-Tac-Toe Options
------------------------------------------------------------------------
>>> X-player set to go first given the initial position.
>>> Game not solved.
------------------------------------------------------------------------
(1) Toggle from [STANDARD] to misere play
(2) Change initial position. Currently set to: (x ---- x--- --o-)
(3) Toggle from [DIAGONALS-NOT-3-IN-ROW] to diagonals-are-3-in-row
------------------------------------------------------------------------
(P) Play Tomorrow's-Tic-Tac-Toe without solving.
(S) Solve and play Tomorrow's-Tic-Tac-Toe
(H) Print Help
(R) Read game tree from file
(L) Return to Load Menu
(Q) Quit Gamesman
Type your selection and press ENTER:
You can add to this menu by calling add-menu-entry! . It will add your rule into the
numbered portion of the menu. The number assigned to a given menu entry depends on the order in which
it was added. That is, the standard-game rule is numbered one because it was the first rule added in
mtttt.scm . To modify any of the other parts of the menu (such as the parts set off with
>>> or the lettered options) you'd need to modify Gamesman.
Let's examine a call to add-menu-entry! that already appears in the game template:
(add-menu-entry! display-standard-misere toggle-standard-misere)
The first argument is the procedure that displays the value of the standard-game rule.
This procedure gets called (with no arguments) each time the menu is printed to display the current
setting of a particular rule. How does display-standard-misere know what to display?
Figure it out!
The second argument is the procedure that gets called to change the standard-game
rule. It is invoked (with no arguments) by Gamesman every time the user types "1 ". As
we've said, toggle-standard-misere is a toggling procedure; it does not read any input
from the user or print anything on the screen. It simply issues a call to set-rule! ,
and the next time this menu is printed display-standard-misere prints the new setting.
To get your rule to appear in the game-specific menu, you must use add-menu-entry! .
You also need to set a default value for every rule, so that when Gamesman prints the game-specific
menu for the very first time, some setting exists for every rule. This is accomplished by a single
call to set-rule! .
How the Eleven Main Functions are Affected
You now know how to use set-rule! , get-rule and
add-menu-entry! . This, of course, is not sufficient to
actually play the game with modified rules. You also will need
modify the eleven main procedures (make-position, make-board,
get-board, get-num-rows, get-num-cols, print-help ,
whose-turn , do-move , print-position ,
generate-moves and primitive-position ) so that they do something a little
different depending on the rules currently in play, i.e., depending on the settings of the rules.
Take print-help , for instance. It must display a help message appropriate to the
rules in play. If you're playing misére Tomorrow's Tic-Tac-Toe, you don't want print-help telling
you that the way to win the game is to get three-in-a-row. Instead, print-help should
always tell you the objective given the current rules. If you are playing
surround and worm-hole compulsory rule is in effect, the help message must take this into account when describing the possible moves.
It is up to you to decide what someone playing your game must know about the current rules--and to
put that information into print-help . Don't make print-help too long, or
it'll get boring; print-help should print a concise message describing how to move
(the "ON YOUR TURN..." part) and how to win (the "THE OBJECTIVE IS..." part) that does not omit any
truly important details. When grading your project, we will use the help message to figure out the
rules you have added. Include enough information to explain the rules in play--be they ones you have
added, the standard game rules or a combination of the two--to someone unfamiliar with the game. But
remember that we do not want to spend an hour reading your help. So make it brief enough for us to
give you your 100% score and move on.
What other functions need to change to accommodate the new rules? Well, it depends on the rules.
One procedure that probably should not change, however, is do-move ; do-move should--to borrow the Nike slogan--just do it. That's why we asked you
not to build error checks into do-move .
Should print-position change? It's up to you. It is nice, however, to see the setting
of some important rules (such as the standard-game rule) printed along with the position
to serve as a reminder. You don't want to think you're playing a standard game--and find out it was
in fact misére only when Deep Blue wins it.
Speaking of the standard-game rule, what is the one procedure that needs to do
something different when playing a misére game than when playing a standard game? Look in the
Tomorrow's Tic-Tac-Toe or 1,2,...10 game module if you aren't sure.
The Rules You Must Add and the Rules You Can Add
You must come up with at least one rule of your own. There are many, many possibilities: a magical
losing square, moves that wrap around the board, randomly generated starting positions, new pieces
with more powerful moves. Feel free to make a completely new game! In addition, you may fool with the
game board: make it bigger or smaller, stretch it out or add something to it (like a wall that no
piece can cross). Remember that how you map the position to the board is up to you. However, if you make the
position too big, Gamesman will not be able to solve the game, so make sure that the total number of
positions in Gamesman's hash table is not above 6000.
In addition to any rules you come up with, every game module must implement the following two
rules:
- Standard/misére play. You lose in the misére game if you would have won in the standard
game. This applies regardless of what other rules you have implemented. Think of it this way:
in the misére game, your goal is to lose.
- Variable initial position. You must provide a way for the user to change the starting
position of the game. This changes not only the initial arrangement of the pieces, but also
which piece starts the game. Your challenge is twofold:
- You must error-check the user's input to death, and
- You should make it as easy as possible to change the initial position. Typing is
painful! If you really want to get fancy, you can make changing the piece that
starts the game a toggling option that is separate from the (more involved) option
to change the initial arrangement of pieces on the board.
We also have come up with a single special rule for each game
that you must implement. These game-specific rules are detailed in
the packet you got the week of November 3.
|