
################################################################
#
# Parse the command-line arguments.
#
################################################################

import sys,math

def errorExit(msg):
  print msg
  sys.exit(1)

def usage():
  errorExit("Usage: ppython tanks.py GrEnable MazeFile AI1 AI2")

if (len(sys.argv) != 5): usage()
GrEnable  = int(sys.argv[1])
PyMaze    = sys.argv[2]
PyPlayerA = sys.argv[3]
PyPlayerB = sys.argv[4]

################################################################
#
# Import the Maze and the two AIs
#
################################################################

MazeModule = __import__(PyMaze)
PlayerAModule = __import__(PyPlayerA)
PlayerBModule = __import__(PyPlayerB)

################################################################
#
# The Maze class.
#
################################################################

if GrEnable:
  import direct.directbase.DirectStart;
  from pandac.PandaModules import *
  from direct.task import Task

class Maze:

  def setupGraphics(self):
    nodes = []
    for y in range(self.sizeY):
      for x in range(self.sizeX):
	floor = loader.loadModelCopy("art/floor")
	floor.reparentTo(render)
	floor.setPos(x*10,y*10,0)
	nodes.append(floor)
	if (y==self.sizeY-1) and (self.blockedN[y][x]):
	  wall = loader.loadModelCopy("art/swall")
	  wall.reparentTo(render)
	  wall.setPos(x*10,(y+1)*10,0)
	  nodes.append(wall)
	if (x==0) and (self.blockedW[y][x]):
	  wall = loader.loadModelCopy("art/ewall")
	  wall.reparentTo(render)
	  wall.setPos((x-1)*10,y*10,0)
	  nodes.append(wall)
	if (self.blockedS[y][x]):
	  wall = loader.loadModelCopy("art/swall")
	  wall.reparentTo(render)
	  wall.setPos(x*10,y*10,0)
	  nodes.append(wall)
	if (self.blockedE[y][x]):
	  wall = loader.loadModelCopy("art/ewall")
	  wall.reparentTo(render)
	  wall.setPos(x*10,y*10,0)
	  nodes.append(wall)
    self.nodes = nodes

  def calcLineOfSight(self,x1,y1,x2,y2):
    if (x1 != x2):
      if (x1 > x2): (x1,y1,x2,y2) = (x2,y2,x1,y1)
      dx = (x2-x1)
      dy = (y2-y1)
      blockede = self.blockedE
      for i in range(dx):
	x = x1 + i
	k = ((2*i+1)*dy+dx)
	yo = k / (dx*2)
	y = y1 + yo
	if (blockede[y][x]): return(0)
	if (yo*dx*2==k) and (blockede[y-1][x]): return(0)
    if (y1 != y2):
      if (y1 > y2): (x1,y1,x2,y2) = (x2,y2,x1,y1)
      dx = (x2-x1)
      dy = (y2-y1)
      blockedn = self.blockedN
      for i in range(dy):
	y = y1 + i
	k = ((2*i+1)*dx+dy)
	xo = k / (dy*2)
	x = x1 + xo
	if (blockedn[y][x]): return(0)
	if (xo*dy*2==k) and (blockedn[y][x-1]): return(0)
    return(1)

  def generateSightTable(self):
    sizex = self.sizeX
    sizey = self.sizeY
    table = [[[0 for i in range(sizey)] for i in range(sizex)] for i in range(sizey)]
    for y1 in range(sizey):
      for x1 in range(sizex):
	for y2 in range(sizey):
	  line = ""
	  for x2 in range(sizex):
	    if self.calcLineOfSight(x1,y1,x2,y2): line += "Y"
	    else: line += "N"
          table[y1][x1][y2] = line
    self.los = table

  def __init__(self, data):
    self.movelist = ["east","north","west","south"]
    self.looklist = ["look-east","look-north","look-west","look-south"]
    lines = data.split("\n")
    nblanks = lines.count("")
    for i in range(nblanks): lines.remove("")
    sizey = len(lines)/2
    if (sizey < 1): return(0)
    sizex = len(lines[0])/2
    if (sizex < 1): return(0)
    if (len(lines)!=sizey*2+1): errorexit("Bad syntax in Maze data")
    for line in lines:
      if (len(line)!=sizex*2+1): errorexit("Bad syntax in Maze data")
    blockedn = [[0 for i in range(sizex)] for i in range(sizey)]
    blockeds = [[0 for i in range(sizex)] for i in range(sizey)]
    blockede = [[0 for i in range(sizex)] for i in range(sizey)]
    blockedw = [[0 for i in range(sizex)] for i in range(sizey)]
    for y in range(sizey):
      for x in range(sizex):
	if (lines[y*2+0][x*2+0]!="+"): errorexit("Bad syntax in Maze data")
	if (lines[y*2+2][x*2+0]!="+"): errorexit("Bad syntax in Maze data")
	if (lines[y*2+0][x*2+2]!="+"): errorexit("Bad syntax in Maze data")
	if (lines[y*2+2][x*2+2]!="+"): errorexit("Bad syntax in Maze data")
	if (lines[y*2+1][x*2+1]!=" "): errorexit("Bad syntax in Maze data")
	if (lines[y*2+0][x*2+1]=="-"): blockedn[sizey-1-y][x] = 1
	if (lines[y*2+2][x*2+1]=="-"): blockeds[sizey-1-y][x] = 1
	if (lines[y*2+1][x*2+2]=="|"): blockede[sizey-1-y][x] = 1
	if (lines[y*2+1][x*2+0]=="|"): blockedw[sizey-1-y][x] = 1
    self.sizeX = sizex
    self.sizeY = sizey
    self.blockedN = blockedn
    self.blockedS = blockeds
    self.blockedE = blockede
    self.blockedW = blockedw
    self.blocked = [blockede,blockedn,blockedw,blockeds]
    self.centerX = (sizex-1)*5
    self.centerY = (sizey-1)*5
    self.size = max(sizex,sizey)
    self.generateSightTable()
    if (GrEnable): self.setupGraphics()

