Building the Web Server Interface

Now that we’ve completed the graphical side of Dice of Doom version 3, we’re ready to write the side that interfaces with the web server.

Writing Our Web Request Handler

The central function for our web server handling is called dod-request-handler. It is the function that we can pass to the serve command in our web server library, and it is responsible for handling all the web requests coming from the web browser. Here is the code for dod-request-handler:

(defparameter *cur-game-tree* nil)
  (defparameter *from-tile* nil)

  (defun dod-request-handler (path header params)
   (if (equal path "game.html")
       (progn (princ "<!doctype html>")
              (tag center ()
                    (princ "Welcome to DICE OF DOOM!")
                    (tag br ())
                   (let ((chosen (assoc 'chosen params)))
                     (when (or (not *cur-game-tree*) (not chosen))
                        (setf chosen nil)
                       (web-initialize))
                      (cond ((lazy-null (caddr *cur-game-tree*))
                              (web-announce-winner (cadr *cur-game-tree*)))
                            ((zerop (car *cur-game-tree*))
                              (web-handle-human
                                  (when chosen
                                        (read-from-string (cdr chosen)))))
                           (t (web-handle-computer))))
                    (tag br ())
                   (draw-dod-page *cur-game-tree* *from-tile*)))
      (princ "Sorry... I don't know that page.")))

First, this function checks whether the current page being fetched from the web server is game.html . This is the page where our game will reside on the web server. At the top of the page, we specify the doctype . When done in this way, it tells the web browser to expect an HTML5-encoded web page. Then we put in some simple HTML to center the page and print a welcome message .

The params passed from the web server library may contain an important value named chosen, which we fetch using this line . If there is no chosen tile, or if the game tree is currently empty , it means the player must be starting a brand-new game. If that’s the case, we will call a function named web-initialize .

Next, we need to find out whether the game has ended. We can tell this by checking if the list of moves is empty (which, as you might remember, is stored in the caddr location of the tree). In that case, we’ll announce a winner .

Following that, we need to see if the current player is player zero, which means the player is the human player. In that case, we’ll call the function web-handle-human to build the rest of the HTML data in the body of the page. We also use the read-from-string function to pull the number of the chosen tile from the chosen parameter, if it exists.

In all other cases, we know we’re dealing with a computer player and hand over control to web-handle-computer to build the rest of the HTML.

Lastly, the dod-request-handler function needs to call the draw-dod-page function to draw the game board, which we do here .

Limitations of Our Game Web Server

The limitations of our game web server are quite significant. First of all, for simplicity’s sake, the dod-request-handler function makes absolutely no effort to try to determine from whom the web request is coming. It behaves as if all game interactions were coming from a single player, and therefore isn’t a true multiplayer server for Dice of Doom. If multiple players were to try to play different games at the same time, the dod-request-handler would get confused and bad things would happen.

It would not be too difficult to expand dod-request-handler into a true web server for multiple, parallel games. To do this, we would need to pull session information out of the header data it receives as an argument from the web server, and then all variables it references (such as *cur-game-tree*, for instance) would need to live in a hash table, using the session information as a key. This way, each player would have her own game tree, and our engine could then serve multiple games in parallel. The implementation of such a multigame version of the dod-request-handler is “an exercise for the reader.”

Another limitation of dod-request-handler is that it reads information from the URL using the read-from-string function. As you’ve learned in earlier chapters, this function can be compromised to run arbitrary code in the hands of an experienced (and evil) Lisper.

Initializing a New Game

Here is the web-initialize function, which initializes our game engine to start a brand-new game of Dice of Doom:

(defun web-initialize ()
    (setf *from-tile* nil)
   (setf *cur-game-tree* (game-tree (gen-board) 0 0 t)))

As you can see, it generates a random game board, builds a tree from it, and then stores the result in the global *cur-game-tree* variable .

Announcing a Winner

Here is the function that announces the winner within the web browser:

(defun web-announce-winner (board)
    (fresh-line)
    (let ((w (winners board)))
      (if (> (length w) 1)
        (format t "The game is a tie between ˜a" (mapcar #'player-letter w))
        (format t "The winner is ˜a" (player-letter (car w)))))
   (tag a (href "game.html")
         (princ " play again")))

It is exactly the same as our previous announce-winner function, except that it now includes some extra code at the end to build a web link , which will allow us to conveniently start a brand-new game, since the current game has ended.

Handling the Human Player

The web-handle-human function is responsible for creating the HTML and doing the bookkeeping when the player taking the current turn is the human player.

(defun web-handle-human (pos)
   (cond ((not pos) (princ "Please choose a hex to move from:"))
          ((eq pos 'pass) (setf *cur-game-tree*
                                (cadr (lazy-car (caddr *cur-game-tree*))))
          (princ "Your reinforcements have been placed.")
           (tag a (href (make-game-link nil))
                (princ "continue")))
         ((not *from-tile*) (setf *from-tile* pos)
                             (princ "Now choose a destination:"))
         ((eq pos *from-tile*) (setf *from-tile* nil)
                                (princ "Move cancelled."))
         (t (setf *cur-game-tree*
                   (cadr (lazy-find-if (lambda (move)
                                         (equal (car move)
                                                (list *from-tile* pos)))
                                       (caddr *cur-game-tree*))))
             (setf *from-tile* nil)
             (princ "You may now ")
            (tag a (href (make-game-link 'pass))
                  (princ "pass"))
             (princ " or make another move:"))))

The recent choices the human has made dictate what this function will do. The web-handle-human function knows the human’s choices by referencing the most recently chosen position, which derives from a variable passed as a parameter through the web request. It also can reference the *from-tile* global variable, which tells it which tile the player initially chose to use as a starting location for a move. It needs both of these values, since a move has both a source location and a destination location.

If the player has not yet chosen a location, we want to print a message requesting that the player choose a hex . If the player chose to pass, we want to print a message saying that player’s reinforcements have been placed . (Remember that reinforcements are placed right after someone passes.)

Next, we check if the *from-tile* variable is nil. If this is the case, it means the player has not yet chosen a starting location for a dice attack. If it’s nil, we can set *from-tile* equal to the location that was just selected , as well as ask the player to select a destination.

If the currently selected location is the same as the *from-tile* variable, it means a tile was selected twice. This must mean the player has changed his mind and wants to undo his selection. Therefore, we will set *from-tile* to nil and print a cancellation message .

In all other cases, it means the player has selected two valid locations for the start and end of an attack. We can now advance the *cur-game-tree* to point to the appropriate next tree inside the lazy list of available moves . We want to print a message, allowing the player to pass or make yet another attack.

We have now completed the code our game server will use to interact with the human player. Next, let’s write a function to handle the computer player.

Handling the Computer Player

Handling the web interface for our computer player is pretty simple. After all, computer players don’t need any fancy user interface stuff to know what’s going on in the game. All the web stuff that happens when the computer is making moves is there solely for the benefit of the human player. Here is the web-handle-computer code that renders the HTML in the web interface as the AI player makes a move:

(defun web-handle-computer ()
   (setf *cur-game-tree* (handle-computer *cur-game-tree*))
   (princ "The computer has moved. ")
   (tag script ()
      (princ
        "window.setTimeout('window.location="game.html?chosen=NIL"',5000)")))

All this function does is call our previous handle-computer function, which will return the next branch that the computer has selected in the game tree. We use this to update our *cur-game-tree* variable . Next, we print a message to state that the player has moved . The last part of the function is a clever little gimmick to spice up our web interface a bit. It puts a smidgen of JavaScript in the HTML of the web page , which forces the web browser to automatically load a new web page in five seconds. This means that as the computer AI player makes its moves, we get to see everything happen in a crude animation!

Drawing the SVG Game Board from Within the HTML

We have only one more function to write to complete version 3 of Dice of Doom: the draw-dod-page function. This function interfaces our page game server code with the SVG code that draws our board.

(defun draw-dod-page (tree selected-tile)
    (svg *board-width*
         *board-height*
         (draw-board-svg (cadr tree)
                         selected-tile
                        (take-all (if selected-tile
                                       (lazy-mapcar
                                        (lambda (move)
                                           (when (eql (caar move)
                                                     selected-tile)
                                                (cadar move)))
                                         (caddr tree))
                                    (lazy-mapcar #'caar (caddr tree)))))))

The most complicated part of this function is the code that determines which tiles on the board are legal tiles for the player to click . If the player has already selected a tile, we want to find all moves where the starting position for the move matches the selected tile and return the destination position for the given move . If the player hasn’t selected a tile yet, we just want to return all the legal starting positions .

We have now completed our fully graphical version of Dice of Doom. Let’s play!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.148.144.228