Untitled

 avatar
unknown
plain_text
a month ago
19 kB
6
Indexable
module JogoTowerDefense where 

import Graphics.Gloss
import Graphics.Gloss.Interface.Pure.Game
import Data.Maybe (fromMaybe)
import Data.List (find)


data Terreno = Relva | Terra | Agua deriving (Eq,Show)

type Mapa = [[Terreno]]

type Posicao = (Float, Float)


type Creditos = Int


data Base = Base
  { -- | Vida da base. Quando esta chega a zero, o jogador perde o jogo.
    vidaBase :: Float,
    posicaoBase :: Posicao,
    creditosBase :: Creditos
  }
  deriving (Show)

type Distancia = Float

type Tempo = Float

data Duracao = Finita Tempo | Infinita deriving (Eq, Show, Ord)


data Torre = Torre { 
    posicaoTorre :: Posicao,
    danoTorre :: Float,
    alcanceTorre :: Float,
    rajadaTorre :: Int, 
    cicloTorre :: Tempo,
    tempoTorre :: Tempo,
    projetilTorre :: Projetil
  }
  deriving (Show)


type Loja = [(Creditos, Torre)]


data TipoProjetil = Fogo | Gelo | Resina deriving (Eq, Show)


data Projetil = Projetil { 
    tipoProjetil :: TipoProjetil,
    duracaoProjetil :: Duracao
  }
  deriving (Show)


data Direcao = Norte | Sul | Este | Oeste deriving (Eq, Show)


data Inimigo = Inimigo
  { -- | Posição do inimigo no mapa.
    posicaoInimigo :: Posicao,
    -- | Direção do último movimento do inimigo.
    direcaoInimigo :: Direcao,
    -- | Vida do inimigo.
    vidaInimigo :: Float,
    -- | Velocidade do inimigo.
    velocidadeInimigo :: Float,
    -- | Dano causado pelo inimigo na base do jogador.
    ataqueInimigo :: Float,
    -- | Créditos que o jogador recebe ao derrotar o inimigo.
    butimInimigo :: Creditos,
    -- | Efeitos secundários ativos no inimigo.
    projeteisInimigo :: [Projetil],
    tipoInimigo :: TipoInimigo
  }
  deriving (Show)

data TipoInimigo = Zombie | Creeper | Esqueleto | Esqueleto2 | Enderman | Cavaleiro deriving (Show,Eq)


data Onda = Onda {
    inimigosOnda :: [Inimigo],
    cicloOnda :: Tempo,
    tempoOnda :: Tempo,
    entradaOnda :: Tempo
  }
  deriving (Show)


data Portal = Portal {
    posicaoPortal :: Posicao,
    ondasPortal :: [Onda],
    ativo :: Bool  -- Indica se o portal está ativo ou inativo
} deriving (Show)



data Jogo = Jogo
  { 
    baseJogo :: Base,
    portaisJogo :: [Portal],
    torresJogo :: [Torre],
    mapaJogo :: Mapa,
    inimigosJogo :: [Inimigo],
    lojaJogo :: Loja 
  }
  deriving (Show)

data ImmutableTowers = ImmutableTowers {jogo :: Jogo, imagens :: [Picture]}

