ComputerCraft Archive

battleship

computer networking cc-tweaked github

Description

Treasure disks for CC: Tweaked

Installation

Copy one of these commands into your ComputerCraft terminal:

wget:wget https://raw.githubusercontent.com/cc-tweaked/treasure-disks/master/data/computercraft/lua/treasure/gopher_atl/battleship/battleship.lua battleship
Archive:wget https://cc.shobie.xyz/cc/get/gh-cc-tweaked-treasure-disks-data-computercraft-lua-treasure-gopher-atl-battlesh battleship
Quick Install: wget https://cc.shobie.xyz/cc/get/gh-cc-tweaked-treasure-disks-data-computercraft-lua-treasure-gopher-atl-battlesh battleship

Usage

Run: battleship

Tags

networking

Source

View Original Source

Code Preview

--[[
battleship,

by GopherAtl, 2013

Do whatever you want, just don't judge me by
what a mess this code is.
--]]
local args={...}
local action=args[1]
local opponentID=nil
local openedSide=nil
local opponent=nil
local myName=""
local opponentReady=false
local myTurn
local targetX,targetY
local shipsLeft=5
local oppShipsLeft=5

local originalTerm = term.current()

--bounding box of the target grid
local targetGridBounds={
    minX=16, maxX=25,
    minY=4, maxY=13
  }


local function doColor(text,background)
  term.setTextColor(text)
  term.setBackgroundColor(background)
end

local function doColor_mono(text,background)
  if text==colors.blue or text==colors.red or text==colors.black or text==colors.lime or background==colors.lightGray then
    term.setTextColor(colors.black)
    term.setBackgroundColor(colors.white)
  else
    term.setTextColor(colors.white)
    term.setBackgroundColor(colors.black)
  end
end

local function doScreenColor()
  if term.isColor() then
    doColor(colors.white,colors.lightGray)
  else
    doColor(colors.black,colors.white)
  end
end

local function toGridRef(x,y)
  return string.sub("ABCDEFGHIJ",x,x)..string.sub("1234567890",y,y)
end


if not term.isColor() then
  doColor=doColor_mono
end

local function quit()
  if openedSide then
    rednet.close(openedSide)
  end
  term.redirect( originalTerm )
  term.setCursorPos(term.getSize())
  print()
  error()
end

local foundModem=false
--find modem
for k,v in pairs(redstone.getSides()) do
  if peripheral.getType(v)=="modem" then
    foundModem=true
    if not rednet.isOpen(v) then
      rednet.open(v)
      openedSide=v
    end
    break
  end
end

if not foundModem then
  print("You must have a modem to play!")
  return
end

if action==nil or (action~="join" and action~="host") then
  print("Invalid parameters. Usage:\n> battleship host\nHosts a game, waits for another computer to join\n> battleship join\nLooks for another game to join")
  quit()
end

--get player name
while true do
  doColor(colors.cyan,colors.black)
  write("player name: ")
  doColor(colors.gray,colors.black)
  myName=read()
  if myName=="" then
    doColor(colors.red,colors.black)
    print("You have to give a name!")
  elseif #myName>11 then
    doColor(colors.red,colors.black)
    print("Max name is 11 characters!")
  else
    break
  end
end

if action=="join" then
  print("Attempting to join a game...\n(press q to cancel)")
  while true do
    local retryTimer=os.startTimer(1);
    rednet.broadcast("bs join "..myName);

    while true do
      local event,p1,p2,p3=os.pullEvent();
      if event=="rednet_message" then
        opponent=string.match(p2,"bs accept %s*(.+)%s*")
        if opponent then
          opponentID=p1
          break
        end
      elseif event=="timer" and p1==retryTimer then
        break
      elseif event=="char" and (p1=="q" or p1=="Q") then
        print("Couldn't find an opponent; quitting")
        quit()
      end
    end
    local joined=false

    if opponentID then
      print("Joining game!")
      rednet.send(opponentID,"bs start")
      break
    end
  end
