Created
March 2, 2012 08:58
-
-
Save avdgaag/1956972 to your computer and use it in GitHub Desktop.
Text based fighting game
This file contains 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
# Make sure you have eventmachine installed and run the server: | |
# | |
# ruby -rubygems game.rb | |
# | |
# When the server is running, clients can connect and issue commands. | |
# Currently supported commands: | |
# | |
# * look: list the players in the arena. | |
# * join [nickname]: join the arena using a nickname | |
# * leave: leave the arena | |
# * atack [player]: try to attack another player by name | |
# | |
# Players attack with a random number of hit points. When your hit points | |
# run out, you die and you leave the arena. | |
require 'eventmachine' | |
# Simple events module so that objects can observe other objects with | |
# any kind of handler object that responds to #call. | |
module Observable | |
def on(event, handler) | |
@observable_events ||= {} | |
@observable_events[event] ||= [] | |
@observable_events[event] << handler | |
self | |
end | |
def trigger(event, *args) | |
@observable_events ||= {} | |
@observable_events[event] ||= [] | |
@observable_events[event].each do |handler| | |
handler.call(self, *args) | |
end | |
end | |
end | |
# The game keeps track of players and makes them interact. | |
class Game | |
include Observable | |
attr_reader :players | |
def initialize | |
@players = [] | |
end | |
def add_player(connection, name) | |
unless player = find_player(connection) | |
player = Player.new(connection, name) | |
@players << player | |
trigger :player_added, player | |
end | |
end | |
def retire_player(connection) | |
if player = find_player(connection) | |
@players.delete(player) | |
trigger :player_retired, player | |
end | |
end | |
def find_player(connection) | |
@players.find do |player| | |
player.connected_via?(connection) | |
end | |
end | |
def find_player_by_name(name) | |
@players.find do |player| | |
player.name == name | |
end | |
end | |
def player_list | |
@players.map { |player| | |
"#{player.name} (#{player.health})" | |
}.sort.join(', ') | |
end | |
def player_count | |
players.count | |
end | |
def make_move(connection, move, target = nil) | |
if player = find_player(connection) | |
case move | |
when :attack then | |
if other_player = find_player_by_name(target) | |
power = player.attack_power | |
begin | |
other_player.take_damage(power) | |
trigger :attack_hit, player, other_player, power | |
rescue Player::Death | |
trigger :attack_mortal_blow, player, other_player | |
retire_player other_player.connection | |
end | |
else | |
trigger :attack_miss, player | |
end | |
end | |
end | |
end | |
end | |
# The player is an actor in the game that can attack, take damage | |
# and die. | |
class Player | |
attr_reader :connection, :name, :health | |
Death = Class.new(StandardError) | |
def initialize(connection, name) | |
@connection, @name = connection, name | |
@health = 20 | |
end | |
def connected_via?(connection) | |
@connection == connection | |
end | |
def take_damage(hit_points) | |
@health -= hit_points | |
raise Death if @health <= 0 | |
end | |
def attack_power | |
rand(1..15) | |
end | |
def render(msg) | |
connection.render(msg) | |
end | |
end | |
# The server manages all connections and the game. It intercepts | |
# incoming commands and generates the output information. | |
class Server | |
attr_reader :connections | |
attr_reader :game | |
def initialize | |
@connections = [] | |
@game = Game.new | |
@game.on :player_added, ->(game, player) { | |
broadcast "#{player.name} has entered the arena." | |
} | |
@game.on :player_retired, ->(game, player) { | |
broadcast "#{player.name} has left the arena." | |
} | |
@game.on :attack_hit, ->(game, player, other_player, power) { | |
broadcast "#{player.name} dealt #{power} damage to #{other_player.name}" | |
} | |
@game.on :attack_miss, ->(game, player) { | |
broadcast "#{player.name} swung his sword in the void and made a utter fool of himself." | |
} | |
@game.on :attack_mortal_blow, ->(game, player, other_player) { | |
other_player.render "You were killed by #{player.name}" | |
player.render "You killed #{other_player.name}" | |
broadcast "#{player.name} dealt a mortal blow to #{other_player.name}" | |
} | |
end | |
def start | |
@signature = EventMachine.start_server('0.0.0.0', 8081, Connection) do |connection| | |
add_connection(connection) | |
connection.on :init, public_method(:add_connection) | |
connection.on :leave, public_method(:remove_connection) | |
connection.on :input, public_method(:perform_action) | |
end | |
end | |
def stop | |
EventMachine.stop_server(@signature) | |
end | |
def perform_action(connection, data) | |
case data | |
when /^join (\w+)$/ then | |
game.add_player(connection, $1) | |
when /^look$/i then | |
connection.render "There are #{game.player_count} players in the arena:" | |
connection.render(game.player_list) | |
when /^attack (\w+)$/ then | |
game.make_move connection, :attack, $1 | |
when /^leave$/i then | |
game.retire_player(connection) | |
else | |
connection.render 'Unrecognized command. Please try again.' | |
end | |
end | |
def add_connection(connection) | |
@connections << connection | |
connection.render 'Welcome to the game.' | |
broadcast "#{@connections.size} users in the lounge." | |
end | |
def remove_connection(connection) | |
@connections.delete(connection) | |
broadcast "#{@connections.size} users left in the lounge." | |
end | |
private | |
def broadcast(msg) | |
@connections.each do |connection| | |
connection.render msg | |
end | |
end | |
end | |
# The connection is a single client connected to the server | |
# and is responsible for taking input and relaying output. | |
class Connection < EventMachine::Connection | |
include Observable | |
attr_accessor :server | |
def post_init | |
trigger :init | |
end | |
def unbind | |
trigger :leave | |
end | |
def render(msg) | |
send_data ">> #{msg}\n" | |
end | |
def receive_data(data) | |
trigger :input, data.strip | |
end | |
end | |
Signal.trap('SIGINT') { | |
puts "Game stopped at #{Time.now}" | |
EventMachine.stop | |
} | |
EM.run { | |
Server.new.start | |
puts "Game started at #{Time.now}" | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment