An Overview of Game Playing

1. Introduction

This article explains how to create a general game player capable of functioning in the Gamemaster competition system. We begin with an overview of the overall architecture of a player. We then show how to instantiate the architecture to build two search-free players, viz. a legal player and a random player. We finish with details assembling everything into an HTML page that can be uploaded to Gamemaster.

2. Architecture

A general game player consists of three parts. (1) The player's executive program is a body of code that manages communication between the player and the game manager (2) The player code consists subroutines called by the executive program to handle the various types of messages received from the game manager. (3) The code base is a set of standard subroutines for processing states and game descriptions.

Executive program. The executive program listens for message from the game manager, processes any messages it receives, and sends replies. Upon receiving a message, the executive program calls the appropriate player subroutine with the relevant components of the message as arguments. If the value of the subroutine is false, the executive program does nothing and listens for more messages. If the subroutine returns an answer other than false, the executive program sends that reply to the game manager.

Player code. Your main job in building a player is to write the message-handling subroutines called by the he executive program. Later in this article, we look at a couple of simple approaches for doing this.

function ping()
 {...code that calls predefined subroutines...}

function start(role,rules,start,play)
 {...code that calls predefined subroutines...} 

function play(move)
 {...code that calls predefined subroutines...} 

function stop(move)
 {...code that calls predefined subroutines...} 

function abort()
 {...code that calls predefined subroutines...}

Code base. In order to facilitate the implementation of these message handlers, the Gamemaster code base contains definitions for the subroutines shown here. There are subroutines for computing the key components of a game.

findroles(rules) - returns an array of roles (all roles)
findbases(rules) - returns an array of factoids (all possible base factoids)
findactions(rules) - returns an array of actions (all possible actions)
findinits(rules) - returns an array of factoids (the initial state)

findcontrol(state, rules) - returns a role (the role in control)
findlegalp(action, state, rules) - returns a boolean (true if legal; false if not)
findlegalx(state, rules) - returns an action (legal in the current state)
findlegals(state, rules) - returns an array of actions (all moves legal in the current state)
findreward(role, state, rules) - returns a number (reward for specified role in current state)
findterminalp(state, rules) - returns a boolean (true if terminal; false otherwise)

simulate(action, state, rules) - returns an array of factoids (the next state)
definerules([], rules) - returns an indexed array of rules

3. Creating a Legal Player

A legal player is the simplest form of game player. In each state, a legal player selects an action based solely on its legality, without consideration of the consequences. Typically, the choice of action is consistent - it selects the same action every time it finds itself in the same state. (In this way, a legal player differs from a random player, which selects different legal actions on different occasions.)

Legal play is not a particularly good general game playing strategy. However, it is a worthwhile exercise to build a legal player (and a random player) just to get familiar with the relevant concepts and to have a basis of comparison for more intelligent players.

Our player utilizes some global variables to retain information about a match while it is being played. The variables used in our implementation are shown below.

library - the name of the manager
player - the name of the player
library - a list of game rules.
role - the player's role in the game.
roles - a list of all roles in the game.
state - the current state of the game as a list of propositions.
startclock - the number of seconds in the startclock
playclock - the number of seconds in the playclock

Using the basic subroutines provided in the GGP code base, building a legal player is simple. See definitions below.

The ping handler simply returns true, since our legal player is always ready to play.

 
function ping ()
 {return 'ready'}

The start subroutine initializes our global variables and then returns ready.

 
function start (r,rs,sc,pc)
 {library = rules;
  role = r;
  startclock = sc;
  playclock = pc;
  roles = findroles(library);
  state = findinits(library);
  return 'ready'}

The play subroutine takes a move as argument and uses the simulate subroutine to compute the resulting state. If the move is nil, the subroutine simply returns the current state (i.e. the initial state). Otherwise, it uses simulate to compute the state resulting from the specified move and the current state. Once our player has the new state, it uses findlegalx to compute a legal move.

 
function play (move)
 {state = simulate(move,state,library)
  return findlegalx(state,library)}

The stop subroutine does nothing. It simply returns false.

 
function stop (id,move)
  {return false}

The abort subroutiner also does nothing and simply returns false.

 
function abort ()
  {return false}

4. Creating a Random Player

A random player is similar to a legal player in that it selects an action for a state based solely on its legality, without consideration of the consequences. A random player differs from a legal player in that it does not simply take the first legal move it finds but rather selects randomly from among the legal actions available in the state, usually choosing a different move on different occasions.

The implementation of a random player is almost identical to the implementation of a legal player. The only difference is in the play method. In selecting an action, our player first computes all legal moves in the given state and then randomly selects from among these choices (using the randomindex subroutine). One way of writing the code for the play handler is shown below.

  function play (move) {state = simulate(move,state,library) var actions=findlegals(state,library); var ind = randomindex(actions.length); return actions[ind]} function randomindex (n) {return Math.floor(Math.random()*n)}

Random players are no smarter than legal players. However, they often appear more interesting because they are unpredictable. Also, they sometimes avoid traps that befall consistent players like legal, which can sometimes maneuver themselves into corners from which they cannot escape. They are also used as standards to show that general game players or specific methods perform better than chance.

A random player consumes slightly more compute time than a legal player, since it must compute all legal moves in each state rather than just one. For most games, this is not a problem; but for games with a large number of possible actions, the difference can be noticeable.

5. Putting It All Together

The final step in creating a player is to assemble the components into an HTML page that can be uploaded to Gamemaster. The HTML code shown below shows how to create a minimal legal game player.

<html> <head> <script src='http://epilog.stanford.edu/javascript/epilog.js'></script> <script src='http://gamemaster.stanford.edu/javascript/localstorage.js'></script> <script src='http://gamemaster.stanford.edu/interpreter/general.js'></script> <script type='text/javascript'> //============================================================================== Player Code //============================================================================== var manager = 'manager'; var player = 'sample'; var role = 'robot'; var rules = []; var startclock = 10; var playclock = 10; var library = []; var roles = []; var state = []; function ping () {return 'ready'} function start (r,rs,sc,pc) {role = r; rules = rs.slice(1); startclock = numberize(sc); playclock = numberize(pc); library = definemorerules([],rules); roles = findroles(library); state = findinits(library); return 'ready'} function play (move) {if (move!==nil) {state = simulate(move,state,library)}; if (findcontrol(state,library)!==role) {return false}; return findlegalx(state,library)} function stop (move) {return false} function abort () {return false} //============================================================================== // End of player code //============================================================================== </script> </head> <body onload='doinitialize()'> <textarea id='transcript' style='font-family:courier' readonly></textarea> </body> </html>

The first three lines in the head of the page load the subroutine libraries that define the player's executive program and the code base. epilog.js defines some underlying subroutines for processing datasets and rulesets. localstorage.js defines the executive program. general.js defines the subroutines in the code base.

The subsequent script element contains the player code (the part that you write), i.e. initial bindings for the global variables and definitions for the five event handlers.

The body has just two requirements. (1) The subroutine doinitialize must be called when the page is loaded in order to initialize the executive program. (2) The body must contain a textarea with id transcript. This is used by the executive program to record message traffic. It is extremely helpful in debugging players.