[bknr-cvs] r2264 - in branches/trunk-reorg/projects/scrabble: src website

bknr at bknr.net bknr at bknr.net
Sat Nov 10 19:47:51 UTC 2007


Author: hhubner
Date: 2007-11-10 14:47:50 -0500 (Sat, 10 Nov 2007)
New Revision: 2264

Modified:
   branches/trunk-reorg/projects/scrabble/src/game.lisp
   branches/trunk-reorg/projects/scrabble/src/package.lisp
   branches/trunk-reorg/projects/scrabble/src/rules.lisp
   branches/trunk-reorg/projects/scrabble/src/start-webserver.lisp
   branches/trunk-reorg/projects/scrabble/src/web.lisp
   branches/trunk-reorg/projects/scrabble/website/scrabble.js
Log:
game play back end enhanced.  move withdrawal untested.


Modified: branches/trunk-reorg/projects/scrabble/src/game.lisp
===================================================================
--- branches/trunk-reorg/projects/scrabble/src/game.lisp	2007-11-08 19:17:13 UTC (rev 2263)
+++ branches/trunk-reorg/projects/scrabble/src/game.lisp	2007-11-10 19:47:50 UTC (rev 2264)
@@ -11,26 +11,31 @@
   (print-unreadable-object (tile-bag stream :type t :identity t)
     (format stream "~A letters remaining" (remaining-tile-count tile-bag))))
 
+(defmethod shake-tile-bag ((tile-bag tile-bag))
+  (with-slots (tiles) tile-bag
+    (dotimes (i (fill-pointer tiles))
+      (let ((tmp (aref tiles i))
+	    (random-index (random (fill-pointer tiles))))
+	(setf (aref tiles i) (aref tiles random-index))
+	(setf (aref tiles random-index) tmp)))))
+
+(defmethod initialize-persistent-instance :after ((tile-bag tile-bag))
+  (shake-tile-bag tile-bag))
+
 (defun make-tile-bag (language)
   (let ((tiles (make-array 102 :adjustable t :fill-pointer 0)))
     (mapcar (lambda (entry)
-              (destructuring-bind (char value count) entry
+              (destructuring-bind (letter value count) entry
                 (dotimes (i count)
-                  (vector-push-extend (make-tile char value) tiles))))
+                  (vector-push-extend (make-tile letter value) tiles))))
             (or (getf *tile-sets* language)
 		(error "language ~A not defined" language)))
