Created
September 19, 2013 22:24
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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