[bknr-cvs] r2336 - in branches/trunk-reorg: bknr/web/src bknr/web/src/web projects/lisp-ecoop/src projects/scrabble/src projects/scrabble/website thirdparty/hunchentoot-0.14.7 thirdparty/parenscript/src thirdparty/slime

hhubner at common-lisp.net hhubner at common-lisp.net
Thu Jan 17 16:36:33 UTC 2008


Author: hhubner
Date: Thu Jan 17 11:36:28 2008
New Revision: 2336

Added:
   branches/trunk-reorg/projects/scrabble/website/scrabble-resizable-attempt.js
Modified:
   branches/trunk-reorg/bknr/web/src/bknr-web.asd
   branches/trunk-reorg/bknr/web/src/web/handlers.lisp
   branches/trunk-reorg/bknr/web/src/web/menu.lisp
   branches/trunk-reorg/bknr/web/src/web/templates.lisp
   branches/trunk-reorg/bknr/web/src/web/web-utils.lisp
   branches/trunk-reorg/projects/lisp-ecoop/src/lisp-ecoop.asd
   branches/trunk-reorg/projects/lisp-ecoop/src/macros.lisp
   branches/trunk-reorg/projects/lisp-ecoop/src/packages.lisp
   branches/trunk-reorg/projects/lisp-ecoop/src/schedule.lisp
   branches/trunk-reorg/projects/lisp-ecoop/src/webserver.lisp
   branches/trunk-reorg/projects/scrabble/src/web.lisp
   branches/trunk-reorg/projects/scrabble/website/scrabble.html
   branches/trunk-reorg/projects/scrabble/website/scrabble.js
   branches/trunk-reorg/thirdparty/hunchentoot-0.14.7/misc.lisp
   branches/trunk-reorg/thirdparty/parenscript/src/js-macrology.lisp
   branches/trunk-reorg/thirdparty/slime/slime.el
   branches/trunk-reorg/thirdparty/slime/swank.lisp
Log:
Save pending lisp-ecoop and scrabble changes.


Modified: branches/trunk-reorg/bknr/web/src/bknr-web.asd
==============================================================================
--- branches/trunk-reorg/bknr/web/src/bknr-web.asd	(original)
+++ branches/trunk-reorg/bknr/web/src/bknr-web.asd	Thu Jan 17 11:36:28 2008
@@ -32,7 +32,8 @@
 		 :xhtmlgen
 		 :puri
 		 :bknr-datastore
-		 :bknr-data-impex)
+		 :bknr-data-impex
+                 :parenscript)
 
     :components ((:file "packages")
 	       
@@ -59,7 +60,6 @@
 						    :depends-on ("parse-xml" "rss")))
 			  :depends-on ("packages"))
 
