This is a simple Flappy Bird clone written in Ruby on Rails. It uses Falcon and Live to implement real-time interactivity.
To run the game, clone the repository and run the following commands:
$ bundle install
$ bundle exec rails server
Then, open your browser and navigate to http://localhost:3000
.
The game is implemented as a server-side rendered view. The game logic is fully implemented in lib/flappy_view.rb.
The game uses a standard Rails controller, implemented in app/controllers/game_controller.rb. The controller consists of two actions:
require 'async/websocket/adapters/rails'
class GameController < ApplicationController
RESOLVER = Live::Resolver.allow(FlappyView)
def index
@view = FlappyView.new
end
skip_before_action :verify_authenticity_token, only: :live
def live
self.response = Async::WebSocket::Adapters::Rails.open(request) do |connection|
Live::Page.new(RESOLVER).run(connection)
end
end
end
The index
action instantiates the game view FlappyView
which is then rendered by the view template app/views/game/index.html.xrb. The live
action is used to accept a WebSocket connection from the client browser.
When the client connects to the server, it binds the <div class="live" data-class="FlappyView" id="...">
tag to a server side instance. User interactions generate events which are sent to the server, and the server can send HTML to the client to update the view. In addition, for things like sound effects, the server can send JavaScript to the client to execute.
The actual implementation of the game logic consists of a main game loop which updates the game physics at 30 FPS (frames per second), and then renders the update to the client browser. As the client browser may be running at something other than 30 FPS, we use CSS transforms with linear interpolation to smooth out the changes in position.
Surprisingly, this game can run on any Rack 3 compatible server, including both Puma and Falcon. Rack 3 requires support for streaming requests and responses, which is sufficient to support WebSockets. The difference then, between servers, is how they choose to expose concurrency to the application. In the case of Puma, it is one thread per request, while Falcon uses one fiber per request.
This project was part of my RubyKaigi 2024 Keynote. The slides are available here.
- Async - The Async library for Ruby, which provides a foundation for scalable concurrency.
- Async::HTTP - The HTTP client and server library for Ruby, built on Async, supporting HTTP/1 and HTTP/2.
- Falcon - The Ruby web server used to run the game, built on Async.
- [Async::WebSocket] - The WebSocket library for Ruby, built on Async, supporting both HTTP/1 and HTTP/2 client and server WebSockets.
- Live - The library used to implement real-time interactivity, built on top of Async::WebSocket.
- Live.js - The JavaScript library used to interact with the server.
- Lively - A single-file live programming environment for Ruby, which also uses Live for real-time interactivity. It has a multiplayer example of the Flappy Bird game, using a similar implementation.