Gamesman - The "Shall We Play a Game?" Project
     
 
   
>>
     Background
   Overview
   Quick Guide
   Definitions
   Files
   Implementation
   Rule Changes
   Simple Graphics
   FAQ

 

 
    
 
   
 
   
 
   
 
Assignment > Rule Changes

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

  1. Know how to change a rule of the game, and
  2. 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):

  1. Load Game Menu
  2. Game-Specific Options Menu
  3. Play Options Menu
  4. 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:

  1. 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.
  2. 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.

 

[Dan Garcia icon] [Department of Computer Science] [Official Website for the University of California, Berkeley]


Gamesman ©2003 Dan Garcia. All rights reserved.
Site design by Steven Chan. Site maintained by Hesam Samimi.