From 49123e5dfecc40e50845950cd674f4c7bcb086a5 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 24 Jun 2024 18:25:43 +0900 Subject: [PATCH] Flappy bird multiplayer. (#1) --- examples/flappy-bird/application.rb | 142 ++++++++++++++++-- examples/flappy-bird/gems.locked | 26 ++-- examples/flappy-bird/public/_static/index.css | 16 +- examples/worms/application.rb | 2 +- lib/lively/environment/application.rb | 8 +- 5 files changed, 162 insertions(+), 32 deletions(-) diff --git a/examples/flappy-bird/application.rb b/examples/flappy-bird/application.rb index c37cb43..6e26212 100755 --- a/examples/flappy-bird/application.rb +++ b/examples/flappy-bird/application.rb @@ -5,6 +5,7 @@ # Copyright, 2024, by Samuel Williams. require_relative 'highscore' +require 'async/variable' WIDTH = 420 HEIGHT = 640 @@ -62,11 +63,13 @@ def jump @velocity = 300.0 end - def render(builder) + def render(builder, remote: false) rotation = (@velocity / 20.0).clamp(-40.0, 40.0) rotate = "rotate(#{-rotation}deg)"; - builder.inline_tag(:div, class: 'bird', style: "left: #{@x}px; bottom: #{@y}px; width: #{@width}px; height: #{@height}px; transform: #{rotate};") + class_name = remote ? 'bird remote' : 'bird' + + builder.inline_tag(:div, class: class_name, style: "left: #{@x}px; bottom: #{@y}px; width: #{@width}px; height: #{@height}px; transform: #{rotate};") end end @@ -160,8 +163,10 @@ def render(builder) end class FlappyBirdView < Live::View - def initialize(...) - super + def initialize(*arguments, multiplayer_state: nil, **options) + super(*arguments, **options) + + @multiplayer_state = multiplayer_state @game = nil @bird = nil @@ -173,6 +178,15 @@ def initialize(...) @prompt = "Press Space to Start" @random = nil + @dead = nil + end + + attr :bird + + def bind(page) + super + + @multiplayer_state.add_player(self) end def close @@ -181,15 +195,15 @@ def close @game = nil end + @multiplayer_state.remove_player(self) + super end def handle(event) case event[:type] when "keypress" - if @game.nil? - start_game! - elsif event.dig(:detail, :key) == " " + if event.dig(:detail, :key) == " " play_sound("quack") if rand > 0.5 @bird&.jump @@ -202,12 +216,13 @@ def forward_keypress end def reset! + @dead = Async::Variable.new @random = Random.new(1) @bird = Bird.new @pipes = [ - Pipe.new(WIDTH * 1/2, HEIGHT/2, random: @random), - Pipe.new(WIDTH * 2/2, HEIGHT/2, random: @random) + Pipe.new(WIDTH + WIDTH * 1/2, HEIGHT/2, random: @random), + Pipe.new(WIDTH + WIDTH * 2/2, HEIGHT/2, random: @random) ] @bonus = nil @score = 0 @@ -247,12 +262,15 @@ def stop_music end def game_over! + Console.info(self, "Player has died.") + @dead.resolve(true) + play_sound("death") stop_music Highscore.create!(ENV.fetch("PLAYER", "Anonymous"), @score) - @prompt = "Game Over! Score: #{@score}. Press Space to Restart" + @prompt = "Game Over! Score: #{@score}." @game = nil self.update! @@ -260,6 +278,11 @@ def game_over! raise Async::Stop end + def preparing(message) + @prompt = message + self.update! + end + def start_game! if @game @game.stop @@ -272,6 +295,10 @@ def start_game! @game = self.run! end + def wait_until_dead + @dead.wait + end + def step(dt) @bird.step(dt) @pipes.each do |pipe| @@ -310,7 +337,7 @@ def step(dt) end end - def run!(dt = 1.0/20.0) + def run!(dt = 1.0/10.0) Async do start_time = Async::Clock.now @@ -357,8 +384,99 @@ def render(builder) end @bonus&.render(builder) + + @multiplayer_state&.players&.each do |player| + if player != self + player.bird&.render(builder, remote: true) + end + end end end end -Application = Lively::Application[FlappyBirdView] +class Resolver < Live::Resolver + def initialize(**state) + super() + + @state = state + end + + def call(id, data) + if klass = @allowed[data[:class]] + return klass.new(id, **data, **@state) + end + end +end + +class MultiplayerState + MINIMUM_PLAYERS = 1 + GAME_START_TIMEOUT = 5 + + def initialize + @joined = Set.new + @players = nil + + @player_joined = Async::Condition.new + + @game = self.run! + end + + attr :players + + def run! + Async do + while true + Console.info(self, "Waiting for players...") + while @joined.size < MINIMUM_PLAYERS + @player_joined.wait + end + + Console.info(self, "Starting game...") + GAME_START_TIMEOUT.downto(0).each do |i| + @joined.each do |player| + player.preparing("Starting game in #{i}...") + end + sleep 1 + end + + @players = @joined.to_a + Console.info(self, "Game started with #{@players.size} players") + + @players.each do |player| + player.start_game! + end + + @players.each do |player| + player.wait_until_dead + end + + Console.info(self, "Game over") + @players = nil + end + end + end + + def add_player(player) + # Console.info(self, "Adding player: #{player}") + @joined << player + player.preparing("Waiting for other players...") + @player_joined.signal + end + + def remove_player(player) + # Console.info(self, "Removing player: #{player}") + @joined.delete(player) + end +end + +class Application < Lively::Application + def self.resolver + Resolver.new(multiplayer_state: MultiplayerState.new).tap do |resolver| + resolver.allow(FlappyBirdView) + end + end + + def body(...) + FlappyBirdView.new(...) + end +end diff --git a/examples/flappy-bird/gems.locked b/examples/flappy-bird/gems.locked index b039ba9..6562163 100644 --- a/examples/flappy-bird/gems.locked +++ b/examples/flappy-bird/gems.locked @@ -9,21 +9,20 @@ PATH GEM remote: https://rubygems.org/ specs: - async (2.11.0) + async (2.12.0) console (~> 1.25, >= 1.25.2) fiber-annotation - io-event (~> 1.5, >= 1.5.1) - timers (~> 4.1) + io-event (~> 1.6) async-container (0.18.2) async (~> 2.10) - async-http (0.66.3) + async-http (0.67.1) async (>= 2.10.2) async-pool (>= 0.6.1) io-endpoint (~> 0.10, >= 0.10.3) io-stream (~> 0.4) protocol-http (~> 0.26.0) protocol-http1 (~> 0.19.0) - protocol-http2 (~> 0.17.0) + protocol-http2 (~> 0.18.0) traces (>= 0.10.0) async-http-cache (0.4.3) async-http (~> 0.56) @@ -55,9 +54,9 @@ GEM fiber-annotation (0.2.0) fiber-local (1.1.0) fiber-storage - fiber-storage (0.1.0) + fiber-storage (0.1.1) io-endpoint (0.10.3) - io-event (1.5.1) + io-event (1.6.4) io-stream (0.4.0) json (2.7.2) live (0.11.0) @@ -73,21 +72,20 @@ GEM protocol-http (0.26.5) protocol-http1 (0.19.1) protocol-http (~> 0.22) - protocol-http2 (0.17.0) + protocol-http2 (0.18.0) protocol-hpack (~> 1.4) protocol-http (~> 0.18) protocol-rack (0.5.1) protocol-http (~> 0.23) rack (>= 1.0) - protocol-websocket (0.12.1) + protocol-websocket (0.13.0) protocol-http (~> 0.2) - rack (3.0.11) + rack (3.1.3) samovar (2.3.0) console (~> 1.0) mapping (~> 1.0) - sqlite3 (2.0.1-arm64-darwin) - sqlite3 (2.0.1-x86_64-linux-gnu) - timers (4.3.5) + sqlite3 (2.0.2-arm64-darwin) + sqlite3 (2.0.2-x86_64-linux-gnu) traces (0.11.1) xrb (0.6.1) @@ -100,4 +98,4 @@ DEPENDENCIES sqlite3 BUNDLED WITH - 2.5.5 + 2.5.9 diff --git a/examples/flappy-bird/public/_static/index.css b/examples/flappy-bird/public/_static/index.css index b2951ac..a5bca0e 100644 --- a/examples/flappy-bird/public/_static/index.css +++ b/examples/flappy-bird/public/_static/index.css @@ -15,6 +15,8 @@ body { position: relative; overflow: hidden; + + transform: translate3d(0,0,0); } .flappy .score { @@ -50,7 +52,13 @@ body { position: absolute; background-size: contain; - transition: all 0.05s linear 0s; + transform: translate3d(0,0,0); + transition: all 0.1s linear 0s; +} + +.flappy .bird.remote { + opacity: 50%; + filter: grayscale(100%); } .flappy .pipe { @@ -59,7 +67,8 @@ body { position: absolute; background-size: contain; - transition: all 0.05s linear 0s; + transform: translate3d(0,0,0); + transition: all 0.1s linear 0s; } .flappy .gemstone { @@ -68,5 +77,6 @@ body { position: absolute; background-size: contain; - transition: all 0.05s linear 0s; + transform: translate3d(0,0,0); + transition: all 0.1s linear 0s; } diff --git a/examples/worms/application.rb b/examples/worms/application.rb index 6ec803d..9c5aec4 100755 --- a/examples/worms/application.rb +++ b/examples/worms/application.rb @@ -160,7 +160,7 @@ def handle(event) end def forward_keypress - "live.forwardEvent(#{JSON.dump(@id)}, event, {value: event.target.value, key: event.key})" + "live.forwardEvent(#{JSON.dump(@id)}, event, {key: event.key})" end def render(builder) diff --git a/lib/lively/environment/application.rb b/lib/lively/environment/application.rb index 3a4922b..22e5ef0 100644 --- a/lib/lively/environment/application.rb +++ b/lib/lively/environment/application.rb @@ -13,8 +13,12 @@ module Environment module Application include Falcon::Environment::Server - def url - "http://localhost:9292" + # def url + # "http://localhost:9292" + # end + + def count + 1 end def application