Untitled
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