# -*- coding: utf-8 -*-
__author__ = 'Carlos'

from PPlay.window import *
from PPlay.sprite import *
import random
import math
random.seed()

# INICIALIZAÇÃO DO JOGO

# Cria a janela do programa.
janela = Window(600, 600)
janela.set_title("Protótipo - Mecânica Básica do Jogo")

# Temos 8 sprites. 4 do personagem andando e 4 parados. No game loop vamos desenhar apenas um deles,
# mas os sprites serão pré-carregados apenas uma vez, antes do game loop.
lista_sprites_heroi = []
#Sprites de 0 a 3: personagem parado.
for contador in range(4):
    sprite = Sprite("hero" + str(contador) + "_1.png", 1)
    sprite.set_total_duration(500)
    lista_sprites_heroi.append(sprite)
# Sprites de 4 a 7: personagem andando.
for contador in range(4):
    sprite = Sprite("hero" + str(contador) + ".png", 4)
    sprite.set_total_duration(500)
    lista_sprites_heroi.append(sprite)

# Pré-carrega sprites dos monstros
sprite_monstro = Sprite("monster.png", 2)
sprite_monstro.set_total_duration(500)

# Pré-carrega sprites de tiles
lista_sprites_terreno = []
sprite = Sprite("cave.png", 1)
lista_sprites_terreno.append(sprite)
TERRENO_TILE_PAREDE_0 = 0
sprite = Sprite("cave2.png", 1)
lista_sprites_terreno.append(sprite)
TERRENO_TILE_PAREDE_1 = 1
sprite = Sprite("cave_floor.png", 1)
lista_sprites_terreno.append(sprite)
TERRENO_TILE_CHAO_0 = 2
sprite = Sprite("cave_floor2.png", 1)
lista_sprites_terreno.append(sprite)
TERRENO_TILE_CHAO_1 = 3
sprite = Sprite("vulcan.png", 3)
sprite.set_total_duration(1000)
lista_sprites_terreno.append(sprite)
TERRENO_TILE_GERADOR = 4
sprite = Sprite("bomb.png", 5)
sprite.set_total_duration(2000)
lista_sprites_terreno.append(sprite)
TERRENO_TILE_BOMBA = 5
sprite = Sprite("boom.png", 3)
sprite.set_total_duration(2000)
lista_sprites_terreno.append(sprite)
TERRENO_TILE_BOOM = 6
sprite = Sprite("cave_floor.png", 1)
lista_sprites_terreno.append(sprite)
TERRENO_TILE_SAIDA = 7


# Propriedades iniciais do personagem.
heroi_velocidade_x = 8
heroi_velocidade_y = 8
heroi_direcao = 0
heroi_movimento = 0
heroi_colidiu = 0
# variuável utilizada para otimizar a instância do sprite atual em lista_sprites_heroi.
sprite_atual = 0

# Associar uma variável à instância de teclado contida em Window
teclado = Window.get_keyboard()  # Função da CLASSE e não da instância! W maiúsculo!

# Variáveis de tempo.
tempo_de_pausa = 0
tempo_de_jogo = 0
inicio_de_jogo = 0
jogo_pausado = 0
pausa_solicitada = 0
num_segundos = 0
tempo_minutos = "0"
tempo_segundos = "0"

# Constantes de mapa e renderização
tamanho_tile_x = 54
tamanho_tile_y = 42


# Estados de jogo
ESTADO_PREPARANDO_JOGO = 0
ESTADO_JOGANDO = 1
ESTADO_TELA_VITORIA = 2
ESTADO_TELA_DERROTA = 3

estado_atual = ESTADO_PREPARANDO_JOGO


# GAME_LOOP
while(True):
    if estado_atual == ESTADO_PREPARANDO_JOGO:
        # Reseta variáveis
        heroi_direcao = 0
        heroi_movimento = 0
        sprite_atual = 0
        tempo_de_pausa = 0
        tempo_de_jogo = 0
        jogo_pausado = 0
        pausa_solicitada = 0
        num_segundos = 0
        inicio_de_jogo = janela.curr_time

        # GERADOR DE MAPAS ALEATÓRIOS

        # Inicialização do terreno. Todas as tiles são inicialmente o chão padrão.
        tamanho_do_mapa = 60
        terreno = [[TERRENO_TILE_CHAO_0 for x in range(tamanho_do_mapa)] for y in range(tamanho_do_mapa)]
        terreno_preenchido = [[0 for x in range(tamanho_do_mapa)] for y in range(tamanho_do_mapa)]
        metade_mapa = int(tamanho_do_mapa / 2)
        # Insere os geradores (eles ficam parados gerando monstrinhos que andam na direção do herói)
        num_raiz_de_geradores = 2
        passo = int(tamanho_do_mapa / (num_raiz_de_geradores + 1))
        x = passo
        y = 0
        i = 0
        j = 0
        while x < tamanho_do_mapa:
            y = passo
            while y < tamanho_do_mapa:
                terreno[x][y] = TERRENO_TILE_GERADOR
                # Geramos um terreno livre em volta do gerador para garantir que os monstrinhos sejam gerados sem
                # interferências.
                i = x - 1
                while i <= (x + 1):
                    j = y - 1
                    while j <= (y + 1):
                        terreno_preenchido[i][j] = 1
                        j += 1
                    i += 1
                y += passo
            x += passo
        # Em seguida, faremos barreiras das quatro bordas do terreno. Nessas 4 bordas, a gente preenche cada uma com
        # um polígono em que duas faces do canto do mapa são retas e a outra é uma espécie de curva.
        #
        # Borda Noroeste
        borda_esquerda = 0
        borda_direita = int(tamanho_do_mapa * 0.25)
        borda_cima = 0
        borda_baixo = int(tamanho_do_mapa * 0.25)
        x = borda_esquerda
        y = borda_baixo
        while x <= borda_direita:
            preenchimento_y = y
            while preenchimento_y >= borda_cima:
                if terreno_preenchido[x][preenchimento_y] == 0:
                    terreno[x][preenchimento_y] = TERRENO_TILE_PAREDE_0
                    terreno_preenchido[x][preenchimento_y] = 1
                preenchimento_y -= 1
            fator_de_curva = random.randint(0,3)
            if (y < (borda_cima + fator_de_curva)):
                y = borda_cima
            else:
                y -= fator_de_curva
            x += 1
        # Borda Nordeste
        borda_esquerda = int(tamanho_do_mapa * 0.75)
        borda_direita = tamanho_do_mapa - 1
        borda_cima = 0
        borda_baixo = int(tamanho_do_mapa * 0.25)
        x = borda_esquerda
        y = borda_cima
        while x <= borda_direita:
            preenchimento_y = y
            while preenchimento_y >= borda_cima:
                if terreno_preenchido[x][preenchimento_y] == 0:
                    terreno[x][preenchimento_y] = TERRENO_TILE_PAREDE_0
                    terreno_preenchido[x][preenchimento_y] = 1
                preenchimento_y -= 1
            fator_de_curva = random.randint(0,3)
            if (y > (borda_baixo - fator_de_curva)):
                y = borda_baixo
            else:
                y += fator_de_curva
            x += 1
        # Borda Sudoeste
        borda_esquerda = 0
        borda_direita = int(tamanho_do_mapa * 0.25)
        borda_cima = int(tamanho_do_mapa * 0.75)
        borda_baixo = tamanho_do_mapa - 1
        x = borda_esquerda
        y = borda_cima
        while x <= borda_direita:
            preenchimento_y = y
            while preenchimento_y <= borda_baixo:
                if terreno_preenchido[x][preenchimento_y] == 0:
                    terreno[x][preenchimento_y] = TERRENO_TILE_PAREDE_0
                    terreno_preenchido[x][preenchimento_y] = 1
                preenchimento_y += 1
            fator_de_curva = random.randint(0,3)
            if (y > (borda_baixo - fator_de_curva)):
                y = borda_baixo
            else:
                y += fator_de_curva
            x += 1
        # Borda Sudeste
        borda_esquerda = int(tamanho_do_mapa * 0.75)
        borda_direita = tamanho_do_mapa - 1
        borda_cima = int(tamanho_do_mapa * 0.75)
        borda_baixo = tamanho_do_mapa - 1
        x = borda_esquerda
        y = borda_baixo
        while x <= borda_direita:
            preenchimento_y = y
            while preenchimento_y <= borda_baixo:
                if terreno_preenchido[x][preenchimento_y] == 0:
                    terreno[x][preenchimento_y] = TERRENO_TILE_PAREDE_0
                    terreno_preenchido[x][preenchimento_y] = 1
                preenchimento_y += 1
            fator_de_curva = random.randint(0,3)
            if (y < (borda_cima + fator_de_curva)):
                y = borda_cima
            else:
                y -= fator_de_curva
            x += 1
        # Agora que terminamos as bordas, adicionamos a saída
        y = tamanho_do_mapa - 1
        x = int(tamanho_do_mapa * 0.3) + random.randint(0,int(tamanho_do_mapa * 0.4))
        terreno[x][y] = TERRENO_TILE_PAREDE_0
        terreno_preenchido[x][y] = 1
        # Em seguida, adicionamos a bomba ao lado da saída
        y = y - 1
        terreno[x][y] = TERRENO_TILE_BOMBA
        terreno_preenchido[x][y] = 1
        bomba_posicao_mapa_x = x
        bomba_posicao_mapa_y = y
        # O personagem inicia ao lado da bomba. O terreno é um chão padrão, por isso, não muda.
        x = x - 1
        heroi_posicao_mapa_x = x + 0.5
        heroi_posicao_mapa_y = y + 0.5
        terreno_preenchido[x][y] = 1
        # Adicionamos algumas barreiras para proteger o personagem
        y -= 2
        xFill = (x - 2)
        while xFill < (x + 3):
            if terreno_preenchido[xFill][y] == 0:
                terreno[xFill][y] = TERRENO_TILE_PAREDE_0
                terreno_preenchido[xFill][y] = 1
            xFill += 1
        # Preenchemos o resto das bordas com barreiras. Isso ajuda a impedir o personagem e os monstros de saírem
        # do mapa
        y = 0
        while y < tamanho_do_mapa:
            if terreno_preenchido[0][y] == 0:
                terreno[0][y] = TERRENO_TILE_PAREDE_0
                terreno_preenchido[0][y] = 1
            if terreno_preenchido[tamanho_do_mapa - 1][y] == 0:
                terreno[tamanho_do_mapa - 1][y] = TERRENO_TILE_PAREDE_0
                terreno_preenchido[tamanho_do_mapa - 1][y] = 1
            y += 1
        x = 0
        while x < tamanho_do_mapa:
            if terreno_preenchido[x][0] == 0:
                terreno[x][0] = TERRENO_TILE_PAREDE_0
                terreno_preenchido[x][0] = 1
            if terreno_preenchido[x][tamanho_do_mapa - 1] == 0:
                terreno[x][tamanho_do_mapa - 1] = TERRENO_TILE_PAREDE_0
                terreno_preenchido[x][tamanho_do_mapa - 1] = 1
            x += 1
        estado_atual = ESTADO_JOGANDO

    elif estado_atual == ESTADO_JOGANDO:
        # ENTRADA DE DADOS

        # Se o jogo não estiver pausado...
        if jogo_pausado == 0:
            # Consideramos por padrão que o personagem está parado
            heroi_movimento = 0
            # Tratamento de eventos de teclado relacionados ao movimento do personagem.
            if(teclado.key_pressed("UP")):
                heroi_direcao = 0
                heroi_movimento = 4
            if(teclado.key_pressed("RIGHT")):
                heroi_direcao = 1
                heroi_movimento = 4
            if(teclado.key_pressed("DOWN")):
                heroi_direcao = 2
                heroi_movimento = 4
            if(teclado.key_pressed("LEFT")):
                heroi_direcao = 3
                heroi_movimento = 4
        # Verifica se "P" foi pressionado para pausar/despausar o jogo
        if(teclado.key_pressed("P")):
            if pausa_solicitada == 0:
                if jogo_pausado == 0:
                    # Pausa o jogo e guarda o tempo de execução
                    jogo_pausado = 1
                    tempo_de_jogo += (janela.time_elapsed() - tempo_de_pausa)
                    janela.set_title("Protótipo - Tempo (Pausado)")
                else:
                    # Despausa o jogo e atualiza o tempo de pausa
                    jogo_pausado = 0
                    tempo_de_pausa += (janela.time_elapsed() - tempo_de_jogo)
                    janela.set_title("Protótipo - Tempo")
            pausa_solicitada = 1
        else:
            pausa_solicitada = 0
        # Verifica se "Esc" foi pressionado para fechar o jogo
        if(teclado.key_pressed("esc")):
            janela.close()

        if jogo_pausado == 0:
            # FÍSICA

            # Move o sprite atual do personagem para a posição do personagem.
            sprite_atual = heroi_direcao + heroi_movimento
            lista_sprites_heroi[sprite_atual].set_position(heroi_posicao_mapa_x, heroi_posicao_mapa_y)

            # move o sprite atual de acordo com o input do teclado
            lista_sprites_heroi[sprite_atual].move_key_x(heroi_velocidade_x * janela.delta_time())
            lista_sprites_heroi[sprite_atual].move_key_y(heroi_velocidade_y * janela.delta_time())

            # PS: Prezados fãs do PPlay, me perdoem. Mas dispensei o seu método padrão de colisão, pois
            # ele não é adequado ao tipo de sprite que estou lidando no meu jogo. Além disso, a verificação
            # a seguir já obtém resultados adequados
            #
            # Verifica colisão com o terreno ao redor do personagem para validar o movimento.
            min_x = math.floor(lista_sprites_heroi[sprite_atual].x)
            min_y = math.floor(lista_sprites_heroi[sprite_atual].y)
            heroi_colidiu = 0
            if abs(lista_sprites_heroi[sprite_atual].x - min_x - 0.5) <= 0.25:
                max_x = min_x
            elif (lista_sprites_heroi[sprite_atual].x - min_x) > 0.75:
                max_x = min_x + 1
            else:
                max_x = min_x
                min_x -= 1
            if abs(lista_sprites_heroi[sprite_atual].y - min_y - 0.5) <= 0.25:
                max_y = min_y
            elif (lista_sprites_heroi[sprite_atual].y - min_y) > 0.75:
                max_y = min_y + 1
            else:
                max_y = min_y
                min_y -= 1
            if min_x < 0:
                min_x = 0
            if min_y < 0:
                min_y = 0
            if min_x >= tamanho_do_mapa:
                min_x = tamanho_do_mapa - 1
            if min_y >= tamanho_do_mapa:
                min_y = tamanho_do_mapa - 1
            if max_x >= tamanho_do_mapa:
                max_x = tamanho_do_mapa - 1
            if max_y >= tamanho_do_mapa:
                max_y = tamanho_do_mapa - 1
            x = min_x
            while x <= max_x:
                y = min_y
                while y <= max_y:
                    if (terreno[x][y] == TERRENO_TILE_PAREDE_0) or (terreno[x][y] == TERRENO_TILE_PAREDE_1) or (terreno[x][y] == TERRENO_TILE_GERADOR) or (terreno[x][y] == TERRENO_TILE_BOMBA) or (terreno[x][y] == TERRENO_TILE_BOOM):
                        heroi_colidiu = 1
                    y += 1
                x += 1

            # Verifica condições de vitória: se o personagem atinge a saída.
            if terreno[min_x][min_y] == TERRENO_TILE_SAIDA:
                estado_atual = ESTADO_TELA_VITORIA

            # Atualiza a posição do personagem para a posição do sprite atual.
            if heroi_colidiu == 0:
                heroi_posicao_mapa_x = lista_sprites_heroi[sprite_atual].x
                heroi_posicao_mapa_y = lista_sprites_heroi[sprite_atual].y

            # RENDERIZAÇÃO

            # Desenha o fundo. É fundamental para apagar o que estava na tela anterior.
            janela.set_background_color((0,0,0))

            # Desenha o terreno atrás da bomba
            lista_sprites_terreno[TERRENO_TILE_CHAO_0].set_position((janela.width / 2) - ((heroi_posicao_mapa_x - bomba_posicao_mapa_x) * tamanho_tile_x), (janela.height / 2) + tamanho_tile_y - ((heroi_posicao_mapa_y - bomba_posicao_mapa_y) * tamanho_tile_y))
            lista_sprites_terreno[TERRENO_TILE_CHAO_0].draw()

            # Desenha o terreno em volta do personagem. O personagem ficará sempre no centro da janela. O mapa é que
            # será movimentado nesse processo.
            #
            # As variáveis min_posicao_y, posicao_x e posicao_y estão no sistema de coordenadas da tela.
            # As variáveis heroi_posicao_mapa_x, heroi_posicao_mapa_y, x, y, min_x, min_y, max_x e max_y estão no sistema de
            # coordenadas do mapa.
            min_x = math.floor(heroi_posicao_mapa_x - (janela.width / (2 * tamanho_tile_x))) - 1
            min_y = math.floor(heroi_posicao_mapa_y - (janela.height / (2 * tamanho_tile_y))) - 1
            max_x = math.ceil(heroi_posicao_mapa_x + (janela.width / (2 * tamanho_tile_x))) + 1
            max_y = math.ceil(heroi_posicao_mapa_y + (janela.height / (2 * tamanho_tile_y))) + 1
            if min_x < 0:
                min_x = 0
            posicao_x = (janela.width / 2) - ((heroi_posicao_mapa_x - min_x) * tamanho_tile_x)
            if min_y < 0:
                min_y = 0
            min_posicao_y = (janela.height / 2) + tamanho_tile_y - ((heroi_posicao_mapa_y - min_y) * tamanho_tile_y)
            if max_x > tamanho_do_mapa:
                max_x = tamanho_do_mapa
            if max_y > tamanho_do_mapa:
                max_y = tamanho_do_mapa
            x = min_x
            while x < max_x:
                posicao_y = min_posicao_y
                y = min_y
                while y < max_y:
                    lista_sprites_terreno[terreno[x][y]].set_position(posicao_x, posicao_y - (lista_sprites_terreno[terreno[x][y]].height - tamanho_tile_y))
                    lista_sprites_terreno[terreno[x][y]].draw()
                    posicao_y += tamanho_tile_y
                    y += 1
                posicao_x += tamanho_tile_x
                x += 1

            # Atualiza a animações de terreno.
            lista_sprites_terreno[TERRENO_TILE_BOMBA].update()
            lista_sprites_terreno[TERRENO_TILE_GERADOR].update()

            # Desenha apenas o sprite do movimento que está sendo feito (direção + andando/parado)
            lista_sprites_heroi[sprite_atual].set_position((janela.width / 2) - (lista_sprites_heroi[sprite_atual].width / 2), (janela.height / 2) - (lista_sprites_heroi[sprite_atual].height / 2))
            lista_sprites_heroi[heroi_direcao + heroi_movimento].draw()
            # Muda o frame da animação
            lista_sprites_heroi[heroi_direcao + heroi_movimento].update()

            # Mostra tempo no seguinte formato: M::SS, sendo que conta de 5 minutos (300.000 ms) até 0.
            num_segundos = int((300000 - (janela.curr_time - inicio_de_jogo) + tempo_de_pausa) / 1000)
            # explode a bomba
            if (num_segundos <= 60) and (num_segundos >= 59):
                terreno[bomba_posicao_mapa_x][bomba_posicao_mapa_y] = TERRENO_TILE_CHAO_0
                lista_sprites_terreno[TERRENO_TILE_BOOM].set_position((janela.width / 2) - ((heroi_posicao_mapa_x - bomba_posicao_mapa_x) * tamanho_tile_x) + (tamanho_tile_x / 2) - (lista_sprites_terreno[TERRENO_TILE_BOOM].width / 2), (janela.height / 2) - ((heroi_posicao_mapa_y - bomba_posicao_mapa_y) * tamanho_tile_y) - (tamanho_tile_y / 2) + (lista_sprites_terreno[TERRENO_TILE_BOOM].height / 2))
                lista_sprites_terreno[TERRENO_TILE_BOOM].draw()
                lista_sprites_terreno[TERRENO_TILE_BOOM].update()
            # libera a saída
            elif num_segundos == 58:
                terreno[bomba_posicao_mapa_x][bomba_posicao_mapa_y + 1] = TERRENO_TILE_SAIDA
            # Mostra o tempo enquanto não se completa os 5 minutos sem contar as pausas.
            if num_segundos >= 0:
                tempo_minutos = str(int(num_segundos / 60))
                num_segundos = int(num_segundos % 60)
                if num_segundos >= 10:
                    tempo_segundos = str(num_segundos)
                else:
                    tempo_segundos =  "0" + str(num_segundos)
                janela.draw_text("Tempo: " + tempo_minutos + ":" + tempo_segundos, 20, janela.height - 30, 18, (255,255,255), "Arial", True, False)
            else:
                estado_atual = ESTADO_TELA_DERROTA

    elif estado_atual == ESTADO_TELA_VITORIA:
        # Escreve Vitória de forma centralizada horizontalmente e verticalmente. PS: 181 é a largura em pixels que a
        # expressão "Vitória" ocupa na tela que foi medida com PrintScreen.
        janela.draw_text("Vitória", int((janela.width - 181) / 2), int((janela.height / 2) - 35), 72, (255,255,255), "Arial", True, False)
        # Descreve como iniciar um novo jogo.
        janela.draw_text("Pressione N para iniciar um novo jogo.", 20, int(janela.height - 60), 18, (255,255,255), "Arial", True, False)

        # Verifica se "N" foi pressionado para iniciar um novo jogo
        if(teclado.key_pressed("N")):
            estado_atual = ESTADO_PREPARANDO_JOGO
        # Verifica se "Esc" foi pressionado para fechar o jogo
        if(teclado.key_pressed("esc")):
            janela.close()

    elif estado_atual == ESTADO_TELA_DERROTA:
        # Escreve Fim de Jogo de forma centralizada horizontalmente e verticalmente.
        janela.draw_text("Fim do Jogo", int((janela.width - 345) / 2), int((janela.height / 2) - 35), 72, (255,255,255), "Arial", True, False)
        # Descreve como iniciar um novo jogo.
        janela.draw_text("Pressione N para iniciar um novo jogo.", 20, int(janela.height - 60), 18, (255,255,255), "Arial", True, False)

        # Verifica se "N" foi pressionado para iniciar um novo jogo
        if(teclado.key_pressed("N")):
            estado_atual = ESTADO_PREPARANDO_JOGO
        # Verifica se "Esc" foi pressionado para fechar o jogo
        if(teclado.key_pressed("esc")):
            janela.close()

    # Atualiza janela.
    janela.update()
