--
--	game-tty.occ -- TTY interface to game
--	Copyright (C) 2004 Fred Barnes <frmb@kent.ac.uk>
--
--	This program is free software; you can redistribute it and/or modify
--	it under the terms of the GNU General Public License as published by
--	the Free Software Foundation; either version 2 of the License, or
--	(at your option) any later version.
--
--	This program is distributed in the hope that it will be useful,
--	but WITHOUT ANY WARRANTY; without even the implied warranty of
--	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
--	GNU General Public License for more details.
--
--	You should have received a copy of the GNU General Public License
--	along with this program; if not, write to the Free Software
--	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
--

#INCLUDE "types.inc"
#INCLUDE "config.inc"
#INCLUDE "course.module"
#USE "game-utils"
#USE "game-client"

--{{{  PROC tty.client (IO.LINK? link, CHAN BYTE kyb?, scr!)
PROC tty.client (IO.LINK? link, CHAN BYTE kyb?, scr!)
  --{{{  PROC screen.if (CHAN MOBILE []BYTE display?, out!, CHAN BYTE key.in?, scr!)
  PROC screen.if (CHAN MOBILE []BYTE display?, out!, CHAN BYTE key.in?, scr!)
    INT nlines:
    MOBILE []MOBILE []BYTE lines:
    INT cline:
    [128]BYTE linebuf:
    INT linepos:
    SEQ
      nlines := 22
      lines := MOBILE [nlines]MOBILE []BYTE
      cline := 1
      lines[0] := "newgame TTY interface."

      linepos := 0

      INITIAL BOOL redraw.all IS TRUE:
      INITIAL BOOL redraw.inline IS FALSE:
      WHILE TRUE
        SEQ
          IF
            redraw.all
              SEQ
                erase.screen (scr!)
                SEQ i = 0 FOR nlines
                  SEQ
                    cursor.x.y (1, BYTE (i + 1), scr!)
                    VAL INT i IS ((cline + i ) \ nlines):
                    out.string (lines[i], 0, scr!)
                redraw.inline := TRUE
                cursor.x.y (1, BYTE (nlines + 1), scr!)
                out.string ("---------------------------------------------------------------------", 0, scr!)
                redraw.all := FALSE
            TRUE
              SKIP
          IF
            redraw.inline
              SEQ
                cursor.x.y (1, BYTE (nlines + 2), scr!)
                out.string (">> ", 0, scr!)
                out.string ([linebuf FOR linepos], 0, scr!)
                scr ! #FF
            TRUE
              SKIP

          PRI ALT
            BYTE b:
            kyb ? b
              IF
                ((b = #08) OR (b = #7F)) AND (linepos > 0)
                  SEQ
                    linepos := linepos - 1
                    -- blank here
                    cursor.x.y (BYTE (4 + linepos), BYTE (nlines + 2), scr!)
                    scr ! ' '
                    redraw.inline := TRUE
                ((b = #0D) OR (b = #0A)) AND (linepos > 0)
                  SEQ
                    out ! [linebuf FOR linepos]
                    -- blank here
                    cursor.x.y (4, BYTE (nlines + 2), scr!)
                    erase.eol (scr!)
                    linepos := 0
                    redraw.inline := TRUE
                (b < #20) OR (b = #7F)
                  scr ! #07       -- ASCII BEL
                TRUE
                  SEQ
                    linebuf[linepos] := b
                    linepos := linepos + 1
                    redraw.inline := TRUE
            MOBILE []BYTE tline:
            display ? tline
              SEQ
                lines[cline] := tline
                redraw.all := TRUE
                cline := (cline + 1) \ nlines
  :
  --}}}

  CHAN MOBILE []BYTE preout:
  PAR
    string.owb (link[in]?, preout!)
    screen.if (preout?, link[out]!, kyb?, scr!)
:
--}}}
--{{{  PROC tty.if.encode (CHAN S.CLIENT.IN in?, CHAN MOBILE []BYTE localin?, out!)
PROC tty.if.encode (CHAN S.CLIENT.IN in?, CHAN MOBILE []BYTE localin?, out!)
  INT t.len:
  [256]BYTE t.buf:
  MOBILE []BYTE str:
  SEQ
    INITIAL INT termcount IS 2:
    INITIAL BOOL running IS TRUE:
    WHILE running
      ALT
        localin ? str
          --{{{  self-message
          IF
            str = ##DIEMESSAGE
              IF
                termcount = 1
                  running := FALSE
                TRUE
                  termcount := termcount - 1
            TRUE
              SEQ
                t.len := 0
                buffer.set.tty (t.buf, t.len, 4)
                buffer.set.string (t.buf, t.len, "<-- ")
                buffer.set.string (t.buf, t.len, str)
                buffer.set.tty (t.buf, t.len, 0)
                out ! [t.buf FOR t.len]
          --}}}
        in ? CASE
          genmsg; str
            --{{{  general message
            SEQ
              t.len := 0
              buffer.set.tty (t.buf, t.len, 3)
              buffer.set.string (t.buf, t.len, "--> ")
              IF
                (SIZE str) = 0
                  buffer.set.tty (t.buf, t.len, 0)
                str[0] = '**'
                  SEQ
                    buffer.set.tty (t.buf, t.len, 5)
                    buffer.set.string (t.buf, t.len, [str FROM 1])
                    buffer.set.tty (t.buf, t.len, 0)
                (str[0] = '<') AND (str[1] = '<')
                  SEQ
                    t.len := 0
                    buffer.set.tty (t.buf, t.len, 4)
                    buffer.set.string (t.buf, t.len, "<-- ")
                    buffer.set.string (t.buf, t.len, [str FROM 3])
                    buffer.set.tty (t.buf, t.len, 0)
                str[0] = '+'
                  SEQ
                    buffer.set.tty (t.buf, t.len, 6)
                    buffer.set.string (t.buf, t.len, [str FROM 1])
                    buffer.set.tty (t.buf, t.len, 0)
                TRUE
                  SEQ
                    buffer.set.tty (t.buf, t.len, 0)
                    buffer.set.string (t.buf, t.len, str)
              out ! [t.buf FOR t.len]
            --}}}
          enter.room; str
            --{{{  someone entering the room
            SEQ
              t.len := 0
              buffer.set.tty (t.buf, t.len, 2)
              buffer.set.string (t.buf, t.len, str)
              buffer.set.tty (t.buf, t.len, 0)
              buffer.set.string (t.buf, t.len, " entered the room")
              out ! [t.buf FOR t.len]
            --}}}
          INT nesw:
          leaving.room; str; nesw
            --{{{  someone leaving the room
            SEQ
              t.len := 0
              buffer.set.tty (t.buf, t.len, 2)
              buffer.set.string (t.buf, t.len, str)
              buffer.set.tty (t.buf, t.len, 0)
              buffer.set.string (t.buf, t.len, " left the room (")
              buffer.set.exit (t.buf, t.len, nesw)
              buffer.set.string (t.buf, t.len, ")")
              out ! [t.buf FOR t.len]
            --}}}
          MOBILE []BYTE oname:
          drop.object; str; oname
            --{{{  someone dropping an object
            SEQ
              t.len := 0
              buffer.set.tty (t.buf, t.len, 7)
              buffer.set.string (t.buf, t.len, str)
              buffer.set.tty (t.buf, t.len, 6)
              buffer.set.string (t.buf, t.len, " dropped ")
              buffer.set.string (t.buf, t.len, oname)
              buffer.set.tty (t.buf, t.len, 0)
              out ! [t.buf FOR t.len]
            --}}}
          MOBILE []BYTE pname:
          say.room; pname; str
            --{{{  someone saying something
            SEQ
              t.len := 0
              buffer.set.tty (t.buf, t.len, 1)
              buffer.set.string (t.buf, t.len, pname)
              buffer.set.tty (t.buf, t.len, 0)
              buffer.set.string (t.buf, t.len, " said *"")
              buffer.set.string (t.buf, t.len, str)
              buffer.set.string (t.buf, t.len, "*"")
              out ! [t.buf FOR t.len]
            --}}}
          MOBILE []BYTE pname:
          action.room; pname; str
            --{{{  someone performing an action
            SEQ
              t.len := 0
              buffer.set.tty (t.buf, t.len, 7)
              buffer.set.string (t.buf, t.len, pname)
              buffer.set.tty (t.buf, t.len, 6)
              buffer.set.string (t.buf, t.len, " ")
              buffer.set.string (t.buf, t.len, str)
              buffer.set.tty (t.buf, t.len, 0)
              out ! [t.buf FOR t.len]
            --}}}
          do.logout
            IF
              termcount = 1
                running := FALSE
              TRUE
                termcount := termcount - 1
    out ! ##DIEMESSAGE
:
--}}}
--{{{  PROC tty.if.decode (CHAN MOBILE []BYTE in?, copyout!, CHAN P.CLIENT.OUT out!, VAL BOOL console)
PROC tty.if.decode (CHAN MOBILE []BYTE in?, copyout!, CHAN P.CLIENT.OUT out!, VAL BOOL console)
  INITIAL BOOL running IS TRUE:
  WHILE running
    MOBILE []BYTE str:
    MOBILE []ARG.POS cmdline:
    INT x, y:
    INITIAL BOOL badcmd IS FALSE:
    SEQ
      in ? str
      args.start.end (str, cmdline)
      IF
        (SIZE cmdline) < 0
          badcmd := TRUE
        (SIZE cmdline) = 1
          VAL []BYTE cmd0 IS [str FROM cmdline[0][start] FOR cmdline[0][len]]:
          --{{{  single word commands
          IF
            ((cmd0 = "help") OR (cmd0 = "?")) OR (cmd0 = "h")
              out ! help
            console AND (cmd0 = "halt")
              out ! halt
            (cmd0 = "look") OR (cmd0 = "l")
              out ! look
            (cmd0 = "north") OR (cmd0 = "n")
              out ! north
            (cmd0 = "east") OR (cmd0 = "e")
              out ! east
            (cmd0 = "south") OR (cmd0 = "s")
              out ! south
            (cmd0 = "west") OR (cmd0 = "w")
              out ! west
            ((cmd0 = "inventory") OR (cmd0 = "inv")) OR (cmd0 = "i")
              out ! inv
            ((cmd0 = "logout") OR (cmd0 = "exit")) OR (cmd0 = "quit")
              out ! logout
            (cmd0 = "status") OR (cmd0 = "stat")
              out ! status
            (cmd0 = "unwield") OR (cmd0 = "y")
              out ! unwield
            (cmd0 = ##DIEMESSAGE)
              SEQ
                out ! do.logout
                running := FALSE
            TRUE
              badcmd := TRUE
          --}}}
        (SIZE cmdline) = 0
          SKIP
        VAL []BYTE cmd0 IS [str FROM cmdline[0][start] FOR cmdline[0][len]]:
        --{{{  multi-word commands (free text)
        cmd0 = "say"
          out ! say; [str FROM cmdline[1][start]]
        --}}}
        (SIZE cmdline) = 2
          VAL []BYTE cmd0 IS [str FROM cmdline[0][start] FOR cmdline[0][len]]:
          --{{{  two word commands
          IF
            (cmd0 = "get") OR (cmd0 = "g")
              out ! get; [str FROM cmdline[1][start]]
            (cmd0 = "use") OR (cmd0 = "u")
              out ! use; [str FROM cmdline[1][start]]
            (cmd0 = "eat") OR (cmd0 = "e")
              out ! eat; [str FROM cmdline[1][start]]
            (cmd0 = "drop") OR (cmd0 = "d")
              out ! drop; [str FROM cmdline[1][start]]
            (cmd0 = "examine") OR (cmd0 = "x")
              out ! examine; [str FROM cmdline[1][start]]
            (cmd0 = "wield") OR (cmd0 = "w")
              out ! wield; [str FROM cmdline[1][start]]
            (cmd0 = "attack") OR (cmd0 = "a")
              out ! attack; [str FROM cmdline[1][start]]
            --{{{  some console two-word commands
            console AND (cmd0 = "splinc")
              INT locn:
              BOOL good:
              SEQ
                locn, good := parse.int ([str FROM cmdline[1][start] FOR cmdline[1][len]])
                IF
                  NOT good
                    badcmd := TRUE
                  TRUE
                    out ! splinc; locn
            --}}}
            TRUE
              badcmd := TRUE
          --}}}
        console AND ((SIZE cmdline) > 2)
          VAL []BYTE cmd0 IS [str FROM cmdline[0][start] FOR cmdline[0][len]]:
          --{{{  console multi-word commands
          IF
            (cmd0 = "sproom") AND ((SIZE cmdline) = 3)
              INT locn:
              BOOL good:
              SEQ
                locn, good := parse.int ([str FROM cmdline[2][start] FOR cmdline[2][len]])
                IF
                  NOT good
                    badcmd := TRUE
                  TRUE
                    out ! sproom; [str FROM cmdline[1][start] FOR cmdline[1][len]]; locn
            (cmd0 = "spdoor") AND ((SIZE cmdline) = 4)
              INT locn, target:
              [2]BOOL good:
              SEQ
                locn, good[0] := parse.int ([str FROM cmdline[2][start] FOR cmdline[2][len]])
                target, good[1] := parse.int ([str FROM cmdline[3][start] FOR cmdline[3][len]])
                IF
                  NOT (good[0] AND good[1])
                    badcmd := TRUE
                  TRUE
                    out ! spdoor; [str FROM cmdline[1][start] FOR cmdline[1][len]]; locn; target
            (cmd0 = "spobj") AND ((SIZE cmdline) = 3)
              INT locn:
              BOOL good:
              SEQ
                locn, good := parse.int ([str FROM cmdline[2][start] FOR cmdline[2][len]])
                IF
                  NOT good
                    badcmd := TRUE
                  TRUE
                    out ! spobj; [str FROM cmdline[1][start] FOR cmdline[1][len]]; locn
            (cmd0 = "link") AND ((SIZE cmdline) = 4)
              INT locn, exit, target:
              [3]BOOL good:
              SEQ
                locn, good[0] := parse.int ([str FROM cmdline[1][start] FOR cmdline[1][len]])
                exit, good[1] := parse.int ([str FROM cmdline[2][start] FOR cmdline[2][len]])
                target, good[2] := parse.int ([str FROM cmdline[3][start] FOR cmdline[3][len]])
                IF
                  (NOT ((good[0] AND good[1]) AND good[2])) OR ((exit < 0) OR (exit > 3))
                    badcmd := TRUE
                  TRUE
                    out ! link; locn; exit; target
            (cmd0 = "spbot") AND ((SIZE cmdline) = 7)
              INT locn, tlocn, tsrc:
              [3]BOOL good:
              SEQ
                locn, good[0] := parse.int ([str FROM cmdline[3][start] FOR cmdline[3][len]])
                tlocn, good[1] := parse.int ([str FROM cmdline[4][start] FOR cmdline[4][len]])
                tsrc, good[2] := parse.int ([str FROM cmdline[5][start] FOR cmdline[5][len]])
                IF
                  (NOT ((good[0] AND good[1]) AND good[2])) OR (locn < 0)
                    badcmd := TRUE
                  TRUE
                    out ! spbot; [str FROM cmdline[1][start] FOR cmdline[1][len]];
                                  [str FROM cmdline[2][start] FOR cmdline[2][len]];
                                  locn; tlocn; tsrc;
                                  [str FROM cmdline[6][start] FOR cmdline[6][len]]
            (cmd0 = "spdobj") AND ((SIZE cmdline) = 4)
              INT locn:
              BOOL good:
              SEQ
                locn, good := parse.int ([str FROM cmdline[3][start] FOR cmdline[3][len]])
                IF
                  NOT good
                    badcmd := TRUE
                  TRUE
                    out ! spdobj; [str FROM cmdline[1][start] FOR cmdline[1][len]];
                                  [str FROM cmdline[2][start] FOR cmdline[2][len]]; locn
            TRUE
              badcmd := TRUE
          --}}}
        TRUE
          badcmd := TRUE
      IF
        badcmd AND ((SIZE cmdline) > 0)
          out ! bad.input; str
        NOT badcmd
          copyout ! str
        TRUE
          SKIP
:
--}}}
--{{{  PROC new.tty.client.if (VAL []BYTE name, IO.LINK! io.link, CT.CLIENT? link, SHARED CT.GAMECTRL! to.game, SHARED CT.LINCLINK! to.linc, VAL BOOL console)
PROC new.tty.client.if (VAL []BYTE name, IO.LINK! io.link, CT.CLIENT? link, SHARED CT.GAMECTRL! to.game, SHARED CT.LINCLINK! to.linc, VAL BOOL console)
  CHAN S.CLIENT.IN scplexed:
  CHAN MOBILE []BYTE inject, scmds, icopy:
  CHAN P.CLIENT.OUT cmds:
  [3]CHAN S.CLIENT.IN sc.chans:
  CHAN P.CLIENT.IN pci:
  CHAN P.CLIENT.OUT pco:
  CHAN S.CLIENT.IN cti, lcti:
  
  PAR
    tty.if.encode (scplexed?, icopy?, io.link[in]!)
    mstr.plex ([io.link[out]?, inject?], scmds!, 1)
    tty.if.decode (scmds?, icopy!, cmds!, console)

    handle.cmd (cmds?, sc.chans[0]!, link[out]!, link[in]?, pco!, pci?, to.game, to.linc, console)
    local.if (name, pco?, pci!, cti?, link[s.out]!, sc.chans[1]!)

    if.split (link[s.in]?, sc.chans[2]!, cti!, lcti!)
    if.local.logic.buf (lcti?, inject!)

    sclient.in.plex (sc.chans?, scplexed!)
:
--}}}
--{{{  PROC tty.client.if (VAL []BYTE name, IO.LINK! iolink, SHARED CT.GAMECTRL! to.game, SHARED CT.LINCLINK! to.linc, VAL BOOL do.register)
PROC tty.client.if (VAL []BYTE name, IO.LINK! iolink, SHARED CT.GAMECTRL! to.game, SHARED CT.LINCLINK! to.linc, VAL BOOL do.register)

  CT.CLIENT? link:
  CT.CLIENT! gamelink:

  SEQ
    link, gamelink := MOBILE CT.CLIENT

    IF
      do.register
        CLAIM to.game
          SEQ
            to.game[in] ! register.name; name
            to.game[out] ? CASE
              name.in.use
                STOP
              ok
                SKIP
      TRUE
        SKIP
    --{{{  attach to game
    INITIAL BOOL attached IS FALSE:
    WHILE NOT attached
      MOBILE []BYTE msg:
      SEQ
        iolink[in] ! "connecting to game..."
        CLAIM to.game
          SEQ
            to.game[in] ! attach.client; name; gamelink
            to.game[out] ? CASE
              ok
                attached := TRUE
              cli.error; msg; gamelink
                SKIP
        IF
          NOT attached
            TIMER tim:
            INT t:
            SEQ
              iolink[in] ! msg
              tim ? t
              tim ? AFTER (t PLUS 1000000)
          TRUE
            SKIP
    --}}}
    new.tty.client.if (name, iolink, link, to.game, to.linc, do.register)
:
--}}}

