import pygame, sys, os, re
from pygame.locals import *

# screen constants & initialization
pygame.init()
pygame.display.set_caption('FIRE IN THE HOLE!!!')
pygame.mouse.set_visible(0)
size = width, height =  640, 480
screen = pygame.display.set_mode(size) # create here so Creator class has a video mode to work with
def load_image(name, colorkey=None):
    fullname = os.path.join('data', name)
    try: image = pygame.image.load(fullname)
    except pygame.error, message:
        print 'Cannot load image:', name
        raise SystemExit, message
    image = image.convert_alpha()
    return image, image.get_rect()
class Block:
    def __init__(self, image): self.img = image
    def draw(self, screen, x, y): screen.blit(self.img,(x,y))
    def is_passable(self): return True
    def is_diggable(self): return True
    def get_facing(self): return 0
    def is_active(self): return False
    def is_goal(self): return False
    def is_trap(self): return False
class EmptyBlock(Block):
    def __init__(self): pass
    def draw(self, screen, x, y): pass
class DirtBlock(Block):
    def is_passable(self): return False
    def is_diggable(self): return True
class WallBlock(Block):
    def is_passable(self): return False
    def is_diggable(self): return False
class GoalBlock(Block):
    def __init__(self, image): Block.__init__(self,image)
    def is_goal(self): return True
class GrassBlock(Block): pass
class TurretBlock(Block):
    def __init__(self, image, facing):
        Block.__init__(self,image)
        if facing == -1: self.fire_img, self.fire_img_rect = load_image("flames_r.png")
        else: self.fire_img, self.fire_img_rect = load_image("flames.png")
        self.facing = facing
        self.active = False
        self.timer = 30
        if self.facing == -1: self.timer += 15
    def is_passable(self): return False
    def is_diggable(self): return False
    def draw(self, screen, x, y):#screen = pygame.display.get_surface()
        screen.blit(self.img,(x,y))
        if self.active:
            if self.facing == -1: screen.blit(self.fire_img,(x-96,y))
            else: screen.blit(self.fire_img,(x+32,y))
        if self.timer == 0:
            self.active = not self.active
            if self.active: self.timer = 30
            else: self.timer = 45
        else: self.timer -= 1
    def get_facing(self): return self.facing
    def is_active(self): return self.active
class TrapBlock(Block):
    def __init__(self,image,image2,number):
        Block.__init__(self,image)
        self.img2 =image2
        self.trapnum = number
        self.triggered = False
        self.counter = 4
    def is_trap(self): return True
    def is_passable(self): return False
    def is_diggable(self):
        if self.triggered: return False
        else: return True
    def draw(self, screen, x, y):
        if self.triggered:
            if self.counter == 0:
                screen.blit(self.img2,(x,y))
                return
            else: self.counter -= 1
        screen.blit(self.img,(x,y))
class Creator:
    def __init__(self):
        self.dirt_img, self.dirt_img_rect = load_image("dirt.png")
        self.turret_img, self.turret_img_rect = load_image("flamethrower.png")
        self.turret_img_r, self.turret_img_rect_r = load_image("flamethrower_r.png")
        self.wall_img, self.wall_img_rect = load_image("brick.png")
        self.goal_img, self.goal_img_rect = load_image("target.png")
        self.grass_img, self.grass_img_rect = load_image("grass.png")
    def __call__(self, value):
        if value == "1": return GrassBlock(self.grass_img)
        elif value == "2": return WallBlock(self.wall_img)
        elif value == "3": return DirtBlock(self.dirt_img)
        elif value == "5": return TurretBlock(self.turret_img,1)
        elif value == "6": return TurretBlock(self.turret_img_r,-1)
        elif value == "7": return GoalBlock(self.goal_img)
        elif int(value) >= 10: return TrapBlock(self.dirt_img,self.wall_img,int(value))
        else: return EmptyBlock() #value == "0" or invalid
create_block = Creator()
class Grid:
    def __init__(self, file_name):
        self.valid = True
        self.move_counter = 0
        self.move_digtime = 0
        self.move_x = 0
        self.move_y = 0
        self.move_direction = -1
        self.still_alive = True
        self.win = False
        self.timer = -1
        self.real_x = 0
        self.real_y = 0
        self.width = 20
        self.height = 15
        self.settimer = 900
        self.rows = []
        # intitialize map
        try: fd = open(os.path.join("data",file_name))
        except IOError:
            self.valid = False
            return
        regread = re.compile(r"\d+") # any number of numeric characters (greedy)
        line = fd.readline()
        data = regread.findall(line) # ignore any amount of whitespace and text in first line
        if len(data) < 2: # first line should have width, height, and (possibly) timer frames
            self.valid = False
            return
        self.width = int(data[0])
        self.height = int(data[1])
        if len(data) >= 3: self.settimer = int(data[2])
        data = []
        for i in range(self.height):
            line = fd.readline()
            data = regread.findall(line) # ignore any amount of whitespace and text in file
            if len(data) < self.width: # if there aren't enough numbers in the line, quit
                self.valid = False
                return
            row = [create_block(data[j]) for j in range(self.width)]
            self.rows.append(row)
    def draw(self, screen):
        y = 240 - self.real_y
        ypos = 0
        for row in self.rows:
            x = 320 - self.real_x
            xpos = 0
            for block in row:
                block.draw(screen, x, y)
                if block.is_active() and block.get_facing() == -1:
                    if ypos == int((self.real_y+16)/32) and  xpos - 3 <= int((self.real_x+16)/32) and int((self.real_x+16)/32) <= xpos:
                        self.still_alive = False
                        return
                if block.is_active() and block.get_facing() == 1:
                    if ypos == int((self.real_y+16)/32) and  xpos <= int((self.real_x+16)/32) and int((self.real_x+16)/32) <= xpos + 3:
                        self.still_alive = False
                        return
                xpos += 1
                x += 32
            ypos += 1
            y += 32
        draw_shroud(screen)
        row = self.rows[0]
        y = 240 - self.real_y
        x = 320 - self.real_x
        for block in row:
            block.draw(screen, x, y)
            x += 32
        if self.timer != -1:
            my_font = pygame.font.Font('data/quartzitalic.ttf',64)
            if (self.timer%30)*2 < 10: screen.blit(my_font.render('%i:0%i' % (self.timer/30, (self.timer%30)*2),True,(0,255,0)),(0,0))
            else: screen.blit(my_font.render('%i:%i' % (self.timer/30, (self.timer%30)*2),True,(0,255,0)),(0,0))
    def update(self):
        if self.timer != -1:
            if self.real_y == 0:
                self.win = True
                return
            elif self.timer == 0:
                self.still_alive = False
                return
            else: self.timer -= 1
        elif self.rows[int(self.real_y/32)][int(self.real_x/32)].is_goal() and self.timer == -1: self.timer = self.settimer
        if self.move_digtime > 1: self.move_digtime -=1
        elif self.move_digtime == 1: # kill block
            self.move_digtime = 0
            if self.rows[int(self.real_y/32+self.move_y/8)][int(self.real_x/32+self.move_x/8)].is_trap():
                for row in self.rows:
                    for curblock in row:
                        if curblock.is_trap() and self.rows[int(self.real_y/32+self.move_y/8)][int(self.real_x/32+self.move_x/8)].trapnum == curblock.trapnum:
                            curblock.triggered = True
            elif self.real_y+self.move_y*4 == 0: self.rows[int(self.real_y/32+self.move_y/8)][int(self.real_x/32+self.move_x/8)] = create_block("1")
            else: self.rows[int(self.real_y/32+self.move_y/8)][int(self.real_x/32+self.move_x/8)] = EmptyBlock()
        elif self.move_counter > 0: # fluid movement
            #(add_x,add_y) = (int(self.move_x/self.move_counter),int(self.move_y/self.move_counter))
            #(self.move_x,self.move_y)=(self.move_x-add_x,self.move_y-add_y)
            (self.real_x,self.real_y)=(self.real_x+self.move_x,self.real_y+self.move_y)
            self.move_counter -=1
        return
    def is_passable(self,x,y): return x >= 0 and x < self.width and y >= 0 and y < self.height and self.rows[y][x].is_passable()
    def is_diggable(self,x,y): return x >= 0 and x < self.width and y >= 0 and y < self.height and self.rows[y][x].is_diggable()
    def move(self,delta_x,delta_y):
        if self.move_counter == 0:
            target_x = int(self.real_x/32) + delta_x
            target_y = int(self.real_y/32) + delta_y
            if self.is_passable(target_x,target_y): (self.move_x,self.move_y,self.move_counter) = (delta_x*8,delta_y*8,4)
            elif self.is_diggable(target_x,target_y): (self.move_x,self.move_y,self.move_counter,self.move_digtime) = (delta_x*8,delta_y*8,4,3)
    def cancel_move(self):
        if(self.move_digtime > 0): (self.move_digtime,self.move_counter) = (0,0)
class Digger:
    def __init__(self):
        self.x = 0
        self.y = 0
        self.img, self.img_rect = load_image("test.png")
        self.grass_img, self.grass_img_rect = load_image("snake_grass.png")
    def draw(self, screen):
        screen.blit(self.img,(320+self.x*32,240+self.y*32))
def lose(screen):
    #print "LOSE"
    lose_img, lose_img_rect = load_image("lose.png")
    screen.blit(lose_img,(0,0))
def win(screen):
    #print "WIN"
    img, img_rect = load_image("win.png")
    screen.blit(img,(0,0))
def intro(screen):
    #print "WIN"
    img, img_rect = load_image("intro.png")
    screen.blit(img,(0,0))
def draw_shroud(screen):
    img, img_rect = load_image("fow.png")
    screen.blit(img,(0,0))
def build_level(level):
    return Grid("Level%s.txt" % level)
class GameManager:
    def __init__(self):
        self.start = False
        self.screen = pygame.display.set_mode(size)
        self.black = 0, 0, 0
        self.fstoggle = False
        self.background, self.background_rect = load_image("black.png")
        self.clock = pygame.time.Clock()
        self.keydown = [False,False,False,False]
        self.timer = 0
        self.score = 0
        self.level = 1
        self.player = Digger()
        self.move_counter = 0
        self.grid = None
        self.skipintro = False
        self.screen.blit(self.background, (0, 0))
        pygame.display.flip()
        pygame.mixer.init()
        pygame.mixer.music.load('data/UndergroundMix.mp3')
        pygame.mixer.music.play(-1)
    def common_events(self,event):
        if event.type == pygame.QUIT: raise SystemExit()
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_ESCAPE: raise SystemExit()
            elif event.key == pygame.K_F1:
                if self.fstoggle: self.screen = pygame.display.set_mode(size)
                else: self.screen = pygame.display.set_mode(size,pygame.FULLSCREEN|pygame.DOUBLEBUF|pygame.HWSURFACE|pygame.NOFRAME)
                self.fstoggle = not self.fstoggle
                return True
        return False
    def loop(self):
        while not self.start and not self.skipintro:
            intro(self.screen)
            pygame.display.flip()
            self.clock.tick(30)
            for event in pygame.event.get():
                if self.common_events(event): pass
                elif event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_c: self.start = True # continue
                    else: (self.level,self.start) = (1,True) # restart
        self.skipintro = False
        self.grid = build_level(self.level)
        if not self.grid.valid: raise SystemExit() # finished the available levels
        while self.grid.still_alive and not self.grid.win:
            for event in pygame.event.get():
                if self.common_events(event): pass
                elif event.type == pygame.KEYDOWN:
                    if event.key >= 273 and event.key <= 276: self.keydown[event.key-273] = True # arrow keys
                elif event.type == pygame.KEYUP:
                    if event.key >= 273 and event.key <= 276:
                        self.keydown[event.key-273] = False # arrow keys
                        self.grid.cancel_move()
            if self.keydown[0]: self.grid.move(0,-1)
            elif self.keydown[1]: self.grid.move(0,1)
            elif self.keydown[2]: self.grid.move(1,0)
            elif self.keydown[3]: self.grid.move(-1,0)
            self.clock.tick(30)
            self.screen.blit(self.background, (0,0))
            self.grid.update()
            self.grid.draw(self.screen)
            self.player.draw(self.screen)
            pygame.display.flip()
        self.keydown = [False,False,False,False] # incase player was holding down a key when they died
        self.start = False
        while not self.start:
            self.clock.tick(30)
            if self.grid.win: win(self.screen)
            else: lose(self.screen)
            for event in pygame.event.get():
                if self.common_events(event): pass
                elif event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_c: (self.start,self.skipintro) = (True,True) # continue
                    else: self.start = True
            pygame.display.flip()
        if self.grid.win: self.level += 1
        else: self.start = False
def main():
    game = GameManager()
    while(1): game.loop()
if __name__ == '__main__': main()
