Ajax版○×ゲーム

http://taoe.jpn.ch:8080/game/game.html に置いてあります。しかし、自分の自宅鯖なので突然なくなる可能性もありますがご容赦下さい。
[id:Horiuchi_H:20051013] のRubyで作った○×ゲームスクリプトcgiに利用して、フロントエンドをAjaxにしてみました。見た目が適当ですけどどんなもんでしょう?
たぶん、先攻・後攻共に勝てないはずですが、何か勝ち筋があれば教えてください。

11/10追記

非同期処理中に連続でリクエストを送ることができた不具合を修正しました。まだ、自宅鯖には反映されていません^^;21:15に反映しました。
http://spark-pg.hp.infoseek.co.jp/game/marubatu.zip にソースを置きましたので、良かったら見てみてください。

以下に、メインのJavaScriptファイルのソースだけ貼り付けておきます。動作させるためには、prototype.jsが必要です。

var Marubatu = Class.create();

Marubatu.prototype = {
  initialize: function(id, message, path) {
    this.view = $(id);
    this.message = $(message);
    this.path = path;
    
    this.marks = new Array();
    this.loading = false;
    this.endGame = false;
    
    this.KEY = 'order=';
    this.O = "○";
    this.X = "×";
    this.E = "  ";
  },
  
  mark: function(index) {
    return (index % 2 == 0)? this.O: this.X;
  },
  
  
  contain: function(index) {
    for (var i = 0; i < this.marks.length; i++) {
      if (this.marks[i] == index) {
        return true;
      }
    }
    return false;
  },
  add: function(index) {
    if (this.contain(index)) return;
    this.marks.push(index);
  },
  set: function(indexs) {
    this.marks = indexs.split(",");
    for (var i = 0; i < this.marks.length; i++) {
      this.marks[i] = parseInt(this.marks[i]);
    }
  },
  
  
  nextStep: function(func, args) {
    this.loading = true;
    
    var parameters = this.KEY + this.marks.join(",");
    var result = "";
    new Ajax.Request(this.path, {
      method: 'post',
      asynchronous: true,
      parameters: parameters,
      onLoading: function() {
        this.message.innerHTML = "now loading...";
      }.bind(this),
      onFailure: function() {
        this.message.innerHTML = "loading fail!";
        this.loading = false;
      }.bind(this),
      onSuccess: function() {
        this.message.innerHTML = result;
        this.loading = false;
      }.bind(this),
      onComplete: function(originalRequest) {
        res = originalRequest.responseText.split(":");
        var index = parseInt(res[0]);
        var win = "";
        if (res.length > 1) win = res[1];
        
        if (!isNaN(index)) {
          this.add(index);
        }
        if (win.length > 0 || isNaN(index)) {
          this.endGame = true;
        }
        
        this.message.innerHTML = this.getMessage();
        if(func) func.apply(this, args);
      }.bind(this)
    });
  },
  
  getMessage: function() {
    var message = "";
    if (this.endGame) {
      message = "end game!<br>";
    }
    message += this.marks.join(",");
    return message;
  },
  
  addStep: function(id, instance) {
    if (this.loading) return;
    this.loading = true;
    
    var index = parseInt(id);
    this.add(index);
    this.nextStep(this.display, [instance]);
  },
  
  display: function(instance) {
    var board = new Array(9);
    for (var i = 0; i < board.length; i++) {
      if (this.endGame) {
        board[i] = this.E;
      } else {
        board[i] = '<a id="' + i + '" onclick="' + instance + '.addStep(' + i + ', \'' + instance + '\');" href="#" >' + this.E + '</a>';
      }
    }
    for (var i = 0; i < this.marks.length; i++) {
      board[this.marks[i]] = this.mark(i);
    }
    
    this.view.innerHTML = "<pre>\n"
                        + board[0] + "|" + board[1] + "|" + board[2] + "\n"
                        + "─┼─┼─\n"
                        + board[3] + "|" + board[4] + "|" + board[5] + "\n"
                        + "─┼─┼─\n"
                        + board[6] + "|" + board[7] + "|" + board[8] + "\n"
                        + "</pre>\n";
  }
};