elseif action=="host" then
  print("Waiting for challenger...\n(Press q to cancel)")
  while true do
    while true do
      local event,p1,p2=os.pullEvent()
      if event=="rednet_message" then
        opponent=string.match(p2,"bs join %s*(.+)%s*")        if opponent then
          print("found player, inviting..")
          opponentID=p1
          break
        end
      elseif event=="char" and (p1=="q" or p1=="Q") then
        print("Couldn't find opponent, quitting")
        quit()
      end
    end

    if opponentID then
      rednet.send(opponentID,"bs accept "..myName)
      local timeout=os.startTimer(1)
      while true do
        local event,p1,p2=os.pullEvent()
        if event=="rednet_message" and p2=="bs start" then
          print("player joined!")
          break
        elseif event=="timer" and p1==timeout then
          print("player joined another game. Waiting for another...")
          opponentID=nil
          break
        end
      end

      if opponentID then
        break
      end
    end
  end
end

local ships={
  {pos=nil,dir="h",size=5,name="carrier",hits=0},
  {pos=nil,dir="h",size=4,name="battleship",hits=0},
  {pos=nil,dir="h",size=3,name="cruiser",hits=0},
  {pos=nil,dir="h",size=3,name="submarine",hits=0},
  {pos=nil,dir="h",size=2,name="destroyer",hits=0},
}

local myShotTable={ {1,1,true},{5,5,false} }
local oppShotTable={ }

local myGrid,oppGrid={title=myName},{title=opponent}

--setup grids
for i=1,10 do
  myGrid[i]={}
  oppGrid[i]={}
  for j=1,10 do
    myGrid[i][j]={hit=false,ship=false}
    oppGrid[i][j]={hit=false,ship=false}
  end
end

local function drawShipsToGrid(ships,grid)
  for i=1,#ships do
    local x,y=table.unpack(ships[i].pos)
    local stepX=ships[i].dir=="h" and 1 or 0
    local stepY=stepX==1 and 0 or 1
    for j=1,ships[i].size do
      grid[x][y].ship=i
      x,y=x+stepX,y+stepY
    end
  end
end

local function drawShotToGrid(shot,grid)
  grid[shot[1]][shot[2]].shot=true
  grid[shot[1]][shot[2]].hit=shot[3]
end

local function makeShot(x,y,grid)
  local tile=grid[x][y]
  if tile.shot==true then
    return nil --already shot here!
  end

  local shot={x,y,tile.ship}
  drawShotToGrid(shot,grid)
  if tile.ship then
    ships[tile.ship].hits=ships[tile.ship].hits+1
    if ships[tile.ship].hits==ships[tile.ship].size then
      os.queueEvent("shipsunk",tile.ship)
    end
  end
  return shot
end


local function drawTile(scrX,scrY,tile)
  term.setCursorPos(scrX,scrY)

  if tile.ship then
    if tile.shot then
      doColor(colors.red,colors.gray)
      term.write("@")
    else
      doColor(colors.white,colors.gray)
      term.write("O")
    end
  else
    if tile.hit then
      doColor(colors.red,colors.gray)
      term.write("x")
    elseif tile.shot then
      doColor(colors.white,colors.lightBlue)
      term.write(".")
    else
      doColor(colors.white,colors.lightBlue)
      term.write(" ")
    end
  end
end

local function drawGrid(scrX,scrY,grid)
  doColor(colors.white,colors.black)
  term.setCursorPos(scrX,scrY+1)
  term.write(" ")
  doColor(colors.white,colors.gray)
  term.setCursorPos(scrX,scrY)
  local pad=11-#grid.title
  term.write(string.rep(" ",math.ceil(pad/2))..grid.title..string.rep(" ",math.floor(pad/2)))

  for gx=1,10 do
    term.setTextColor(colors.white)
    term.setBackgroundColor(colors.black)
    term.setCursorPos(scrX+gx,scrY+1)
    term.write(gx==10 and "0" or string.char(string.byte("0")+gx))

    term.setCursorPos(scrX,scrY+gx+1)
    term.write(string.char(string.byte("A")+gx-1))
    for gy=1,10 do
      drawTile(scrX+gx,scrY+gy+1,grid[gx][gy])
    end
  end
  doColor(colors.white,colors.black)
end

