• Nenhum resultado encontrado

Programação Funcional Aulas 12 & 13

N/A
N/A
Protected

Academic year: 2021

Share "Programação Funcional Aulas 12 & 13"

Copied!
14
0
0

Texto

(1)

Programa¸c˜

ao Funcional – Aulas 12 & 13

Sandra Alves

DCC/FCUP

2015/16

O Jogo da Vida

• Um aut´omato celular inventado pelo matem´atico John H. Conway. • O jogo desenrola-se numa grelha bi-dimensional.

• Cada posi¸c˜ao est´a vazia ou tem uma c´elula. • A col´onia de c´elulas evolui por gera¸c˜oes.

• Determinamos uma nova gera¸c˜ao pelas seguintes regras:

1. morrem as c´elulas com menos do que 2 ou mais do que 3 vizinhos; 2. sobrevivem c´elulas com 2 ou 3 vizinhos;

3. nasce uma nova c´elula em cada posi¸c˜ao vazia com exactamente 3 vizinhos. http://en.wikipedia.org/wiki/Conway%27s_Game_of_Life

Objetivo

Um programa que:

• simula a passagem de n gera¸c˜oes;

• mostra a sucess˜ao de gera¸c˜oes no terminal.

Baseado na solu¸c˜ao do livro Programming in Haskell de Graham Hutton (cap´ıtulo 9). Representa¸c˜ao do jogo

Vamos representar a col´onia de c´elulas por uma lista de coordenadas:

type Pos = (Int,Int) -- coluna, linha

type Cells = [Pos] -- coordenadas das c´elulas

Exemplo: um glider. glider :: Cells

(2)

Para facilitar a visualiza¸c˜ao: • largura e altura limitadas;

width, height :: Int width = 80

height = 24

• lados esquerdo/direito e de topo/baixo s˜ao ligados.

Algumas fun¸c˜oes auxiliares

-- testar se uma posi¸c˜ao est´a viva ou morta -- usa ‘elem’ (do prel´udio-padr˜ao)

isAlive, isEmpty :: Cells -> Pos -> Bool isAlive ps p = elem p ps

isEmpty ps p = not (isAlive ps p) -- obter as 8 posi¸c˜oes vizinhas

neighbs :: Pos -> [Pos]

neighbs (x,y) = map wrap [(x-1,y-1), (x,y-1), (x+1,y-1), (x-1,y),

(3)

(x+1,y) , (x-1,y+1), (x,y+1) , (x+1,y+1)] -- garantir que uma posi¸c˜ao est´a dentro do tabuleiro

wrap :: Pos -> Pos

wrap (x,y) = ((x-1) ‘mod‘ width + 1, (y-1) ‘mod‘ height + 1) -- contar c´elulas vivas entre as vizinhas

liveneighbs :: Cells -> Pos -> Int

liveneighbs ps = length . filter (isAlive ps) . neighbs Transi¸c˜ao entre gera¸c˜oes

A nova gera¸c˜ao depende apenas da gera¸c˜ao atual. Assim, vamos definir uma fun¸c˜ao de transi¸c˜ao entre gera¸c˜oes.

As novas celulas s˜ao as sobreviventes mais os nascimentos: nextgen :: Cells -> Cells

nextgen ps = survivors ps ++ births ps

Falta definir duas fun¸c˜oes auxiliares: survivors, births :: Cells -> Cells -- sobreviventes duma gera¸c˜ao

survivors :: Cells -> Cells survivors ps

= [p | p<-ps, elem (liveneighbs ps p) [2,3]] -- nascimentos duma gera¸c˜ao

-- ‘nub’ remove repetidos duma lista births :: Cells -> Cells births ps

= [p | p<-nub (concat (map neighbs ps)), isEmpty ps p,

liveneighbs ps p == 3] Visualiza¸c˜ao

Uma fun¸c˜ao para fazer a anima¸c˜ao de n gera¸c˜oes da col´onia partindo duma configura¸c˜ao inicial. life :: Cells -> Int -> IO ()

life ps n | n>0 = do { cls ; printCells ps ; wait 500 ; life (nextgen ps) (n-1) } | otherwise = return ()

Esta fun¸c˜ao n˜ao devolve um resultado ´util — o objetivo ´e fazer anima¸c˜ao no terminal. Fun¸c˜oes auxiliares de IO:

(4)

cls :: IO () -- limpar o terminal printCells :: Cells -> IO () -- mostrar a col´onia

wait :: Int -> IO () -- esperar (ms)

Usamos:

• fun¸c˜ao usleep para esperar (standard POSIX);

• sequˆencias ANSI de controlo do terminal (http://en.wikipedia.org/wiki/ANSI_escape_code) para limpar e posicionar o texto.

Sum´ario

• Uma implementa¸c˜ao simples do jogo do vida de Conway.

• Separa¸c˜ao entre computa¸c˜ao e intera¸c˜ao patente nos tipos de fun¸c˜oes; por ex:

liveneighbs :: Cells -> Pos -> Int -- computa¸c˜ao nextgen :: Cells -> Cells -- computa¸c˜ao printCells :: Cells -> IO () -- visualiza¸c˜ao life :: Cells -> Int -> IO () -- intera¸c˜ao • Facilita a compreens˜ao e extens˜ao do programa.

Extens˜oes

• Ler a configura¸c˜ao inicial da entrada padr˜ao. • Contar o n´umero de c´elulas ao longo das gera¸c˜oes. • Detetar casos especiais:

– todas as c´elulas mortas; – repeti¸c˜oes (naturezas mortas); – ciclos de periodo fixo.

• Melhorar a visualiza¸c˜ao: s´ımbolos Unicode, cores, etc. • Configura¸c˜ao inicial aleat´oria (usando randomRIO)

import System.Random

main = do x<-randomRIO (1,10) -- entre 1 e 10 print x

A biblioteca Gloss

• Para fazer desenhos, anima¸c˜oes, simula¸c˜oes e jogos 2D; • Simples: pensada para ensino de programa¸c˜ao;

• Implementada usando OpenGL e GLUT (mas n˜ao ´e preciso aprender nada disto) • S´ıtio oficial: http://gloss.ouroborus.net/

(5)

Primeiro exemplo import Graphics.Gloss

main = display window white ex1

window = InWindow "Gloss" (800,600) (0,0) ex1 = circleSolid 100

Compilar e executar: $ ghc exemplo.hs $ ./exemplo

(Esc ou fechar a janela para sair.)

O exemplo em detalhe import Graphics.Gloss main :: IO ()

main = display window white ex1 — desenhar em fundo branco window :: Display

window = InWindow "Gloss" (800,600) (0,0) — numa janela com 800x600 pixels

ex1 :: Picture

ex1 = circleSolid 100

— um c´ırculo cheio com 100 pixels de raio Figuras

As figuras geom´etricas s˜ao valores de tipo Picture.

A biblioteca gloss exporta muitas fun¸c˜oes para construir figuras; alguns exemplos: circleSolid :: Float -> Picture — c´ırculo dado o raio

rectangleSolid :: Float -> Float -> Picture — rectangulo dada largura, altura line :: Path -> Picture — linha poligonal

polygon :: Path -> Picture — pol´ıgono cheio type Path = [Point] — percurso type Point = (Float,Float) — coordenada x,y

(6)

Cores

Podemos mudar a cˆor de uma figura: color :: Color -> Picture -> Picture

As cores usuais est˜ao pr´e-definidas na biblioteca: red, green, blue, yellow, cyan, magenta, ... Sobrepor figuras

Podemos sobrepor v´arias figuras numa s´o: pictures :: [Picture] -> Picture

ex2 = pictures [color red (circleSolid 100),

color white (rectangleSolid 100 50)]

Transla¸c˜oes e rota¸c˜oes

Por omiss˜ao as figuras s˜ao desenhadas na origem (coordenadas (0, 0)). Para desenhar noutro ponto basta fazer uma transla¸c˜ao:

translate :: Float -> Float -> Picture -> Picture — transla¸c˜ao por dx, dy

Tamb´em podemos fazer rota¸c˜oes por um ˆangulo (em graus): rotate :: Float -> Picture -> Picture

ex3 = pictures [translate 100 100 (circleSolid 50), rotate 45 (rectangleSolid 100 50)]

(7)

Ampliar ou reduzir

Podemos tamb´em ampliar ou reduzir figuras. scale :: Float -> Float -> Picture -> Picture

— mudar a escala dados factores x, y ex4 = pictures [circleSolid 50,

translate 0 100

(scale 1 0.5 (circleSolid 50))]

Simula¸c˜oes

Tamb´em podemos usar o Gloss para fazer anima¸c˜ao de simula¸c˜oes discretas ao longo do tempo. A fun¸c˜ao simulate da biblioteca faz a anima¸c˜ao; precisamos de lhe passar:

1. um modelo inicial ;

2. uma fun¸c˜ao para converter um modelo numa figura;

3. uma fun¸c˜ao para avan¸car o tempo do modelo por um intervalo ∆t. Exemplo

Simular o movimento de uma bola:

• movimento linear e uniforme (velocidade constante); • sem atrito nem gravidade;

• colis˜oes com os limites duma “caixa” virtual (janela). Modelo

— posi¸c˜ao e velocidade

type Ball = (Point, Vector) — definidos na biblioteca Gloss type Point = (Float,Float) type Vector = (Float,Float)

(8)

Fun¸c˜ao de desenho

drawBall :: Ball -> Picture drawBall ((x,y),(dx,dy))

= translate x y (color red (circleSolid ballRadius)) — raio da bola em pixels (constante)

ballRadius :: Float ballRadius = 10

Atualizar posi¸c˜ao e detetar colis˜oes Calculamos a nova posi¸c˜ao:

x0 = x + ∆t × dx y0 = y + ∆t × dy Se uma das coordenadas ultrapassar os limites:

• limitamos a coordenada;

• invertemos a componente correspondente do vector velocidade.

updateBall :: ViewPort -> Float -> Ball -> Ball

updateBall _ dt ((x,y),(dx,dy)) = ((x’,y’), (dx’,dy’)) where (x’,dx’) = clip x dx (maxX-ballRadius)

(y’,dy’) = clip y dy (maxY-ballRadius) clip h dh max

| h’ > max = (max, -dh) | h’ < -max= (-max, -dh)

(9)

| otherwise = (h’, dh) where h’ = h + dt*dh — limites da “caixa” virtual

maxX, maxY :: Float maxX = 300

maxY = 300

Programa principal main = do

ball <- randomBall

simulate window black fps ball drawBall updateBall — n´umero de atualiza¸c˜oes por segundo (”frames per second”) fps :: Int

fps = 60

window = InWindow "Gloss Ball" ... — inicializar parˆametros aleatoriamente randomBall :: IO Ball

randomBall = ... Simula¸c˜oes e jogos

Para uma simula¸c˜ao especificamos uma fun¸c˜ao de atualiza¸c˜ao do “estado do mundo” com a passagem de ∆t segundos:

simulate :: ...

-> model — estado inicial -> (model -> Picture) – fun¸c˜ao de desenho -> (ViewPort -> Float -> model -> model)

— fun¸c˜ao de atualiza¸c˜ao -> IO ()

Num jogo, al´em de simular a passagem de tempo, necessitamos de reagir a eventos causados pelo jogador: • pressionar/largar teclas;

• mover o cursor do rato;

• pressionar/largar bot˜oes do rato; • etc.

A fun¸c˜ao play que permite tratar estes eventos. play :: ...

-> world – estado inicial -> (world -> Picture) — desenhar -> (Event -> world -> world) — reagir a eventos -> (Float -> world -> world) — atualiza¸c˜ao -> IO ()

(10)

Asteroids

• Um dos primeiros jogos v´ıdeo de arcada (1979)

• O jogador controla uma nave espacial num “mundo” 2D

• Deve disparar sobre os aster´oides e evitar ser atingido pelos fragmentos Vamos usar o Gloss para implementar um jogo deste g´enero.

Objetos em jogo Trˆes tipos:

1. a nave do jogador;

2. os aster´oides (v´arios tamanhos); 3. os lasers (disparados pela nave).

Representamos o mundo do jogo por uma lista de objetos: type World = [Object]

Cada objeto cont´em informa¸c˜ao de forma e de movimento: type Object = (Shape, Movement)

Formas

Representamos as trˆes formas de objetos por um novo tipo com trˆes construtores: data Shape = Asteroid Float — aster´oide (tamanho)

| Laser Float — laser (tempo restante) | Ship — nave do jogador

Os asteroides tˆem um parˆametro que especifica o seu tamanho Os lasers tˆem como parˆametro o tempo restante antes de “decairem”

(11)

Desenhar objetos

drawObj :: Object -> Picture

drawObj (shape, ((x,y), _, ang, _))

= translate x y (rotate ang (drawShape shape)) drawShape :: Shape -> Picture

drawShape Ship = color green ship drawShape (Laser _) = color yellow laser drawShape (Asteroid size)

= color red (scale size size asteroid) ship, laser, asteroid :: Picture — figuras b´asicas ...

Desenhar o mundo

Recorde que o “mundo” ´e uma lista de objetos: type World = [Object]

Basta desenhar todos os objetos e combinar as figuras: drawWorld :: World -> Picture

drawWorld objs = pictures (map drawObj objs) Reagir a eventos

Pressionar teclas ← ou →: iniciar rota¸c˜ao da nave Levantar teclas ← ou →: parar rota¸c˜ao da nave Pressionar tecla ↑: acelerar a nave

Pressionar barra de espa¸cos: disparar laser A fun¸c˜ao que reage a eventos ´e:

react :: Event -> World -> World Invariantes:

• O “mundo” ´e uma lista de objetos. • Esta lista nunca ´e vazia.

• O primeiro objeto ´e sempre a nave do jogador.

— iniciar/terminar rota¸c˜ao `a esquerda

react (EventKey (SpecialKey KeyLeft) keystate _ _) (ship:objs)

= (ship’:objs)

where (Ship, (pos, vel, ang, angV)) = ship

angV’ = if keystate==Down then (-180) else 0 ship’ = (Ship, (pos, vel, ang, angV’))

(12)

Movimento dos objetos

O movimento de cada objeto ´e caracterizado por: • posi¸c˜ao (x, y)

• velocidade linear (dx, dy) • orienta¸c˜ao ang

• velocidade de rota¸c˜ao angV

Representamos em Haskell por um tuplo: type Movement = (Point, — posi¸c˜ao

Vector, — velocidade linear Float, — orienta¸c˜ao (graus)

Float) — velocidade angular (graus/s) Atualiza¸c˜ao da posi¸c˜ao

• Calcular nova posi¸c˜ao e orienta¸c˜ao ap´os ∆t.

• Movimento limitado a uma “caixa”: −maxW idth ≤ x ≤ maxW idth −maxHeight ≤ y ≤ maxHeight • Se um objeto sair da janela deve re-entrar pelo lado oposto (“wrap around ”)

• Velocidade linear e de rota¸c˜ao s˜ao constantes (at´e o objeto ser destruido) move :: Float -> Movement -> Movement

move dt ((x,y), (dx,dy), ang, angV) = ((x’,y’), (dx,dy), ang’, angV)

where x’ = wrap (x+dt*dx) maxWidth y’ = wrap (y+dt*dy) maxHeight ang’ = ang + dt*angV

wrap h max | h > max = h-2*max | h < -max= h+2*max | otherwise = h

Dete¸c˜ao de colis˜oes

Num jogo de a¸c˜ao ´e ´util detetar colis˜oes entre objetos: 1. entre os lasers e aster´oides;

2. entre a nave e os aster´oides.

(13)

Colis˜ao de um ponto

Vamos aproximar o aster´oide por uma “bola” de centro (x0, y0).

Um ponto (x, y) colidiu com o aster´oide se est´a dentro da bola: (x − x0)2+ (y − y0)2≤ r2

— testar uma colis˜ao entre um laser e um aster´oide hits :: Object -> Object -> Bool

hits (Laser _, ((x,y), _, _, _)) (Asteroid sz, ((x’,y’), _, _, _)) = (x-x’)**2 + (y-y’)**2 <= (sz*10)**2 hits _ _ = False

Processar colis˜oes

Para cada aster´oide que ´e atingido por algum laser : 1. partir em fragmentos mais pequenos;

2. remover fragementos demasiados pequenos.

Sobrevivem ap´os cada passo de simula¸c˜ao: 1. a nave do jogador;

2. os fragmentos resultantes de todas as colis˜oes; 3. os aster´oides e lasers n˜ao envolvidos em colis˜oes. collisions :: [Object] -> [Object]

collisions (ship:objs) = ship:(frags ++ objs’ ++ objs’’) where rocks = filter isAsteroid objs

lasers = filter isLaser objs

frags = concat [fragment rock | rock<-rocks, any (‘hits‘rock) lasers] objs’ = [obj | obj<-rocks,

not (any (‘hits‘obj) lasers)] objs’’= [obj | obj<-lasers,

not (any (obj‘hits‘) rocks)]

fragment :: Object -> [Object] – fragmentar um aster´oide ...

(14)

Fun¸c˜ao de atualiza¸c˜ao

Dado o intervalo de tempo ∆t: 1. atualizar a posi¸c˜ao cada objeto; 2. remover lasers que tenham “decaido”; 3. processar colis˜oes.

Exprimimos como a composi¸c˜ao de trˆes fun¸c˜oes: updateWorld :: Float -> World -> World

updateWorld dt = collisions . decay dt . map (moveObj dt)

decay :: Float -> World -> World ... %— ver c´odigo

C´odigo (implementa¸c˜ao de Prof. Pedro Vasconcelos:)

Referências

Documentos relacionados

A...(razão social), por seu(s) representante(s) legal(is), interessada em participar do PREGÃO Nº:00006/2016 - FSP, da:Faculdade de Saúde Pública, declara, sob as penas da lei,

• O Java ME fornece um ambiente para aplicativos em execução em uma grande variedade de dispositivos móveis e integrados, como:. – Telefones celulares

– Tipo de dados que o objeto armazena, ou seja, os estados possíveis que ele pode assumir (atributos) – Tipos de operações que podem ser executadas pelo. objeto, ou seja, o

para o usuário digitar um nome; pega o valor digitado e altera na variável nome do objeto da Conta Bancária, através do método Alterar Nome.. Realiza saque/depósito, e depois

– Crie um construtor parametrizado inicializando todas as variáveis com os valores recebidos dos parâmetros. – Crie um construtor default (Inicializando as variáveis da

• Diferentes componentes de um sistema não devem revelar detalhes internos de suas..

• É possível comprar acessando o método comprar, passando como parâmetro dois objetos, um do tipo Pessoa e outro do tipo Produto.. M.; Programação Orientada

– É o processo de esconder todos os detalhes de um objeto que não contribuem para suas características