Skip to content

Instantly share code, notes, and snippets.

@NOTtheMessiah
Created September 19, 2013 22:24
Show Gist options
  • Save NOTtheMessiah/6630704 to your computer and use it in GitHub Desktop.
Save NOTtheMessiah/6630704 to your computer and use it in GitHub Desktop.
SpaceWar! Originally 40 pages of code written for the PDP-1 and rendered on an oscilloscope, this imitation version, written in Elm, is intended to serve as an example of how Functional Reactive Programming can be used for video games.
-- SpaceWar Elm by NOTtheMessiah (http://twitter.com/N0TtheMessiah)
--
-- Initial Commit
-- Todo: cleanup, graphics, scoreboard, options, respawn delay
--
-- Player 1 uses wasd and shift
-- Player 2 uses arrow keys and space
import Keyboard
import Window
type Positioned a = { a | x:Float, y:Float }
type Moving a = { a | vx:Float, vy:Float }
type Facing aa = { aa | a:Float }
-- MODEL
start1 = (200,100)
start2 = (-200,-100)
ship x y = {cooldown = 5.0} |> Positioned x y |> Moving 0 0 |> Facing 0
bullet (x,y) (vx,vy) a = { ts = 100.0} |> Moving (vx+2*cos(a)) (vy+2*sin(a)) |> Positioned x y
defaultState = { player1 = ship (fst start1) (snd start1), player2 = ship (fst start2) (snd start2) , fire = []}
getPos {x,y} = (x,y)
getVel {vx,vy} = (vx,vy)
dist2pos a b = (b.x - a.x) * (b.x - a.x) + (b.y - a.y) * (b.y - a.y)
unitVec x y = (x/sqrt(x*x+y*y),y/sqrt(x*x+y*y))
scalarMap f (x,y) = (f x, f y)
vecOp bf (a,b) (c,d) = (bf a c,bf b d)
isCollide r s1 s2 = dist2pos s1 s2 < r*r
-- UPDATE -- ('m' is for model)
newton t m = { m | x <- m.x + t*m.vx , y <- m.y + t*m.vy }
cooling t m = { m | cooldown <- m.cooldown - t }
resetCooling m = { m | cooldown <- 30.0 }
nextBullet t m = { m | x <- m.x + t*cos(m.a) , y <- m.y + t*sin(m.a), ts <- m.ts - t}
gravity t m = { m | vx <- m.vx - 0.0002 * m.x / (m.x+m.y)*(m.x+m.y), vy <- m.vy - 0.0002 * m.y / (m.x+m.y)*(m.x+m.y) }
drag t m = { m | vx <- m.vx - 0.01 * t*m.vx, vy <- m.vy - 0.01 * t*m.vy}
thrust {y} m = { m | vx <- m.vx + 0.1 * cos(m.a)*(toFloat y) , vy <- m.vy + 0.1 * sin(m.a)*(toFloat y) }
turn {x} m = { m | a <- m.a + (toFloat x)/8}
torus (w, h) m = {m | x <- mod ((round m.x) + w `div` 2) w - (w `div` 2), y <- mod ((round m.y) + h `div` 2) h - (h `div` 2)}
collideShip game = if isCollide 20 game.player1 game.player2 then { game | player1 <- ship (fst start1) (snd start1), player2 <- ship (fst start2) (snd start2)} else game
-- In this section, I shamelessly repeat code in the rush to publish.
collideBullet game = if foldr (||) False (map (isCollide 20 game.player1) game.fire) then {game | player1 <- ship (fst start1) (snd start1)} else game
collideBullet2 game = if foldr (||) False (map (isCollide 20 game.player2) game.fire) then {game | player2 <- ship (fst start1) (snd start1)} else game
mkBullet sh g = if (sh && (g.player1.cooldown < 0.0)) then {g | player1 <- resetCooling g.player1, fire <- bullet (vecOp (+) (20*cos(g.player2.a),20*sin(g.player2.a)) (getPos g.player2)) (getVel g.player2) g.player2.a :: g.fire} else g
mkBullet2 sh g = if (sh && (g.player2.cooldown < 0.0)) then {g | player2 <- resetCooling g.player2, fire <- bullet (vecOp (+) (20*cos(g.player1.a),20*sin(g.player1.a)) (getPos g.player1)) (getVel g.player1) g.player1.a :: g.fire} else g
stepShip (t, dir, (w,h)) = newton t . drag t . thrust dir . turn dir . torus (w,h) . gravity t . cooling t
stepBullet t = newton t . gravity t . (\ m -> { m | ts <- m.ts - t })
cullBullets xs =
case xs of
[] -> []
hd::tl -> if hd.ts > 0 then hd :: (cullBullets tl) else (cullBullets tl)
step (t, dir, dir2, sh, ct, (w,h)) game = (mkBullet sh . mkBullet2 ct . collideShip . collideBullet . collideBullet2) { game |
player1 <- stepShip (t, dir, (w,h)) game.player1
--, (player1, player2) <- collideShip game.player1 game.player2
, player2 <- stepShip (t, dir2, (w,h)) game.player2
, fire <- cullBullets <| map (stepBullet t) game.fire}
-- DISPLAY
render (w,h) {player1,player2,fire} =
let half n = n `div` 2
drawBullet b = ngon 8 6 |> filled lime |> move (getPos b)
in collage w h
([ rect (toFloat w) (toFloat h) |> filled (rgb 17 23 23)
, ngon 6 20 |> filled black |> move (0,0)
, ngon 3 20 |> filled yellow |> move (player1.x, player1.y) |> rotate player1.a
, rect 3 20 |> filled red |> move (player1.x, player1.y) |> rotate player1.a
, ngon 3 20 |> filled cyan |> move (getPos player2) |> rotate player2.a
, rect 3 20 |> filled blue |> move (player2.x, player2.y) |> rotate player2.a
] ++ map drawBullet fire)
-- CONTROLS
input = let delta = lift (\t -> t/20) (fps 25)
--in sampleOn delta (lift3 (,,) delta Keyboard.arrows Window.dimensions)
in sampleOn delta (lift6 (,,,,,) delta Keyboard.arrows Keyboard.wasd Keyboard.shift Keyboard.space Window.dimensions)
main = lift2 render Window.dimensions (foldp step defaultState input)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment