Untitled
unknown
plain_text
10 months ago
19 kB
8
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
atualizaJogoComDeltaEditor is loading...
Leave a Comment