-    #+(or)
-    (dotimes (i (fill-pointer tiles))
-      (let ((tmp (aref tiles i))
-            (random-index (random (fill-pointer tiles))))
-        (setf (aref tiles i) (aref tiles random-index))
-        (setf (aref tiles random-index) tmp)))
     (make-object 'tile-bag :tiles tiles)))
 
 (define-condition no-tiles-remaining (simple-error)
   ())
 
-(deftransaction draw-tile (tile-bag)
+(defun draw-tile (tile-bag)
   (unless (plusp (remaining-tile-count tile-bag))
     (error 'no-tiles-remaining))
   (with-slots (tiles) tile-bag
@@ -38,31 +43,57 @@
         (aref tiles (1- (fill-pointer tiles)))
       (decf (fill-pointer tiles)))))
 
-(defun make-move (board placed-tiles)
-  "Actually perform a move.  BOARD contains the already placed tiles,
-PLACED-TILES contains the letters for the move to make.  BOARD is
-modified to include the tiles placed. Returns the two values that
-CALCULATE-SCORE returns for the move."
-  (check-move-legality board placed-tiles)
-  (prog1
-      (mapcar (lambda (word-result)
-                (list (word-text word-result) (word-score word-result)))
-              (words-formed board placed-tiles))
-    (dolist (placed-tile placed-tiles)
-      (put-letter board (tile-of placed-tile) (x-of placed-tile) (y-of placed-tile)))))
+(defun draw-tiles (tile-bag count)
+  (loop for i below count
+	collect (draw-tile tile-bag)))
 
+(defmethod undraw-tiles ((tile-bag tile-bag) tiles)
+  (dolist (tile tiles)
+    (vector-push-extend tile (tiles-of tile-bag)))
+  (shake-tile-bag tile-bag))
+
 (defclass player (user)
   ((games :initform nil :accessor games-of))
   (:metaclass persistent-class))
 
 (defclass participant (store-object)
   ((player :initarg :player :reader player-of)
-   (tray :initarg :tray :accessor tray-of))
+   (tray :initarg :tray :accessor tray-of)
+   (score :initform 0 :accessor score-of))
   (:metaclass persistent-class))
 
+(defmethod print-object ((participant participant) stream)
+  (print-unreadable-object (participant stream :type t)
+    (format stream "~A score: ~A, ~A tile~:P"
+	    (user-login (player-of participant))
+	    (score-of participant)
+	    (length (tray-of participant)))))
+
 (defmethod tray-size ((participant participant))
   (length (tray-of participant)))
 
+(defclass move (store-object)
+  ((participant :initarg :participant
+		:reader participant-of)
+   (placed-tiles :initarg :placed-tiles
+		 :reader placed-tiles)
+   (new-tiles-drawn :initarg :new-letters-drawn
+		      :reader new-tiles-drawn-of
+		      :documentation "New letters that have been drawn after
+the move, should the move need to be undone")
+   (words-formed :initarg :words-formed
+		 :reader words-formed-of)
+   (score :initarg :score
+	  :reader score-of))
+  (:metaclass persistent-class))
+
+(defmethod print-object ((move move) stream)
+  (print-unreadable-object (move stream :type t)
+    (format stream "by ~A, ~A points (~{~A~^, ~})"
+	    (player-of (participant-of move))
+	    (reduce #'+ (mapcar #'cdr (words-formed-of move)))
+	    (words-formed-of move))))
+
 (defclass game (store-object)
   ((language :initarg :language
 	     :reader language-of)
@@ -72,7 +103,10 @@
 	     :accessor tile-bag-of)
    (participants :initarg :participants
 		 :accessor participants-of
-		 :documentation "List of participants in this game"))
+		 :documentation "List of participants in this game")
+   (moves :initform nil
+	  :accessor moves-of
+	  :documentation "List of moves that have been made in this game"))
   (:metaclass persistent-class))
 
 (deftransaction make-game (language players)
@@ -94,16 +128,130 @@
 		 :tile-bag tile-bag
 		 :participants participants)))
 
+(defmethod next-participant-of ((game game))
+  (car (participants-of game)))
+
 (defun participants-turn-p (game participant)
-  (eq participant (car (participants-of game))))
+  (eq participant (next-participant-of game)))
 
 (defun ensure-participants-turn (game participant)
-  (assert (participants-turn-p game participant) nil
-	  "It's not ~s's turn."))
+  (unless (participants-turn-p game participant) 
+    (error "It's not ~A's turn." (user-login (player-of participant)))))
 
-(deftransaction next-participants-turn (game)
-  (with-accessors ((participants participants-of))
-      game
-    (let ((first (pop participants)))
-      (setf participants (nconc participants (list first))))))
+(defun rotate-participants (game)
+  (with-slots (participants) game
+    (setf participants (append (cdr participants) (list (car participants))))))
 
+(defun unrotate-participants (game)
+  (with-slots (participants) game
+    (setf participants (append (last participants) (butlast participants)))))
+
+(defmethod letter-equal ((letter1 character) (letter2 character))
+  (eql letter1 letter2))
+
+(defmethod letter-equal ((letter1 character) (letter2 (eql nil)))
+  nil)
+
+(defmethod letter-equal ((tile1 tile) (tile2 tile))
+  (letter-equal (letter-of tile1) (letter-of tile2)))
+
+(defun ensure-participant-has-tiles (participant placed-tiles)
+  (let ((tray-letters (mapcar #'letter-of (tray-of participant)))
+	(placed-letters (mapcar (compose #'letter-of #'tile-of) placed-tiles)))
+    (dolist (letter placed-letters)
+      (let ((has-letter (find letter tray-letters :test #'letter-equal))
+	    (has-blank (find nil tray-letters)))
+      (unless (or has-letter has-blank)
+	(error "participant ~A does not have tile ~A" participant letter))
+      (setf tray-letters
+	    (if has-letter
+		(remove letter tray-letters :test #'letter-equal :count 1)
+		(remove nil tray-letters :key #'letter-of :count 1)))))))
+
+(defun remove-letters-from-participant-tray (participant tiles)
+  (let (removed-tiles)
+    (with-slots (tray) participant
+      (dolist (tile tiles)
+	(push (find tile tray :test #'letter-equal) removed-tiles)
+	(setf tray (remove tile tray :test #'letter-equal :count 1))))
+    removed-tiles))
+
+(defun draw-new-letters (tile-bag participant count)
+  (let ((drawn (draw-tiles tile-bag (min count (remaining-tile-count tile-bag)))))
+    (setf (tray-of participant)
+	  (append drawn (tray-of participant)))
+    drawn))
+
+(defun make-move (game participant placed-tiles)
+  (ensure-participants-turn game participant)
+  (ensure-participant-has-tiles participant placed-tiles)
+  (check-move-legality (board-of game) placed-tiles)
+  (with-transaction (:make-move)
+    (let ((words-formed (mapcar (lambda (word-result)
+				  (cons (word-text word-result) (word-score word-result)))
+				(words-formed (board-of game) placed-tiles))))
+      (dolist (placed-tile placed-tiles)
+	(put-letter (board-of game) (tile-of placed-tile) (x-of placed-tile) (y-of placed-tile)))
+      (let ((tiles-used (remove-letters-from-participant-tray participant (mapcar #'tile-of placed-tiles)))
+	    (tiles-drawn (draw-new-letters (tile-bag-of game) participant (length placed-tiles)))
+	    (score (reduce #'+ (mapcar #'cdr words-formed))))
+	(when (= 7 (length tiles-used))
+	  (incf score 50))
+	(incf (score-of participant) score)
+	(push (make-object 'move
+			   :participant participant
+			   :placed-tiles placed-tiles
+			   :new-tiles-drawn tiles-drawn
+			   :words-formed words-formed
+			   :score score)
+	      (moves-of game))))
+    (rotate-participants game)))
+
+(defclass move-withdrawal (store-object)
+  ((participant :initarg :participant
+		:reader participant-of)
+   (reason :initarg :reason
+	   :reader reason-of))
+  (:metaclass persistent-class))
+
+(defun withdraw-last-move (game reason)
+  (let ((move (car (moves-of game))))
+    (unless move
+      (error "no move in game to withdraw"))
+    (unless (typep game 'move)
+      (error "last move was not a letter placement, can't be withdrawn"))
+    (with-transaction (:withdraw-last-move)
+      (with-slots (participant placed-tiles new-tiles-drawn score) move
+	(decf (score-of participant) score)
+	(setf (tiles-of (tray-of participant))
+	      (append (set-difference (tiles-of (tray-of participant))
+				      new-tiles-drawn)
+		      (mapcar #'tile-of placed-tiles)))
+	(undraw-tiles (tile-bag-of game) new-tiles-drawn)
+	(dolist (placement placed-tiles)
+	  (put-letter (board-of game) nil (x-of placement) (y-of placement)))
+	(push (make-object 'move-withdrawal
+			   :participant participant
+			   :reason reason)
+	      (moves-of game))))))
+
+(defclass tile-swap (store-object)
+  ((participant :initarg :participant
+		:reader participant-of)
+   (count :initarg :count
+	  :reader count-of))
+  (:metaclass persistent-class))
+
+(defun swap-tiles (game participant tiles)
+  (unless (<= 7 (remaining-tile-count (tile-bag-of game)))
+    (error "not enough remaining tiles to swap"))
+  (with-transaction (:swap-tiles)
+    (setf (tray-of participant)
+	  (append (set-difference (tray-of participant)
+				  tiles)
+		  (draw-tiles (tile-bag-of game) (length tiles))))
+    (undraw-tiles (tile-bag-of game) tiles)
+    (push (make-object 'tile-swap
+		       :participant participant
+		       :count (length tiles))
+	  (moves-of game))))

Modified: branches/trunk-reorg/projects/scrabble/src/package.lisp
===================================================================
--- branches/trunk-reorg/projects/scrabble/src/package.lisp	2007-11-08 19:17:13 UTC (rev 2263)
+++ branches/trunk-reorg/projects/scrabble/src/package.lisp	2007-11-10 19:47:50 UTC (rev 2264)
@@ -11,7 +11,7 @@
            "FIELD-TYPE"
 
 	   "TILE"
-	   "CHAR-OF"
+	   "LETTER-OF"
 	   "VALUE-OF"
 
 	   "BOARD"
@@ -29,6 +29,10 @@
 
 	   "MAKE-TILE-PLACEMENTS"
 	   "MAKE-MOVE"
+	   "MOVE"
+	   "SCORE-OF"
+	   "PARTICIPANT-OF"
+	   "WORDS-FORMED-OF"
 
 	   "GAME"
 	   "LANGUAGE-OF"

Modified: branches/trunk-reorg/projects/scrabble/src/rules.lisp
===================================================================
--- branches/trunk-reorg/projects/scrabble/src/rules.lisp	2007-11-08 19:17:13 UTC (rev 2263)
+++ branches/trunk-reorg/projects/scrabble/src/rules.lisp	2007-11-10 19:47:50 UTC (rev 2264)
@@ -60,7 +60,7 @@
     (terpri stream)
     (dotimes (x 15)
       (dotimes (y 15)
-        (format stream "~C " (aif (at-xy board x y) (char-of it) #\.)))
+        (format stream "~C " (aif (at-xy board x y) (letter-of it) #\.)))
       (terpri stream))))
 
 
@@ -70,21 +70,21 @@
 (defmethod at-placement ((board board) tile-placement)
   (at-xy board (x-of tile-placement) (y-of tile-placement)))
 
-(deftransaction put-letter (board tile x y)
+(defun put-letter (board tile x y)
   (setf (aref (placed-tiles-of board) x y) tile))
 
 (defclass tile (store-object)
-  ((char :reader char-of :initarg :char)
+  ((letter :reader letter-of :initarg :letter)
    (value :reader value-of :initarg :value))
   (:metaclass persistent-class))
 
 (defmethod print-object ((tile tile) stream)
   (print-unreadable-object (tile stream :type t :identity nil)
-    (with-slots (char value) tile
-      (format stream "~A (~A)" char value))))
+    (with-slots (letter value) tile
+      (format stream "~A (~A)" (when letter (char-name letter)) value))))
 
-(defun make-tile (char value)
-  (make-object 'tile :char char :value value))
+(defun make-tile (letter value)
+  (make-object 'tile :letter letter :value value))
 
 (defmethod placed-tile-adjacent ((board board) (tile-placement tile-placement))
   "Check whether the given TILE-PLACEMENT on the board is adjacent to
@@ -197,5 +197,5 @@
 (defun word-text (word-result)
   "Convert the letter in a word result returned by WORDS-FORMED to a
 string."
-  (coerce (mapcar (compose #'char-of #'car) word-result) 'string))
+  (coerce (mapcar (compose #'letter-of #'car) word-result) 'string))
 

Modified: branches/trunk-reorg/projects/scrabble/src/start-webserver.lisp
===================================================================
--- branches/trunk-reorg/projects/scrabble/src/start-webserver.lisp	2007-11-08 19:17:13 UTC (rev 2263)
+++ branches/trunk-reorg/projects/scrabble/src/start-webserver.lisp	2007-11-10 19:47:50 UTC (rev 2264)
@@ -3,15 +3,18 @@
 (defclass scrabble-store (mp-store random-mixin)
   ())
 
-(defun open-scrabble-store ()
+(defun open-scrabble-store (&optional delete-old-p)
   (ignore-errors (close-store))
-  (asdf:run-shell-command "rm -rf /tmp/scrabble-store/")
+  (when delete-old-p
+    (sb-ext:run-program "/bin/rm" '("-rf" "/tmp/scrabble-store/") :environment nil))
   (make-instance 'scrabble-store :directory "/tmp/scrabble-store/"
 		 :subsystems (list (make-instance 'store-object-subsystem)
 				   (make-instance 'random-mixin-subsystem)))
-  (let ((user1 (make-user "user1" :class 'player :full-name "User Eins"))
-	(user2 (make-user "user2" :class 'player :full-name "User Zwei")))
-    (make-game :de (list user1 user2))))
+  (unless (class-instances 'user)
+    (format t "creating test users and game~%")
+    (let ((user1 (make-user "user1" :class 'player :full-name "User Eins"))
+	  (user2 (make-user "user2" :class 'player :full-name "User Zwei")))
+      (make-game :de (list user1 user2)))))
 
 (defparameter *website-directory*
   (make-pathname :name nil :type nil :version nil

Modified: branches/trunk-reorg/projects/scrabble/src/web.lisp
===================================================================
--- branches/trunk-reorg/projects/scrabble/src/web.lisp	2007-11-08 19:17:13 UTC (rev 2263)
+++ branches/trunk-reorg/projects/scrabble/src/web.lisp	2007-11-10 19:47:50 UTC (rev 2264)
@@ -27,18 +27,29 @@
 (defmethod encode-json ((tile-bag tile-bag) stream)
   (encode-json-plist (list :remaining-tiles (remaining-tile-count tile-bag)) stream))
 
+(defmethod encode-json ((move move) stream)
+  (encode-json-plist (list :participant-login (user-login (player-of (participant-of move)))
+			   :score (score-of move)
+			   :words (mapcar (lambda (word-cons)
+					    (list :word (car word-cons)
+						  :score (cdr word-cons)))
+					  (words-formed-of move)))
+		     stream))
+			     
+
 (defmethod encode-json ((board board) stream)
   (princ #\[ stream)
   (dotimes (x 15)
     (dotimes (y 15)
       (awhen (at-xy board x y)
-	(encode-json (list x y (char-of it) (value-of it)) stream)
+	(encode-json (list x y (letter-of it) (value-of it)) stream)
 	(princ #\, stream))))
   (princ #\] stream))
 
 (defmethod encode-json ((participant participant) stream)
   (start-session)
-  (encode-json-plist (append (list :name (user-full-name (player-of participant))
+  (encode-json-plist (append (list :login (user-login (player-of participant))
+				   :name (user-full-name (player-of participant))
                                    :remaining-tiles)
                              (list (if (equal (user-login (player-of participant))
                                               (session-value :user))
@@ -66,20 +77,27 @@
 	      (:tr (:td "Password") (:td (:input :type "PASSWORD" :name "password")))
 	      (:tr (:td) (:td (:input :type "SUBMIT")))))))))
 
-(defun parse-move (tray string)
+(defun parse-move (participant string)
   "Given a string X1,Y1,LETTER1,X2,Y2,LETTER2..., return a list of
-moves ((X1 Y1 LETTER-TILE2) (X2 Y2 LETTER-TILE2) ...)"
-  (let (list-of-moves)
+moves ((X1 Y1 LETTER-TILE2) (X2 Y2 LETTER-TILE2) ...).  LETTERx may
+either be a single-character letter which will be used directly or a
+character name (like LATIN_CAPITAL_LETTER_U_WITH_DIAERESIS) which will
+be converted into a character using NAME-CHAR."
+  (let ((tray (tray-of participant))
+	list-of-moves)
     (labels
 	((use-letter (letter)
-	   (awhen (find letter tray :key #'char-of)
+	   (awhen (or (find letter tray :key #'letter-of)
+		      (find nil tray :key #'letter-of))
 	     (setf tray (remove it tray))
 	     (return-from use-letter it))
-	   (error "tray does not contain letter ~A" letter))
+	   (error "tray of ~A does not contain letter ~A" participant letter))
 	 (recurse (x-string y-string letter &rest more)
 	   (push (list (parse-integer x-string)
 		       (parse-integer y-string)
-		       (use-letter (aref letter 0)))
+		       (use-letter (if (= 1 (length letter))
+				       (aref letter 0)
+				       (name-char letter))))
 		 list-of-moves)
 	   (when more
 	     (apply #'recurse more))))
@@ -97,13 +115,18 @@
 (define-easy-handler (make-move-handler :uri "/make-move" :default-request-type :post)
     (move game)
   (start-session)
-  ;; MOVE contains the move to make.  (This should) check that it is
-  ;; the session users' turn, that the move is legal, then actually
-  ;; make the move.
-  (let* ((game (find-game game))
-	 (participant (find (find-user (session-value :user)) (participants-of game) :key #'player-of)))
-    (scrabble:make-move (board-of game)
-			(parse-move (tray-of participant) move))))
+  ;; MOVE contains the move to make, GAME is the object ID of the game.
+  (with-output-to-string (s)
+    (handler-case
+	(progn
+	  (let* ((game (find-game game))
+		 (participant (find (find-user (session-value :user)) (participants-of game) :key #'player-of)))
+	    (scrabble:make-move game
+				participant
+				(parse-move participant move))
+	    (encode-json-plist (list :game game) s)))
+      (error (e)
+	(encode-json-plist (list :error (princ-to-string e)) s)))))
 
 (define-easy-handler (games :uri "/games") ()
   (start-session)

Modified: branches/trunk-reorg/projects/scrabble/website/scrabble.js
===================================================================
--- branches/trunk-reorg/projects/scrabble/website/scrabble.js	2007-11-08 19:17:13 UTC (rev 2263)
+++ branches/trunk-reorg/projects/scrabble/website/scrabble.js	2007-11-10 19:47:50 UTC (rev 2264)
@@ -31,10 +31,59 @@
 		    ["triple-word",null,null,"double-letter",null,null,null,"triple-word",
 		     null,null,null,"double-letter",null,null,"triple-word"]];
 
+// Scrabble rule enforcement
+
+function checkMoveLegality(placedTiles)
+{
+    // Given the board and list of placed tiles, either throw an error or
+    // return if the move is legal.
+
+    var positions = map(function (placement) { return [ placement[0], placement[1] ] }, placedTiles)
+	.sort(function (a, b) { (a[0] > b[0]) || (a[1] > b[1])});
+
+    if (filter(partial(operator.ne, positions[0][0]), map(function (position) { return position[0] }, positions)).length
+	&& filter(partial(operator.ne, positions[0][1]), map(function (position) { return position[1] }, positions)).length) {
+	throw "not-in-a-row";
+    }
+
+    var startOfPlacement = positions[0];
+    var endOfPlacement = positions[positions.length - 1];
+
+    for (var x = startOfPlacement[0]; x <= endOfPlacement[0]; x++) {
+	for (var y = startOfPlacement[1]; y <= endOfPlacement[1]; y++) {
+	    if (!letterAt(x, y) && (findValue(positions, [ x, y ]) == -1)) {
+		throw "placement-with-holes";
+	    }
+	}
+    }
+
+    if (findValue(positions, [ 7, 7 ]) == -1) {
+	var found = false;
+	for (var x = startOfPlacement[0]; !found && x <= endOfPlacement[0]; x++) {
+	    for (var y = startOfPlacement[1]; !found && y <= endOfPlacement[1]; y++) {
+		if (((x > 0) && letterAt(x - 1, y))
+		    || ((x < 14) && letterAt(x + 1, y))
+		    || ((y > 0) && letterAt(x, y - 1))
+		    || ((y < 14) && letterAt(x, y + 1))) {
+		    found = true;
+		}
+	    }
+	}
+	if (!found) {
+	    throw "not-touching-other-tile";
+	}
+    }
+}
+	
+	
+//
+
 function getFieldScore(x, y) {
     return boardScoring[x][y] || 'standard';
 }
 
+var theirTrays;
+
 var gameID = 108;
 var board;
 
@@ -67,6 +116,13 @@
     setElementPosition(myTrayContainer, { x: border + 194, y: border + 665 });
     appendChildNodes(container, myTrayContainer);
 
+    theirTrays = DIV();
+    theirTrays.style.position = 'absolute';
+    theirTrays.style.width = 7 * 44 + 'px';
+    theirTrays.style.height = '70px';
+    setElementPosition(theirTrays, { x: border + 16 * 40, y: border + 40 });
+    appendChildNodes(container, theirTrays);
+
     var shuffleButton = DIV(null, "shuffle");
     shuffleButton.style.color = 'white';
     shuffleButton.style.position = 'absolute';
@@ -176,9 +232,15 @@
 var move = [];
 
 function makeMove(x, y, letter) {
-    move.push([x, y, letter]);
+    move[move.length] = [x, y, letter];
     $('move').onclick = submitMove;
     $('move').innerHTML = move.toString();
+    try {
+	checkMoveLegality(move);
+    }
+    catch (e) {
+	$('move').innerHTML = e.toString();
+    }
 }
 
 function clearMove() {
@@ -197,9 +259,21 @@
     res.addCallbacks(moveSuccess, moveFailure);
 }
 
-function moveSuccess()
+function moveSuccess(result)
 {
-    clearMove();
+    var response;
+    try {
+	response = eval('(' + result.responseText + ')');
+    }
+    catch (e) {
+	alert("invalid JSON reply: " + result.responseText);
+	return;
+    }
+    if (response.error) {
+	alert(response.error);
+    } else {
+	clearMove();
+    }
 }
 
 function moveFailure(e)
@@ -207,8 +281,12 @@
     alert('failed: ' + e);
 }
 
+var specialKeyCodes = { 59: 'LATIN_CAPITAL_LETTER_U_WITH_DIAERESIS',
+			192: 'LATIN_CAPITAL_LETTER_O_WITH_DIAERESIS',
+			222: 'LATIN_CAPITAL_LETTER_A_WITH_DIAERESIS' };
+
 function letterKeyPressed(type, args, obj) {
-    var letter = String.fromCharCode(args[0]);
+    var letter = specialKeyCodes[args[0]] || String.fromCharCode(args[0]);
     var x = cursor.x;
     var y = cursor.y;
     cursor.advance();
@@ -310,6 +388,13 @@
     tray = newTray;
 }
 
+function addTheirTray (participant) {
+    appendChildNodes(theirTrays, DIV(null,
+				     DIV(null, participant.name),
+				     DIV(null, map(function () { return IMG({ src: 'images/NIL.png' }) },
+						   new Array(participant.remainingTiles)))));
+}
+
 function drawGameState (gameState) {
     for (var i = 0; i < gameState.board.length; i++) {
 	var x = gameState.board[i][0];
@@ -317,19 +402,29 @@
 	var char = gameState.board[i][2];
 	setLetter(x, y, char);
     }
+    for (var i = 0; i < gameState.participants.length; i++) {
+	var participant = gameState.participants[i];
+	if (typeof participant.remainingTiles == 'number') {
+	    addTheirTray(participant);
+	} else {
+	    makeMyTray(map(function (entry) { return entry.letter }, participant.remainingTiles));
+	}
+    }
 }
 
-var legalLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÜß".split("");
+var legalLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
 
 function init() {
     makeBoard();
-    var d = loadJSONDoc("/game/" + gameID);
-    d.addCallbacks(drawGameState, alert);
 
     var letterKeyCodes = [];
     for (var i = 0; i < legalLetters.length; i++) {
 	letterKeyCodes[i] = legalLetters[i].charCodeAt(0);
     }
+    // add mozilla key codes
+    letterKeyCodes.push(59); // u"
+    letterKeyCodes.push(192); // o"
+    letterKeyCodes.push(222); // a"
     var letterKeyListener = new YAHOO.util.KeyListener(document,
 	{ keys: letterKeyCodes },
 	{ fn: letterKeyPressed, scope: this, correctScope: true });
@@ -345,6 +440,6 @@
     moveDisplay.style.position = 'absolute';
     setElementPosition(moveDisplay, { x: border + 550, y: border + 665 });
     appendChildNodes(document.body, moveDisplay);
-    var d = loadJSONDoc("/game/108");
+    var d = loadJSONDoc("/game/" + gameID);
     d.addCallbacks(drawGameState, alert);
 }




More information about the Bknr-cvs mailing list