mapa01 =
    [[r, t, t, t, r, r, r, r, r, r, r, r, r, r, r, r, r, r, r, r, r, r, r, r, r, r],
     [r, t, r, t, r, r, r, r, r, r, r, r, r, r, r, r, r, r, r, r, r, r, r, r, r, r],
     [t, t, r, t, r, r, r, r, r, r, r, r, r, r, r, r, r, r, r, r, r, r, r, r, r, r],
     [r, r, r, t, r, r, r, r, r, r, r, r, r, r, r, r, r, r, t, t, t, t, t, t, r, r],
     [r, t, t, t, r, r, r, r, r, r, r, r, r, a, a, a, a, r, t, r, r, r, r, t, r, r],
     [r, t, r, r, r, r, r, r, r, r, a, a, a, a, a, a, a, a, t, r, r, r, r, t, t, t],
     [r, t, r, r, r, r, r, r, a, a, a, a, a, a, a, a, a, a, t, a, a, a, r, r, r, r],
     [r, t, t, t, t, t, t, t, a, a, a, a, a, a, r, r, r, a, t, a, a, a, r, r, r, r],
     [r, r, r, r, r, r, r, t, a, a, r, r, r, r, r, r, r, r, t, r, a, a, r, r, r, r],
     [r, r, r, a, a, a, a, t, t, t, r, r, r, t, t, t, t, t, t, r, a, a, a, r, r, r],
     [r, r, a, a, a, a, a, a, a, t, r, r, r, t, r, r, r, r, r, r, a, a, a, a, r, r],
     [r, r, a, a, r, r, r, r, r, t, r, r, r, t, r, r, r, r, r, r, r, a, a, a, r, r],
     [r, a, a, r, t, t, t, t, t, t, r, r, r, t, r, r, r, r, r, r, r, r, a, a, r, r],
     [a, a, a, r, t, r, r, r, r, r, r, a, a, t, a, a, r, r, r, r, r, r, a, a, r, r],
     [a, a, r, r, t, r, r, r, r, r, a, a, a, t, a, a, a, r, r, r, r, r, a, a, a, r],
     [a, r, r, r, t, t, t, t, t, t, t, t, t, t, a, a, a, r, r, r, r, r, r, a, a, a],
     [a, r, r, r, r, r, r, r, a, a, a, a, a, a, a, a, a, a, r, r, r, r, r, a, a, a],
     [a, r, r, r, r, r, r, r, r, a, a, a, a, a, a, a, a, a, r, r, r, r, r, r, a, a]
    ] 
    where
        t = Terra
        r = Relva
        a = Agua


larguratile, alturatile :: Float
larguratile = 50
alturatile = 50

larguraJanela,alturaJanela :: Int
larguraJanela = 1300
alturaJanela = 900

inimigoTeste :: Inimigo
inimigoTeste = Inimigo {
     posicaoInimigo = (-625.0, 325.0),
     direcaoInimigo = Este, -- direção inicial padrão
     vidaInimigo = 100,
     velocidadeInimigo = 50,
     ataqueInimigo = 10,
     butimInimigo = 20,
     projeteisInimigo = [],
     tipoInimigo = Zombie
}

nivel01 :: Jogo
nivel01 = Jogo { 
    baseJogo = Base { vidaBase = 100, posicaoBase = (600.0, 250.0), creditosBase = 60 },
    portaisJogo = [criarPortalComOndas (-625.0, 325.0)],
    torresJogo = [],
    mapaJogo = mapa01,
    lojaJogo = [],
    inimigosJogo = [inimigoTeste]
}


criarPortalComOndas :: Posicao -> Portal
criarPortalComOndas pos = 
    let portal = Portal {
        posicaoPortal = pos,
        ondasPortal = [criarOnda1, criarOnda2],
        ativo = True  -- Define o portal como ativo por padrão
    }
    in portal


-- Definição da primeira onda
criarOnda1 :: Onda
criarOnda1 = criarOnda 
    [ criarInimigo Zombie (-625.0, 325.0), 
      criarInimigo Creeper (-625.0, 325.0)]  
    5.0                           
    10.0                             

-- Definição da segunda onda
criarOnda2 :: Onda
criarOnda2 = criarOnda 
    [ criarInimigo Zombie (-625.0, 325.0), 
      criarInimigo Zombie (-625.0, 325.0), 
      criarInimigo Creeper (-625.0, 325.0) ]  
    3.0              
    15.0 


