// Game engine for The Streets of Noir
// Game logic for individual levels should be saved in levelN.js

// The command line
var com

function setcomstyle(vals) {
  att = ["background", "color", "fontFamily", "fontWeight", "textAlign"]
  for (j in vals) com.box.style[att[j]] = vals[j]
}
function setcomout(str) {
  setcomstyle(["black", "lightgray", "sans-serif", "normal", "center"])
  if (typeof(str) != "undefined") com.box.value = str
  com.box.blur()
}
function setcomin(str) {
  setcomstyle(["black", "white", "monospace", "bold", "left"])
  if (typeof(str) != "undefined") com.box.value = str
}
function pausecom(hap) {
  pauseinputs(true)
  com.hapnext = typeof(hap) == "undefined" ? false : hap
  setcomout("Press Enter to continue")
}

function enablebutton(bname, off) {
  var b = document.getElementById(bname)
  b.disabled = off
  b.style.color = off ? "gray" : "lightgray"
  b.style.fontStyle = off ? "italic" : "normal"
}
function pauseinputs(off) {
  com.paused = off
  enablebutton("restart", off)
  enablebutton("save", off)
  enablebutton("load", off || !getcookie("noirgame"))
}

// Whether an event is an Enter keypress, which we treat as special
function isenter(e) {
  return e.which == 13
}

function stripcom(str) {
  r = ""
  a = str.split(" ")
  for (s in a) {
    if (a[s].length) {
      if (r.length) r += " "
      r += a[s].toLowerCase()
    }
  }
  return r
}

// Run on command line keypress (which is really any key press)
function compress(e) {
  if (com.paused) {
    if (isenter(e)) {
      pauseinputs(false)
      setcomin("")
      if (typeof(com.hapnext) == "function") com.hapnext()
    }
    return false
  }
  if (com.waiting) {
    setcomin("")
    com.waiting = false
  }
  if (isenter(e)) {
    com.box.value = stripcom(com.box.value)
    if (com.box.value) parsecom(com.box.value)
    return false
  }
  return true
}


// Output boxes
// These can be updated slowly, to make it clear that there's
//   something going on.
var loc, des, exits

function obox(name) {
  this.name = name
  this.box = document.getElementById(name)
  this.val = ""
  this.ustart = 0
  this.uspeed = 0.4  // number of characters per millisecond
  this.udelay = 20 // milliseconds between update calls
  this.update = function () {
    this.box.value = this.val
  }
  this.slowupdate = function () {
    if (this.box.value == this.val) return
    n = Math.floor(((new Date()).getTime() - this.ustart) * this.uspeed)
    this.box.value = this.val.substr(0, n)
    var thiscopy = this
    setTimeout(function () { thiscopy.slowupdate() }, this.udelay)
  }
  this.set = function (v) {
    if (!v) return
    this.val = v
    this.ustart = (new Date()).getTime()
    this.box.value = ""
    this.slowupdate()
  }
}

// Gamestate - keeps track of all game information
// Room connectivity and events and such should be based only
//   on gamestate
// When the game is saved/loaded, only the gamestate object is preserved
// gamestate.level: current level
// gamestate.at: current location
var gamestate

// The array of rooms - this supports several members which can
// either be member variables or methods (isn't Javascript great?)
// If they're methods, they should take no arguments and return
// an object of whatever type it's replacing, and should only
// reference the gamestate variable, and "this"
// For instance, the first argument (loc) should either be a string
//   or a no-argument function returning a string
var rooms
function room(l, d, e, w, h1, h2) {
  // String naming the room
  this.loc = typeof(l) == "undefined" ? "" : l
  // String describing the room
  this.des = typeof(d) == "undefined" ? "" : d
  // Array of four booleans saying if you can go in that direction
  this.exits = typeof(e) == "undefined" ? [0,0,0,0] : e
  // Array of four ints saying what room you go to
  this.whither = typeof(w) == "undefined" ? [0,0,0,0] : w
  // Okay, these are a little different. If it's false, nothing special
  //   happens when you enter the room. If it's a function, it's executed
  //   when you enter the room.
  // happen1 is executed *before* the loc and description are found.
  // happen2 is executed *after* they're found.
  this.happen1 = typeof(h1) == "undefined" ? false : h1
  this.happen2 = typeof(h2) == "undefined" ? false : h2
}

// Converts array of booleans into a list of possible exits
function estring(ex) {
  e = ""
  dstr = ["north ", "east ", "south ", "west "]
  for (d in dstr) if (ex[d]) e += dstr[d]
  return e ? e : " "
}

function showroom() {
  var r = rooms[gamestate.at]
  loc.set(typeof(r.loc) == "function" ? r.loc() : r.loc)
  d = typeof(r.des) == "function" ? r.des() : r.des
//  d = gamestate.turn + "\n\n" + d
  des.set(d)
  exits.set(estring(typeof(r.exits) == "function" ? r.exits() : r.exits))
}
// Run upon entering a room
function enterroom(r) {
  var r = rooms[gamestate.at]
  if (typeof(r.happen1) == "function") r.happen1()
  showroom()
  if (typeof(r.happen2) == "function") r.happen2()
}


// Lots of game logic happens here
function parsecom(t) {
  setcomin("")
  d = -1
  switch (t) {
  case "restart": case "restart game": case "start over": case "new game": rstart() ; return
  case "save": case "save game": savegame() ; return
  case "load": case "load game": loadgame() ; return
  case "west": case "w": case "go west": d += 1
  case "south": case "s": case "go south": d += 1
  case "east": case "e": case "go east": d += 1
  case "north": case "n": case "go north": d += 1
    break
  default:
    setcomout("That's not in your repertoire of commands")
    com.waiting = true
    return
  }
  if (d >= 0) {
    e = rooms[gamestate.at].exits
    if (typeof(e) == "function") e = e()
    if (!e[d]) {
      setcomout("You can't go in that direction.")
      com.waiting = true
      return
    }
    w = rooms[gamestate.at].whither
    if (typeof(w) == "function") w = w()
    gamestate.at = w[d]
    enterroom()
  }
}

// Run on body load
function startup() {
  loc = new obox("location")
  des = new obox("description")
  exits = new obox("exits")
  com = new Object()
  com.box = document.getElementById("commands")
  com.box.onkeypress = compress
  rooms = new Array()
  gamestate = new Object()
  builddungeon()
  document.onkeydown = function (e) { com.box.focus() ; return true }
  rstart()
}

function getcookie(cname) {
  cs = document.cookie.split('; ')
  for (i in cs) {
    if (cs[i].indexOf(cname + "=") != -1)
      return ((cs[i].split('='))[1])
  }
  return false
}
function savegame() {
  s = []
  for (x in gamestate) {
    delim = typeof(gamestate[x]) == 'number' ? '#' : '$'
    s.push(x + delim + gamestate[x])
  }
  document.cookie = "noirgame=" + s.join("|")
  setcomout("Game saved")
  enablebutton("load", false)
  com.waiting = true
}
function loadgame() {
  s = getcookie("noirgame")
  if (s) {
    s = s.split("|")
    gamestate = new Object()
    for (i in s) {
      if (s[i].indexOf("#") > -1) {
        p = s[i].split("#")
        gamestate[p[0]] = parseInt(p[1])
      } else {
        p = s[i].split("$")
        gamestate[p[0]] = p[1]
      }
    }
    showroom()
    setcomout("Game loaded")
  } else {
    setcomout("No save game found!")
  }
  com.waiting = true
}
// Run whenever game is started or restarted
function rstart() {
  initializestate()
  showroom()
  pauseinputs(false)
  setcomout("Type commands and press Enter")
  com.waiting = true
}


