Closed TheSeamau5 closed 9 years ago
Here is the full code of the little project that's causing this:
Model.elm
module Shooter.Model where
import Array (..)
----------
-- MATH --
----------
type alias Point =
{ x : Float
, y : Float
}
add : Point -> Point -> Point
add p q =
Point (p.x + q.x) (p.y + q.y)
square : Float -> Float
square x = x * x
distanceSquared : Point -> Point -> Float
distanceSquared p q =
square (p.x - q.x) + square (p.y - q.y)
distance : Point -> Point -> Float
distance p q =
sqrt (distanceSquared p q)
------------
-- ENTITY --
------------
type alias Entity a =
{ a | position : Point
, velocity : Point
, radius : Float
}
collide : Entity a -> Entity b -> Bool
collide entity1 entity2 =
square (entity1.radius + entity2.radius) <=
distanceSquared entity1.position entity2.position
move : Entity a -> Entity a
move entity =
{ entity | position <- entity.position `add` entity.velocity }
setVelocity : Point -> Entity a -> Entity a
setVelocity velocity entity =
{ entity | velocity <- velocity }
-------------------
-- GAME ENTITIES --
-------------------
type alias Bullet = Entity { attack : Int }
type alias Ship a =
Entity { a | health : Int
, maxHealth : Int }
type alias Hero = Ship {}
hero : Hero
hero =
{ position = Point 200 10
, velocity = Point 0 0
, radius = 20
, health = 10
, maxHealth = 10
}
type alias Enemy = Ship {}
enemy : Enemy
enemy =
{ position = Point 200 300
, velocity = Point 0 0
, radius = 20
, health = 1
, maxHealth = 1
}
type alias GameState =
{ hero : Hero
, enemies : Array Enemy
, bullets : Array Bullet
}
gameState : GameState
gameState =
{ hero = hero
, enemies = push enemy empty
, bullets = empty
}
isAlive : Ship a -> Bool
isAlive ship =
ship.health > 0
hit : Bullet -> Ship a -> Ship a
hit bullet ship =
{ ship | health <- ship.health - bullet.attack }
createHeroBullet : Hero -> Int -> Bullet
createHeroBullet hero attack =
let bulletStartingPosition =
hero.position `add` Point 0 ((hero.radius / 2) + 2)
bulletVelocity =
Point 0 2
bulletRadius =
5 * attack
in
{ position = bulletStartingPosition
, velocity = bulletVelocity
, radius = bulletRadius
, attack = attack
}
createEnemyBullet : Enemy -> Int -> Bullet
createEnemyBullet enemy attack =
let bulletStartingPosition =
enemy.position `add` Point 0 -((enemy.radius / 2) + 2)
bulletVelocity =
Point 0 -2
bulletRadius =
5 * attack
in
{ position = bulletStartingPosition
, velocity = bulletVelocity
, radius = bulletRadius
, attack = attack
}
Input.elm
module Shooter.Input where
import Shooter.Model (..)
import Keyboard
import Signal (..)
import Time (..)
type alias Input =
{ arrows : Point
, space : Bool
}
toInput : { x : Int, y : Int} -> Bool -> Input
toInput arrows space =
{ arrows =
{ x = toFloat arrows.x
, y = toFloat arrows.y
}
, space = space
}
userInput : Signal Input
userInput =
map2 toInput Keyboard.arrows Keyboard.space
timeDelta : Signal Time
timeDelta =
fps 60
Render.elm
module Shooter.Render where
import Graphics.Element (..)
import Graphics.Collage (..)
import Color (..)
import Shooter.Model as Model
import Array (..)
renderHero : Model.Hero -> Form
renderHero hero =
move (hero.position.x, hero.position.y) <|
filled blue <|
circle hero.radius
renderEnemy : Model.Enemy -> Form
renderEnemy enemy =
move (enemy.position.x, enemy.position.y) <|
filled red <|
circle enemy.radius
renderBullet : Model.Bullet -> Form
renderBullet bullet =
move (bullet.position.x, bullet.position.y) <|
filled green <|
circle bullet.radius
render : Model.GameState -> Element
render game =
let enemyForms =
map renderEnemy game.enemies
bulletForms =
map renderBullet game.bullets
heroForm =
renderHero game.hero
in
collage 400 400
(toList
(push heroForm (enemyForms `append` bulletForms)))
Update.elm
module Shooter.Update where
import Shooter.Input (..)
import Shooter.Model (..)
import Array (..)
pipe : (a -> b -> b) -> Array a -> b -> b
pipe f array b =
case get 0 array of
Nothing -> b
Just a ->
pipe f (slice 1 (length array) array) (f a b)
update : Input -> GameState -> GameState
update input game =
let bullets =
if input.space == True
then
push (createHeroBullet game.hero 1) game.bullets
else
game.bullets
heroVelocity =
input.arrows
applyBullets entity =
pipe hit
(filter (collide entity) game.bullets)
entity
hero =
setVelocity heroVelocity
(move
(applyBullets game.hero))
enemies =
map move
(filter isAlive
(map applyBullets game.enemies))
in
{ game | hero <- hero
, enemies <- enemies
, bullets <- bullets
}
Shooter.elm (the main file)
import Shooter.Model (..)
import Shooter.Input (..)
import Shooter.Update (..)
import Shooter.Render (..)
import Signal (..)
main =
map render
(foldp update gameState userInput)
I have a sneaking suspicion that it has to do with me using Arrays alongside all this collage stuff... I'm not sure... I have to further investigate
Same bug, smaller code:
Model.elm
module Platformer.Model where
import Array (..)
type alias Point =
{ x : Float
, y : Float
}
type alias Entity a =
{ a | position : Point
, velocity : Point
, dimensions : Point
}
type alias Hero =
Entity { isGrounded : Bool }
hero : Hero
hero =
{ position = Point 0 0
, velocity = Point 0 0
, dimensions = Point 10 10
, isGrounded = True
}
type alias Platform =
Entity {}
type alias Game =
{ hero : Hero
, platforms : Array Platform
}
game : Game
game =
{ hero = hero
, platforms = empty
}
Render.elm
module Platformer.Render where
import Platformer.Model (Game, Hero, Platform)
import Graphics.Collage (..)
import Graphics.Element (..)
import Color (..)
import Array (..)
renderHero : Hero -> Form
renderHero hero =
move (hero.position.x, hero.position.y) <|
filled green <|
rect hero.dimensions.x hero.dimensions.y
renderPlatform : Platform -> Form
renderPlatform platform =
move (platform.position.x, platform.position.y) <|
filled blue <|
rect platform.dimensions.x platform.dimensions.y
render : Game -> Element
render game =
collage 400 400
(toList
(push (renderHero game.hero)
(map renderPlatform game.platforms)))
Platformer.elm
import Platformer.Model (..)
import Platformer.Render (..)
main = render game
Is it possible to get it down to one file? To properly debug, it's best if we can get it down to as small as possible. Have we gotten there?
Interesting, when you switch to lists, everything works. So my suspicion seems warranted.
Model.elm
module Platformer.Model where
import List (..)
type alias Point =
{ x : Float
, y : Float
}
type alias Entity a =
{ a | position : Point
, velocity : Point
, dimensions : Point
}
type alias Hero =
Entity { isGrounded : Bool }
hero : Hero
hero =
{ position = Point 0 0
, velocity = Point 0 0
, dimensions = Point 10 10
, isGrounded = True
}
type alias Platform =
Entity {}
type alias Game =
{ hero : Hero
, platforms : List Platform
}
game : Game
game =
{ hero = hero
, platforms = []
}
Render.elm
module Platformer.Render where
import Platformer.Model (Game, Hero, Platform)
import Graphics.Collage (..)
import Graphics.Element (..)
import Color (..)
import List (..)
renderHero : Hero -> Form
renderHero hero =
move (hero.position.x, hero.position.y) <|
filled green <|
rect hero.dimensions.x hero.dimensions.y
renderPlatform : Platform -> Form
renderPlatform platform =
move (platform.position.x, platform.position.y) <|
filled blue <|
rect platform.dimensions.x platform.dimensions.y
render : Game -> Element
render game =
collage 400 400
((::) (renderHero game.hero)
(map renderPlatform game.platforms))
Platformer.elm
import Platformer.Model (..)
import Platformer.Render (..)
main = render game
Nope, I haven't gotten there, but I feel like I'm almost there.
AHA!
import Graphics.Collage (filled, square, Form, collage)
import Graphics.Element (Element)
import Array (..)
import Color (green)
renderBox : (Float, Float) -> Form
renderBox (x,y) =
filled green <|
square 10
scene : Element
scene =
collage 400 400
(toList
(map renderBox empty))
main : Element
main = scene
There seems to be an issue with mapping renderBox on empty
Could it be related to this:
When I asText
an empty list
import Text (asText)
main =
asText []
I get this:
which is expected.
But if I toList
an empty array and asText
it
import Text (asText)
import Array (..)
main =
asText
(toList
(map (\x -> x * x) empty))
I get this:
which is cryptic. I'd expect to just get the empty list with nothing inside.
In all those cases I was mapping a function over an empty array that was getting converted back to a list. Maybe that could be the problem?
Quite sketchy. I am not very familiar with the Array
implementation, so I would not be terribly surprised if there was some bug there.
Yeah it is.
I think I've found it, the bug is with the Array.map
function.
If I convert an empty array to a list and ask for its length:
import Text (asText)
import Array (..)
import List
main =
asText
(List.length
(toList empty))
I get:
But, if I first map
a function on an empty array and then call toList
and ask for its length:
import Text (asText)
import Array (..)
import List
main =
asText
(List.length
(toList
(map (\x -> x * x) empty)))
I get:
I'm not familiar with the Array implementation either, but at least now you know where to look :D
So, I totally just found a quickfix to it and everything works! Yay!
The commit: fbea1ba
Solved in: https://github.com/elm-lang/core/pull/171
Seems like I got a runtime error.
Chrome is pointing at the
stepperHelp
function inNative.Graphics.Collage
. Somehow, something weird got through there and the compiler was silent.