DOING TIME IN DNJ DILEMMA!
Q:
How does the game allow its left and right-hand players to play their cards
independently of each other?
A: A small helping of
asynchronous (parallel) processing, achieved via JScript timer events.
In
DNJ Dilemma I use JScript's setTimeout() timer events to create
parallel (asychronous) processes for the left and right hand players'
card-selection routines. This allows either player (human or computer
strategy) to play first in each round, and lets me add 'thinking time'
delays to make the computer strategies appear more 'human'.
For details of timer events in JScript,
see the
scripting section in MSDN Web Workshop.
Also, click here for a special note about
parameter-passing in setTimeout().
Here's a flow diagram of the playing sequence for a round of
DNJ Dilemma standard edition:

initRound() - splitting things into two
initRound() does some start-of-round housekeeping,
then spawns asynchronous left- and right-hand player processes. It then
terminates, and the browser sits back and waits for the two player processes to
complete.
Here's how initRound() spawns the process for the
right-hand player:
var xdelay = genRandNum(1, 3) * 1000
timer1 = setTimeout("processCard('r', rplay(rplayer, 'play'))", xdelay)
First it generates a random delay period of between one and
three seconds (in milliseconds). Then it calls JScript's setTimeout() method,
telling it to execute the function processCard() after the specified
delay period, using the value returned by the vectored right-hand strategy
function rplay() as a parameter (see vectored
functions for more details).
In DNJ Dilemma standard edition the left-hand player can be
human. If so, initRound() enables the on-screen Cooperate/Defect
buttons, which call processCard() in response to their onclick
events. If the left-hand player is a computer strategy, initRound()
generates a timer-delayed call to the vectored left-hand strategy function.
processCard() - terminating each player's process.
The browser now has two asynchronous processes running, one
of which may consist of waiting for a human player to press a card-select
button. Both processes will terminate with a call to processCard(), which
has two jobs:
-
Respond to the selection of a card by storing 'c' or 'd'
in an xTEMP variable (see below) and displaying a back-of-card
image;
-
Check if both cards have now been played, and if so,
call the end-of-round function processRound().
Here's how processCard() decides whether or not to
call processRound():
if (LTEMP != " " & RTEMP != " ") {
processRound()
}
LTEMP and RTEMP are global variables, initialised to "
" by initRound() and updated with the players' current-round options at
the start of processCard(). When neither have
a value of " ", then both cards must have been played - it's as
simple as that!
processRound() - bringing it back together again
processRound() has a lot to do, including:
-
Turning the cards over to show each player's option;
-
Calculating the scores for that round based on the two
players' options (i.e. using data returned by the two asynchronous play
processes);
-
Updating the 'electronic scoreboard' displays;
-
Updating the game history displays and analysis arrays;
-
Deciding whether to initiate another round, or end the
game.
Here's how it decides whether to play another round or end
the game:
if (game.roundnum < sysroundlimit ) {
timer1 = setTimeout("initRound()", delayafterround)
} else {
timer1 = setTimeout("endGame()", delayafterround)
}
game.roundnum is incremented by initRound(). processRound() compares it to sysroundlimit
(a random value
between six and 16, generated at the start of each game), and acts
accordingly.
As you can see, initRound() and endGame() are called via timer events, rather than directly. This is to improve
the game's interface, by leaving the end-of-round displays on screen for a
second or so before clearing them for the next round or end of game. Instead
of a literal value, I've used a variable (delayafterround) for the delay, so
that the game's 'high-speed play' option can set its value to zero, and thus
speed up the play.
In the DNJ Dilemma source files (dnjdmstd.htm and dnjdmtourn.htm), you'll see timer events used in place of
direct calls in many of the play stages.
Special note - parameter
passing in SetTimeout(),
JScript's setTimeout method takes two parameters - the function call to
be executed, and the delay (in milliseconds) before executing it. The first
parameter is a string expression, not a direct function reference, so
you might say:
handleVar
= setTimeout("initRound()", 500)
There's no problem passing the names of objects and global
variables as parameters to the function call, for example:
handleVar
= setTimeout("showMessage(game.message)", 500)
However, when calling setTimeout() from within a function,
you can't specify one of the function's local variables as a parameter, for
example:
function
myfunc() {
var localvar = "Hello World"
handleVar = setTimeout("showMessage(localvar)", 500)
This will fail, because the actual timer event (the call to
showMessage()) is executed outside the context of the function that called
setTimeout(), so the original function's local variables aren't accessible.
One way of getting round this is to generate setTimeout()'s
function-call parameter's dynamically, like this:
handleVar
= setTimeout("showMessage('"+localvar+"')", 500)
If the value of localvar was "Hello World", then
the above code would evaluate to:
handleVar
= setTimeout("showMessage('Hello World')", 500)
- and don't forget to switch between " and '
quote-marks when generating embedded literal values!
|