pymissile / battle.py

import pygame
import pymunk
import random
import operator
from itertools import product, starmap, chain, repeat
from math import sqrt

from scene import Scene
from constants import *
from vector import *
from physic import space, to_pymunk, to_pygame
from sound import *
from resource import render_cached
from art import get_art, build_surface
from actor import Actor

class GameState(object):
  
  def __init__(self):
    self.level = 1
    self.cities = [1, 1, 1, 1, 1, 1]
    self.silos = [10, 10, 10]
    self.points = 0
    self.time_to_go = 20
    self.spawn = True
  
  def next_level(self):
    self.level += 1
    self.time_to_go = 10 + self.level * 10
    self.spawn = True
    
  def tick(self):
    if self.time_to_go >= 1:
      self.time_to_go -= 1
    else:
      self.spawn = False

def draw_alpha_circle(screen, color, alpha, r, position):
    surface = pygame.surface.Surface((r*2, r*2), pygame.SRCALPHA, 32)
    pygame.draw.circle(surface, color + (alpha,), (r, r), r)
    screen.blit(surface, position)

class HUD(Actor):
  
  def __init__(self, **kwargs):
    super(HUD, self).__init__(**kwargs)

  def draw(self, screen):
    text = 'LEVEL: %i POINTS: %i TIME LEFT: %i ' % (self.scene._state.level, self.scene._state.points, self.scene._state.time_to_go)
    text_surf = render_cached(text=text, color = DINGLEY)
    screen.blit(text_surf, (10, 10))

class Points(Actor):
  
  def __init__(self, **kwargs):
    super(Points, self).__init__(**kwargs)
    self._pos = kwargs['start_pos']
    self._points = kwargs['points']
    self._alpha = 255
    self.scene._state.points += self._points
    
  def update(self):
    self._pos = sub(self._pos, (0, 4))
    self._decrease_alpha(8)
    if self._alpha == 0:
      self.die()
    
  def draw(self, screen):
    surf = render_cached(text='+ %i' % (self._points), color=DINGLEY + (self._alpha,), drop_shadow=False)
    screen.blit(surf, self._pos)
    
class Explosion(Actor):
  
  def __init__(self, speed=2, size=40, shrapnels=True, kills=False, **kwargs):
    super(Explosion, self).__init__(**kwargs)
    self._start = kwargs['start_pos']
    self._radius = 0
    self._max_radius = size
    self._speed = speed
    self._alpha = 0
    self._kills = kills
    self._killed = 0
    
    if shrapnels:
      l = [1,1,1,1,1,1,1,1,2,2,2,3,3]
      for _ in xrange(20):
        Shrapnel(scene=self.scene, start_pos=self._start, size=random.choice(l))

  def _in_range(self, rocket):
    x1, y1 = self._start
    x2, y2 = rocket._current
    return abs(sqrt((x2 - x1)**2 + (y2 - y1)**2)) <= self._radius
          
  def update(self):
    self._radius += self._speed

    if self._kills:
      rockets = (a for a in self.scene._actors if isinstance(a, Rocket) and self._in_range(a))
      for r in rockets:
        r.die()
        self._killed += 1
        Points(scene=self.scene, start_pos=r._current, points=sum(xrange(self._killed+1))*100)
    
    self._alpha = 255 - int(1.0 * self._radius / self._max_radius * 255)
    if self._radius >= self._max_radius:
      self.die()
      
  def draw(self, screen):
    final_position = map(sum, zip(self._start, (-self._radius, -self._radius)))
    draw_alpha_circle(screen, HIGHLAND, self._alpha, self._radius, final_position)

class Rocket(Actor):
  
  def __init__(self, speed=1, target=None, **kwargs):
    super(Rocket, self).__init__(**kwargs)
    self._start = kwargs['start_pos']
    self._current = kwargs['start_pos']
    self._vector = mul_scalar(kwargs['vector'], speed*0.5)
    self._target = magnitude(sub(target, self._start)) if target else None
    self._moving = True
    self.z = -1

  def update(self):
    if not self._moving: return
    
    self._current = [e for e in add(self._current, self._vector)]
    c = map(int, self._current)
    explode = False

    if self._target and magnitude(sub(self._current, self._start)) > self._target:
        explode = True
    if self._current[1] >= SCREEN_HEIGHT:
        explode = True
        
    for a in self.scene._actors:
      if isinstance(a, Building):
        mask = pygame.mask.from_surface(a._surf)
        fixed = sub(c, a.pos)
        try: 
          if mask.get_at(fixed): 
            explode = True
            a.hit()
        except IndexError: pass
        
    if explode:
      self._moving = False
      play_explosion()
      Explosion(start_pos=self._current, speed=18, size=300, shrapnels=False, scene=self.scene)
      Explosion(start_pos=self._current, speed=3, size=40, scene=self.scene, kills=True)
      self.die()
      
  def draw(self, screen):
    pygame.draw.aaline(screen, DINGLEY, self._start, self._current)
    x, y = map(int, self._current)
    for p in starmap(lambda a,b: [x+a, y+b], product((-1, 0, 1), (-1, 0, 1))):
      screen.set_at(p, RED)

r = random.randint
rc = lambda: random.choice(GREENS)

class Shrapnel(Actor):
  
  def __init__(self, mass=1, force=None, **kwargs):
    super(Shrapnel, self).__init__(**kwargs)
    self._color = rc()
    self._current = kwargs['start_pos']
    self.radius = kwargs['size']
    inertia = pymunk.moment_for_circle(mass, 0, self.radius)
    self.body = pymunk.Body(mass, inertia)
    self.body.position = to_pymunk(self._current)
    
    shape = pymunk.Circle(self.body, self.radius)
    space.add(self.body, shape)
    self.shape = shape
    if force:
      self.body.apply_force(force)
    else:
      self.body.apply_impulse((r(-200, 201), r(-75, 301)))

  def draw(self, screen):
    final_position = to_pygame(self.body.position)
    draw_alpha_circle(screen, self._color, self._alpha, int(self.radius), final_position)

  def update(self):
    self._decrease_alpha(3)
    if self.body.position.y < 0 or self._alpha == 0:
      space.remove(self.shape, self.body)
      self.die()