function moveTargetIndicator(newX,newY)
  --if x has changed...
  if targetX and targetY then
    drawTile(targetX+targetGridBounds.minX-1,targetY+targetGridBounds.minY-1,oppGrid[targetX][targetY])
  end
  doColor(colors.yellow,colors.lightGray)
  if newX~=targetX then
    --space over old
    if targetX then
      term.setCursorPos(targetGridBounds.minX+targetX-1,targetGridBounds.maxY+1)
      term.write(" ")
      term.setCursorPos(targetGridBounds.minX+targetX-1,targetGridBounds.minY-3)
      term.write(" ")
    end
    --draw new
    term.setCursorPos(targetGridBounds.minX+newX-1,targetGridBounds.maxY+1)
    term.write("^")
    term.setCursorPos(targetGridBounds.minX+newX-1,targetGridBounds.minY-3)
    term.write("v")

    targetX=newX
  end
  if newY~=targetY then
    --space over old
    if targetY then
      term.setCursorPos(targetGridBounds.maxX+1,targetGridBounds.minY+targetY-1)
      term.write(" ")
      term.setCursorPos(targetGridBounds.minX-2,targetGridBounds.minY+targetY-1)
      term.write(" ")
    end
    --draw new
    term.setCursorPos(targetGridBounds.maxX+1,targetGridBounds.minY+newY-1)
    term.write("<")
    term.setCursorPos(targetGridBounds.minX-2,targetGridBounds.minY+newY-1)
    term.write(">")

    targetY=newY
  end
  term.setCursorPos(15,15)
  term.write("Target : "..toGridRef(targetX,targetY))
  --if the target tile is a valid target, draw a "+"
  if not oppGrid[targetX][targetY].shot then
    term.setCursorPos(targetX+targetGridBounds.minX-1,targetY+targetGridBounds.minY-1)
    doColor(colors.yellow,colors.lightBlue)
    term.write("+")
  end
end

local log={}

local termWidth,termHeight=term.getSize()

local logHeight=termHeight-3
local logWidth=termWidth-28

for i=1,logHeight do
  log[i]=""
end