criarOnda :: [Inimigo] -> Tempo -> Tempo -> Onda
criarOnda inimigos ciclo entrada = 
    let novaOnda = Onda {
        inimigosOnda = inimigos,
        cicloOnda = ciclo,
        tempoOnda = ciclo,
        entradaOnda = entrada
    }
    in  novaOnda

spawnInimigo :: Onda -> (Maybe Inimigo, Onda)
spawnInimigo onda =
    case inimigosOnda onda of
        [] -> (Nothing, onda)  -- Se não houver mais inimigos para spawnar, retorna Nothing e a onda atual.
        (inimigo:resto) -> (Just inimigo, onda { inimigosOnda = resto, tempoOnda = cicloOnda onda })  -- Lança o próximo inimigo.



atualizarOnda :: Onda -> Tempo -> Bool -> (Maybe Inimigo, Onda)
atualizarOnda onda delta anteriorConcluida =
    let novaEntrada = if anteriorConcluida then entradaOnda onda - delta else entradaOnda onda
        novoTempo = tempoOnda onda - delta
        (inimigoAtualizado, novaOnda) = if novoTempo <= 0 
            then spawnInimigo onda 
            else (Nothing, onda)  -- Retorna Nothing se não houver inimigos para spawnar
    in (inimigoAtualizado, novaOnda { entradaOnda = max 0 novaEntrada,
                                       tempoOnda = max 0 novoTempo })


atualizaJogo :: Jogo -> [Onda] -> Tempo -> Jogo
atualizaJogo jogo ondas delta =
    let (novasOndas, novosInimigos) = atualizarOndas ondas delta
        novosInimigosJogo = inimigosJogo jogo ++ novosInimigos  -- Adiciona os novos inimigos à lista
    in jogo { 
        inimigosJogo = novosInimigosJogo 
    }

atualizarOndas :: [Onda] -> Tempo -> ([Onda], [Inimigo])
atualizarOndas ondas delta =
    foldl atualizarOndaComSpawn ([], []) ondas  -- Atualiza cada onda e coleta os novos inimigos
  where
    atualizarOndaComSpawn (novasOndas, novosInimigos) onda =
        let (inimigoAtualizado, novaOnda) = atualizarOnda onda delta True  -- Assume que a onda anterior foi concluída
        in (novasOndas ++ [novaOnda], case inimigoAtualizado of
                                        Just inimigo -> novosInimigos ++ [inimigo]
                                        Nothing      -> novosInimigos)


iniciarOnda :: Portal -> Tempo -> (Maybe Inimigo, Portal)
iniciarOnda portal delta =
    if not (ativo portal) || null (ondasPortal portal)
    then (Nothing, portal)  -- Se o portal não estiver ativo ou não houver ondas, retorna Nothing
    else let (inimigoAtualizado, novaOnda) = spawnInimigo (head (ondasPortal portal)) 
         in case inimigoAtualizado of
              Just inimigo -> (Just inimigo, portal { ondasPortal = novaOnda : tail (ondasPortal portal) })  -- Lança o inimigo e atualiza a lista de ondas
              Nothing -> (Nothing, portal)  -- Se não houver inimigos para spawnar


atualizarPortal :: Portal -> Tempo -> Portal
atualizarPortal portal delta =
    if ativo portal then
        let ondaAtual = head (ondasPortal portal)  -- Obtém a primeira onda do portal
            (inimigoAtualizado, novaOnda) = spawnInimigo ondaAtual  -- Chama spawnInimigo corretamente
        in if ondaConcluida novaOnda then
            let novoPortal = portal { ondasPortal = tail (ondasPortal portal), ativo = not (null (tail (ondasPortal portal))) }
            in novoPortal
        else 
            portal  -- Retorna o portal sem alterações se a onda ainda estiver ativa
    else
        portal  -- Se o portal não estiver ativo, retorna como está


iniciarNovaOnda :: Jogo -> Jogo
iniciarNovaOnda jogo =
    let ondas = portaisJogo jogo >>= \portal -> ondasPortal portal
        proximaOnda = find (not . ondaConcluida) ondas
    in case proximaOnda of
           Nothing -> jogo
           Just novaOnda -> 
               let novosInimigos = inimigosOnda novaOnda
                   novoJogo = jogo { inimigosJogo = inimigosJogo jogo ++ novosInimigos }
               in  novoJogo

-- Função para desenhar o estado do jogo
desenharEstado :: ImmutableTowers -> Picture
desenharEstado ImmutableTowers {jogo = jogoatual, imagens = [relva,terra,agua,imgInimigo,base,portal]} =
    Pictures [desenhaMapa (mapaJogo jogoatual) [relva,terra,agua], 
              desenhaBase (baseJogo jogoatual) base,
              desenhaPortal (portaisJogo jogoatual) portal,
              desenhaInimigos (inimigosJogo jogoatual) [imgInimigo]]


desenhaMapa :: Mapa -> [Picture] -> Picture
desenhaMapa mapa imagens = 
    Translate (-625) (-425) $ Pictures [desenhaTile (fromIntegral x, fromIntegral y) terreno imagens | 
                                        (y, linha) <- zip [0..] (reverse mapa), 
                                        (x, terreno) <- zip [0..] linha]

desenhaTile :: (Float, Float) -> Terreno -> [Picture] -> Picture
desenhaTile (x, y) terreno [relva, terra, agua] = 
    Translate (x * larguratile) (y * alturatile) img
  where img = case terreno of
                 Relva -> relva
                 Terra -> terra
                 Agua  -> agua

-- Função para desenhar a base
desenhaBase :: Base -> Picture -> Picture
desenhaBase base imgBase =
    let (x, y) = posicaoBase base
    in Translate x y imgBase  -- Translada a imagem da base para a posição correta

-- Função para desenhar os portais
desenhaPortal :: [Portal] -> Picture -> Picture
desenhaPortal portais imgPortal =
    Pictures [Translate x y imgPortal | Portal {posicaoPortal = (x, y)} <- portais]  -- Desenha cada portal na sua posição

-- Função para desenhar os inimigos
desenhaInimigos :: [Inimigo] -> [Picture] -> Picture
desenhaInimigos inimigos [imgInimigo] =
    Pictures [let (x,y) = posicaoInimigo inimigo in 
              (Translate x y (imgInimigo)) | inimigo <- inimigos]


ondaConcluida :: Onda -> Bool
ondaConcluida onda = null (inimigosOnda onda) && entradaOnda onda <= 0 && tempoOnda onda <= 0


criarInimigo :: TipoInimigo -> Posicao -> Inimigo
criarInimigo tipoInimigo pos = Inimigo {
    posicaoInimigo = pos,
    direcaoInimigo = Este, -- direção inicial padrão
    vidaInimigo = vidaInicial tipoInimigo,
    velocidadeInimigo = velocidadeInicial tipoInimigo,
    ataqueInimigo = ataqueInicial tipoInimigo,
    butimInimigo = butimInicial tipoInimigo,
    projeteisInimigo = [],
    tipoInimigo = tipoInimigo
}

vidaInicial :: TipoInimigo -> Float
vidaInicial tipoInimigo = case tipoInimigo of
    Zombie  -> 100.0  -- Zombies são mais resistentes
    Creeper -> 75.0   -- Creepers são um pouco mais frágeis


velocidadeInicial :: TipoInimigo -> Float
velocidadeInicial tipoInimigo = case tipoInimigo of
    Zombie  -> 5.0   -- Zombies são mais lentos
    Creeper -> 5.5 -- Creepers são mais rápidos

ataqueInicial :: TipoInimigo -> Float
ataqueInicial tipoInimigo = case tipoInimigo of
    Zombie  -> 20.0   -- Zombies têm ataque moderado
    Creeper -> 50.0   -- Creepers têm ataque forte (explosão)

butimInicial :: TipoInimigo -> Creditos
butimInicial tipoInimigo = case tipoInimigo of
    Zombie  -> 10     -- Zombies dão menos créditos quando derrotados
    Creeper -> 15     -- Creepers dão mais créditos por serem mais perigosos

-- -- vai "buscar" a função que carrega as imagens do tipoInimigo que recebe
-- carregarImagensInimigo :: TipoInimigo -> IO ImagensInimigo
-- carregarImagensInimigo tipoInimigo =
--   case tipoInimigo of
--     Zombie -> carregarImagensZombie
--     Creeper -> carregarImagensCreeper
--     _ -> error "Tipo de inimigo não suportado"

-- --dentro das imagens associadas ao inimigo devolve aquela que corresponde à sua direção atual
-- selecionaImagemInimigo :: Inimigo -> ImagensInimigo -> Picture
-- selecionaImagemInimigo inimigo imagens =
--   case direcaoInimigo inimigo of
--     Oeste -> imgEsquerda imagens
--     Este  -> imgDireita imagens
--     Norte -> imgNorte imagens
--     Sul   -> imgSul imagens

obterPosicoesTerra :: Mapa -> [Posicao]
obterPosicoesTerra mapa = [(fromIntegral x + 0.5, fromIntegral y + 0.5) | 
    (linha, y) <- zip mapa [0..], 
    (x, terreno) <- zip [0..] linha, 
    terreno == Terra]


estaNoMapa :: Posicao -> Mapa -> Bool
estaNoMapa (x, y) mapa = 
    let (xMapa, yMapa) = converterCoordenadas (x,y)
    in floor xMapa >= 0 && floor xMapa < length (head mapa) && floor yMapa >= 0 && floor yMapa < length mapa

terrenoETerra :: Posicao -> Mapa -> Bool
terrenoETerra (x,y) mapa = 
    let (xMapa, yMapa) = converterCoordenadas (x,y)
    in estaNoMapa (x,y) mapa && ((mapa !! floor yMapa) !! floor xMapa == Terra)

moverInimigo :: Tempo -> Inimigo -> Mapa -> Inimigo
moverInimigo tempo inimigo mapa = 
    let direcao = direcaoInimigo inimigo
        (x,y) = posicaoInimigo inimigo
        largura = (larguratile/2) + 1
        altura = (alturatile/2) + 1
    in case direcao of
        Norte -> if terrenoETerra (x, y+altura) mapa then inimigo { posicaoInimigo = (x, y+(velocidadeInimigo inimigo *tempo)) }
                 else if terrenoETerra (x+largura, y) mapa then inimigo { direcaoInimigo = Este }
                 else if terrenoETerra (x-largura, y) mapa then inimigo { direcaoInimigo = Oeste }
                 else inimigo
        Sul -> if terrenoETerra (x, y-altura) mapa then inimigo { posicaoInimigo = (x, y-(velocidadeInimigo inimigo *tempo)) }
               else if terrenoETerra (x+largura, y) mapa then inimigo { direcaoInimigo = Este }
               else if terrenoETerra (x-largura, y) mapa then inimigo { direcaoInimigo = Oeste }
               else inimigo
        Este -> if terrenoETerra (x+largura, y) mapa then inimigo { posicaoInimigo = (x+(velocidadeInimigo inimigo *tempo), y) }
                else if terrenoETerra (x, y-altura) mapa then inimigo { direcaoInimigo = Sul }
                else if terrenoETerra (x, y+altura) mapa then inimigo { direcaoInimigo = Norte }
                else inimigo
        Oeste -> if terrenoETerra (x-largura, y) mapa then inimigo { posicaoInimigo = (x-(velocidadeInimigo inimigo *tempo), y) }
                 else if terrenoETerra (x, y-largura) mapa then inimigo { direcaoInimigo = Sul }
                 else if terrenoETerra (x, y+altura) mapa then inimigo { direcaoInimigo = Norte }
                 else inimigo

converterCoordenadas :: (Float, Float) -> (Float, Float)
converterCoordenadas (xGloss, yGloss) =
    let xMapa = (xGloss + (650)) / larguratile
        yMapa = (450.5 - yGloss) / alturatile
    in (xMapa, yMapa)

-- Função para atualizar a posição de todos os inimigos
atualizarInimigos :: [Inimigo] -> Tempo -> Mapa -> [Inimigo]
atualizarInimigos inimigos delta mapa =
    map (\inimigo -> moverInimigo delta inimigo mapa) inimigos

-- Adaptação da função atualizaJogo para incluir a atualização dos inimigos
atualizaJogoComDelta :: Tempo -> ImmutableTowers -> ImmutableTowers
atualizaJogoComDelta delta (ImmutableTowers {jogo = jogo, imagens = listaimagens}) = 
    let novosPortais = map (`atualizarPortal` delta) (portaisJogo jogo)
        jogoAtualizado = jogo { portaisJogo = novosPortais }
        -- Atualiza os inimigos
        posicoesTerra = obterPosicoesTerra (mapaJogo jogoAtualizado)
        novosInimigos = atualizarInimigos (inimigosJogo jogoAtualizado) delta (mapaJogo jogoAtualizado)
        jogoComInimigosAtualizados = jogoAtualizado { inimigosJogo = novosInimigos }
        -- Verifica se há ondas ativas
        ondasAtivas = any (not . ondaConcluida) (concatMap ondasPortal (portaisJogo jogoComInimigosAtualizados))
    in (ImmutableTowers {jogo = jogoComInimigosAtualizados, imagens = listaimagens}) -- Retorna o jogo atualizado



-- carregarImagensZombie :: IO ImagensInimigo
-- carregarImagensZombie = do
--   imgEsquerda <- loadBMP "soldado.bmp"
--   imgDireita  <- loadBMP "soldado.bmp"
--   imgNorte    <- loadBMP "soldado.bmp"
--   imgSul      <- loadBMP "soldado.bmp"
--   return $ ImagensInimigo imgEsquerda imgDireita imgNorte imgSul

-- carregarImagensCreeper :: IO ImagensInimigo
-- carregarImagensCreeper = do
--   imgEsquerda <- loadBMP "soldado.bmp"
--   imgDireita  <- loadBMP "soldado.bmp"
--   imgNorte    <- loadBMP "soldado.bmp"
--   imgSul      <- loadBMP "soldado.bmp"
--   return $ ImagensInimigo imgEsquerda imgDireita imgNorte imgSul


--Carrega a imagem da base 
carregarBase :: IO Picture
carregarBase = do
    base <- loadBMP "base.bmp"
    return (base)


--Carrega a imagem do portal 
carregarPortal :: IO Picture
carregarPortal = do
    portal <- loadBMP "portal.bmp"
    return (portal)

reageEventos :: Event -> ImmutableTowers -> ImmutableTowers
reageEventos _ immutable = immutable

main :: IO ()
main = do
    -- Carrega as imagens dos terrenos
    relva <- loadBMP "relva.bmp"
    terra <- loadBMP "terra.bmp"
    agua  <- loadBMP "agua.bmp"
    
    -- Carrega as imagens dos inimigos
    imgInimigo <- loadBMP "soldado.bmp"
    -- Carrega as imagens da base e do portal
    base <- loadBMP "base.bmp"
    portal <- loadBMP "portal.bmp"
    
    -- Inicializa o nível 1
    let jogoInicial = ImmutableTowers {jogo = nivel01, imagens = [relva,terra,agua,imgInimigo,base,portal]}

    -- Inicia o loop principal do jogo usando play
    play (InWindow "ImmutableTowers" (1300,900) (0, 0))
         white
         60
         jogoInicial
         desenharEstado
         reageEventos
         atualizaJogoComDelta
Leave a Comment