|
@@ -0,0 +1,136 @@
|
|
|
+local Rx = require 'rx'
|
|
|
+require 'rx-love'
|
|
|
+
|
|
|
+-- Map parameters
|
|
|
+local width = 800
|
|
|
+local height = 600
|
|
|
+local border = 10
|
|
|
+
|
|
|
+-- Player parameters
|
|
|
+local paddleWidth = 20
|
|
|
+local paddleHeight = 80
|
|
|
+local paddleSpeed = 500
|
|
|
+
|
|
|
+-- Ball parameters
|
|
|
+local ballSize = 20
|
|
|
+
|
|
|
+-- Maps keys to players and directions
|
|
|
+local keyMap = {
|
|
|
+ w = {'player1', -1},
|
|
|
+ s = {'player1', 1},
|
|
|
+ up = {'player2', -1},
|
|
|
+ down = {'player2', 1}
|
|
|
+}
|
|
|
+
|
|
|
+local state
|
|
|
+
|
|
|
+-- Declare initial state of game
|
|
|
+function love.load()
|
|
|
+ state = {
|
|
|
+ ball = {
|
|
|
+ x = width / 2 - ballSize / 2,
|
|
|
+ y = height / 2 - ballSize / 2,
|
|
|
+ width = ballSize,
|
|
|
+ height = ballSize,
|
|
|
+ direction = 3 * math.pi / 4,
|
|
|
+ speed = 400
|
|
|
+ },
|
|
|
+ player1 = {
|
|
|
+ x = border,
|
|
|
+ y = height / 2 - paddleHeight / 2,
|
|
|
+ width = paddleWidth,
|
|
|
+ height = paddleHeight
|
|
|
+ },
|
|
|
+ player2 = {
|
|
|
+ x = width - (border + paddleWidth / 2),
|
|
|
+ y = height / 2 - paddleHeight / 2,
|
|
|
+ width = paddleWidth,
|
|
|
+ height = paddleHeight
|
|
|
+ }
|
|
|
+ }
|
|
|
+end
|
|
|
+
|
|
|
+-- Helper functions
|
|
|
+local function identity(...) return ... end
|
|
|
+local function const(val) return function() return val end end
|
|
|
+local function dt() return love.update:getValue() end
|
|
|
+local function move(player, direction)
|
|
|
+ state[player].y = state[player].y + direction * paddleSpeed * dt()
|
|
|
+end
|
|
|
+
|
|
|
+-- Respond to key presses to move players
|
|
|
+for _, key in pairs({'up', 'down', 'w', 's'}) do
|
|
|
+ love.update
|
|
|
+ :map(const(key))
|
|
|
+ :filter(love.keyboard.isDown)
|
|
|
+ :map(function(key)
|
|
|
+ return unpack(keyMap[key])
|
|
|
+ end)
|
|
|
+ :subscribe(move)
|
|
|
+end
|
|
|
+
|
|
|
+-- Move ball
|
|
|
+love.update
|
|
|
+ :skip(1)
|
|
|
+ :subscribe(function()
|
|
|
+ state.ball.x = state.ball.x + math.cos(state.ball.direction) * state.ball.speed * dt()
|
|
|
+ state.ball.y = state.ball.y + math.sin(state.ball.direction) * state.ball.speed * dt()
|
|
|
+ end)
|
|
|
+
|
|
|
+-- Collision on top and bottom
|
|
|
+love.update
|
|
|
+ :filter(function()
|
|
|
+ return state.ball.y < 0 or state.ball.y > height - ballSize
|
|
|
+ end)
|
|
|
+ :subscribe(function()
|
|
|
+ state.ball.direction = -state.ball.direction
|
|
|
+ state.ball.y = math.max(state.ball.y, 0)
|
|
|
+ state.ball.y = math.min(state.ball.y, height - ballSize)
|
|
|
+ end)
|
|
|
+
|
|
|
+-- Paddle collision
|
|
|
+love.update
|
|
|
+ :map(function()
|
|
|
+ local left = state.ball.x - ballSize / 2 < state.player1.x + paddleWidth and
|
|
|
+ state.ball.y + ballSize / 2 > state.player1.y and
|
|
|
+ state.ball.y + ballSize / 2 < state.player1.y + paddleHeight
|
|
|
+
|
|
|
+ local right = state.ball.x + ballSize / 2 > state.player2.x and
|
|
|
+ state.ball.y + ballSize / 2 > state.player2.y and
|
|
|
+ state.ball.y + ballSize / 2 < state.player2.y + paddleHeight
|
|
|
+
|
|
|
+ return left and 'left' or (right and 'right' or nil)
|
|
|
+ end)
|
|
|
+ :compact()
|
|
|
+ :subscribe(function(side)
|
|
|
+ local x, y = math.cos(state.ball.direction), math.sin(state.ball.direction)
|
|
|
+ x = -x
|
|
|
+ state.ball.direction = math.atan2(y, x)
|
|
|
+ if side == 'left' then
|
|
|
+ state.ball.x = math.max(state.ball.x, state.player1.x + paddleWidth + ballSize / 2)
|
|
|
+ elseif side == 'right' then state.ball.x = math.max(state.ball.x, state.player1.x + paddleWidth)
|
|
|
+ state.ball.x = math.min(state.ball.x, state.player2.x - ballSize / 2)
|
|
|
+ end
|
|
|
+
|
|
|
+ state.ball.speed = state.ball.speed + 20
|
|
|
+ end)
|
|
|
+
|
|
|
+-- Game over
|
|
|
+love.update
|
|
|
+ :filter(function()
|
|
|
+ return state.ball.x < 0 or state.ball.x + ballSize > width
|
|
|
+ end)
|
|
|
+ :subscribe(love.load)
|
|
|
+
|
|
|
+-- Draw state
|
|
|
+love.draw:subscribe(function()
|
|
|
+ local g = love.graphics
|
|
|
+
|
|
|
+ g.setColor(0, 0, 0)
|
|
|
+ g.rectangle('fill', 0, 0, width, height)
|
|
|
+
|
|
|
+ g.setColor(255, 255, 255)
|
|
|
+ g.rectangle('fill', state.player1.x, state.player1.y, state.player1.width, state.player1.height)
|
|
|
+ g.rectangle('fill', state.player2.x, state.player2.y, state.player2.width, state.player2.height)
|
|
|
+ g.rectangle('fill', state.ball.x, state.ball.y, state.ball.width, state.ball.height)
|
|
|
+end)
|