Skip to content

Instantly share code, notes, and snippets.

@r00k
Last active October 26, 2016 22:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save r00k/8c2409fd63dbd19371d11a55d7a77d2a to your computer and use it in GitHub Desktop.
Save r00k/8c2409fd63dbd19371d11a55d7a77d2a to your computer and use it in GitHub Desktop.
<body style="margin: 0;">
<div id="log" style="position: absolute; height: 100vh; overflow: hidden;"></div>
<div id="main"></div>
</body>
<script src="main.js"></script>
<script type="text/javascript">
var node = document.getElementById('main');
var app = Elm.Main.embed(node);
// Note: if your Elm module is named "MyThing.Root" you
// would call "Elm.MyThing.Root.embed(node)" instead.
</script>
<script type="text/javascript">
var show = function(el) {
return function(msg) { el.innerHTML = msg + '<br />' + el.innerHTML; }
}(document.getElementById('log'))
var protocol = window.location === "https:" ? "wss:" : "ws:"
var ws = new WebSocket(protocol + window.location.host + window.location.pathname)
ws.onopen = function() { show('websocket opened') }
ws.onclose = function() { show('websocket closed') }
ws.onmessage = function(m) { show(m.data) }
</script>
-- See this document for more information on making Pong:
-- http://elm-lang.org/blog/pong
module Main exposing (..)
import Color exposing (..)
import Collage exposing (..)
import Debug
import Element exposing (..)
import Keyboard
import Text
import Time exposing (..)
import Window exposing (Size)
import Html.App as App
import Html exposing (..)
import AnimationFrame
import Task
import WebSocket
-- MODEL
( gameWidth, gameHeight ) =
( 600, 400 )
( halfWidth, halfHeight ) =
( 300, 200 )
type State
= Play
| Pause
type alias Ball =
{ x : Float
, y : Float
, vx : Float
, vy : Float
}
type alias Player =
{ x : Float
, y : Float
, vx : Float
, vy : Float
, dir : Int
, score : Int
}
type alias Model =
{ state : State
, ball : Ball
, player1 : Player
, player2 : Player
, size : Size
}
player : Float -> Player
player x =
Player x 0 0 0 0 0
defaultModel : Model
defaultModel =
{ state = Pause
, ball = Ball 0 0 200 200
, player1 = player (20 - halfWidth)
, player2 = player (halfWidth - 20)
, size = Size 0 0
}
server : String
server = "ws://localhost:4567"
init =
( defaultModel, Task.perform (\_ -> NoOp) Resize (Window.size) )
-- UPDATE
type Msg
= Resize Size
| Player1 Int
| Player2 Int
| Tick Time
| TogglePlay
| NoOp
| WebSocketMessage String
tellSocket : Cmd Msg
tellSocket =
WebSocket.send server "I got a thing!"
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
NoOp ->
(model, Cmd.none)
Resize size ->
({ model | size = size }, Cmd.none)
Player1 dir ->
let
{ player1 } = model
in
({ model | player1 = { player1 | dir = dir } }, tellSocket)
Player2 dir ->
let
{ player2 } = model
in
({ model | player2 = { player2 | dir = dir } }, tellSocket)
WebSocketMessage str ->
Debug.crash "hey"
(model, Cmd.none)
TogglePlay ->
let
newState =
case model.state of
Play ->
Pause
Pause ->
Play
in
({ model | state = newState }, tellSocket)
Tick delta ->
let
{ state, ball, player1, player2 } =
model
score1 =
if ball.x > halfWidth then
1
else
0
score2 =
if ball.x < -halfWidth then
1
else
0
newState =
if score1 /= score2 then
Pause
else
state
newBall =
if state == Pause then
ball
else
updateBall delta ball player1 player2
in
({ model
| state = newState
, ball = newBall
, player1 = updatePlayer delta score1 player1
, player2 = updatePlayer delta score2 player2
}, Cmd.none)
updateBall : Time -> Ball -> Player -> Player -> Ball
updateBall dt ball paddle1 paddle2 =
if not (near 0 halfWidth ball.x) then
{ ball | x = 0, y = 0 }
else
physicsUpdate dt
{ ball
| vx = stepV ball.vx (within paddle1 ball) (within paddle2 ball)
, vy = stepV ball.vy (ball.y < 7 - halfHeight) (ball.y > halfHeight - 7)
}
updatePlayer : Time -> Int -> Player -> Player
updatePlayer dt points player =
let
movedPlayer =
physicsUpdate dt { player | vy = toFloat player.dir * 600 }
in
{ movedPlayer
| y = clamp (22 - halfHeight) (halfHeight - 22) movedPlayer.y
, score = player.score + points
}
physicsUpdate dt obj =
{ obj
| x = obj.x + obj.vx * dt
, y = obj.y + obj.vy * dt
}
near k c n =
n >= k - c && n <= k + c
within paddle ball =
near paddle.x 8 ball.x && near paddle.y 20 ball.y
stepV v lowerCollision upperCollision =
if lowerCollision then
abs v
else if upperCollision then
-(abs v)
else
v
-- VIEW
view : Model -> Html Msg
view model =
let
{ ball, player1, player2, state } =
model
{ width, height } =
model.size
scores =
txt (Text.height 50) (toString player1.score ++ " " ++ toString player2.score)
in
toHtml <|
container width height middle <|
collage gameWidth
gameHeight
[ rect gameWidth gameHeight
|> filled pongGreen
, oval 15 15
|> make ball
, rect 10 40
|> make player1
, rect 10 40
|> make player2
, toForm scores
|> move ( 0, gameHeight / 2 - 40 )
, toForm
(if state == Play then
spacer 1 1
else
txt identity msg
)
|> move ( 0, 40 - gameHeight / 2 )
]
pongGreen =
rgb 60 100 60
textGreen =
rgb 160 200 160
txt f string =
Text.fromString string
|> Text.color textGreen
|> Text.monospace
|> f
|> leftAligned
msg =
"SPACE to start, WS and &uarr;&darr; to move"
make obj shape =
shape
|> filled white
|> move ( obj.x, obj.y )
-- WIRING
keyboardProcessor down keyCode =
case ( down, keyCode ) of
( True, 87 ) ->
Player1 1
( True, 83 ) ->
Player1 -1
( False, 87 ) ->
Player1 0
( False, 83 ) ->
Player1 0
( True, 38 ) ->
Player2 1
( True, 40 ) ->
Player2 -1
( False, 38 ) ->
Player2 0
( False, 40 ) ->
Player2 0
( False, 32 ) ->
TogglePlay
_ ->
NoOp
main =
App.program
{ init = init
, update = update
, view = view
, subscriptions =
(\_ ->
Sub.batch
[ Window.resizes Resize
, Keyboard.downs (keyboardProcessor True)
, Keyboard.ups (keyboardProcessor False)
, AnimationFrame.diffs (Tick << inSeconds)
, WebSocket.listen server WebSocketMessage
]
)
}
require 'sinatra'
require 'sinatra-websocket'
set :server, 'thin'
set :sockets, []
get '/' do
if !request.websocket?
erb :index
else
request.websocket do |ws|
ws.onopen do
settings.sockets << ws
end
ws.onmessage do |msg|
EM.next_tick { settings.sockets.each{|s| s.send(msg) } }
end
ws.onclose do
settings.sockets.delete(ws)
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment