import pygame, math, random
from pygame import *
from math import *
from random import *

init()
sx, sy = 640, 350
screen = display.set_mode((sx, sy))
screenpos = lambda (x, y): (int(x), int(y))

class trajectory:
  def __init__(self, r, v):
    self.setr(r)
    self.setv(v)
    self.rp = self.x + self.vx, self.y + self.vy
  def setr(self, r):
    self.x, self.y = self.r = r
  def setv(self, v):
    self.vx, self.vy = self.v = v
  def at(self, t):
    return self.x + t * self.vx, self.y + t * self.vy

class projectile:
  def __init__(self, traj, ws = [], ticker = 0):
    self.traj = traj
    self.walls = ws
    self.age = ticker
    self.ticker = ticker  # time since last bounce
    self.setbounce()
    self.checkbounce()
    self.setpos()
    self.r = 3
  def setpos(self):
    self.pos = self.traj.at(self.ticker)
  def setbounce(self):
    self.tbounce, self.ntraj, self.wbounce = None, None, None
    for w in self.walls:
      c = w.catchtraj(self.traj)
      if c and (self.tbounce is None or c[0] < self.tbounce):
        self.tbounce, self.ntraj = c
        self.wbounce = w
  def checkbounce(self):
    while self.tbounce is not None and self.ticker > self.tbounce:
      self.ticker -= self.tbounce
      self.traj = self.ntraj
      self.setbounce()
  def update(self, dt):
    self.ticker += dt
    self.age += dt
    self.checkbounce()
    self.setpos()
  def draw(self, surf):
    draw.circle(surf, (255, 255, 255), screenpos(self.pos), self.r)

class wallseg:
  def __init__(self, p0, p1):
    (self.x0, self.y0), (self.x1, self.y1) = self.p0, self.p1 = p0, p1
    self.dx, self.dy = self.d = self.x1 - self.x0, self.y1 - self.y0
    self.dmag2 = self.dx ** 2 + self.dy ** 2
    self.dmag = sqrt(self.dmag2)
    self.dhatx, self.dhaty = self.dx / self.dmag, self.dy / self.dmag
    self.det = self.x1 * self.y0 - self.y1 * self.x0
    self.p0dhat = self.x0 * self.dhatx + self.y0 * self.dhaty
    self.C, self.S = self.dx / self.dmag, self.dy / self.dmag
    self.C2 = (self.dx ** 2 - self.dy ** 2) / self.dmag2
    self.S2 = 2 * self.dx * self.dy / self.dmag2
  def dpoint(self, (x, y)):
    num = self.dx * (self.y1 - y) - self.dy * (self.x1 - x)
    return num / self.dmag
  def bounce(self, (x, y)):
    return self.C2 * x + self.S2 * y, -self.C2 * y + self.S2 * x
  def catchtraj(self, t):
    dp = self.dpoint(t.r)
    if dp < 0: return None
    dp2 = self.dpoint(t.rp)
    if dp2 >= dp: return None
    time = dp / (dp - dp2)
    ax, ay = t.at(time)
    k = self.dhatx * ax + self.dhaty * ay - self.p0dhat
    if k < 0 or k >= self.dmag: return None
    return time, trajectory((ax, ay), self.bounce(t.v))
#    den = t.vx * self.dy - t.vy * self.dx
#    num = self.det + t.rx * self.dy - t.ry * self.dx
  def dtosprite(self, s, dmax2 = None):
    dx, dy = s.px - self.x0, s.py - self.y0
    tdx = dx * self.C + dy * self.S
    if tdx > self.dmag: return None
    if tdx < 0:
      d2 = dx ** 2 + dy ** 2
      if dmax2 is not None and d2 > dmax2: return None
      return sqrt(d2)
    return abs(dy * self.C - dx * self.S)
  def scootsprite(self, s, d):
    dx, dy = s.px - self.x0, s.py - self.y0
    tdx = dx * self.C + dy * self.S
    if tdx > self.dmag: return None
    if tdx < 0:
      dist = sqrt(dx ** 2 + dy ** 2)
      return d * dx / dist, d * dy / dist
    return d * self.dhaty, -d * self.dhatx
  def draw(self, surf):
    draw.line(surf, (255, 255, 255), self.p0, self.p1)
    draw.circle(surf, (255, 0, 255), screenpos(self.p0), 4)

class wizard:
  def __init__(self, pos):
    self.px, self.py = self.pos = pos
    self.castcount = 0
    self.pursuit = None
    self.v = 200.
    self.r = 10
    self.harm = 0
  def shift(self, (dx, dy)):
#    if self.harm: return False    # Fix so you can be moved even if you're hurt.
    self.px += dx
    self.py += dy
    self.pos = self.px, self.py
  def setpursuit(self, p):
    self.pursuit = p
  def update(self, dt):
    if not self.cancast():
      self.castcount -= dt
      if self.castcount < 0: self.castcount = 0
    if not self.harm and self.pursuit:
      d = self.v * dt
      dx, dy = self.pursuit[0] - self.px, self.pursuit[1] - self.py
      dist = sqrt(dx ** 2 + dy ** 2)
      if dist < d:
        self.px, self.py = self.pos = self.pursuit
        self.pursuit = None
      else:
        self.shift((dx * d / dist, dy * d / dist))
    if self.harm:
      self.harm = max(self.harm - 500. * dt, 0)
  def cancast(self):
    return self.castcount == 0
  def draw(self, surf):
    draw.circle(surf, (255, int(255 - self.harm), 0), screenpos(self.pos), self.r)
  def trajtoward(self, (x, y), v):
    dx, dy = x - self.px, y - self.py
    if dx == 0 and dy == 0: dy = 1
    d = sqrt(dx ** 2 + dy ** 2)
    return trajectory(self.pos, (dx * v / d, dy * v / d))

def collide(thing1, thing2):
  (x1, y1), (x2, y2) = thing1.pos, thing2.pos
  r = thing1.r + thing2.r
  dx, dy = x2 - x1, y2 - y1
  if abs(dx) > r or abs(dy) > r: return False
  return dx ** 2 + dy ** 2 < r ** 2

rpos = lambda: (randint(0, sx - 1), randint(0, sy - 1))
polars = [(uniform(0, 2*pi), uniform(1./3, 1./2)) for j in range(14)]
polars.sort()
torect = lambda r, a: (sx * (1./2 + r * cos(a)), sy * (1/2. - r * sin(a)))
ps = [torect(r, a) for a, r in polars]
ws = [wallseg(ps[j], ps[(j+1)%14]) for j in range(14)]
projs = []

rintpoint = lambda: torect(1/4., uniform(0, 2*pi))

you = wizard((sx/2, sy*2/3))
oz = wizard((sx/2, sy/3))
hrad = 0
escaping = False
clock = time.Clock()
traj = None
while not escaping:
  clock.tick(60)
  dt = clock.get_time() / 1000.
  e = event.get()
  kdowns = [x.key for x in e if x.type == KEYDOWN]
  if K_ESCAPE in kdowns: escaping = True
  if K_SPACE in kdowns: px, py = rpos()
  for x in e:
    if x.type == MOUSEBUTTONDOWN and you.cancast():
      traj = you.trajtoward(x.pos, 400.)
      projs.append(projectile(traj, ws))
      projs[-1].update(0.08)
      you.castcount = 1.
  kpress = key.get_pressed()

  dwalk = dt * 200.
  if kpress[K_UP]: you.shift((0, -dwalk))
  if kpress[K_DOWN]: you.shift((0, dwalk))
  if kpress[K_LEFT]: you.shift((-dwalk, 0))
  if kpress[K_RIGHT]: you.shift((dwalk, 0))


  screen.fill((0, 0, 0))
  for j in reversed(range(len(projs))):
    projs[j].update(dt)
    projs[j].draw(screen)
    if projs[j].age > 10.:
      del projs[j]
    elif collide(projs[j], oz):
      del projs[j]
      oz.harm = 255.
    elif collide(projs[j], you):
      del projs[j]
      you.harm = 255.

  if oz.pursuit is None: oz.setpursuit(rintpoint())
  if oz.cancast():
    traj = oz.trajtoward((you.px, you.py), 400.)
    projs.append(projectile(traj, ws))
    projs[-1].update(0.08)
    oz.castcount = 1.
  for wiz in (you, oz):
    wiz.update(dt)
    tocheck = True
    while tocheck:
      dmin = None
      for w in ws:
        d = w.dtosprite(wiz)
        if d is not None and (dmin is None or d < dmin):
          dmin, wmin = d, w
      tocheck = dmin is not None and dmin < wiz.r
      if tocheck:
        d = 1.001 * wiz.r - dmin
        s = wmin.scootsprite(wiz, d)
        wiz.shift(s)
        wiz.pursuit = None
    wiz.draw(screen)
  for w in ws: w.draw(screen)
  pygame.display.flip()
quit()
print clock.get_fps()