class Building(Actor):
  
  def __init__(self, **kwargs):
    super(Building, self).__init__(**kwargs)
    self._inx = kwargs['inx']
    self._is_hit = False
    self._hit_img = get_art(self.__class__.__name__.lower() + '_ruin')
    self._img = get_art(self.__class__.__name__.lower())
    self._surf = build_surface(self.scene._game.tick, self._img)
  
  def hit(self):
    self._is_hit = True
    self._on_hit()
  
  def _on_hit(self):
    pass
  
  @property
  def is_hit(self):
    return self._is_hit
    
  @property
  def pos(self):
    return self.inx_to_center()- self._surf.get_width()/2, SCREEN_HEIGHT - self._surf.get_height()
  
  def inx_to_center(self):
    raise Exception()
  
  @property
  def to_hit_pos(self):
    pos = self.inx_to_center(), SCREEN_HEIGHT - self._surf.get_height()
    return pos

  def draw(self, screen):
    self._surf = build_surface(self.scene._game.tick, self._hit_img if self._is_hit else self._img)
    screen.blit(self._surf, self.pos)

class City(Building):
  
  def __init__(self, **kwargs):
    super(City, self).__init__(**kwargs)
    
  def inx_to_center(self):
    return (SCREEN_WIDTH / 10) * (self._inx + (2 if self._inx <= 2 else 3))
  
  def _on_hit(self):
    self.scene._state.cities[self._inx] = -1
  
class Silo(Building):
  
  def __init__(self, **kwargs):
    super(Silo, self).__init__(**kwargs)
    self.rockets = 10
    
  def inx_to_center(self):
    return (SCREEN_WIDTH / 10) * (self._inx * 4 + 1)
  
  def _on_hit(self):
    self.scene._state.silos[self._inx] = -1
  
  @property
  def firing_vetor(self):
    return self._get_firing_vetor(self._inx)
  
  def _get_firing_vetor(self, inx):
    mpos = pygame.mouse.get_pos()
    v = normalize(sub(mpos, (self.inx_to_center(), SCREEN_HEIGHT)))
    return v
      
  def draw(self, screen):
    if not self.is_hit and self.rockets:
      v = [e*30 for e in self._get_firing_vetor(self._inx)]
      s = self.inx_to_center(), SCREEN_HEIGHT
      pygame.draw.line(screen, RED, s, add(s, v), 2)
    super(Silo, self).draw(screen)
      
  def fire(self):
    if not self.rockets or self.is_hit: return
    self.rockets -= 1
    self.scene._state.silos[self._inx] -= 1
  
    v = [e*30 for e in self._get_firing_vetor(self._inx)]
    s = (self.inx_to_center(), SCREEN_HEIGHT)

    play_kick()

    Rocket(start_pos=add(s, v), 
           target=pygame.mouse.get_pos(), 
           vector=self.firing_vetor, 
           speed=25,
           scene=self.scene)

class Battle(Scene):
  
  key = 'battle'
  
  def __init__(self, game):
    super(Battle, self).__init__(game)
    self._state = GameState()
    self._event_handler = {pygame.MOUSEBUTTONUP: self._fire_rocket,
                           pygame.KEYUP: self._handle_key}
    self._background = pygame.transform.scale(pygame.image.load('back.jpg'), (SCREEN_WIDTH, SCREEN_HEIGHT)).convert()
    self._silos = {}
    self._cities = {}
    self._initialized = False
    self._enemy_timeout = r(10, 300)
    self.paused = False
    HUD(scene=self)
  
  def _handle_key(self, event):
    if event.key == pygame.K_SPACE:
      self.paused = not self.paused
    
  def enter(self, **kwargs):
    if 'backnav' in kwargs: 
      for s in self._silos:
        if not self._silos[s].is_hit:
          self._silos[s].rockets += 10
    else:
      if self._initialized: return
      for inx, city in enumerate(self._state.cities):
        self._cities[inx] = City(scene=self, inx=inx)
      for inx, silo in enumerate(self._state.silos):
        self._silos[inx] = Silo(scene=self, inx=inx)
      self._initialized = True

  def _fire_rocket(self, event):
    if self.paused: return 
    n = event.button -1
    if n not in (0, 1, 2): return
    silo = self._silos[n]
    silo.fire()
  
  def _check_spawn_missile(self):
    if not self._state.spawn: return
    self._enemy_timeout -= 1
    if not self._enemy_timeout:
      self._enemy_timeout = r(10, 300)
      
      pos = random.randrange(0, SCREEN_WIDTH + 1), 0
      target = random.choice(list(self._cities.values()) + list(self._silos.values()))

      ro = Rocket(start_pos=pos, 
                 speed=5,
                 vector=normalize(sub(target.to_hit_pos, pos)), 
                 scene=self)
  @property
  def _level_completed(self):
    return not self._state.spawn and not any(isinstance(a, Rocket) for a in self._actors)
    
  def update_state(self):
    if self.paused: return
    super(Battle, self).update_state()
    self._check_spawn_missile()
    if not self._game.tick % FPS: self._state.tick()
    if self._level_completed:
      self._game.navigate('intermediate', state=self._state, copy=self._game._screen.copy())
      
    space.step(1/50.0)
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.