-		 #+notyet
 		 (:module "web" :components ((:file "site")
 					     ;; data
 					     (:file "host")

Modified: branches/trunk-reorg/bknr/web/src/web/handlers.lisp
==============================================================================
--- branches/trunk-reorg/bknr/web/src/web/handlers.lisp	(original)
+++ branches/trunk-reorg/bknr/web/src/web/handlers.lisp	Thu Jan 17 11:36:28 2008
@@ -229,7 +229,7 @@
 	       (setf (session-variable :login-redirect-uri)
 		     (redirect-uri (request-uri req)))
 	       (redirect (website-make-path *website* "login") req))
-	     (if (member :notrap net.aserve::*debug-current* :test #'eq)
+	     (if hunchentoot:*catch-errors-p*
 		 (handle handler req)
 		 (handler-bind ((error #'(lambda (e)
 					   (with-bknr-http-response (*req* :content-type "text/html; charset=UTF-8"
@@ -533,7 +533,7 @@
 (defun show-page-with-error-handlers (fn req &key response title)
   (unless response
     (setf response *response-ok*))	; can't default because used from macros and *response-ok* is not a constant
-  (if (member :notrap net.aserve::*debug-current*)
+  (if hunchentoot:*catch-errors-p*
       (with-bknr-http-response (req :content-type "text/html; charset=UTF-8" :response response)
 	(with-http-body (req *ent*)
 	  (website-show-page *website* fn title)))

Modified: branches/trunk-reorg/bknr/web/src/web/menu.lisp
==============================================================================
--- branches/trunk-reorg/bknr/web/src/web/menu.lisp	(original)
+++ branches/trunk-reorg/bknr/web/src/web/menu.lisp	Thu Jan 17 11:36:28 2008
@@ -50,7 +50,7 @@
       (when title
         (html ((:div :class "title") (:princ-safe title))))
       (dolist (item (menu-items menu))
-	(let ((item-is-active (in-subtree (puri:uri-path (net.aserve:request-uri *req*)) (item-url item))))
+	(let ((item-is-active (in-subtree (request-uri) (item-url item))))
 	  (with-slots (url title active-image inactive-image) item
             (let ((link-url (format nil "~A~A" (website-base-href *website*) url)))
               (cond

Modified: branches/trunk-reorg/bknr/web/src/web/templates.lisp
==============================================================================
--- branches/trunk-reorg/bknr/web/src/web/templates.lisp	(original)
+++ branches/trunk-reorg/bknr/web/src/web/templates.lisp	Thu Jan 17 11:36:28 2008
@@ -12,11 +12,11 @@
                                  "/usr/local/share/xml/catalog.ports"))
 
 (eval-when (:load-toplevel :execute)
-  (let ((env-catalog (assoc :xmlcatalog ext:*environment-list*)))
+  (let ((env-catalog (sb-ext:posix-getenv "XMLCATALOG")))
     (when env-catalog
-      (pushnew (cdr env-catalog) *template-dtd-catalog* :test #'equal))))
+      (pushnew env-catalog *template-dtd-catalog* :test #'equal))))
 
-;; user-error is supposed to be raised when an error is provoced by
+;; user-error is supposed to be raised when an error is provoked by
 ;; the user (i.e. by supplying invalid form data).
 
 (define-condition user-error (simple-error)
@@ -272,7 +272,7 @@
                                handler req)))))))
 
 (defun invoke-with-error-handlers (fn handler req)
-  (if (member :notrap net.aserve::*debug-current*)
+  (if hunchentoot:*catch-errors-p*
       (handler-case
 	  (funcall fn)
 	(template-not-found (c)

Modified: branches/trunk-reorg/bknr/web/src/web/web-utils.lisp
==============================================================================
--- branches/trunk-reorg/bknr/web/src/web/web-utils.lisp	(original)
+++ branches/trunk-reorg/bknr/web/src/web/web-utils.lisp	Thu Jan 17 11:36:28 2008
@@ -293,4 +293,4 @@
 	(princ " />"))))
 
 (defun encode-urlencoded (string)
-  (regex-replace-all #?r"\+" (net.aserve::encode-form-urlencoded string) "%20"))
\ No newline at end of file
+  (url-encode string))
\ No newline at end of file

Modified: branches/trunk-reorg/projects/lisp-ecoop/src/lisp-ecoop.asd
==============================================================================
--- branches/trunk-reorg/projects/lisp-ecoop/src/lisp-ecoop.asd	(original)
+++ branches/trunk-reorg/projects/lisp-ecoop/src/lisp-ecoop.asd	Thu Jan 17 11:36:28 2008
@@ -16,15 +16,22 @@
   :description "Website for the LISP ECOOP Workshops"
   :long-description ""
 
-  :depends-on (:bknr-modules :cxml :klammerscript)
+  :depends-on (:bknr-datastore
+               :bknr-web
+               :cxml)
 
   :components ((:file "packages")
 	       (:file "config" :depends-on ("packages"))
 	       (:file "macros" :depends-on ("config"))
+               #+(or)
 	       (:file "schedule" :depends-on ("macros"))
+               #+(or)
 	       (:file "participant" :depends-on ("macros" "schedule"))
+               #+(or)
 	       (:file "mail" :depends-on ("participant"))
+               #+(or)
 	       (:file "tags" :depends-on ("participant"))
+               #+(or)
 	       (:file "handlers" :depends-on ("participant"))
-	       (:file "webserver" :depends-on ("handlers"))
+	       (:file "webserver" :depends-on (#+(or) "handlers"))
 	       (:file "init" :depends-on ("webserver"))))

Modified: branches/trunk-reorg/projects/lisp-ecoop/src/macros.lisp
==============================================================================
--- branches/trunk-reorg/projects/lisp-ecoop/src/macros.lisp	(original)
+++ branches/trunk-reorg/projects/lisp-ecoop/src/macros.lisp	Thu Jan 17 11:36:28 2008
@@ -1,6 +1,6 @@
 (in-package :lisp-ecoop)
 
-(defvar *dtd* (ext:unix-namestring (merge-pathnames #p"src/lisp-ecoop.dtd" lisp-ecoop.config::*root-directory*)))
+(defvar *dtd* (namestring (merge-pathnames #p"src/lisp-ecoop.dtd" lisp-ecoop.config::*root-directory*)))
 
 (defun compute-slot (class slot)
   (destructuring-bind (name access &rest rest &key attribute element &allow-other-keys) slot

Modified: branches/trunk-reorg/projects/lisp-ecoop/src/packages.lisp
==============================================================================
--- branches/trunk-reorg/projects/lisp-ecoop/src/packages.lisp	(original)
+++ branches/trunk-reorg/projects/lisp-ecoop/src/packages.lisp	Thu Jan 17 11:36:28 2008
@@ -23,7 +23,6 @@
 (defpackage :lisp-ecoop
   (:use :cl
 	:cl-user
-	:ext
 	:cl-interpol
 	:cl-ppcre
 	:bknr.utils
@@ -34,8 +33,7 @@
 	:bknr.images
         :bknr.impex
 	:lisp-ecoop.config
-	:net.aserve
-	:net.post-office
+	:hunchentoot
 	:xhtml-generator)
   (:shadowing-import-from :cl-interpol #:quote-meta-chars)
   (:export #:participant
@@ -70,16 +68,14 @@
 	:cl-user
 	:cl-ppcre
 	:cl-interpol
-	:ext
 	:bknr.web
 	:bknr.utils
 	:bknr.datastore
 	:bknr.user
 	:bknr.images
-	:net.aserve
+	:hunchentoot
 	:xhtml-generator
 	:lisp-ecoop.config
 	:lisp-ecoop)
   (:shadowing-import-from :cl-interpol #:quote-meta-chars)
-  (:shadowing-import-from :acl-compat.mp #:process-kill #:process-wait)
   (:export #:hello))
\ No newline at end of file

Modified: branches/trunk-reorg/projects/lisp-ecoop/src/schedule.lisp
==============================================================================
--- branches/trunk-reorg/projects/lisp-ecoop/src/schedule.lisp	(original)
+++ branches/trunk-reorg/projects/lisp-ecoop/src/schedule.lisp	Thu Jan 17 11:36:28 2008
@@ -34,6 +34,8 @@
   ())
 
 (defun parse-time-spec (string)
+  (error "cannot parse time ~A yet" string)
+  #+(or)
   (or (ignore-errors (parse-integer string))
       (ext:parse-time string :default-zone -2))) ; XXX deal with time zone correctly!
 

Modified: branches/trunk-reorg/projects/lisp-ecoop/src/webserver.lisp
==============================================================================
--- branches/trunk-reorg/projects/lisp-ecoop/src/webserver.lisp	(original)
+++ branches/trunk-reorg/projects/lisp-ecoop/src/webserver.lisp	Thu Jan 17 11:36:28 2008
@@ -5,9 +5,11 @@
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
+#+(or)
 (defun make-daily-statistics ()
   (bknr.stats::make-yesterdays-stats :delete-events t :remove-referer-hosts '("lisp-ecoop.bknr.net")))
 
+#+(or)
 (defun publish-lisp-ecoop (&key (port *webserver-port*) (listeners 20) (base-href *base-path*))
 
   (unless (bknr.cron:cron-job-with-name "daily webserver statistics")
@@ -34,3 +36,11 @@
 		 :javascript-urls (list (format nil "~Astatic/javascript.js" base-href)))
 
   (start :port port :listeners listeners))
+
+(defun start-webserver (&key (port 9000))
+  (when (and (boundp '*server*) *server*)
+    (stop-server *server*))
+  (setq *dispatch-table*
+	(list 'dispatch-easy-handlers
+	      (create-folder-dispatcher-and-handler "/" *website-directory*)))
+  (setq *server* (start-server :port port)))
\ No newline at end of file

Modified: branches/trunk-reorg/projects/scrabble/src/web.lisp
==============================================================================
--- branches/trunk-reorg/projects/scrabble/src/web.lisp	(original)
+++ branches/trunk-reorg/projects/scrabble/src/web.lisp	Thu Jan 17 11:36:28 2008
@@ -46,6 +46,7 @@
 
 (defmethod encode-json ((tile blank-tile) stream)
   (encode-json-plist (append (list :letter nil
+                                   :letter-name nil
                                    :value 0)
                              (awhen (used-for tile)
                                (list :used-for it)))

Added: branches/trunk-reorg/projects/scrabble/website/scrabble-resizable-attempt.js
==============================================================================
--- (empty file)
+++ branches/trunk-reorg/projects/scrabble/website/scrabble-resizable-attempt.js	Thu Jan 17 11:36:28 2008
@@ -0,0 +1,615 @@
+// -*- JavaScript -*-
+
+var boardScoring = [["triple-word",null,null,"double-letter",null,null,null,"triple-word",
+		     null,null,null,"double-letter",null,null,"triple-word"],
+		    [null,"double-word",null,null,null,"triple-letter",null,null,null,"triple-letter",
+		     null,null,null,"double-word",null],
+		    [null,null,"double-word",null,null,null,"double-letter",null,"double-letter",
+		     null,null,null,"double-word",null,null],
+		    ["double-letter",null,null,"double-word",null,null,null,"double-letter",
+		     null,null,null,"double-word",null,null,"double-letter"],
+		    [null,null,null,null,"double-word",null,null,null,null,null,"double-word",
+		     null,null,null,null],
+		    [null,"triple-letter",null,null,null,"triple-letter",null,null,null,"triple-letter",
+		     null,null,null,"triple-letter",null],
+		    [null,null,"double-letter",null,null,null,"double-letter",null,"double-letter",
+		     null,null,null,"double-letter",null,null],
+		    ["triple-word",null,null,"double-letter",null,null,null,"double-word",
+		     null,null,null,"double-letter",null,null,"triple-word"],
+		    [null,null,"double-letter",null,null,null,"double-letter",null,"double-letter",
+		     null,null,null,"double-letter",null,null],
+		    [null,"triple-letter",null,null,null,"triple-letter",null,null,null,"triple-letter",
+		     null,null,null,"triple-letter",null],
+		    [null,null,null,null,"double-word",null,null,null,null,null,"double-word",
+		     null,null,null,null],
+		    ["double-letter",null,null,"double-word",null,null,null,"double-letter",
+		     null,null,null,"double-word",null,null,"double-letter"],
+		    [null,null,"double-word",null,null,null,"double-letter",null,"double-letter",
+		     null,null,null,"double-word",null,null],
+		    [null,"double-word",null,null,null,"triple-letter",null,null,null,"triple-letter",
+		     null,null,null,"double-word",null],
+		    ["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.x, placement.y ] }, placedTiles)
+    .sort(function (a, b) { return (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";
+    }
+  }
+}
+
+// Size calculations
+
+var fieldBorderSize = 4;
+var fieldSize = 40;
+var tileBorderSize = 3;
+var tileSize = 34;
+var cellSize = 44;
+
+function calculateFieldSize()
+{
+  // Our maximum field size is 44x44 pixels.  If the window is not
+  // high enough to accomodate the board, scale down.
+
+  var requiredHeight = 16 * 44 + 40; // 16 fields (including player tray) + borders
+  var viewportHeight = YAHOO.util.Dom.getViewportHeight();
+  fieldSize = Math.floor((Math.min(requiredHeight, viewportHeight) - 40) / 16);
+  fieldBorderSize = Math.floor(fieldSize / 10);
+  fieldSize -= fieldBorderSize;
+  tileSize = Math.floor(fieldSize * 34 / 40);
+  tileBorderSize = Math.round((fieldSize - tileSize) / 2);
+  cellSize = fieldBorderSize + fieldSize;
+//   alert('fieldSize: ' + fieldSize + ' fieldBorderSize: ' + fieldBorderSize
+//         + ' tileSize: ' + tileSize + ' tileBorderSize: ' + tileBorderSize);
+}
+  
+
+//
+
+function getFieldScore(x, y) {
+  return boardScoring[x][y] || 'standard';
+}
+
+var theirTrays;
+var tray = [];
+
+var gameID = 108;
+var board;
+
+var border = 10;
+
+function makeBoard() {
+  calculateFieldSize();
+  var container = $('playfield');
+  board = [];
+  for (x = 0; x < 15; x++) {
+    board[x] = [];
+    for (y = 0; y < 15; y++) {
+      var element = IMG();
+      element.style.position = 'absolute';
+      element.style.width = fieldSize + 'px';
+      element.style.height = fieldSize + 'px';
+      element.xPos = x;
+      element.yPos = y;
+      var imageName = (x == 7 && y == 7) ? "start-field" : getFieldScore(x, y);
+      element.src = 'images/' + imageName + '.png';
+      setElementPosition(element, { x: border + x * cellSize, y: border + y * cellSize });
+      YAHOO.util.Event.on(element, 'click', emptyTileClicked);
+      board[x][y] = element;
+    }
+    appendChildNodes(container, board[x]);
+  }
+
+  var shuffleButton = DIV(null, "shuffle");
+  shuffleButton.style.color = 'white';
+  shuffleButton.style.position = 'absolute';
+  shuffleButton.onclick = shuffleMyTray;
+  setElementPosition(shuffleButton, { x: border + 480, y: border + 665 });
+  appendChildNodes(container, shuffleButton);
+
+  var gameLog = DIV({ id: 'gameLog' }, "");
+  gameLog.style.position = 'absolute';
+  gameLog.style.width = '280px';
+  gameLog.style.height = '250px';
+  gameLog.style.textAlign = 'left';
+  gameLog.style.overflowY = 'scroll';
+  setElementPosition(gameLog, { x: border + 680, y: border + 400 });
+  appendChildNodes($('playfield'), gameLog);
+
+  var nextTurn = DIV({ id: 'nextTurn' }, "");
+  nextTurn.style.position = 'absolute';
+  nextTurn.style.width = '280px';
+  nextTurn.style.textAlign = 'left';
+  setElementPosition(nextTurn, { x: border + 680, y: border + 665 });
+  appendChildNodes($('playfield'), nextTurn);
+
+  var nextTurn = DIV({ id: 'status' }, "");
+  nextTurn.style.position = 'absolute';
+  nextTurn.style.width = '280px';
+  nextTurn.style.textAlign = 'left';
+  setElementPosition(nextTurn, { x: border + 680, y: border + 680 });
+  appendChildNodes($('playfield'), nextTurn);
+}
+
+function setLetter(x, y, letter, isBlank) {
+  var image = IMG({ src: 'images/' + letter + (isBlank ? "-blank" : "") + '.png'});
+  image.style.position = 'absolute';
+  image.style.top = '3px';
+  image.style.left = '3px';
+  image.style.width = tileSize + 'px';
+  image.style.height = tileSize + 'px';
+  setElementPosition(image, { x: border + x * cellSize + tileBorderSize,
+                                   y: border + y * cellSize + tileBorderSize });
+  appendChildNodes($('playfield'), image);
+  board[x][y].letterNode = image;
+  board[x][y].letter = letter;
+}
+
+function removeLastLetterFromMove() {
+}
+
+function letterAt(x, y) {
+  return board[x][y].letter && !board[x][y].justPlaced;
+}
+
+function Cursor()
+{
+  var image = new IMG({ src: 'images/cursor.png' });
+  image.style.position = 'absolute';
+  image.style.top = '-' + tileBorderSize + 'px';
+  image.style.left = '-' + tileBorderSize + 'px';
+
+  appendChildNodes($('playfield'), image);
+  this.image = image;
+  this.x = -1;
+  this.y = -1;
+  this.direction = 0;
+
+  this.set = function(x, y) {
+    this.x = x;
+    this.y = y;
+    this.image.top = 
+    this.image.style.visibility = 'visible';
+    board[x][y].cursor = this.image;
+  };
+
+  this.clear = function() {
+    if (this.x != -1) {
+      this.image.style.visibility = 'hidden';
+      board[this.x][this.y].cursor = undefined;
+      this.x = this.y = -1;
+      this.direction = 0;
+    }
+  };
+
+  this.advance = function(isHoriz) {
+    var horizontal = 1;
+    var vertical = 2;
+    var direction = this.direction;
+    if (direction == 0) {
+      // Direction not determined
+      if (isHoriz != undefined) {
+        direction = isHoriz ? horizontal : vertical;
+      } else if (((this.y < 14) && letterAt(this.x, this.y + 1))
+                 || ((this.y > 1)
+                     && letterAt(this.x, this.y - 1)
+                     && !letterAt(this.x, this.y - 2))
+                 || ((this.x > 1)
+                     && letterAt(this.x - 1, this.y)
+                     && letterAt(this.x - 2, this.y))) {
+        direction = vertical;
+      } else {
+        direction = horizontal;
+      }
+    }
+    var x = this.x;
+    var y = this.y;
+    this.clear();
+    this.direction = direction;
+    if (this.direction == horizontal) {
+      x++;
+    } else {
+      y++;
+    }
+    if ((x != 15) && (y != 15)) {
+      this.set(x, y);
+    }
+  };
+}
+
+var cursor = new Cursor;
+
+function emptyTileClicked() {
+  cursor.clear();
+  cursor.set(this.xPos, this.yPos);
+}
+
+var move = [];
+
+function makeMask()
+{
+  var mask = IMG({ src: 'images/mask.png'});
+  mask.style.position = 'absolute';
+  mask.style.top = tileBorderSize + 'px';
+  mask.style.left = tileBorderSize + 'px';
+  mask.style.zIndex = '20';
+  return mask;
+}
+
+function addLetterToMove(x, y, tile) {
+  mask = makeMask();
+  appendChildNodes(board[x][y], mask);
+  board[x][y].letterNode = tile;
+  board[x][y].letter = tile.letter;
+  board[x][y].justPlaced = true;
+  tile.mask = mask;
+  tile.anim = new YAHOO.util.Motion(tile, { points: { to: [ border + x * cellSize + tileBorderSize,
+                                                            border + y * cellSize + tileBorderSize ]}},
+                                    0.15,
+                                    YAHOO.util.Easing.easeBoth);
+  tile.anim.animate();
+
+  move[move.length] = { x: x, y: y, tile: tile };
+  try {
+    checkMoveLegality(move);
+    $('move').onclick = submitMove;
+    $('move').innerHTML = "submit move";
+    displayStatus('');
+  }
+  catch (e) {
+    if (typeof e != 'string') {
+      alert(e.message);
+    } else {
+      displayStatus(e);
+    }
+    $('move').onclick = undefined;
+    $('move').innerHTML = e.toString();
+  }
+}
+
+function confirmMove() {
+  for (var i = 0; i < move.length; i++) {
+    removeElement(move[i].tile.mask);
+    move[i].tile.mask = undefined;
+  }
+  cursor.clear();
+  move = [];
+  $('move').onclick = null;
+  $('move').innerHTML = '';
+  
+}
+
+function moveAsString() {
+  // We internally keep the move as array of objects, but send it to the server rather unstructured:
+  var serverMessage = [];
+  for (var i = 0; i < move.length; i++) {
+    serverMessage.push(move[i].x);
+    serverMessage.push(move[i].y);
+    serverMessage.push(move[i].tile.letterName);
+    serverMessage.push(move[i].tile.letterName == undefined);
+  }
+  return serverMessage.toString();
+}
+
+function submitMove()
+{
+  var queryString = MochiKit.Base.queryString({ move: moveAsString(), game: gameID });
+  var res = MochiKit.Async.doXHR("/place-tiles",
+      { method: 'POST',
+        sendContent: queryString,
+        headers: { "Content-Type": "application/x-www-form-urlencoded" } });
+  res.addCallbacks(moveSuccess, moveFailure);
+}
+
+function moveSuccess(result)
+{
+  try {
+    var response;
+    try {
+      response = eval('(' + result.responseText + ')');
+    }
+    catch (e) {
+      alert("invalid JSON reply: " + result.responseText);
+      return;
+    }
+    if (response.error) {
+      alert(response.error);
+    } else {
+      confirmMove();
+      $('playfield')['score-' + response.move.participantLogin].innerHTML = response.move.playerScore.toString();
+      displayMyTray(response.tray);
+    }
+  }
+  catch (e) {
+    alert('error during moveSuccess: ' + e.message);
+  }
+}
+
+function moveFailure(e)
+{
+  alert('failed: ' + e);
+}
+
+function letterKeyPressed(e) {
+  if (e.which == 0 || e.altKey || e.ctrlKey || e.shiftKey) {
+    // not a letter key
+    return;
+  }
+
+  var letter = String.fromCharCode(e.which).toUpperCase();
+
+  var x = cursor.x;
+  var y = cursor.y;
+  var tilePosition = -1;
+  for (var i = 0; (tilePosition == -1) && (i < tray.length); i++) {
+    if (tray[i].letter == letter) {
+      tilePosition = i;
+    }
+  }
+  if (tilePosition == -1) {
+    for (var i = 0; (tilePosition == -1) && (i < tray.length); i++) {
+      if (tray[i].letter == undefined) {
+        tilePosition = i;
+      }
+    }
+  }
+  if (tilePosition == -1) {
+    displayStatus('you-dont-have-that-letter', letter);
+  } else {
+    var isHoriz;
+    if (move.length > 0) {
+      isHoriz = (move[0].x != x);
+    }
+    cursor.advance(isHoriz);
+    if (!letterAt(x, y)) {
+      var tile = tray[tilePosition];
+      tray.splice(tilePosition, 1);
+      addLetterToMove(x, y, tile);
+    }
+  }
+}
+
+var leftKey = 37;
+var upKey = 38;
+var rightKey = 39;
+var downKey = 40;
+var backspaceKey = 8;
+
+function functionKeyPressed(type, args, obj) {
+  var x = cursor.x;
+  var y = cursor.y;
+
+  switch (args[0]) {
+  case rightKey:
+    while (x < 14)
+      if (!letterAt(++x, y))
+        break;
+    break;
+  case leftKey:
+    while (x > 0)
+      if (!letterAt(--x, y))
+        break;
+    break;
+  case upKey:
+    while (y > 0)
+      if (!letterAt(x, --y))
+        break;
+    break;
+  case downKey:
+    while (y < 14)
+      if (!letterAt(x, ++y))
+        break;
+    break;
+  case backspaceKey:
+    if (move.length) {
+      removeLastLetterFromMove();
+    }
+  }
+  if ((x >= 0) && (x <= 14) && (y >= 0) && (y <= 14)) {
+    cursor.clear();
+    cursor.set(x, y);
+  }
+  YAHOO.util.Event.preventDefault(args[1]);
+}
+
+function clearBoard() {
+  for (x = 0; x < 15; x++) {
+    for (y = 0; y < 15; y++) {
+      var letterNode = board[x][y].letterNode;
+      if (letterNode) {
+        letterNode.anim = new YAHOO.util.Motion(letterNode,
+            { points: { to: [ border + 7 * cellSize + tileBorderSize,
+                              border + 7 * cellSize + tileBorderSize ]}},
+                                                0.15);
+        letterNode.anim.onComplete.subscribe(function () { removeElement(this); });
+        letterNode.anim.animate();
+      }
+    }
+  }
+}
+
+function trayClick(letter) {
+  this.clicked = !this.clicked;
+  this.anim = new YAHOO.util.Motion(this, { points: { by: [ 0, (this.clicked ? 15 : -15 ) ]}}, 0.15);
+  this.anim.animate();
+}
+
+function displayMyTray(letters) {
+  map(removeElement, tray);
+  tray = [];
+  for (var i = 0; i < letters.length; i++) {
+    var element = IMG({src: 'images/' + letters[i].letterName + '.png'});
+    element.letter = letters[i].letter;
+    element.letterName = letters[i].letterName;
+    element.style.position = 'absolute';
+    element.style.width = tileSize + 'px';
+    element.style.height = tileSize + 'px';
+    element.style.zIndex = '10';
+    element.onclick = trayClick;
+    setElementPosition(element, { x: border + i * fieldSize,
+                                       y: border + 15 * cellSize + 10 });
+    tray[i] = element;
+  }
+  appendChildNodes($('playfield'), tray);
+}
+
+function shuffleMyTray() {
+  var count = tray.length;
+  var newTray = [];
+  for (var i = 0; i < count; i++) {
+    do {
+      index = Math.floor(Math.random() * count);
+    } while (newTray[index]);
+    newTray[index] = tray[i];
+    newTray[index].anim = new YAHOO.util.Motion(tray[i], { points: { to: [ border + 194 + i * 40,
+                                                                           border + 665 ] }},
+                                                0.5);
+    newTray[index].anim.animate();
+    newTray[index].clicked = false;
+  }
+  tray = newTray;
+}
+
+var otherPlayerIndex = 0;
+
+function makeTheirTray (participant) {
+  var tileCount = (typeof participant.remainingTiles == 'number') ? participant.remainingTiles : participant.remainingTiles.length;
+
+  var tray = [];
+  for (var i = 0; i < tileCount; i++) {
+    var element = IMG({src: 'images/null.png'});
+    element.style.position = 'absolute';
+    element.style.width = tileSize + 'px';
+    element.style.height = tileSize + 'px';
+    element.style.zIndex = '10';
+    setElementPosition(element, { x: border + 15 * cellSize + 10 + i * fieldSize,
+                                       y: border + 80 * otherPlayerIndex });
+    tray[i] = element;
+  }
+  appendChildNodes($('playfield'), tray);
+
+  var nameTag = DIV(null, participant.name);
+  nameTag.style.position = 'absolute';
+  nameTag.style.width = '200px';
+  nameTag.style.textAlign = 'left';
+  setElementPosition(nameTag, { x: border + 680, y: border + 80 * otherPlayerIndex + 50 });
+  appendChildNodes($('playfield'), nameTag);
+
+  var scoreTag = DIV(null, participant.score);
+  scoreTag.style.position = 'absolute';
+  scoreTag.style.width = '80px';
+  scoreTag.style.textAlign = 'right';
+  setElementPosition(scoreTag, { x: border + 870, y: border + 80 * otherPlayerIndex + 50 });
+  appendChildNodes($('playfield'), scoreTag);
+  $('playfield')['score-' + participant.login] = scoreTag;
+
+  otherPlayerIndex++;
+}
+
+function renderMoveAsText(move)
+{
+  var retval = move.participantLogin;
+  if (move.type == 'move') {
+    retval +=  " score: " + move.score;
+    for (var i = 0; i < move.words.length; i++) {
+      retval += " " + move.words[i][0] + "(" + move.words[i][1] + ")";
+    }
+  } else {
+    retval += move.type;
+  }
+
+  return retval;
+}
+
+function displayWhosTurnItIs(name) {
+  replaceChildNodes($('nextTurn'),
+                    "Next: " + name);
+}
+
+function drawGameState (gameState) {
+  try {
+    for (var i = 0; i < gameState.board.length; i++) {
+      var x = gameState.board[i][0];
+      var y = gameState.board[i][1];
+      var char = gameState.board[i][2];
+      setLetter(x, y, char, gameState.board[i].length > 3);
+    }
+    var firstParticipant = gameState.participants[0];
+    displayWhosTurnItIs(firstParticipant.name);
+    for (var i = 0; i < gameState.participants.length; i++) {
+      var participant = gameState.participants[i];
+      makeTheirTray(participant);
+      if (typeof participant.remainingTiles != 'number') {
+        displayMyTray(participant.remainingTiles);
+      }
+    }
+    for (var i = 0; i < gameState.moves.length; i++) {
+      appendChildNodes($('gameLog'), DIV(null, renderMoveAsText(gameState.moves[i])));
+    }
+  }
+  catch (e) {
+    alert('error ' + e + ' in drawGameState');
+  }
+}
+
+function displayStatus(status)
+{
+  replaceChildNodes('status', status);
+}
+
+function init() {
+  makeBoard();
+
+  // does not work for ie (document.body needed)?
+  YAHOO.util.Event.on(window, 'keypress', letterKeyPressed);
+
+  var functionKeyListener = new YAHOO.util.KeyListener(document,
+      { keys: [ leftKey, upKey, rightKey, downKey, backspaceKey ] },
+      { fn: functionKeyPressed, scope: this, correctScope: true });
+  functionKeyListener.enable();
+
+  var moveDisplay = DIV({ id: 'move' }, "");
+  moveDisplay.style.color = 'white';
+  moveDisplay.style.position = 'absolute';
+  setElementPosition(moveDisplay, { x: border + 550, y: border + 665 });
+  appendChildNodes(document.body, moveDisplay);
+  var d = loadJSONDoc("/game/" + gameID);
+  d.addCallbacks(drawGameState, function (error) { alert("Request error: " + error.message); });
+}

Modified: branches/trunk-reorg/projects/scrabble/website/scrabble.html
==============================================================================
--- branches/trunk-reorg/projects/scrabble/website/scrabble.html	(original)
+++ branches/trunk-reorg/projects/scrabble/website/scrabble.html	Thu Jan 17 11:36:28 2008
@@ -1,18 +1,18 @@
 <html>
-  <head>
-    <link rel="stylesheet" type="text/css" href="/yui/reset-fonts-grids/reset-fonts-grids.css" />
-    <link rel="stylesheet" type="text/css" href="scrabble.css" />
-    <script type="text/javascript" src="/yui/yahoo/yahoo-min.js"> </script>
-    <script type="text/javascript" src="/yui/event/event-min.js"> </script>
-    <script type="text/javascript" src="/yui/dom/dom-min.js"> </script>
-    <script type="text/javascript" src="/yui/animation/animation-min.js"> </script>
-    <script type="text/javascript" src="/MochiKit/MochiKit.js"> </script>
-    <script type="text/javascript" src="scrabble.js"> </script>
-  </head>
-  <body onload="init()">
-    <div id="playfield">
-    </div>
-    <!--     <div style="position: absolute; right: 20px; top: 20px;"><a style="color: white;" href="/login?login=hans">hans</a></div> -->
-    <!--     <div style="position: absolute; right: 20px; top: 40px;"><a style="color: white;" href="/login?login=marna">marna</a></div> -->
-  </body>
+ <head>
+  <link rel="stylesheet" type="text/css" href="/yui/reset-fonts-grids/reset-fonts-grids.css" />
+  <link rel="stylesheet" type="text/css" href="scrabble.css" />
+  <script type="text/javascript" src="/yui/yahoo/yahoo-min.js"> </script>
+  <script type="text/javascript" src="/yui/event/event-min.js"> </script>
+  <script type="text/javascript" src="/yui/dom/dom-min.js"> </script>
+  <script type="text/javascript" src="/yui/animation/animation-min.js"> </script>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"> </script>
+  <script type="text/javascript" src="scrabble.js"> </script>
+ </head>
+ <body onload="init()">
+  <div id="playfield">
+  </div>
+  <div style="position: absolute; right: 20px; top: 20px;"><a style="color: white;" href="/login?login=hans">hans</a></div>
+  <div style="position: absolute; right: 20px; top: 40px;"><a style="color: white;" href="/login?login=marna">marna</a></div>
+ </body>
 </html>

Modified: branches/trunk-reorg/projects/scrabble/website/scrabble.js
==============================================================================
--- branches/trunk-reorg/projects/scrabble/website/scrabble.js	(original)
+++ branches/trunk-reorg/projects/scrabble/website/scrabble.js	Thu Jan 17 11:36:28 2008
@@ -1,386 +1,380 @@
 // -*- JavaScript -*-
 
-var boardScoring = [["triple-word",null,null,"double-letter",null,null,null,"triple-word",
-    null,null,null,"double-letter",null,null,"triple-word"],
-    [null,"double-word",null,null,null,"triple-letter",null,null,null,"triple-letter",
-    null,null,null,"double-word",null],
-    [null,null,"double-word",null,null,null,"double-letter",null,"double-letter",
-    null,null,null,"double-word",null,null],
-    ["double-letter",null,null,"double-word",null,null,null,"double-letter",
-    null,null,null,"double-word",null,null,"double-letter"],
-    [null,null,null,null,"double-word",null,null,null,null,null,"double-word",
-    null,null,null,null],
-    [null,"triple-letter",null,null,null,"triple-letter",null,null,null,"triple-letter",
-    null,null,null,"triple-letter",null],
-    [null,null,"double-letter",null,null,null,"double-letter",null,"double-letter",
-    null,null,null,"double-letter",null,null],
-    ["triple-word",null,null,"double-letter",null,null,null,"double-word",
-    null,null,null,"double-letter",null,null,"triple-word"],
-    [null,null,"double-letter",null,null,null,"double-letter",null,"double-letter",
-    null,null,null,"double-letter",null,null],
-    [null,"triple-letter",null,null,null,"triple-letter",null,null,null,"triple-letter",
-    null,null,null,"triple-letter",null],
-    [null,null,null,null,"double-word",null,null,null,null,null,"double-word",
-    null,null,null,null],
-    ["double-letter",null,null,"double-word",null,null,null,"double-letter",
-    null,null,null,"double-word",null,null,"double-letter"],
-    [null,null,"double-word",null,null,null,"double-letter",null,"double-letter",
-    null,null,null,"double-word",null,null],
-    [null,"double-word",null,null,null,"triple-letter",null,null,null,"triple-letter",
-    null,null,null,"double-word",null],
-    ["triple-word",null,null,"double-letter",null,null,null,"triple-word",
-    null,null,null,"double-letter",null,null,"triple-word"]];
-
-// for now
-function requestError (error) {
-    alert("Request error: " + error.message)
-}
+var scrabbleRules = {
+  boardScoring: [["triple-word",null,null,"double-letter",null,null,null,"triple-word",
+                  null,null,null,"double-letter",null,null,"triple-word"],
+                 [null,"double-word",null,null,null,"triple-letter",null,null,null,"triple-letter",
+                  null,null,null,"double-word",null],
+                 [null,null,"double-word",null,null,null,"double-letter",null,"double-letter",
+                  null,null,null,"double-word",null,null],
+                 ["double-letter",null,null,"double-word",null,null,null,"double-letter",
+                  null,null,null,"double-word",null,null,"double-letter"],
+                 [null,null,null,null,"double-word",null,null,null,null,null,"double-word",
+                  null,null,null,null],
+                 [null,"triple-letter",null,null,null,"triple-letter",null,null,null,"triple-letter",
+                  null,null,null,"triple-letter",null],
+                 [null,null,"double-letter",null,null,null,"double-letter",null,"double-letter",
+                  null,null,null,"double-letter",null,null],
+                 ["triple-word",null,null,"double-letter",null,null,null,"double-word",
+                  null,null,null,"double-letter",null,null,"triple-word"],
+                 [null,null,"double-letter",null,null,null,"double-letter",null,"double-letter",
+                  null,null,null,"double-letter",null,null],
+                 [null,"triple-letter",null,null,null,"triple-letter",null,null,null,"triple-letter",
+                  null,null,null,"triple-letter",null],
+                 [null,null,null,null,"double-word",null,null,null,null,null,"double-word",
+                  null,null,null,null],
+                 ["double-letter",null,null,"double-word",null,null,null,"double-letter",
+                  null,null,null,"double-word",null,null,"double-letter"],
+                 [null,null,"double-word",null,null,null,"double-letter",null,"double-letter",
+                  null,null,null,"double-word",null,null],
+                 [null,"double-word",null,null,null,"triple-letter",null,null,null,"triple-letter",
+                  null,null,null,"double-word",null],
+                 ["triple-word",null,null,"double-letter",null,null,null,"triple-word",
+                  null,null,null,"double-letter",null,null,"triple-word"]],
 
-// Scrabble rule enforcement
+  // Scrabble rule enforcement
 
-function checkMoveLegality(placedTiles)
-{
+  checkMoveLegality : function (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.x, placement.y ] }, placedTiles)
-    .sort(function (a, b) { return (a[0] - b[0]) || (a[1] - b[1])});
+      .sort(function (a, b) { return (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";
+        && 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";
-	    }
-	}
+      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";
-	}
+      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) {
+  fieldScore : function(x, y) {
     return boardScoring[x][y] || 'standard';
-}
+  }
+};
 
 var theirTrays;
 var tray = [];
 
+var gameID = 108;
 var board;
 
-var border = 10;
-
 function makeBoard() {
-    var container = $('playfield');
-    board = [];
-    for (x = 0; x < 15; x++) {
-	board[x] = [];
-	for (y = 0; y < 15; y++) {
-	    var element = DIV();
-	    element.style.position = 'absolute';
-	    element.style.width = '40px';
-	    element.style.height = '40px';
-	    var imageName = (x == 7 && y == 7) ? "start-field" : getFieldScore(x, y);
-	    element.style.backgroundImage = 'url(images/' + imageName + '.png)';
-	    element.x = x;
-	    element.y = y;
-	    setElementPosition(element, { x: border + x * 44, y: border + y * 44 });
-	    YAHOO.util.Event.on(element, 'click', emptyTileClicked)
-            board[x][y] = element;
-	}
-	appendChildNodes(container, board[x]);
-    }
-
-    var shuffleButton = DIV(null, "shuffle");
-    shuffleButton.style.color = 'white';
-    shuffleButton.style.position = 'absolute';
-    shuffleButton.onclick = shuffleMyTray;
-    setElementPosition(shuffleButton, { x: border + 480, y: border + 665 });
-    appendChildNodes(container, shuffleButton);
-
-    var gameLog = DIV({ id: 'gameLog' }, "");
-    gameLog.style.position = 'absolute';
-    gameLog.style.width = '280px';
-    gameLog.style.height = '250px';
-    gameLog.style.textAlign = 'left';
-    gameLog.style.overflowY = 'scroll';
-    setElementPosition(gameLog, { x: border + 680, y: border + 400 });
-    appendChildNodes($('playfield'), gameLog);
-
-    var nextTurn = DIV({ id: 'nextTurn' }, "");
-    nextTurn.style.position = 'absolute';
-    nextTurn.style.width = '280px';
-    nextTurn.style.textAlign = 'left';
-    setElementPosition(nextTurn, { x: border + 680, y: border + 665 });
-    appendChildNodes($('playfield'), nextTurn);
-
-    var nextTurn = DIV({ id: 'status' }, "");
-    nextTurn.style.position = 'absolute';
-    nextTurn.style.width = '280px';
-    nextTurn.style.textAlign = 'left';
-    setElementPosition(nextTurn, { x: border + 680, y: border + 680 });
-    appendChildNodes($('playfield'), nextTurn);
+  var container = $('playfield');
+  board = [];
+  for (x = 0; x < 15; x++) {
+    board[x] = [];
+    for (y = 0; y < 15; y++) {
+      var element = DIV();
+      element.style.position = 'absolute';
+      element.style.width = '40px';
+      element.style.height = '40px';
+      var imageName = (x == 7 && y == 7) ? "start-field" : scrabbleRules.fieldScore(x, y);
+      element.style.backgroundImage = 'url(images/' + imageName + '.png)';
+      element.x = x;
+      element.y = y;
+      setElementPosition(element, { x: x * 44, y: y * 44 });
+      YAHOO.util.Event.on(element, 'click', emptyTileClicked)
+        board[x][y] = element;
+    }
+    appendChildNodes(container, board[x]);
+  }
+
+  var shuffleButton = DIV(null, "shuffle");
+  shuffleButton.style.color = 'white';
+  shuffleButton.style.position = 'absolute';
+  shuffleButton.onclick = shuffleMyTray;
+  setElementPosition(shuffleButton, { x: 480, y: 665 });
+  appendChildNodes(container, shuffleButton);
+
+  var gameLog = DIV({ id: 'gameLog' }, "");
+  gameLog.style.position = 'absolute';
+  gameLog.style.width = '280px';
+  gameLog.style.height = '250px';
+  gameLog.style.textAlign = 'left';
+  gameLog.style.overflowY = 'scroll';
+  setElementPosition(gameLog, { x: 680, y: 400 });
+  appendChildNodes($('playfield'), gameLog);
+
+  var nextTurn = DIV({ id: 'nextTurn' }, "");
+  nextTurn.style.position = 'absolute';
+  nextTurn.style.width = '280px';
+  nextTurn.style.textAlign = 'left';
+  setElementPosition(nextTurn, { x: 680, y: 665 });
+  appendChildNodes($('playfield'), nextTurn);
+
+  var nextTurn = DIV({ id: 'status' }, "");
+  nextTurn.style.position = 'absolute';
+  nextTurn.style.width = '280px';
+  nextTurn.style.textAlign = 'left';
+  setElementPosition(nextTurn, { x: 680, y: 680 });
+  appendChildNodes($('playfield'), nextTurn);
 }
 
 function setLetter(x, y, letter, isBlank) {
-    var image = IMG({ src: 'images/' + letter + (isBlank ? "-blank" : "") + '.png'});
-    image.style.position = 'absolute';
-    image.style.top = '3px';
-    image.style.left = '3px';
-    setElementPosition(image, { x: border + x * 44 + 3, y: border + y * 44 + 3 });
-    appendChildNodes($('playfield'), image);
-    board[x][y].letterNode = image;
-    board[x][y].letter = letter;
-}
-
-function placeLetter(x, y, tile) {
+  var image = IMG({ src: 'images/' + letter + (isBlank ? "-blank" : "") + '.png'});
+  image.style.position = 'absolute';
+  image.style.top = '3px';
+  image.style.left = '3px';
+  setElementPosition(image, { x: x * 44 + 3, y: y * 44 + 3 });
+  appendChildNodes($('playfield'), image);
+  board[x][y].letterNode = image;
+  board[x][y].letter = letter;
 }
 
 function removeLastLetterFromMove() {
 }
 
 function letterAt(x, y) {
-    return board[x][y].letter && !board[x][y].justPlaced;
+  return board[x][y].letter && !board[x][y].justPlaced;
 }
 
 function Cursor()
 {
-    var image = new IMG({ src: 'images/cursor.png' });
-    image.style.position = 'absolute';
-    image.style.top = '-3px';
-    image.style.left = '-3px';
-
-    this.image = image;
-    this.x = -1;
-    this.y = -1;
-    this.direction = 0;
-
-    this.set = function(x, y) {
-	this.x = x;
-	this.y = y;
-	appendChildNodes(board[x][y], this.image);
-	board[x][y].cursor = this.image;
-    };
-
-    this.clear = function() {
-	if (this.x != -1) {
-	    removeElement(board[this.x][this.y].cursor);
-	    board[this.x][this.y].cursor = undefined;
-	    this.x = this.y = -1;
-	    this.direction = 0;
-	}
-    };
-
-    this.advance = function(isHoriz) {
-	var horizontal = 1;
-	var vertical = 2;
-	var direction = this.direction;
-	if (direction == 0) {
-	    // Direction not determined
-	    if (isHoriz != undefined) {
-		direction = isHoriz ? horizontal : vertical;
-	    } else if (((this.y < 14) && letterAt(this.x, this.y + 1))
-                       || ((this.y > 1)
-			   && letterAt(this.x, this.y - 1)
-			   && !letterAt(this.x, this.y - 2))
-                       || ((this.x > 1)
-			   && letterAt(this.x - 1, this.y)
-			   && letterAt(this.x - 2, this.y))) {
-		direction = vertical;
-	    } else {
-		direction = horizontal;
-	    }
-	}
-	var x = this.x;
-	var y = this.y;
-	this.clear();
-	this.direction = direction;
-	if (this.direction == horizontal) {
-	    x++;
-	} else {
-	    y++;
-	}
-	if ((x != 15) && (y != 15)) {
-	    this.set(x, y);
-	}
-    };
+  var image = new IMG({ src: 'images/cursor.png' });
+  image.style.position = 'absolute';
+  image.style.top = '-3px';
+  image.style.left = '-3px';
+
+  this.image = image;
+  this.x = -1;
+  this.y = -1;
+  this.direction = 0;
+
+  this.set = function(x, y) {
+    this.x = x;
+    this.y = y;
+    appendChildNodes(board[x][y], this.image);
+    board[x][y].cursor = this.image;
+  };
+
+  this.clear = function() {
+    if (this.x != -1) {
+      removeElement(board[this.x][this.y].cursor);
+      board[this.x][this.y].cursor = undefined;
+      this.x = this.y = -1;
+      this.direction = 0;
+    }
+  };
+
+  this.advance = function(isHoriz) {
+    var horizontal = 1;
+    var vertical = 2;
+    var direction = this.direction;
+    if (direction == 0) {
+      // Direction not determined
+      if (isHoriz != undefined) {
+        direction = isHoriz ? horizontal : vertical;
+      } else if (((this.y < 14) && letterAt(this.x, this.y + 1))
+                 || ((this.y > 1)
+                     && letterAt(this.x, this.y - 1)
+                     && !letterAt(this.x, this.y - 2))
+                 || ((this.x > 1)
+                     && letterAt(this.x - 1, this.y)
+                     && letterAt(this.x - 2, this.y))) {
+        direction = vertical;
+      } else {
+        direction = horizontal;
+      }
+    }
+    var x = this.x;
+    var y = this.y;
+    this.clear();
+    this.direction = direction;
+    if (this.direction == horizontal) {
+      x++;
+    } else {
+      y++;
+    }
+    if ((x != 15) && (y != 15)) {
+      this.set(x, y);
+    }
+  };
 }
 
 var cursor = new Cursor;
 
 function emptyTileClicked() {
-    cursor.clear();
-    cursor.set(this.x, this.y);
+  cursor.clear();
+  cursor.set(this.x, this.y);
 }
 
 var move = [];
 
 function makeMask()
 {
-    var mask = IMG({ src: 'images/mask.png'});
-    mask.style.position = 'absolute';
-    mask.style.top = '3px';
-    mask.style.left = '3px';
-    mask.style.zIndex = '20';
-    return mask;
-}
-
-function addLetterToMove(x, y, tile) {
-    mask = makeMask();
-    appendChildNodes(board[x][y], mask);
-    board[x][y].letterNode = tile;
-    board[x][y].letter = tile.letter;
-    board[x][y].justPlaced = true;
-    tile.mask = mask;
-    tile.anim = new YAHOO.util.Motion(tile, { points: { to: [ border + x * 44 + 3,
-                                                              border + y * 44 + 3 ]}},
-                                      0.15,
-                                      YAHOO.util.Easing.easeBoth);
-    tile.anim.animate();
-
-    move[move.length] = { x: x, y: y, tile: tile };
-    try {
-	checkMoveLegality(move);
-	$('move').onclick = submitMove;
-	$('move').innerHTML = "submit move";
-	displayStatus('');
-    }
-    catch (e) {
-	if (typeof e != 'string') {
-	    alert(e.message);
-	} else {
-	    displayStatus(e);
-	}
-	$('move').onclick = undefined;
-	$('move').innerHTML = e.toString();
+  var mask = IMG({ src: 'images/mask.png'});
+  mask.style.position = 'absolute';
+  mask.style.top = '3px';
+  mask.style.left = '3px';
+  mask.style.zIndex = '20';
+  return mask;
+}
+
+function addLetterToMove(x, y, tile, letter) {
+  mask = makeMask();
+  appendChildNodes(board[x][y], mask);
+  board[x][y].letterNode = tile;
+  if (!tile.letter) {
+    tile.letter = letter;
+  }
+  board[x][y].justPlaced = true;
+  tile.mask = mask;
+  tile.anim = new YAHOO.util.Motion(tile, { points: { to: [ x * 44 + 3,
+                                                            y * 44 + 3 ]}},
+                                    0.15,
+                                    YAHOO.util.Easing.easeBoth);
+  tile.anim.animate();
+
+  move[move.length] = { x: x, y: y, tile: tile };
+  try {
+    checkMoveLegality(move);
+    $('move').onclick = submitMove;
+    $('move').innerHTML = "submit move";
+    displayStatus('');
+  }
+  catch (e) {
+    if (typeof e != 'string') {
+      alert(e.message);
+    } else {
+      displayStatus(e);
     }
+    $('move').onclick = undefined;
+    $('move').innerHTML = e.toString();
+  }
 }
 
 function confirmMove() {
-    for (var i = 0; i < move.length; i++) {
-	removeElement(move[i].tile.mask);
-	move[i].tile.mask = undefined;
-    }
-    cursor.clear();
-    move = [];
-    $('move').onclick = null;
-    $('move').innerHTML = '';
-    
+  for (var i = 0; i < move.length; i++) {
+    removeElement(move[i].tile.mask);
+    move[i].tile.mask = undefined;
+  }
+  cursor.clear();
+  move = [];
+  $('move').onclick = null;
+  $('move').innerHTML = '';
+  
 }
 
 function moveAsString() {
-    // We internally keep the move as array of objects, but send it to the server rather unstructured:
-    var serverMessage = [];
-    for (var i = 0; i < move.length; i++) {
-	serverMessage.push(move[i].x);
-	serverMessage.push(move[i].y);
-	serverMessage.push(move[i].tile.letterName);
-	serverMessage.push(move[i].tile.letterName == undefined);
-    }
-    return serverMessage.toString();
+  // We internally keep the move as array of objects, but send it to the server rather unstructured:
+  var serverMessage = [];
+  for (var i = 0; i < move.length; i++) {
+    serverMessage.push(move[i].x);
+    serverMessage.push(move[i].y);
+    serverMessage.push(move[i].tile.letterName || move[i].tile.letter);
+    serverMessage.push(move[i].tile.letterName == undefined);
+  }
+  return serverMessage.toString();
 }
 
 function submitMove()
 {
-    var queryString = MochiKit.Base.queryString({ move: moveAsString() });
-    var res = MochiKit.Async.doXHR("/place-tiles",
-	{ method: 'POST',
+  var queryString = MochiKit.Base.queryString({ move: moveAsString(), game: gameID });
+  var res = MochiKit.Async.doXHR("/place-tiles",
+      { method: 'POST',
         sendContent: queryString,
         headers: { "Content-Type": "application/x-www-form-urlencoded" } });
-    res.addCallbacks(moveSuccess, moveFailure);
+  res.addCallbacks(moveSuccess, moveFailure);
 }
 
 function moveSuccess(result)
 {
+  try {
+    var response;
     try {
-	var response;
-	try {
-	    response = eval('(' + result.responseText + ')');
-	}
-	catch (e) {
-	    alert("invalid JSON reply: " + result.responseText);
-	    return;
-	}
-	if (response.error) {
-	    alert(response.error);
-	} else {
-	    confirmMove();
-	    alert(response.move.playerScore);
-	    $('playfield')['score-' + response.move.participantLogin].innerHTML = response.move.playerScore.toString();
-	    displayMyTray(response.tray);
-	}
+      response = eval('(' + result.responseText + ')');
     }
     catch (e) {
-	alert('error during moveSuccess: ' + e.message);
+      alert("invalid JSON reply: " + result.responseText);
+      return;
     }
+    if (response.error) {
+      alert(response.error);
+    } else {
+      confirmMove();
+      alert(response.move.playerScore);
+      $('playfield')['score-' + response.move.participantLogin].innerHTML = response.move.playerScore.toString();
+      displayMyTray(response.tray);
+    }
+  }
+  catch (e) {
+    alert('error during moveSuccess: ' + e.message);
+  }
 }
 
 function moveFailure(e)
 {
-    alert('failed: ' + e);
+  alert('failed: ' + e);
 }
 
 function letterKeyPressed(e) {
-    if (e.which == 0 || e.altKey || e.ctrlKey || e.shiftKey) {
-	// not a letter key
-	return;
+  if (e.which == 0 || e.altKey || e.ctrlKey || e.shiftKey) {
+    // not a letter key
+    return;
+  }
+
+  var letter = String.fromCharCode(e.which).toUpperCase();
+
+  var x = cursor.x;
+  var y = cursor.y;
+  var tilePosition = -1;
+  for (var i = 0; (tilePosition == -1) && (i < tray.length); i++) {
+    if (tray[i].letter == letter) {
+      tilePosition = i;
     }
-
-    var letter = String.fromCharCode(e.which).toUpperCase();
-
-    var x = cursor.x;
-    var y = cursor.y;
-    var tilePosition = -1;
+  }
+  if (tilePosition == -1) {
     for (var i = 0; (tilePosition == -1) && (i < tray.length); i++) {
-	if (tray[i].letter == letter) {
-	    tilePosition = i;
-	}
-    }
-    if (tilePosition == -1) {
-	for (var i = 0; (tilePosition == -1) && (i < tray.length); i++) {
-	    if (tray[i].letter == undefined) {
-		tilePosition = i;
-	    }
-	}
-    }
-    if (tilePosition == -1) {
-	displayStatus('you-dont-have-that-letter', letter);
-    } else {
-	var isHoriz;
-	if (move.length > 0) {
-	    isHoriz = (move[0].x != x);
-	}
-	cursor.advance(isHoriz);
-	if (!letterAt(x, y)) {
-	    var tile = tray[tilePosition];
-	    tray.splice(tilePosition, 1);
-	    addLetterToMove(x, y, tile);
-	}
+      if (tray[i].letter == undefined) {
+        tilePosition = i;
+      }
+    }
+  }
+  if (tilePosition == -1) {
+    displayStatus('you-dont-have-that-letter', letter);
+  } else {
+    var isHoriz;
+    if (move.length > 0) {
+      isHoriz = (move[0].x != x);
+    }
+    cursor.advance(isHoriz);
+    if (!letterAt(x, y)) {
+      var tile = tray[tilePosition];
+      tray.splice(tilePosition, 1);
+      addLetterToMove(x, y, tile, letter);
     }
+  }
 }
 
 var leftKey = 37;
@@ -390,439 +384,200 @@
 var backspaceKey = 8;
 
 function functionKeyPressed(type, args, obj) {
-    var x = cursor.x;
-    var y = cursor.y;
+  var x = cursor.x;
+  var y = cursor.y;
 
-    switch (args[0]) {
-    case rightKey:
-	while (x < 14)
-	    if (!letterAt(++x, y))
-		break;
-	break;
-    case leftKey:
-	while (x > 0)
-	    if (!letterAt(--x, y))
-		break;
-	break;
-    case upKey:
-	while (y > 0)
-	    if (!letterAt(x, --y))
-		break;
-	break;
-    case downKey:
-	while (y < 14)
-	    if (!letterAt(x, ++y))
-		break;
-	break;
-    case backspaceKey:
-	if (move.length) {
-	    removeLastLetterFromMove();
-	}
-    }
-    if ((x >= 0) && (x <= 14) && (y >= 0) && (y <= 14)) {
-	cursor.clear();
-	cursor.set(x, y);
+  switch (args[0]) {
+  case rightKey:
+    while (x < 14)
+      if (!letterAt(++x, y))
+        break;
+    break;
+  case leftKey:
+    while (x > 0)
+      if (!letterAt(--x, y))
+        break;
+    break;
+  case upKey:
+    while (y > 0)
+      if (!letterAt(x, --y))
+        break;
+    break;
+  case downKey:
+    while (y < 14)
+      if (!letterAt(x, ++y))
+        break;
+    break;
+  case backspaceKey:
+    if (move.length) {
+      removeLastLetterFromMove();
     }
-    YAHOO.util.Event.preventDefault(args[1]);
+  }
+  if ((x >= 0) && (x <= 14) && (y >= 0) && (y <= 14)) {
+    cursor.clear();
+    cursor.set(x, y);
+  }
+  YAHOO.util.Event.preventDefault(args[1]);
 }
 
 function clearBoard() {
-    for (x = 0; x < 15; x++) {
-	for (y = 0; y < 15; y++) {
-	    var letterNode = board[x][y].letterNode;
-	    if (letterNode) {
-		letterNode.anim = new YAHOO.util.Motion(letterNode, { points: { to: [ border + 7 * 44 + 3,
-										      border + 7 * 44 + 3 ]}},
-							0.15);
-		letterNode.anim.onComplete.subscribe(function () { removeElement(this); });
-		letterNode.anim.animate();
-	    }
-	}
+  for (x = 0; x < 15; x++) {
+    for (y = 0; y < 15; y++) {
+      var letterNode = board[x][y].letterNode;
+      if (letterNode) {
+        letterNode.anim = new YAHOO.util.Motion(letterNode, { points: { to: [ 7 * 44 + 3,
+                                                                              7 * 44 + 3 ]}},
+                                                0.15);
+        letterNode.anim.onComplete.subscribe(function () { removeElement(this); });
+        letterNode.anim.animate();
+      }
     }
+  }
 }
 
 function trayClick(letter) {
-    this.clicked = !this.clicked;
-    this.anim = new YAHOO.util.Motion(this, { points: { by: [ 0, (this.clicked ? 15 : -15 ) ]}}, 0.15);
-    this.anim.animate();
+  this.clicked = !this.clicked;
+  this.anim = new YAHOO.util.Motion(this, { points: { by: [ 0, (this.clicked ? 15 : -15 ) ]}}, 0.15);
+  this.anim.animate();
 }
 
 function displayMyTray(letters) {
-    map(removeElement, tray);
-    tray = [];
-    for (var i = 0; i < letters.length; i++) {
-	var element = IMG({src: 'images/' + letters[i].letterName + '.png'});
-	element.letter = letters[i].letter;
-	element.letterName = letters[i].letterName;
-	element.style.position = 'absolute';
-	element.style.width = '34px';
-	element.style.height = '34px';
-	element.style.zIndex = '10';
-	element.onclick = trayClick;
-	setElementPosition(element, { x: border + i * 40, y: border + 665 });
-	tray[i] = element;
-    }
-    appendChildNodes($('playfield'), tray);
+  map(removeElement, tray);
+  tray = [];
+  for (var i = 0; i < letters.length; i++) {
+    var element = IMG({src: 'images/' + letters[i].letterName + '.png'});
+    element.letter = letters[i].letter;
+    element.letterName = letters[i].letterName;
+    element.style.position = 'absolute';
+    element.style.width = '34px';
+    element.style.height = '34px';
+    element.style.zIndex = '10';
+    element.onclick = trayClick;
+    setElementPosition(element, { x: i * 40, y: 665 });
+    tray[i] = element;
+  }
+  appendChildNodes($('playfield'), tray);
 }
 
 function shuffleMyTray() {
-    var count = tray.length;
-    var newTray = [];
-    for (var i = 0; i < count; i++) {
-	do {
-	    index = Math.floor(Math.random() * count);
-	} while (newTray[index]);
-	newTray[index] = tray[i];
-	newTray[index].anim = new YAHOO.util.Motion(tray[i], { points: { to: [ border + 194 + i * 40,
-                                                                               border + 665 ] }},
-                                                    0.5);
-	newTray[index].anim.animate();
-	newTray[index].clicked = false;
-    }
-    tray = newTray;
+  var count = tray.length;
+  var newTray = [];
+  for (var i = 0; i < count; i++) {
+    do {
+      index = Math.floor(Math.random() * count);
+    } while (newTray[index]);
+    newTray[index] = tray[i];
+    newTray[index].anim = new YAHOO.util.Motion(tray[i], { points: { to: [ 194 + i * 40,
+                                                                           665 ] }},
+                                                0.5);
+    newTray[index].anim.animate();
+    newTray[index].clicked = false;
+  }
+  tray = newTray;
 }
 
 var otherPlayerIndex = 0;
 
 function makeTheirTray (participant) {
-    var tileCount = (typeof participant.remainingTiles == 'number') ? participant.remainingTiles : participant.remainingTiles.length;
+  var tileCount = (typeof participant.remainingTiles == 'number') ? participant.remainingTiles : participant.remainingTiles.length;
 
-    var tray = [];
-    for (var i = 0; i < tileCount; i++) {
-	var element = IMG({src: 'images/null.png'});
-	element.style.position = 'absolute';
-	element.style.width = '34px';
-	element.style.height = '34px';
-	element.style.zIndex = '10';
-	setElementPosition(element, { x: border + 680 + i * 40, y: border + 80 * otherPlayerIndex });
-	tray[i] = element;
-    }
-    appendChildNodes($('playfield'), tray);
-
-    var nameTag = DIV(null, participant.name);
-    nameTag.style.position = 'absolute';
-    nameTag.style.width = '200px';
-    nameTag.style.textAlign = 'left';
-    setElementPosition(nameTag, { x: border + 680, y: border + 80 * otherPlayerIndex + 50 });
-    appendChildNodes($('playfield'), nameTag);
-
-    var scoreTag = DIV(null, participant.score);
-    scoreTag.style.position = 'absolute';
-    scoreTag.style.width = '80px';
-    scoreTag.style.textAlign = 'right';
-    setElementPosition(scoreTag, { x: border + 870, y: border + 80 * otherPlayerIndex + 50 });
-    appendChildNodes($('playfield'), scoreTag);
-    $('playfield')['score-' + participant.login] = scoreTag;
+  var tray = [];
+  for (var i = 0; i < tileCount; i++) {
+    var element = IMG({src: 'images/null.png'});
+    element.style.position = 'absolute';
+    element.style.width = '34px';
+    element.style.height = '34px';
+    element.style.zIndex = '10';
+    setElementPosition(element, { x: 680 + i * 40, y: 80 * otherPlayerIndex });
+    tray[i] = element;
+  }
+  appendChildNodes($('playfield'), tray);
+
+  var nameTag = DIV(null, participant.name);
+  nameTag.style.position = 'absolute';
+  nameTag.style.width = '200px';
+  nameTag.style.textAlign = 'left';
+  setElementPosition(nameTag, { x: 680, y: 80 * otherPlayerIndex + 50 });
+  appendChildNodes($('playfield'), nameTag);
+
+  var scoreTag = DIV(null, participant.score);
+  scoreTag.style.position = 'absolute';
+  scoreTag.style.width = '80px';
+  scoreTag.style.textAlign = 'right';
+  setElementPosition(scoreTag, { x: 870, y: 80 * otherPlayerIndex + 50 });
+  appendChildNodes($('playfield'), scoreTag);
+  $('playfield')['score-' + participant.login] = scoreTag;
 
-    otherPlayerIndex++;
+  otherPlayerIndex++;
 }
 
 function renderMoveAsText(move)
 {
-    var retval = move.participantLogin;
-    if (move.type == 'move') {
-	retval +=  " score: " + move.score;
-	for (var i = 0; i < move.words.length; i++) {
-	    retval += " " + move.words[i][0] + "(" + move.words[i][1] + ")";
-	}
-    } else {
-	retval += move.type;
-    }
+  var retval = move.participantLogin;
+  if (move.type == 'move') {
+    retval +=  " score: " + move.score;
+    for (var i = 0; i < move.words.length; i++) {
+      retval += " " + move.words[i][0] + "(" + move.words[i][1] + ")";
+    }
+  } else {
+    retval += move.type;
+  }
 
-    return retval;
+  return retval;
 }
 
 function displayWhosTurnItIs(name) {
-    replaceChildNodes($('nextTurn'),
-                      "Next: " + name);
+  replaceChildNodes($('nextTurn'),
+                    "Next: " + name);
 }
 
 function drawGameState (gameState) {
-    try {
-	for (var i = 0; i < gameState.board.length; i++) {
-	    var x = gameState.board[i][0];
-	    var y = gameState.board[i][1];
-	    var char = gameState.board[i][2];
-	    setLetter(x, y, char, gameState.board[i].length > 3);
-	}
-	var firstParticipant = gameState.participants[0];
-	displayWhosTurnItIs(firstParticipant.name);
-	for (var i = 0; i < gameState.participants.length; i++) {
-	    var participant = gameState.participants[i];
-	    makeTheirTray(participant);
-	    if (typeof participant.remainingTiles != 'number') {
-		displayMyTray(participant.remainingTiles);
-	    }
-	}
-	// small "Kilian fix" - probably there is a nicer way to do it...
-	if (gameState.moves != null) {
-	    for (var i = 0; i < gameState.moves.length; i++) {
-		appendChildNodes($('gameLog'), DIV(null, renderMoveAsText(gameState.moves[i])));
-	    }
-	}
-    }
-    catch (e) {
-	alert('error ' + e + ' in drawGameState');
-    }
+  try {
+    for (var i = 0; i < gameState.board.length; i++) {
+      var x = gameState.board[i][0];
+      var y = gameState.board[i][1];
+      var char = gameState.board[i][2];
+      setLetter(x, y, char, gameState.board[i].length > 3);
+    }
+    var firstParticipant = gameState.participants[0];
+    displayWhosTurnItIs(firstParticipant.name);
+    for (var i = 0; i < gameState.participants.length; i++) {
+      var participant = gameState.participants[i];
+      makeTheirTray(participant);
+      if (typeof participant.remainingTiles != 'number') {
+        displayMyTray(participant.remainingTiles);
+      }
+    }
+    for (var i = 0; gameState.moves && (i < gameState.moves.length); i++) {
+      appendChildNodes($('gameLog'), DIV(null, renderMoveAsText(gameState.moves[i])));
+    }
+  }
+  catch (e) {
+    alert('error ' + e + ' in drawGameState');
+  }
 }
 
 function displayStatus(status)
 {
-    replaceChildNodes('status', status);
+  replaceChildNodes('status', status);
 }
 
+function init() {
+  makeBoard();
 
-// Publication
-
-function Publication() {
-    this.callbacks = [];
-    this.object2callback = {};
-}
-
-function extendWithPublication (obj) {
-    var value;
-    Publication.call(obj);
-    for (p in Publication.prototype) {
-	value = Publication.prototype[p];
-	if (value instanceof Function)
-	    obj[p] = value;
-    }
-}
-
-Publication.prototype = {
-    subscribe : function(fnOrObject, /*optional*/ method) {
-	if (!(((fnOrObject instanceof Function) && (method == undefined))
-	      || ((fnOrObject instanceof Object) && !(fnOrObject instanceof Function)
-		  && (method instanceof Function))))
-	    throw new Error("bad args to subscribe. use either subscribe(fn) or subscribe(obj, method)");
-	if (this._isAlreadySubscribed(fnOrObject))
-	    return null;
-	var callback = this._makeCallback(fnOrObject, method);
-	this._associateObjectWithCallback(fnOrObject, callback);
-	this._registerCallback(callback);
-    },
-    unsubscribe : function(fnOrObject) {
-	var callback = this._associatedCallback(fnOrObject);
-	this._unregisterCallback(callback);
-    },
-    unsubscribeAll : function() {
-        this.callbacks = [];
-	this.object2callback = {};	
-    },
-    publish : function(/*optional*/ message) {
-	this.callbacks.forEach(
-	    function (fn) {
-		fn(message);
-	    });
-    },
-    _makeCallback : function (fnOrObject, /*optional*/ method) {
-	if (fnOrObject instanceof Function) {
-	    return fnOrObject;
-	} else {
-	    return function (message) { method.call(fnOrObject, message) };
-	}
-    },
-    _associateObjectWithCallback : function (fnOrObject, callback) {
-	if (!(fnOrObject instanceof Function)) {
-	    this.object2callback[fnOrObject] = callback;
-	}
-    },
-    _associatedCallback : function (fnOrObject) {
-	if (fnOrObject instanceof Function)
-	    return fnOrObject;
-	else
-	    return this.callbacks[fnOrObject];
-    },
-    _registerCallback : function(callback) {
-	this.callbacks.push(callback);
-    },
-    _unregisterCallback : function(callback) {
-	this.callbacks = this.callbacks.filter(
-	    function (c) {
-		(c === callback)		
-	    }); 
-    },
-    _isAlreadySubscribed : function (fnOrObject) {
-	return (this.object2callback[fnOrObject])
-	    || (this.callbacks.some(function (callback) {callback === fnOrObject}))
-    }
-};
-
-// loginManager
-var loginManager = {isLoggedIn:
-		    function () {
-			return this.loginName !== null;
-		    },
-		    loggedInAs:
-		    function () {
-			return this.loginName;
-		    },
-		    login:
-		    function (name, pwd) {
-		    	loadJSONDoc("/login?login=" + name + "&password=" + pwd).addCallbacks(
-			    this._requestLoginName,
-			    this._requestError);
-		    },
-		    logout:
-		    function () {
-		    	loadJSONDoc("/logout").addCallbacks(
-			    this._requestLoginName,
-			    this._requestError);
-		    },
-		    publishLoginName:
-		    function () {
-			this._requestLoginName();
-		    },
-		    _setLoginName:
-		    function (loginName) {
-			var oldLoginName = loginManager.loginName;
-			loginManager.loginName = loginName;
-			// 			if (oldLoginName != loginName)
-			// 			    loginManager.publish(loginName);
-			loginManager.publish(loginName);
-		    },
-		    _requestLoginName:
-		    function () {
-		    	loadJSONDoc("/logged-in-as").addCallbacks(
-			    loginManager._setLoginName,
-			    loginManager._requestError);
-		    },
-		    _requestError:
-		    function (error) {
-			alert("Request error: " + error.message)
-		    }
-		   };
-extendWithPublication(loginManager);
-
-var loginView = {loginManagerCallback:
-		 function (loginName) {
-		     if (loginName)
-			 loginView.changeForLoggedIn(loginName);
-		     else
-			 loginView.changeForLoggedOut();		     
-		 },
-		 show:
-		 function () {
-		     var container = $('playfield');
-		     var form = FORM({id:"loginForm", action:"post", onsubmit:"return false"},
-			 DIV(
-			     null,
-			     LABEL({'for':"username"}, "Username: "),
-			     INPUT({id:"username", name:"username", size:"20", type:"text"}),
-			     LABEL({'for':"password"}, "Password: "),
-			     INPUT({id:"password", name:"password", size:"20", type:"password"}),
-			     P({id:"message"}),
-			     BUTTON({id:"button", onlick:"return false"}, "Login")	    
-			 ));
-		     form.style.position = 'absolute';
-		     setElementPosition(form, { x: 750, y: 200 });
-		     setElementDimensions(form, {w:200, h:150});
-		     loginManager.subscribe(loginView, this.loginManagerCallback);		     
-		     appendChildNodes(container, form)
-		 },
-		 hide:
-		 function () {
-		     loginManager.subscribe(loginView, loginView.loginManagerCallback);		     
-		     removeElement($('loginForm'));
-		 },
-		 changeForLoggedIn:
-		 function (name) {
-		     loginView.hide();
-		     gameListView.show();
-		 },
-		 changeForLoggedOut:
-		 function () {
-		     $('message').innerHTML = "Enter your username and password to log in.";
-		     $('button').innerHTML = "Login";
-		     $('button').onclick = function () { loginManager.login($('username').value, $('password').value)};
-		 }};
-
-var gameListManager = {logout:
-		       function () {		       	         
-			   gameListView.hide();
-			   loginView.show();
-			   loginManager.logout();			      
-		       },
-		       playGame:
-		       function (id) {
-		       	   var d = loadJSONDoc("/play-game?gameid=" + id);
-			   d.addCallbacks(
-			       function () {
-				   var d = loadJSONDoc("/game");
-    				   d.addCallbacks(drawGameState, function (error) { alert("Request error: " + error.message); });
-				   gameListView.hide();
-				   returnToGameListView.show();				   
-			       },
-			       requestError);
-		       }
-		      };
-
-var gameListView = {show:
-		    function () {
-		    	var container = $('playfield');			
-			var div = DIV({id:"gameListView"});
-			div.innerHTML = '<button onclick="gameListManager.logout();">Logout</button><br /><button onclick="gameListManager.playGame(108);">play game 108</button>';
-			div.style.position = 'absolute';
-			setElementPosition(div, { x: 750, y: 200 });
-			setElementDimensions(div, {w:200, h:150});
-			appendChildNodes(container, div);
-		    },
-		    hide:
-		    function () {
-		    	removeElement($('gameListView'));
-		    }};
-
-
-// a stub really
-var returnToGameListView = {show:
-			    function () {
-			    	var container = $('playfield');				
-				var button = BUTTON({id:'returnToGameListButton'}, "return to game list");
-				button.style.position = 'absolute';
-				setElementPosition(button, { x: 750, y: 200 });
-				setElementDimensions(button, {w:150, h:30});
-				YAHOO.util.Event.on(button, 'click', this.returnToGameList, null, this);				
-				// loginManager.subscribe(loginView, this.loginManagerCallback);		     
-				appendChildNodes(container, button);
-			    },
-			    hide:
-			    function () {
-				removeElement('returnToGameListButton');
-				// FIXME for now we reload
-				document.location = "scrabble.html";
-			    },
-			    returnToGameList:
-			    function () {
-				var d = loadJSONDoc("/leave-game");
-				d.addCallbacks(
-				    function () {
-				    	returnToGameListView.hide();
-					gameListView.show();
-				    },
-				    requestError)}
-			   };
-
+  // does not work for ie (document.body needed)?
+  YAHOO.util.Event.on(window, 'keypress', letterKeyPressed);
 
-function init() {
-    makeBoard();
-    
-    // does not work for ie (document.body needed)?
-    YAHOO.util.Event.on(window, 'keypress', letterKeyPressed);
-    
-    var functionKeyListener = new YAHOO.util.KeyListener(document,
-    	{ keys: [ leftKey, upKey, rightKey, downKey, backspaceKey ] },
-    	{ fn: functionKeyPressed, scope: this, correctScope: true });
-    functionKeyListener.enable();    
-    var moveDisplay = DIV({ id: 'move' }, "");
-    moveDisplay.style.color = 'white';
-    moveDisplay.style.position = 'absolute';
-    setElementPosition(moveDisplay, { x: border + 550, y: border + 665 });
-    appendChildNodes(document.body, moveDisplay);
-    loginView.show();
-    loginManager.subscribe(function (loginName) {
-    	if (loginName) {
-    	    // loginView.hide();
-    	}
-    });
-    loginManager.publishLoginName();
+  var functionKeyListener = new YAHOO.util.KeyListener(document,
+      { keys: [ leftKey, upKey, rightKey, downKey, backspaceKey ] },
+      { fn: functionKeyPressed, scope: this, correctScope: true });
+  functionKeyListener.enable();
+
+  var moveDisplay = DIV({ id: 'move' }, "");
+  moveDisplay.style.color = 'white';
+  moveDisplay.style.position = 'absolute';
+  setElementPosition(moveDisplay, { x: 550, y: 665 });
+  appendChildNodes(document.body, moveDisplay);
+  loadJSONDoc("/game/" + gameID)
+    .addCallbacks(drawGameState, function (error) { alert("Request error: " + error.message); });
 }

Modified: branches/trunk-reorg/thirdparty/hunchentoot-0.14.7/misc.lisp
==============================================================================
--- branches/trunk-reorg/thirdparty/hunchentoot-0.14.7/misc.lisp	(original)
+++ branches/trunk-reorg/thirdparty/hunchentoot-0.14.7/misc.lisp	Thu Jan 17 11:36:28 2008
@@ -180,7 +180,10 @@
 (defun enough-url (url url-prefix)
   "Returns the relative portion of URL relative to URL-PREFIX, similar
 to what ENOUGH-NAMESTRING does for pathnames."
-  (subseq url (mismatch url url-prefix)))
+  (let ((start (mismatch url url-prefix)))
+    (if start
+        (subseq url start)
+        "")))
 
 (defun create-folder-dispatcher-and-handler (uri-prefix base-path &optional content-type)
   "Creates and returns a dispatch function which will dispatch to a

Modified: branches/trunk-reorg/thirdparty/parenscript/src/js-macrology.lisp
==============================================================================
--- branches/trunk-reorg/thirdparty/parenscript/src/js-macrology.lisp	(original)
+++ branches/trunk-reorg/thirdparty/parenscript/src/js-macrology.lisp	Thu Jan 17 11:36:28 2008
@@ -4,7 +4,9 @@
 
 ;;; literals
 (defmacro defpsliteral (name string)
-  `(define-ps-special-form ,name (expecting) (list 'js-literal ,string)))
+  `(define-ps-special-form ,name (expecting)
+    (declare (ignore expecting))
+    (list 'js-literal ,string)))
 
 (defpsliteral this      "this")
 (defpsliteral t         "true")
@@ -15,45 +17,54 @@
 (defpsliteral undefined "undefined")
 
 (defmacro defpskeyword (name string)
-  `(define-ps-special-form ,name (expecting) (list 'js-keyword ,string)))
+  `(define-ps-special-form ,name (expecting)
+    (declare (ignore expecting))
+    (list 'js-keyword ,string)))
 
 (defpskeyword break    "break")
 (defpskeyword continue "continue")
 
 (define-ps-special-form array (expecting &rest values)
+  (declare (ignore expecting))
   (cons 'array-literal (mapcar (lambda (form) (compile-parenscript-form form :expecting :expression))
                                values)))
 
 (define-ps-special-form aref (expecting array &rest coords)
+  (declare (ignore expecting))
   (list 'js-aref (compile-parenscript-form array :expecting :expression)
         (mapcar (lambda (form)
                   (compile-parenscript-form form :expecting :expression))
                 coords)))
 
 (define-ps-special-form {} (expecting &rest arrows)
+  (declare (ignore expecting))
   (cons 'object-literal (loop for (key value) on arrows by #'cddr
                               collect (cons key (compile-parenscript-form value :expecting :expression)))))
 
 ;;; operators
 (define-ps-special-form incf (expecting x &optional (delta 1))
+  (declare (ignore expecting))
   (if (equal delta 1)
       (list 'unary-operator "++" (compile-parenscript-form x :expecting :expression) :prefix t)
       (list 'operator '+= (list (compile-parenscript-form x :expecting :expression)
                                 (compile-parenscript-form delta :expecting :expression)))))
 
 (define-ps-special-form decf (expecting x &optional (delta 1))
+  (declare (ignore expecting))
   (if (equal delta 1)
       (list 'unary-operator "--" (compile-parenscript-form x :expecting :expression) :prefix t)
       (list 'operator '-= (list (compile-parenscript-form x :expecting :expression)
                                 (compile-parenscript-form delta :expecting :expression)))))
 
 (define-ps-special-form - (expecting first &rest rest)
+  (declare (ignore expecting))
   (if (null rest)
       (list 'unary-operator "-" (compile-parenscript-form first :expecting :expression) :prefix t)
       (list 'operator '- (mapcar (lambda (val) (compile-parenscript-form val :expecting :expression))
                                  (cons first rest)))))
 
 (define-ps-special-form not (expecting x)
+  (declare (ignore expecting))
   (let ((form (compile-parenscript-form x :expecting :expression))
         (not-op nil))
     (if (and (eql (first form) 'operator)
@@ -72,6 +83,7 @@
         (list 'unary-operator "!" form :prefix t))))
 
 (define-ps-special-form ~ (expecting x)
+  (declare (ignore expecting))
   (list 'unary-operator "~" (compile-parenscript-form x :expecting :expressin) :prefix t))
 
 (defun flatten-blocks (body)
@@ -97,18 +109,21 @@
 
 ;;; function definition
 (define-ps-special-form %js-lambda (expecting args &rest body)
+  (declare (ignore expecting))
   (list 'js-lambda (mapcar (lambda (arg)
                              (compile-parenscript-form arg :expecting :symbol))
                            args)
         (compile-parenscript-form `(progn , at body))))
 
 (define-ps-special-form %js-defun (expecting name args &rest body)
+  (declare (ignore expecting))
   (list 'js-defun (compile-parenscript-form name :expecting :symbol)
         (mapcar (lambda (val) (compile-parenscript-form val :expecting :symbol)) args)
 	(compile-parenscript-form `(progn , at body))))
 
 ;;; object creation
 (define-ps-special-form create (expecting &rest args)
+  (declare (ignore expecting))
   (list 'js-object (loop for (name val) on args by #'cddr collecting
                          (let ((name-expr (compile-parenscript-form name :expecting :expression)))
                            (assert (or (stringp name-expr)
@@ -121,6 +136,7 @@
                            (list name-expr (compile-parenscript-form val :expecting :expression))))))
 
 (define-ps-special-form %js-slot-value (expecting obj slot)
+  (declare (ignore expecting))
   (if (ps::ps-macroexpand slot)
       (list 'js-slot-value (compile-parenscript-form obj :expecting :expression) (compile-parenscript-form slot))
       (compile-parenscript-form obj :expecting :expression)))
@@ -157,6 +173,7 @@
                        (compile-parenscript-form else :expecting :expression)))))
 
 (define-ps-special-form switch (expecting test-expr &rest clauses)
+  (declare (ignore expecting))
   (let ((clauses (mapcar (lambda (clause)
 			     (let ((val (car clause))
 				   (body (cdr clause)))
@@ -207,9 +224,11 @@
       (list 'js-assign lhs rhs)))
 
 (define-ps-special-form setf1% (expecting lhs rhs)
+  (declare (ignore expecting))
   (smart-setf (compile-parenscript-form lhs :expecting :expression) (compile-parenscript-form rhs :expecting :expression)))
 
 (define-ps-special-form defvar (expecting name &rest value)
+  (declare (ignore expecting))
   (append (list 'js-defvar (compile-parenscript-form name :expecting :symbol))
           (when value
             (assert (= (length value) 1) () "Wrong number of arguments to defvar: ~s" `(defvar ,name , at value))
@@ -228,6 +247,7 @@
 	collect (compile-parenscript-form (third decl) :expecting :expression)))
 
 (define-ps-special-form do (expecting decls termination-test &rest body)
+  (declare (ignore expecting))
   (let ((vars (make-for-vars decls))
 	(steps (make-for-steps decls))
 	(test (compile-parenscript-form `(not ,(first termination-test)) :expecting :expression))
@@ -235,20 +255,24 @@
     (list 'js-for vars steps test body)))
 
 (define-ps-special-form doeach (expecting decl &rest body)
+  (declare (ignore expecting))
   (list 'js-for-each
         (compile-parenscript-form (first decl) :expecting :symbol)
         (compile-parenscript-form (second decl) :expecting :expression)
 	(compile-parenscript-form `(progn , at body))))
 
 (define-ps-special-form while (expecting test &rest body)
+  (declare (ignore expecting))
   (list 'js-while (compile-parenscript-form test :expecting :expression)
                   (compile-parenscript-form `(progn , at body))))
 
 (define-ps-special-form with (expecting expression &rest body)
+  (declare (ignore expecting))
   (list 'js-with (compile-parenscript-form expression :expecting :expression)
 		 (compile-parenscript-form `(progn , at body))))
 
 (define-ps-special-form try (expecting form &rest clauses)
+  (declare (ignore expecting))
   (let ((catch (cdr (assoc :catch clauses)))
         (finally (cdr (assoc :finally clauses))))
     (assert (not (cdar catch)) nil "Sorry, currently only simple catch forms are supported.")
@@ -260,23 +284,28 @@
           :finally (when finally (compile-parenscript-form `(progn , at finally))))))
 
 (define-ps-special-form regex (expecting regex)
+  (declare (ignore expecting))
   (list 'js-regex (string regex)))
 
 ;;; TODO instanceof
 (define-ps-special-form instanceof (expecting value type)
+  (declare (ignore expecting))
   (list 'js-instanceof (compile-parenscript-form value :expecting :expression)
         (compile-parenscript-form type :expecting :expression)))
 
 ;;; single operations
 (mapcar (lambda (op) (eval `(define-ps-special-form ,op (expecting value)
+                             (declare (ignore expecting))
                              (list 'js-named-operator ',op (compile-parenscript-form value)))))
         '(throw delete void typeof new))
 
 (define-ps-special-form return (expecting &optional value)
+  (declare (ignore expecting))
   (list 'js-return (compile-parenscript-form value :expecting :expression)))
 
 ;;; conditional compilation
 (define-ps-special-form cc-if (expecting test &rest body)
+  (declare (ignore expecting))
   (list 'cc-if test (mapcar #'compile-parenscript-form body)))
 
 ;;; standard macros

Modified: branches/trunk-reorg/thirdparty/slime/slime.el
==============================================================================
--- branches/trunk-reorg/thirdparty/slime/slime.el	(original)
+++ branches/trunk-reorg/thirdparty/slime/slime.el	Thu Jan 17 11:36:28 2008
@@ -6287,7 +6287,9 @@
            ("*SLIME macroexpansion*" :mode lisp-mode :reusep t) package
          (slime-macroexpansion-minor-mode)
          (erase-buffer)
-         (insert expansion)
+         (save-excursion
+           (insert expansion))
+         (indent-sexp)
          (font-lock-fontify-buffer))))))
 
 (defun slime-eval-macroexpand-inplace (expander)
@@ -6316,6 +6318,59 @@
              (indent-sexp)
              (goto-char point))))))))
 
+(defun slime-enclosing-macro-context-establishers ()
+  (flet ((establishes-context-p (form-spec)
+           (let ((operator-name (first form-spec)))
+             (when (stringp operator-name)
+               (let ((symbol-name (slime-cl-symbol-name operator-name)))
+                 (or (equal symbol-name "macrolet") (equal symbol-name "symbol-macrolet")))))))
+    (multiple-value-bind (form-specs indices points)
+        (slime-enclosing-form-specs)
+      (loop for form-spec in form-specs
+            for index in indices
+            for point in points
+            when (establishes-context-p form-spec)
+              collect form-spec into form-specs* and
+              collect index into indices* and
+              collect point into points*
+            finally (return (values form-specs* indices* points*))))))
+
+(defun slime-collect-macro-context ()
+  (multiple-value-bind (form-specs indices points)
+      (slime-enclosing-macro-context-establishers)
+    (save-excursion
+      (let ((context))
+        (cl-mapc #'(lambda (form-spec index point)
+                     (when (= index 2)
+                       (destructuring-bind (operator-name) form-spec
+                         (goto-char point)
+                         (slime-forward-sexp)
+                         (forward-char)
+                         (push (cons operator-name (slime-parse-sexp-at-point 1 t)) context))))
+                 form-specs indices points)
+        context))))
+
+(defun slime-rebuild-macro-context-around-string (string context)
+  (if (null context)
+      string
+      (destructuring-bind (let-operator . bindings) (first context)
+        (format "(%s %s %s)" let-operator bindings
+                (slime-rebuild-macro-context-around-string string (rest context))))))
+
+(defun slime-macroexpand-locally-1 (&optional repeatedly)
+  (interactive "P")
+  (let ((sexp (first (slime-sexp-at-point-for-macroexpansion)))
+        (macro-context (slime-collect-macro-context)))
+    (if repeatedly
+        (slime-eval-macroexpand 'swank:swank-macroexpand-locally
+                                (slime-rebuild-macro-context-around-string
+                                 (format "(swank::macroexpand-locally %s)" sexp)
+                                 macro-context))
+        (slime-eval-macroexpand 'swank:swank-macroexpand-locally-1
+                                (slime-rebuild-macro-context-around-string
+                                 (format "(swank::macroexpand-locally-1 %s)" sexp)
+                                 macro-context)))))
+
 (defun slime-macroexpand-1 (&optional repeatedly)
   "Display the macro expansion of the form at point.  The form is
 expanded with CL:MACROEXPAND-1 or, if a prefix argument is given, with

Modified: branches/trunk-reorg/thirdparty/slime/swank.lisp
==============================================================================
--- branches/trunk-reorg/thirdparty/slime/swank.lisp	(original)
+++ branches/trunk-reorg/thirdparty/slime/swank.lisp	Thu Jan 17 11:36:28 2008
@@ -78,7 +78,8 @@
   "Abbreviate dotted package names to their last component if T.")
 
 (defvar *swank-io-package*
-  (let ((package (make-package :swank-io-package :use '())))
+  (let ((package (or (find-package :swank-io-package)
+                     (make-package :swank-io-package :use '()))))
     (import '(nil t quote) package)
     package))
 
@@ -2401,6 +2402,22 @@
       (let ((*print-readably* nil))
         (disassemble (fdefinition (from-string name)))))))
 
+(defslimefun swank-macroexpand-locally (string)
+  (apply-macro-expander #'eval string))
+
+(defslimefun swank-macroexpand-locally-1 (string)
+  (apply-macro-expander #'eval string))
+
+(defmacro macroexpand-locally (form &environment env)
+   (multiple-value-bind (expansion expanded-p)
+       (macroexpand form env)
+     `(values ',expansion ',expanded-p)))
+
+(defmacro macroexpand-locally-1 (form &environment env)
+   (multiple-value-bind (expansion expanded-p)
+       (macroexpand-1 form env)
+     `(values ',expansion ',expanded-p)))
+
 

 ;;;; Simple completion
 



More information about the Bknr-cvs mailing list