function $(x){return document.getElementById(x);}

var WIDTH=32,HEIGHT=32;
var MAP_MAGIC = 0x032f24c2;
var SQRT2 = 362;

var debuglevel=0;

var session = {};
var objs = {};
var screen_x, screen_y;
var tile_lut = [];
var scr = [];
var world = {};
var objs_valid = false;
var resync = 0;
var class_radiobuttons = [];

// protocol message -> dx,dy
var move_cmds = {
  'l':[-1,0],
  'd':[0,1],
  'u':[0,-1],
  'r':[1,0],
  'dl':[-1,1],
  'ul':[-1,-1],
  'dr':[1,1],
  'ur':[1,-1]
};

// keys -> protocol message
var keymap_move = {
  // wasd... but what should be the diagonals?
  'w':'u',
  'a':'l',
  's':'d',
  'd':'r',
  'q':'ul',
  'e':'ur',
  'z':'dl',
  'c':'dr',

  // number pad keys
  '1':'dl',
  '2':'d',
  '3':'dr',
  '4':'l',
  '6':'r',
  '7':'ul',
  '8':'u',
  '9':'ur',

  // nethack keys
  'h':'l',
  'j':'d',
  'k':'u',
  'l':'r',
  'b':'dl',
  'y':'ul',
  'n':'dr',
  'u':'ur'
};

function tr()
{
  var trElem = document.createElement('tr');
  for(var i=0;i<arguments.length; i++) {
    var td = document.createElement('td');
    td.appendChild(arguments[i]);
    trElem.appendChild(td);
  }
  return trElem;
}

var log_offset={'log':0, 'chat':0};
function log(msg, whichlog)
{
  whichlog = whichlog || 'log';
  var l=$(whichlog);
  log_offset[whichlog]++;
  var div = document.createElement('div');
  div.style.backgroundColor = (log_offset[whichlog]&1) ? "#eeeeee" : "#cccccc";
  div.appendChild(document.createTextNode(msg));

  while(l.childNodes.length >= 10)
    l.removeChild(l.childNodes[0]);
  l.appendChild(div);
}

function debuglog(level, msg)
{
  if(debuglevel >= level) { log(msg); }
}

function update_status(msg, node)
{
  var stat = $(node || 'status');
  while(stat.childNodes[0]) { stat.removeChild(stat.childNodes[0]); }
  var s = $('status');
  stat.appendChild(document.createTextNode(msg));
}

var inputline;
var inputfocus = false;
function cmd_input_onfocus() { inputfocus = true; }
function cmd_input()
{
  if(session.token) {
    var cmd_xhr = new XMLHttpRequest();
    cmd_xhr.open("POST", "s/chat/"+session.token, true);
    cmd_xhr.setRequestHeader("Content-type", "text/plain");
    cmd_xhr.setRequestHeader("Content-length", inputline.value.length);
    cmd_xhr.send(inputline.value);
  }
  inputfocus = false;
  inputline.value = '';
  inputline.blur();
}


function rot(x,n) { return (x<<n) | (x>>(32-n)) & 0xffffffff; }
function hash(a,b)
{
  var c=MAP_MAGIC;
  a += 0xb2ae9013;
  b += 0x2cd8e04a;
  c ^= b; c -= rot(b,14);
  a ^= c; a -= rot(c,11);
  b ^= a; b -= rot(a,25);
  c ^= b; c -= rot(b,16);
  a ^= c; a -= rot(c,4);
  b ^= a; b -= rot(a,14);
  c ^= b; c -= rot(b,24);

  return c&65535;
}

function build_tile_lut()
{
  var i;
  var tile_lut_r = {};
  var N=0;
  for(i=0;i<256;i++) {
    var j=i;
    // edges override corners
    if(j&1) { j|=0x10; j|=0x20; }
    if(j&2) { j|=0x10; j|=0x40; }
    if(j&4) { j|=0x20; j|=0x80; }
    if(j&8) { j|=0x40; j|=0x80; }
    if(!tile_lut_r[j]) { 
      tile_lut_r[j] = N;
      tile_lut[i]=N;
      N++;
    } else {
      tile_lut[i] = tile_lut_r[j];
    }
  } 
}


function mapinterp(map, x,y, u,s,x0,y0)
{
  map[x+y*33] = u+(0|(s*(hash(x+x0,y+y0)-32768)/25600));
}

function clamp(tile,x,y)
{
  tile[x+y*33] = Math.max(0,7*Math.min(65535,tile[x+y*33])/65536);
}

function gen_subdivide(tile,x0,y0,scale,x,y)
{
  if(scale <= 1) { return; }
  var hscale = scale>>1;

  // generate five points
  // a 1 b
  // 3 5 4
  // c 2 d
  var a = tile[x0+y0*33];
  var b = tile[(x0+scale)+y0*33];
  var c = tile[x0+(y0+scale)*33];
  var d = tile[(x0+scale)+(y0+scale)*33];
  mapinterp(tile, x0+hscale,y0, (a+b)>>1,scale<<8, x,y);
  mapinterp(tile, x0+hscale,y0+scale, (c+d)>>1,scale<<8, x,y);
  mapinterp(tile, x0,y0+hscale, (a+c)>>1,scale<<8, x,y);
  mapinterp(tile, x0+scale,y0+hscale, (b+d)>>1,scale<<8, x,y);
  mapinterp(tile, x0+hscale,y0+hscale, (a+b+c+d)>>2,SQRT2*scale, x,y);
  gen_subdivide(tile, x0,y0,hscale, x,y);
  gen_subdivide(tile, x0+hscale,y0,hscale, x,y);
  gen_subdivide(tile, x0,y0+hscale,hscale, x,y);
  gen_subdivide(tile, x0+hscale,y0+hscale,hscale, x,y);
  // normalize and clamp
  clamp(tile, x0+hscale,y0);
  clamp(tile, x0+hscale,y0+scale);
  clamp(tile, x0,y0+hscale);
  clamp(tile, x0+scale,y0+hscale);
  clamp(tile, x0+hscale,y0+hscale);
}

function gen_tile(tile,x,y)
{
  tile[0] = hash(x,y);
  tile[32] = hash(x+32,y);
  tile[32*33] = hash(x,32+y);
  tile[32*33+32] = hash(x+32,y+32);
  gen_subdivide(tile,0,0,32,x,y);
  clamp(tile, 0,0);
  clamp(tile, 0,32);
  clamp(tile, 32,0);
  clamp(tile, 32,32);
}

function gen_screen()
{
  var tbl = document.createElement('table');
  var thead = document.createElement('thead');
  var tbody = document.createElement('tbody');
  tbl.appendChild(thead);
  var y,idx;
  for(y=0,idx=0;y<HEIGHT;y++) {
    var tr = document.createElement('tr');
    for(var x=0;x<WIDTH;x++) {
      var td = document.createElement('td');
      td.width = '16px';
      td.height = '16px';
      td.style.background = 'url("tiles.gif") 0 0 no-repeat';
      scr[idx++] = td;
      tr.appendChild(td);
    }
    tbody.appendChild(tr);
  }
  tbl.cellSpacing = '0px';
  tbl.cellPadding = '0px';
  tbl.appendChild(tbody);

  var idiv = $('inputdiv');
  inputline = document.createElement('input');
  inputline.style.width = "100%";
  inputline.onchange = cmd_input;
  inputline.onfocus = cmd_input_onfocus;
  idiv.appendChild(inputline);

  return tbl;
}

function get_tile(x,y)
{
  if(!world[y]) { world[y] = {}; }
  if(world[y][x]) { return world[y][x]; }
  world[y][x] = [];
  gen_tile(world[y][x], x<<5, y<<5);
  return world[y][x];
}

function update_screen_tile(X,Y)
{
  X >>= 5; Y >>= 5;
  var tile = get_tile(X, Y);
  var tileup = get_tile(X, Y-1);
  var tileleft = get_tile(X-1, Y);
  var tileidx,idx,x,y;
  X <<= 5; Y <<= 5;

  if(scr[0] === undefined) {
    var maptable = $('map');
    build_tile_lut();
    while(maptable.childNodes[0]) { maptable.removeChild(maptable.childNodes[0]); }
    maptable.appendChild(gen_screen());
  }

  screen_x = X; screen_y = Y;
  // plz to optimize this
  for(y=0,idx=0;y<HEIGHT;y++) {
    for(x=0,tileidx=y*33;x<WIDTH;x++,tileidx++) {
      var l,u,lu,ru,ld;
      var c = tile[tileidx]|0;
      var r = tile[1+tileidx]|0;
      var d = tile[33+tileidx]|0;
      var rd = tile[tileidx+34]|0;
      ru = tile[tileidx-32]|0;
      ld = tile[tileidx+32]|0;
      if(x === 0) {
        l = tileleft[31+tileidx]|0;
        lu = tileleft[tileidx-2]|0;
        ld = tileleft[tileidx+64]|0;
      } else {
        l = tile[tileidx-1]|0;
      }
      if(y === 0) {
        u = tileup[x+31*33]|0;
        lu = tileup[x-1+31*33]|0;
        ru = tileup[x+1+31*33]|0;
      } else {
        u = tile[tileidx-33]|0;
      }
      if(x>0 && y>0) {
        lu = tile[tileidx-34]|0;
      } else if(x === 0 && y === 0) {
        lu = c;
      }

      var t = ((u>c ? 1:0)|
               (l>c ? 2:0)|
               (r>c ? 4:0)|
               (d>c ? 8:0)|
               (lu>c ? 16:0)|
               (ru>c ? 32:0)|
               (ld>c ? 64:0)|
               (rd>c ? 128:0));
      var charidx = c*94 + tile_lut[t]*2 + (hash(X+x,Y+y)&1);
      var _x = -16*(charidx&15), _y = -16*(charidx>>4);
      scr[idx++].style.backgroundPosition = _x+"px "+_y+"px";
    }
  }
}

function world_height(x,y)
{
  var tile = get_tile(x>>5,y>>5);
  return tile[(x&31) + (y&31)*33];
}

function destroy_sprite(obj)
{
  var img = obj._sprite;
  if(img === undefined) {return;}
  if(img.parentNode) {
    img.parentNode.removeChild(img);
  }
  img.removeChild(img.childNodes[0]);
  delete obj._sprite;
}

function redraw(obj)
{
  var x = obj.x - screen_x,
      y = obj.y - screen_y;
  var img = obj._sprite;
  var id = obj.id;
  var _id;

  if(screen_x === undefined) {
    if(id == session.objid) {
      update_screen_tile(obj.x, obj.y);
      x = obj.x - screen_x;
      y = obj.y - screen_y;
      for(_id in objs) {
        if(_id != obj.id) {
          redraw(objs[_id]);
        }
      }
    } else {
      // hm, ignore other objects while we don't have the screen up
      return;
    }
  } else if(x<0 || y<0 || x>=WIDTH || y>=HEIGHT) {
    if(id == session.objid) {
      // our own avatar moved off screen, so move the screen!
      // when that happens we implicitly change tiles, so we need to nuke the
      // objects we know about and go back to our single object; the server
      // will update us with what we need to know about the rest of 'em

      // in fact the server will also send us our own object, which we need to
      // watch out for.
      for(_id in objs) {
        destroy_sprite(objs[_id]);
      }
      objs = {}; objs_valid = false;
      screen_x = undefined; // when we get the update for our own object this will redraw
      screen_y = undefined;
      return;
    } else {
      // some other thing moved off-screen; this is an implicit removal
      if(img.parentNode) {
        img.parentNode.removeChild(img);
      }
      if(objs[id].name) {
        log(objs[id].name+" left the screen");
      }
      delete objs[id];
      return;
    }
  }
  if(!img.parentNode) {
    $('map').appendChild(img);
  }
  img.style.left = (x*16)+"px";
  img.style.top = (y*16 - 8)+"px";
  img.style.zIndex = y;
  return true;
}

function object_remove(objid)
{
  if(objid == session.objid) {
    //window.alert("the server deleted me, the bastard!");
    // this will happen when you log out/close the window i guess
    return;
  }
  if(objs[objid]) {
    destroy_sprite(objs[objid]);
    delete objs[objid];
  }
}

function object_add(objid, data)
{
  if(objs[objid] === undefined) {
    objs[objid] = data;
  }
  else {
    for(var k in data) {
      objs[objid][k] = data[k];
    }
  }
  var o = objs[objid];
  o.id = objid;
  if(o._sprite === undefined && o.x !== undefined) {
    o._sprite = document.createElement('div');
    var img = document.createElement('img');
    img.src = o.img;
    o._sprite.appendChild(img);
    o._sprite.style.position = "absolute";
    o._sprite.style.width = "16px";
    o._sprite.style.height = "24px";
    redraw(o);
  }
}

function object_action(objid, action)
{
//  log(objid+" action "+action);
  var o = objs[objid];
  if(o) {
    var dx = move_cmds[action][0],
        dy = move_cmds[action][1];
    o.x += dx;
    o.y += dy;
    redraw(o);
  } else {
    debuglog(1,"update for object "+objid+"?");
  }
}

function canreach(obj,x,y)
{
  var height = world_height(x,y);
  return height >= obj.swim &&
    height < obj.climb;
}

var action_seq=0;
var aqueue = [];
var action_client_state=0;
var action_client;
function action_xhr_handler() {
  if(action_client.readyState == 4) {
    if(action_client.status != 200) {
      log("uh oh, the server dropped some actions, so we're out of sync");
    }
    var ack = eval(action_client.responseText);
    if(!ack[0]) {
      debuglog(1, "whoops, desync.  trying to recover..");
      // reset our sequence and remove any queued actions
      // we'll get an object update on our position
      action_seq = ack[1]-1;
      aqueue = [];
      resync++;
    }
    if(aqueue.length > 0) {
      // more actions already?
      action_client = new XMLHttpRequest();
      action_client.open("POST", "s/a/"+session.token+"/"+
                         (action_seq-aqueue.length+1)+"/"+
                         aqueue.join("/"), true);
      action_client.onreadystatechange = action_xhr_handler;
      aqueue = [];
      action_client.send("");
    } else {
      action_client_state = 0;
    }
  }
}

function send_action(action)
{
  // fire off an XHR to send action to server
  action_seq++;
  if(action_client_state === 0) {
    action_client = new XMLHttpRequest();
    action_client_state = 1;
    action_client.open("POST", "s/a/"+session.token+"/"+action_seq+"/"+action, true);
    action_client.onreadystatechange = action_xhr_handler;
    action_client.send("");
  } else {
    aqueue[aqueue.length] = action;
  }
}

function enemy_at(x,y)
{
  // until i index objects by their position, this is gonna kinda suck!
  for(var id in objs) {
    var o = objs[id];
    // are they here, and are they hostile?
    if(o.x == x && o.y == y && o.h) {
      return true;
    }
  }
  return false;
}

function enemy_adjacent(x,y)
{
  for(var id in objs) {
    var o = objs[id];
    // are they hostile, and are they adjacent?
    if(o.h && Math.abs(o.x-x) <= 1 && Math.abs(o.y-y) <= 1) {
      return true;
    }
  }
  return false;
}

//var _safe_warning_timer;
function safe_move_warning()
{
//  if(_safe_warning_timer)
//    return;
  update_status('[Move would give enemy an opportunity to attack; use the wasd, hjkl, or number pad keys to attack]', 'banner');
//  setTimeout(function(){
//      _safe_warning_timer=undefined;
//    }, 5000);
}

function move_avatar(action, safe)
{
  // can't move until we're in sync with the server
  if(resync>0 || !session.objid || !objs_valid) { return; }
  var o = objs[session.objid];
  var dx = move_cmds[action][0],
      dy = move_cmds[action][1];
  var x = o.x + dx,
      y = o.y + dy;
  if(o.hp === 0) {
    // we are dead.
    return;
  }
  if(enemy_at(x, y)) {
    // melee attack in that direction; do not move
    send_action("M"+action);
    return;
  }
  if(!canreach(o, x, y)) { return; }

  if(safe && enemy_adjacent(x,y)) {
    safe_move_warning();
    return;
  }

  send_action(action);
  o.x = x;
  o.y = y;
  redraw(o);
  update_status(x+", "+y, "banner");
}

var stats_to_show = ['name', 'level', 'xp', 'hp', 'maxhp'];
var prev_stats = {};
var _stats_flash_timer;
var _stat_flash = {};
function update_stats(data)
{
  // holy shit is this tedious
  // someone make this cooler
  var need_to_flash = false;
  if(_stats_flash_timer) {
    clearTimeout(_stats_flash_timer);
    _stats_flash_timer = undefined;
  }
  var s=$('status');
  while(s.childNodes[0]) { s.removeChild(s.childNodes[0]); }
  var t = document.createElement('table');
  t.appendChild(document.createElement('thead'));
  var tbody = document.createElement('tbody');
  for(var i in stats_to_show) {
    var k = stats_to_show[i];
    if(data[k] === undefined) { continue; }
    var tr = document.createElement('tr');
    var td1 = document.createElement('td');
    var td2 = document.createElement('td');
    td1.appendChild(document.createTextNode(k));
    td2.appendChild(document.createTextNode(data[k]));
    if(prev_stats[k]) {
      if(prev_stats[k] < data[k] || _stat_flash[k] == 1) {
        td2.style.backgroundColor = '#0f0';
        _stat_flash[k] = 1;
        need_to_flash = true;
      } 
      if(prev_stats[k] > data[k] || _stat_flash[k] == -1) {
        td2.style.backgroundColor = '#f00';
        _stat_flash[k] = -1;
        need_to_flash = true;
      }
    }
    prev_stats[k] = data[k];
    tr.appendChild(td1);
    tr.appendChild(td2);
    tbody.appendChild(tr);
  }
  t.border = 1;
  t.appendChild(tbody);
  s.appendChild(t);
  if(data.hp === 0){
    // shit, we're dead
    objs[session.objid]._sprite.style.opacity = 0.5;
  }
  if(need_to_flash) {
    _stats_flash_timer = setTimeout(function(){
      update_stats(prev_stats);
      _stat_flash = {};
      _stats_flash_timer=undefined;
    }, 2000);
  }
}

function update_status(msg, node)
{
  var stat = $(node || 'status');
  while(stat.childNodes[0]) { stat.removeChild(stat.childNodes[0]); }
  var s = $('status');
  stat.appendChild(document.createTextNode(msg));
}

function recv_update(txt)
{
  debuglog(1, "recv_update: "+txt);
  var updatelist = eval(txt);
  if(updatelist === undefined) { return; }
  var people_here = [];
  for(var i=0;i<updatelist.length;i++) {
    var update = updatelist[i];
    var objid = update[0];
    if(objid < 0) {
      if(objid == -1) {
        log(update[1]);
      }
      else if(objid == -2) {
        log(update[1], "chat");
      }
    } else {
      var data = update[1];
      var t = typeof(data);
      if(data === undefined) {
        object_remove(objid);
      } else if(t == "string") {
        // update
        object_action(objid, data);
      } else {
        // add/full update
        object_add(objid, data);
        if(objid != session.objid && data.name) {
          if(objs_valid) {
            log(data.name+" entered the screen");
          } else {
            people_here[people_here.length] = data.name;
          }
        }
      }
      if(objid == session.objid && objs[objid].x !== undefined) {
        if(data.x) {
          // this is either a resync or we just switched tiles
          debuglog(2, 'received resync');
          resync = -1;
          redraw(objs[objid]);
        }
        update_stats(objs[objid]);
      }
    }
  }
  if(!objs_valid && objs[session.objid] !== undefined) {
    // we're going to assume this was a full update
    if(people_here.length > 1) {
      var last_person = people_here[people_here.length-1];
      delete people_here[people_here.length-1];
      log(people_here.join(", ")+" and "+last_person+" are here.");
    } else if(people_here.length == 1) {
      log(people_here[0]+" is here.");
    }
    // fixme: move the screen update logic out of redraw and into here
//    update_screen_tile(objs[session.objid].x, objs[session.objid].y);
//  redraw all objects
    objs_valid = true;
  }
}

var nupdates=0;
var downlink_url;
var downlink_client;

var startrequestloop;

function update_xhr_handler() {
  debuglog(3, "update "+nupdates+": rs="+(downlink_client.readyState || "?"));
  if(downlink_client.readyState == 4 && downlink_client.status == 200) {
    recv_update(downlink_client.responseText);
    return startrequestloop();
  } else if (downlink_client.readyState == 4 && downlink_client.status != 200) {
    log("lost connection to downlink!");
// retrying in 2 seconds");
    // try again in two seconds!
//    setTimeout(function() { startrequestloop() }, 2000);
// this doesn't really work
  }
}

startrequestloop = function() {
// for some reason if i don't keep making new downlink_client xmlhttprequests
// the damn thing stops working.
  downlink_client = new XMLHttpRequest();
  debuglog(2, "starting update request "+(++nupdates));
  downlink_client.open("GET", downlink_url, true);
  downlink_client.onreadystatechange = update_xhr_handler;
  downlink_client.send("");
};

var logout_xhr;
function logout()
{
  if(session.token) {
    logout_xhr = new XMLHttpRequest();
    logout_xhr.open("GET", "s/logout/"+session.token, false);
    logout_xhr.send("");
  }
}

var login_xhr;
function login_onreadystate()
{
  if(login_xhr.readyState == 4) {
    if(login_xhr.status == 200) {
      debuglog(1, "login: "+login_xhr.responseText);
      var resp = eval(login_xhr.responseText);
      if(resp[0] == 'ok') {
        session.token = resp[1];
        session.objid = resp[2];
        downlink_url = "s/rcv/"+session.token;
        update_status("Welcome!  Use the arrows or wasd or hjkl keys to move, enter to chat.", "banner");
        startrequestloop();
      } else {
        update_status(resp[1], "banner");
      }
    } else {
      update_status("err connecting: "+login_xhr.status, "banner");
    }
  }
}

function get_charclass()
{
  for(var i=0;i<class_radiobuttons.length;i++) {
    if(class_radiobuttons[i].checked) {
      return i+1;
    }
  }
  return 0;
}

// yeah this isn't secure at all, sorry.
function login_new()
{
  var name = $('charname').value;
  var passwd1 = $('passwd1').value;
  var passwd2 = $('passwd2').value;
  if(passwd1 != passwd2) {
    update_status("Passwords don't match!", "banner");
    return;
  }
  login_xhr = new XMLHttpRequest();
  login_xhr.open("GET", "s/login?new=1&name="+escape(name)+"&class="+get_charclass()+"&passwd="+passwd1, true);
  login_xhr.onreadystatechange = login_onreadystate;
  login_xhr.send("");
  update_status("Logging in...", "banner");
}

function login()
{
  var name = $('charname').value;
  var passwd = $('passwd').value;
  login_xhr = new XMLHttpRequest();
  login_xhr.open("GET", "s/login?name="+escape(name)+"&passwd="+passwd, true);
  login_xhr.onreadystatechange = login_onreadystate;
  login_xhr.send("");
  update_status("Logging in...", "banner");
}

window.onkeypress = function(e)
{
  var k;
  if(window.event) { k=e.keyCode; }
  else { k=e.which; }
  k = String.fromCharCode(k);
  if(k == "\r" && inputline) {
    if(!inputfocus) {
      inputfocus = true;
      // this is ridiculous.  if you focus the inputline from within this
      // handler, in safari you will get the key in the input box.  which is a
      // carriage return.  which kinda fucks this up.
      setTimeout(function() { inputline.focus(); }, 1);
    } else {
      cmd_input();
    }
    return;
  }
  if(inputfocus) { return; }
  if(keymap_move[k]) {
    var action = keymap_move[k];
    move_avatar(action);
  }
};

var keys_down = {};
var arrow_repeat_timer;
function arrow_update()
{
  if(keys_down[38]) {
    if(keys_down[37]) {
      move_avatar("ul", true);
    } else if(keys_down[39]) {
      move_avatar("ur", true);
    } else {
      move_avatar("u", true);
    }
  } else if(keys_down[40]) {
    if(keys_down[37]) {
      move_avatar("dl", true);
    } else if(keys_down[39]) {
      move_avatar("dr", true);
    } else {
      move_avatar("d", true);
    }
  } else if(keys_down[37]) {
    move_avatar("l", true);
  } else if(keys_down[39]) {
    move_avatar("r", true);
  } else {
    if(arrow_repeat_timer) {
      clearTimeout(arrow_repeat_timer);
      arrow_repeat_timer = undefined;
    }
  }
  // repeat while an arrow is down
  if(arrow_repeat_timer) {
    clearTimeout(arrow_repeat_timer);
  }
  arrow_repeat_timer = setTimeout(arrow_update, 120);
}

window.onkeydown = function(e) {
  // detect arrow keys
  if(inputfocus) { return; }
  if(e.keyCode) {
    var wasdown = keys_down[e.keyCode];
    keys_down[e.keyCode] = 1;
    if(!wasdown || !arrow_repeat_timer) {
      arrow_update();
    }
  }
};

window.onkeyup = function(e) {
  if(e.keyCode) {
    delete keys_down[e.keyCode];
    arrow_update();
  }
};

function login_screen()
{
  var classlist_xhr = new XMLHttpRequest();
  classlist_xhr.open("GET", "s/classlist", false);
  classlist_xhr.send("");
  var classlist = eval(classlist_xhr.responseText);
  var t = document.createElement('table');
  t.appendChild(document.createElement('thead'));
  var tb = document.createElement('tbody');
  for(var i=0;i<classlist.length;i++) {
    var icon = document.createElement('img');
    var radiobutton = document.createElement('input');
    radiobutton.type = 'radio';
    radiobutton.name = 'charclass';
    if(i === 0) { radiobutton.checked = true; }
    class_radiobuttons[i] = radiobutton;
    icon.src = classlist[i].avatar;
    tb.appendChild(tr(radiobutton, icon, document.createTextNode(classlist[i].name)));
  }
  t.appendChild(tb);
  $('classlist').appendChild(t);
  $('charname').focus();
}