local function printLog()
  doColor(colors.white,colors.black)
  for i=1,logHeight do
    term.setCursorPos(28,1+i)
    local name,line=string.match(log[i],"(<[^>]+> )(.*)")
    if name then
      doColor(colors.lightBlue,colors.black)
      write(name)
      doColor(colors.white,colors.black)
      write(line..string.rep(" ",logWidth-#log[i]))
    else
      write(log[i]..string.rep(" ",logWidth-#log[i]))
    end
  end
end



--shipX/Y are the position of ship on grid; gridX/Y are the offset of the top-left of grid
local function drawShip(size,align,x,y,char)
  local stepX=align=="h" and 1 or 0
  local stepY=stepX==1 and 0 or 1
  for j=1,size do
    term.setCursorPos(x,y)
    term.write(char)
    x,y=x+stepX,y+stepY
  end
end

local function setStatusLine(lineNum,text)
  doScreenColor()
  local pad=math.floor((termWidth-#text)/2)
  term.setCursorPos(1,16+lineNum)
  term.write((" "):rep(pad)..text..(" "):rep(termWidth-#text-pad))
end


doScreenColor()
term.clear()

drawGrid(2,2,myGrid)

setStatusLine(1,"Started game with "..opponent.." at computer #"..(opponentID or "nil"))

local function getShipBounds(ship)
  return {
     minX=ship.pos[1],
     minY=ship.pos[2],
     maxX=ship.pos[1]+(ship.dir=="h" and ship.size-1 or 0),
     maxY=ship.pos[2]+(ship.dir=="v" and ship.size-1 or 0)
   }
end

local function getPointBounds(x,y)
  return {
    minX=x,
    minY=y,
    maxX=x,
    maxY=y,
  }
end

local function boundsIntersect(boundsA,boundsB)
  return not (
      boundsA.minX>boundsB.maxX or
      boundsA.maxX<boundsB.minX or
      boundsA.minY>boundsB.maxY or
      boundsA.maxY<boundsB.minY
    )
end


local function checkShipCollision(shipIndex)
  local myBounds=getShipBounds(ships[shipIndex])
  for i=1,#ships do
    if i~=shipIndex and ships[i].pos then
      if boundsIntersect(myBounds,getShipBounds(ships[i])) then
        return i
      end
    end
  end
  return 0
end



local function randomizeShips()
  for i=1,5 do
    ships[i].pos=nil
  end
  for i=1,5 do
    local ship=ships[i]
    local dir
    local x,y
    repeat
      --random orientation
      dir=math.random(2)==1 and "v" or "h"
      --random position
      x = math.random(dir=="v" and 10 or (10-ship.size))
      y = math.random(dir=="h" and 10 or (10-ship.size))
      ship.pos={x,y}
      ship.dir=dir
    until checkShipCollision(i)==0
  end
end



local function shipPlacement()
  local selection=1
  local collidesWith=0
  local dragging=false
  local moveShip=nil
  local clickedOn=nil
  local clickedAt=nil

  doScreenColor()
  term.setCursorPos(28,3)
  write("use arrows to move ship")
  term.setCursorPos(28,4)
  write("press space to rotate")
  term.setCursorPos(28,5)
  write("tab selects next ship")
  if term.isColor() then
    term.setCursorPos(28,6)
    write("click and drag ships")
    term.setCursorPos(28,7)
    write("right-click ship to")
    term.setCursorPos(28,8)
    write("  rotate")
  end
  term.setCursorPos(28,9)
  write('"r" to randomize ships')
  term.setCursorPos(28,10)
  write('"f" when finished')
  randomizeShips()
  setStatusLine(1,"Arrange your ships on the grid")

  while true do
    --local placed=0
    --draw sea
    doColor(colors.white,colors.lightBlue)
    for i=1,10 do
      term.setCursorPos(3,3+i)
      term.write("          ")
    end
    --draw ships
    for i=1,#ships do
     --draw ship at sea if it's placed
      if ships[i].pos then
        if collidesWith~=0 and (collidesWith==i or selection==i) then
          doColor(selection==i and colors.red or colors.pink,colors.gray)
          drawShip(ships[i].size,ships[i].dir,2+ships[i].pos[1],3+ships[i].pos[2],"@")
        else
          doColor(selection==i and colors.lime or colors.white,colors.gray)
          drawShip(ships[i].size,ships[i].dir,2+ships[i].pos[1],3+ships[i].pos[2],"O")
        end
      end
    end

    local event,p1,p2,p3=os.pullEvent()
    if event=="key" then
      if not dragging then
        if p1==keys.tab then
          if collidesWith==0 then
            selection=(selection%5)+1
          else
            local t=selection
            selection=collidesWith
            collidesWith=t
          end
        elseif p1==keys.up then
          moveShip={0,-1}
        elseif p1==keys.down then
          moveShip={0,1}
        elseif p1==keys.left then
          moveShip={-1,0}
        elseif p1==keys.right then
          moveShip={1,0}
        elseif p1==keys.space then
          moveShip={0,0}
          ships[selection].dir=ships[selection].dir=="h" and "v" or "h"
        elseif p1==keys.f then
          if collidesWith~=0 then
            setStatusLine(2,"You can't finalize with ships overlapping!")
          else
            break
          end
        elseif p1==keys.r then
          randomizeShips();
        end
      end
    elseif event=="mouse_click" then
      clickedOn=nil
      --click event! figure out what we clicked on
      local clickBounds=getPointBounds(p2,p3)
      local clickGridBounds=getPointBounds(p2-2,p3-3)

      for i=1,#ships do
        if ships[i].pos and boundsIntersect(clickGridBounds,getShipBounds(ships[i])) and
           (collidesWith==0 or collidesWith==i or i==selection) then
          --select it
          --if we're switching between the colliding ships, swap selection
          if collidesWith~=0 and i~=selection then
            collidesWith=selection
          end
          --mode="place"
          clickedOn=ships[i]
          clickedOffset={p2-2-ships[i].pos[1],p3-3-ships[i].pos[2]}
          selection=i
          break
        --[[else
          local labelBounds={minX=15,maxX=24,minY=2*i,maxY=1+2*i}
          if boundsIntersect(clickBounds,labelBounds) and
             (collidesWith==0 or collidesWith==i or i==selection) then
            if collidesWith~=0 then
              if i~=selection then
                collidesWith=selection
              end
            else
              mode="select"
            end
            clickedOn=ships[i]
            clickedOffset={0,0}
            selection=i
            if ships[i].pos==nil then
              ships[i].pos={1,1}
              collidesWith=checkShipCollision(selection)
              break
            end
          end--]]
        end
      end
      if not clickedOn and collidesWith==0 and
          boundsIntersect(clickBounds,{minX=15,maxX=22,minY=13,maxY=13}) then
        break
      elseif clickedOn and p1==2 then
        --can't drag from a right-click!
        clickedOn=nil
        if ships[selection].dir=="h" then
          ships[selection].dir="v"
          moveShip={p2-2-ships[selection].pos[1],-(p2-2-ships[selection].pos[1])}
        else
          ships[selection].dir="h"
          moveShip={p3-3-(ships[selection].pos[2]+ships[selection].size-1),p3-3-(ships[selection].pos[2])}
        end
      end
    elseif event=="mouse_drag" and clickedOn~=nil then
      --mode="place"
      moveShip={
         p2-2-clickedOffset[1]-ships[selection].pos[1],
         p3-3-clickedOffset[2]-ships[selection].pos[2]}
    end

    if moveShip then
      local curShip=ships[selection]
      --calc position limits based on ship size and alignment
      local maxX=curShip.dir=="h" and (11-curShip.size) or 10
      local maxY=curShip.dir=="v" and (11-curShip.size) or 10
      --apply move and clamp to limits
      local newPos={
        math.min(math.max(curShip.pos[1]+moveShip[1],1),maxX),
        math.min(math.max(curShip.pos[2]+moveShip[2],1),maxY)
      }
      --place the ship
      ships[selection].pos=newPos
      --check for collisions with other ships

      collidesWith=checkShipCollision(selection)
      moveShip=nil
    end
  end
end

local function displayGameHelp()
  doScreenColor()
  term.setCursorPos(28,3)
  write("arrows to move cursor")
  term.setCursorPos(28,4)
  write("space to fire")
  if term.isColor() then
    term.setCursorPos(28,6)
    write("click on grid to fire")
  end
end

local function hideHelpArea()
  doScreenColor()
  for y=3,13 do
    term.setCursorPos(28,y)
    write(string.rep(" ",32))
  end
end


local function runGame()

  --first, ship placement phase!!
  shipPlacement()

  hideHelpArea()

  --hide the old help, draw the new

  --tell the other guy we're done
  rednet.send(opponentID,"bs ready")
  if not opponentReady then
    setStatusLine(1,"Waiting for opponent to finish placing ships")
    while not opponentReady do
      os.pullEvent()
    end
  end

  --now, play the game
  --draw my final ship positions intto the grid
  drawShipsToGrid(ships,myGrid)


  --if I'm host, flip a coin
  if action=="host" then
    math.randomseed(os.time())
    myTurn=math.floor(100*math.random())%2==0
    rednet.send(opponentID,"bs cointoss "..tostring(not myTurn))
    if myTurn then
      setStatusLine(2,"Your turn, take your shot!")
    else
      setStatusLine(2,"Opponent's turn, waiting...")
    end
  else
    --I joined, wait for coin toss
    setStatusLine(2,"waiting for coin toss...")
    while myTurn==nil do
      os.pullEvent()
    end
  end


  setStatusLine(1,"")
  if myTurn then
    --I won, I go first
    displayGameHelp()
  end

  --draw a target grid
  drawGrid(2,2,myGrid)
  drawGrid(15,2,oppGrid)
  --initialize target indicators
  moveTargetIndicator(5,5)
  --game turn loop
  while true do
    --wait for my turn
    while not myTurn do
      os.pullEvent()
    end
    --my turn!
    while true do
      local e,p1,p2,p3,p4,p5=os.pullEvent()
      if e=="mouse_click" then
        local clickBounds=getPointBounds(p2,p3)
        if boundsIntersect(clickBounds,targetGridBounds) then
          moveTargetIndicator(p2-15,p3-3)
          local shot=makeShot(targetX,targetY,oppGrid)
          if shot then
            --valid shot, tell the other guy
            rednet.send(opponentID,"bs shot "..targetX.." "..targetY)
            break
          end
        end
      elseif e=="char" then
        p1=string.lower(p1)
        if p1>="a" and p1<="j" then
          --row selected
          moveTargetIndicator(targetX,string.byte(p1)-string.byte("a")+1)
        elseif p1>="0" and p1<="9" then
          local t=string.byte(p1)-string.byte("0")
          if t==0 then t=10 end
          moveTargetIndicator(t,targetY)
        end
      elseif e=="key" then
        if p1==keys.enter or p1==keys.space and targetX and targetY then
          local shot=makeShot(targetX,targetY,oppGrid)
          if shot then
            rednet.send(opponentID,"bs shot "..targetX.." "..targetY)
            break
          end
        elseif p1==keys.up then
          moveTargetIndicator(targetX,math.max(targetY-1,1))
        elseif p1==keys.down then
          moveTargetIndicator(targetX,math.min(targetY+1,10))
        elseif p1==keys.left then
          moveTargetIndicator(math.max(targetX-1,1),targetY)
        elseif p1==keys.right then
          moveTargetIndicator(math.min(targetX+1,10),targetY)
        end
      end
    end
    --shot sent, wait for my turn to resolve (top coroutine will switch turns and draw the hit to the grid)
    setStatusLine(2,"Waiting for opponent...")
    while myTurn do
      os.pullEvent()
    end
  end
end

local gameRoutine=coroutine.create(runGame)
--if advanced terminal, default focus to chat, can play with mouse
local inChat=term.isColor()
local savedCursorPos={7,19}

--redirect just to block scroll
local redir={}
for k,v in pairs(originalTerm) do
  if k~="scroll" then
    redir[k]=v
  else
    redir[k]=function() end
  end
end
originalTerm = term.redirect(redir)

--run the game routine once
coroutine.resume(gameRoutine)
--hide cursor
term.setCursorBlink(false)

while true do
  local e,p1,p2,p3,p4,p5=os.pullEventRaw()
  if e=="terminate" then
    quit()
  elseif e=="shipsunk" then
    setStatusLine(1,opponent.." sank your "..ships[p1].name.."!")
    rednet.send(opponentID,"bs sink")
    shipsLeft=shipsLeft-1
    if shipsLeft==1 then
      setStatusLine(3,"You only have 1 ship left!")
    elseif shipsLeft>1 then
      setStatusLine(3,"You have "..shipsLeft.." ships left!")
    else
      rednet.send(opponentID,"bs win")
      setStatusLine(3,"You lost the game!")
      break
    end
  elseif e=="rednet_message" then
    local cmd,args=string.match(p2,"^bs (%S+)%s?(.*)")
    if cmd=="ready" then
      opponentReady=true
      os.queueEvent("kickcoroutine")
    elseif cmd=="cointoss" then
      myTurn=args=="true"
      if myTurn then
        setStatusLine(2,"Your turn, take your shot!")
      else
        setStatusLine(2,"Opponent's turn, waiting...")
      end
      os.queueEvent("kickcoroutine")
    elseif cmd=="shot" then
      if myTurn then
        setStatusLine(3,"What the?! Got a shot but not their turn! Ignoring")
      else
        local tx, ty=string.match(args,"(%d+) (%d+)")
        tx,ty=tonumber(tx),tonumber(ty)
        local tile=myGrid[tx][ty]
        local shot=makeShot(tx,ty,myGrid)
        rednet.send(opponentID,"bs result "..(shot[3] and "hit" or "miss"))
        drawTile(2+tx,3+ty,tile)
        myTurn=true
        os.queueEvent("kickcoroutine")
        displayGameHelp()
        setStatusLine(1,opponent.." fired at "..toGridRef(tx,ty).." and "..(shot[3] and "hit" or "missed"))
        setStatusLine(2,"Your turn, take your shot!")
      end
    elseif cmd=="sink" then
      setStatusLine(1,"You sank one of "..opponent.."'s ships!")
      oppShipsLeft=oppShipsLeft-1
      if oppShipsLeft==0 then
        setStatusLine(2,opponent.." has no ships left!")
      elseif oppShipsLeft==1 then
        setStatusLine(2,"Sink 1 more to win!")
      else
        setStatusLine(2,"They have "..oppShipsLeft.." ships left.")
      end
    elseif cmd=="result" then
      if not myTurn then
        setStatusLine(3,"What the?! Got a shot result but not my turn! Ignoring")
      else
        local tile=oppGrid[targetX][targetY]
        tile.hit=args=="hit"
        drawTile(targetX+15,targetY+3,tile)
        myTurn=false
        doColor(tile.hit and colors.red or colors.white,colors.lightGray)
        term.setCursorPos(17,16)
        term.write(tile.hit and "HIT!" or "MISS")
        setStatusLine(2,"Waiting for opponent...")
        os.queueEvent("kickcoroutine")
      end

    elseif cmd=="win" then
      --we won!
      setStatusLine(3,"You won the game! Congratulations!")
      break
    end
  --everything else goes to gameRoutine
  else
    --all other events go to this routine
    local succ,err=coroutine.resume(gameRoutine,e,p1,p2,p3,p4,p5)
    if not succ then
      print("game coroutine crashed with the following error: "..err)
      quit()
    end

    if coroutine.status(gameRoutine)=="dead" then
      --game over
      break
    end
  end

end

term.setCursorPos(1,19)
term.clearLine()
term.write("  Press any key to continue...")
os.pullEvent("key")
--if a char event was queued following the key event, this will eat it
os.sleep(0)

term.setTextColor(colors.white)
term.setBackgroundColor(colors.black)
term.clear()
quit()
--