################################################################
#
# The Tank Class
#
################################################################

class Tank:

  def __init__(self,ai,cr,cg,cb,x,y):
    if (GrEnable):
      self.color = ColorAttrib.makeFlat(Vec4(cr,cg,cb,1.0))
      self.black = ColorAttrib.makeFlat(Vec4(0,0,0,1.0))
      self.blood = ColorAttrib.makeFlat(Vec4(1.0,1.0,1.0,1.0))
      self.tank   = loader.loadModelCopy("art/tank")
      self.turret = loader.loadModelCopy("art/turret")
      self.turret.reparentTo(render)
      self.tank.reparentTo(render)
    self.f = 0
    self.t = 0
    self.x = x
    self.y = y
    self.oldf = 0
    self.oldt = 0
    self.oldx = x
    self.oldy = y
    self.oldcansee = 0
    self.health = 20
    self.unstable = 0
    self.cansee = 0
    self.basetime = 0
    self.ai = ai


  def facing(self,enemy):
    target = self.t
    angle = math.atan2(enemy.y - self.y, enemy.x - self.x)*180.0/math.pi
    while (angle < target - 180): angle += 360
    while (angle > target + 180): angle -= 360
    return (angle >= target-45) and (angle <= target+45.0)


  def update(self,steps,action,enemy,maze):
    self.oldx = self.x
    self.oldy = self.y
    self.oldf = self.f
    self.oldt = self.t
    self.basetime = steps
    if (action=="east") and (maze.blockedE[self.y][self.x]==0):
	self.x += 1
        self.f = 0
    if (action=="north") and (maze.blockedN[self.y][self.x]==0): 
	self.y += 1
        self.f = 90
    if (action=="west") and (maze.blockedW[self.y][self.x]==0):
	self.x -= 1
        self.f = 180
    if (action=="south") and (maze.blockedS[self.y][self.x]==0):
	self.y -= 1
        self.f = 270
    if (action=="look-east"):
	self.t = 0
    if (action=="look-north"):
	self.t = 90
    if (action=="look-west"):
	self.t = 180
    if (action=="look-south"):
	self.t = 270
    if (action=="fire") and (self.unstable==0) and (self.cansee):
	self.t = math.atan2(enemy.y - self.y, enemy.x - self.x)*180.0/math.pi
        enemy.health -= 1
        enemy.unstable = 1
        if (GrEnable):
	  enemy.blood = ColorAttrib.makeFlat(Vec4(1.0,enemy.health*0.05,enemy.health*0.05,1.0))
    if (self.x == enemy.x) and (self.y == enemy.y):
	self.x = self.oldx
	self.y = self.oldy
    if (self.unstable > 0): self.unstable -= 1


  def reposition(self,timestep):
    elapsed = (timestep - self.basetime)*2.0
    stage1 = min(elapsed,1.0)
    stage2 = min(max(elapsed-1.0,0.0),1.0)
    oldf = self.oldf
    oldt = self.oldt
    if (oldf < self.f - 180): oldf+=360
    if (oldt < self.t - 180): oldt+=360
    if (oldf > self.f + 180): oldf-=360
    if (oldt > self.t + 180): oldt-=360
    f = self.f * stage1 + oldf * (1.0-stage1)
    t = self.t * stage1 + oldt * (1.0-stage1)
    x = self.x * stage2 + self.oldx * (1.0-stage2)
    y = self.y * stage2 + self.oldy * (1.0-stage2)
    self.tank.setPos(x*10,y*10,0)
    self.turret.setPos(x*10,y*10,0)
    self.tank.setHpr(f,0,0)
    self.tank.node().setAttrib(self.blood)
    cansee = self.oldcansee
    if (stage1 == 1.0): cansee = self.cansee
    if (cansee): self.turret.node().setAttrib(self.black)
    else:        self.turret.node().setAttrib(self.color)
    if (self.unstable): self.turret.setHpr(t,0,30)
    else: self.turret.setHpr(t,0,0)


################################################################
#
# The Game Class
#
################################################################

class Game:

  def setupGraphics(self):
    camera.setPos(self.maze.centerX-self.maze.size*3,self.maze.centerY-self.maze.size*10,self.maze.size*15)
    camera.lookAt(self.maze.centerX,self.maze.centerY,0)
    base.disableMouse()
    base.setBackgroundColor(Vec4(0))
    lights = LightAttrib.makeAllOff()
    light_a = AmbientLight("ambientLight")
    light_a.setColor(Vec4(0.5,0.5,0.6,1.0))
    lights = lights.addLight(light_a)
    light_d = DirectionalLight("dirLight")
    light_d.setColor(Vec4(1.0,1.0,1.0,1.0))
    light_d.setPoint(Point3(100,-30,50))
    light_d.setDirection(Vec3(-100,30,-50))
    lights = lights.addLight(light_d)
    render.node().setAttrib(lights)
    self.tspeed = 2.0
    taskMgr.add(self.controller, "controller")

  def __init__(self):
    self.steps = 0
    self.maze = Maze(MazeModule.MazeData)
    ai_A = PlayerAModule.TankAI()
    ai_B = PlayerBModule.TankAI()
    sx = self.maze.sizeX - 1
    sy = self.maze.sizeY - 1
    self.tanks = [Tank(ai_A,0.3,1.0,0.3,0 ,0 ),
		  Tank(ai_B,0.3,0.3,1.0,sx,sy)]
    if (GrEnable): self.setupGraphics()

  def singleStep(self):
    steps = self.steps+1
    if (self.tanks[0].health==0): errorExit("winner: 1 "+PyPlayerB)
    if (self.tanks[1].health==0): errorExit("winner: 0 "+PyPlayerA)
    active = self.tanks[steps & 1]
    enemy = self.tanks[1 - (steps & 1)]
    active.oldcansee = active.cansee
    if (self.maze.los[enemy.y][enemy.x][active.y][active.x] == "Y") and (active.facing(enemy)):
      if (enemy.facing(active)==0):
        active.cansee=1
    if (active.cansee):
      action = active.ai.chooseAction(self.maze,active,enemy)
    else:
      action = active.ai.chooseAction(self.maze,active,0)
    active.update(steps,action,enemy,self.maze)
    if (self.maze.los[enemy.y][enemy.x][active.y][active.x] == "Y") and (active.facing(enemy)):
      active.cansee = 1
    else:
      active.cansee = 0
    self.steps = steps
    
  def controller(self,task):
    self.tlast = task.time
    ftimestep = task.time * self.tspeed
    timestep = int(ftimestep)
    while (timestep > self.steps): self.singleStep()
    self.tanks[0].reposition(ftimestep)
    self.tanks[1].reposition(ftimestep)
    return Task.cont

################################################################
#
# Do it.
#
################################################################

game = Game()

if (GrEnable): run()
else:
  while(1): game.singleStep()

