flash/src/com/caucho/games/sudoku/BoardComponent.mxml

<?xml version="1.0"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" 
           width="250" height="250" creationComplete="onCreationComplete()"
           addedToStage="onAddedToStage()">

  <mx:Script>
  <![CDATA[
  import flash.display.*;
  import mx.controls.*;
  import com.caucho.bam.*;
  import com.caucho.hmtp.*;

  private const CELL_WIDTH:int = 40;

  private var _currentX:int = 4;
  private var _currentY:int = 4;
  private var _board:SudokuBoard = null;
  private var _client:HmtpClient;
  private var _gameJid:String;
  private var _playerId:String;
  private var _scoreBoard:ScoreBoard;
  private var _infoLabel:Label;

  private function onCreationComplete() : void
  {
    width = CELL_WIDTH * 10;
    height = CELL_WIDTH * 10;

    render();
  }

  private function onAddedToStage() : void
  {
    stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
    stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
  }

  protected function tryEntry(x:int, y:int, value:int):void
  {
    _client.querySet(_gameJid, new MoveQuery(x, y, value), 
                     onQueryResult, onQueryError);
  }

  public function onKeyDown(event:KeyboardEvent) : void
  {
    if (_board == null)
      return;

    if (event.keyCode == Keyboard.UP) {
      if (_currentY > 0)
        _currentY--;
    }
    else if (event.keyCode == Keyboard.DOWN) {
      if (_currentY < _board.size - 1)
        _currentY++;
    }
    else if (event.keyCode == Keyboard.LEFT) {
      if (_currentX > 0)
        _currentX--;
    }
    else if (event.keyCode == Keyboard.RIGHT) {
      if (_currentX < _board.size - 1)
        _currentX++;
    }
    else {
      var value:int = event.charCode - "0".charCodeAt();

      if (value > 0 && value <= _board.size)
        tryEntry(_currentX, _currentY, value);
    }

    render();
  }

  public function onMouseDown(event:MouseEvent) : void
  {
    if (_board == null)
      return;

    if (event.localX >= _board.size * CELL_WIDTH + CELL_WIDTH/2||
        event.localY >= _board.size * CELL_WIDTH + CELL_WIDTH/2)
      return;

    var newX:int = (event.localX - CELL_WIDTH/2) / CELL_WIDTH;
    var newY:int = (event.localY - CELL_WIDTH/2) / CELL_WIDTH;

    if (newX != _currentX || newY != _currentY) {
      _currentX = newX;
      _currentY = newY;

      render();
    }
  }

  public function renderReset(event:Event):void
  {
    render();
  }

  public function labelReset(event:Event):void
  {
    _infoLabel.text = "";
  }

  public function render(x:int = -1, y:int = -1) : void
  {
    graphics.clear();

    var size:int = 9;

    if (_board != null)
      size = _board.size;

    graphics.beginFill(0xFFFFFF);

    graphics.lineStyle(3, 0x000000);
    graphics.drawRect(CELL_WIDTH/2, CELL_WIDTH/2, 
                      size * CELL_WIDTH + 1, size * CELL_WIDTH + 1);
    graphics.endFill();

    var i:int;

    // draw the vertical lines
    for (i = 1; i < 9; i++) {
      if (i % 3 == 0)
        graphics.lineStyle(3, 0x000000);
      else
        graphics.lineStyle(1, 0x000000);
      
      graphics.moveTo(CELL_WIDTH/2 + i * CELL_WIDTH, CELL_WIDTH/2);
      graphics.lineTo(CELL_WIDTH/2 + i * CELL_WIDTH, 
                      CELL_WIDTH/2 + size * CELL_WIDTH + 1);
    }

    // draw the horizontal lines
    for (i = 1; i < 9; i++) {
      if ((i % 3) == 0)
        graphics.lineStyle(3, 0x000000);
      else
        graphics.lineStyle(1, 0x000000);
      
      graphics.moveTo(CELL_WIDTH/2, CELL_WIDTH/2 + i * CELL_WIDTH);
      graphics.lineTo(CELL_WIDTH/2 + size * CELL_WIDTH + 1, 
                      CELL_WIDTH/2 + i * CELL_WIDTH);
    }

    graphics.beginFill(0xFFFF00, 0.5);
    graphics.lineStyle(0);
    graphics.drawRect(CELL_WIDTH/2 + CELL_WIDTH * _currentX, 
                      CELL_WIDTH/2 + CELL_WIDTH * _currentY,
                      CELL_WIDTH, CELL_WIDTH);
    graphics.endFill();

    if (x >=0 && y >= 0) {
      // x and y indicate a cell to flash red
      graphics.beginFill(0xFF0000);
      graphics.lineStyle(0, 0x000000);
      graphics.drawRect(CELL_WIDTH/2 + x * CELL_WIDTH, 
                        CELL_WIDTH/2 + y * CELL_WIDTH, 
                        CELL_WIDTH, CELL_WIDTH);
      graphics.endFill();
    }
  }

  public function onQueryResult(to:String, from:String, value:Object):void
  {
    if (value is MoveResult) {
      handleMoveResult(to, from, MoveResult(value));
    }
  }

  public function onQueryError(to:String, from:String, 
                               value:Object, error:BamError):void
  {
    trace("onQueryError(" + to + ", " + from + ", " + 
                        value + ", " + error + ")");

    if (error is MoveError) {
      var query:MoveQuery = MoveQuery(value);
      var me:MoveError = MoveError(error);

      _infoLabel.text = me.text;

      render(query.x, query.y);

      // in 300ms, rerender without the red cell
      var timer:Timer = new Timer(300);
      timer.addEventListener(TimerEvent.TIMER, renderReset);
      timer.start();

      // in 1s, remove message
      timer = new Timer(1000);
      timer.addEventListener(TimerEvent.TIMER, labelReset);
      timer.start();
    }
  }

  private function handleMoveResult(to:String, 
                                    from:String, 
                                    result:MoveResult):void
  {
    var x:int = -1;
    var y:int = -1;

    if (result.success) {
      board.setEntry(result.x, result.y, result.value);

      if (result.playerId == _playerId)
        renderEntry(result.x, result.y, result.value, 0x0000ff);
      else
        renderEntry(result.x, result.y, result.value, 0xff0000);
    }
    else {
      // set the incorrect cell to flash
      x = result.x;
      y = result.y;

      // in 300ms, rerender without the red cell
      var timer:Timer = new Timer(300);
      timer.addEventListener(TimerEvent.TIMER, renderReset);
      timer.start();

      // in 1s, remove message
      timer = new Timer(1000);
      timer.addEventListener(TimerEvent.TIMER, labelReset);
      timer.start();

      _infoLabel.text = "Incorrect!";
    }

    if (result.playerId == _playerId)
      _scoreBoard.yourScore = result.score;
    else
      _scoreBoard.opponentScore = result.score;

    render(x, y);
  }

  public function get board():SudokuBoard
  {
    return _board;
  }

  public function set board(b:SudokuBoard):void
  {
    _board = b;

    width = CELL_WIDTH * (_board.size + 1);
    height = CELL_WIDTH * (_board.size + 1);

    removeAllChildren();

    for (var x:int = 0; x < _board.size; x++) {
      for (var y:int = 0; y < _board.size; y++) {
        var entry:int = _board.getEntry(x, y);

        if (entry != 0)
          renderEntry(x, y, entry);
      }
    }

    render();
  }

  private function renderEntry(x:int, y:int, value:int, 
                               color:uint = 0x000000):void
  {
    var label:Label = new Label();
    label.text = value.toString();
    label.x = CELL_WIDTH/2 + x * CELL_WIDTH + CELL_WIDTH / 4;
    label.y = CELL_WIDTH/2 + y * CELL_WIDTH;
    label.setStyle("color", color);
    label.setStyle("fontSize", 30);
    label.setStyle("fontFamily", "Arial");

    addChild(label);
  }

  public function set client(c:HmtpClient):void
  {
    _client = c;
    _client.addMessageListener(MoveResult, handleMoveResult);
  }

  public function set playerId(id:String):void
  {
    _playerId = id;
  }

  public function get playerId():String
  {
    return _playerId;
  }

  public function set gameJid(jid:String):void
  {
    _gameJid = jid;
  }

  public function get gameJid():String
  {
    return _gameJid;
  }

  public function set scoreBoard(sb:ScoreBoard):void
  {
    _scoreBoard = sb;
  }

  public function set infoLabel(label:Label):void
  {
    _infoLabel = label;
  }

  ]]>
  </mx:Script>
</mx:Canvas>