Created
December 17, 2011 14:39
-
-
Save melborne/1490370 to your computer and use it in GitHub Desktop.
Revolver for Russian Roulette
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
class Gun | |
class ChamberError < StandardError; end | |
attr_reader :chamber | |
def initialize | |
@chamber = [] | |
end | |
def set_cartridge | |
raise ChamberError, 'The chamber is full' unless @chamber.empty? | |
@chamber << Cartridge.new | |
end | |
def trigger | |
return nil if chamber.empty? | |
reset_chamber | |
'Bang!' | |
end | |
private | |
def reset_chamber | |
@chamber.clear | |
end | |
end | |
class Cartridge | |
include Comparable | |
def <=>(other) | |
self.class <=> other.class | |
end | |
end | |
class Revolver < Gun | |
class CylinderError < StandardError; end | |
CYLINDER_SIZE = 6 | |
attr_reader :cylinder, :hammer | |
def initialize | |
@cylinder = Array.new(CYLINDER_SIZE) | |
@hammer = false | |
super | |
end | |
def set_cartridge(pos=nil) | |
case pos | |
when nil | |
pos = @cylinder.rotate.index(nil) | |
raise CylinderError, 'Cylinder is full' unless pos | |
pos = (pos + 1) % CYLINDER_SIZE | |
else | |
raise CylinderError, 'the position of the cylinder is not empty' if cylinder[pos] | |
end | |
@cylinder[pos] = Cartridge.new | |
end | |
def chamber | |
[ @cylinder[0] ].compact | |
end | |
def reset_chamber | |
@cylinder[0] = nil | |
end | |
private :reset_chamber | |
def cocking | |
@cylinder.rotate! | |
@hammer = true | |
end | |
def trigger | |
return nil unless @hammer | |
@hammer = false | |
super | |
end | |
def spin_cylinder | |
@cylinder.rotate!(rand CYLINDER_SIZE) | |
end | |
end | |
class Pistol < Gun | |
class NoMagazineError < StandardError; end | |
class FullMagazineError < StandardError; end | |
attr_reader :magazine | |
undef :set_cartridge | |
def initialize | |
@magazine = nil | |
super | |
end | |
def chamber | |
raise ChamberError, 'A magazine is not set' unless magazine | |
[ magazine.top ].compact | |
end | |
def set_magazine(mag=Magazine.new) | |
raise FullMagazineError, 'A magazine is already set' if magazine | |
@magazine = mag | |
end | |
def pull_magazine | |
raise NoMagazineError, 'A magazine is not set' unless magazine | |
@magazine = nil | |
end | |
def trigger | |
super.tap { @magazine.pop } | |
end | |
end | |
class Magazine | |
include Comparable | |
attr_reader :magazine | |
def initialize(opt={}) | |
opt = {:size => 10, :cartridge => 0}.merge(opt) | |
@size = opt[:size] | |
@magazine = Array.new(opt[:cartridge]) { Cartridge.new } | |
end | |
def <=>(other) | |
self.class <=> other.class | |
end | |
def top | |
@magazine.first | |
end | |
def pop | |
@magazine.pop | |
end | |
end | |
if __FILE__ == $0 | |
pis = Pistol.new | |
pis.set_magazine Magazine.new(:cartridge => 3) | |
5.times { p pis.trigger } | |
end |
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
require "rspec" | |
require_relative "gun" | |
describe Gun do | |
before do | |
@gun = Gun.new | |
end | |
context "chamber" do | |
it "should be empty at default" do | |
@gun.chamber.should be_empty | |
end | |
it "should be empty after triggering" do | |
@gun.set_cartridge | |
@gun.trigger | |
@gun.chamber.should be_empty | |
end | |
end | |
context "set_cartridge" do | |
it "should set a cartridge to the chamber" do | |
@gun.set_cartridge | |
@gun.chamber.should == [Cartridge.new] | |
end | |
it "should be error when the chamber has a cartridge" do | |
->{ 2.times { @gun.set_cartridge } }.should raise_error(Gun::ChamberError) | |
end | |
end | |
context "trigger" do | |
it "should return 'Bang!'" do | |
@gun.set_cartridge | |
@gun.trigger.should == 'Bang!' | |
end | |
it "should be nil when the chamber is empty" do | |
@gun.chamber.should be_empty | |
@gun.trigger.should be_nil | |
end | |
end | |
end | |
describe Revolver do | |
before do | |
@rev = Revolver.new | |
end | |
context "set_cartridge" do | |
it "should set a cartridge to the cylinder pos 1" do | |
@rev.set_cartridge | |
@rev.cylinder[1].should == Cartridge.new | |
end | |
it "should set 3 cartridges to the cylinder pos 1-4" do | |
3.times { @rev.set_cartridge } | |
@rev.cylinder.should == [nil, Cartridge.new, Cartridge.new, Cartridge.new, nil, nil] | |
end | |
it "should be error when it called more than the cylinder rooms" do | |
Revolver::CYLINDER_SIZE.times { @rev.set_cartridge } | |
->{ @rev.set_cartridge }.should raise_error(Revolver::CylinderError) | |
end | |
it "should can set cartridges other than pos 1" do | |
[2, 5].each { |pos| @rev.set_cartridge(pos) } | |
@rev.cylinder.should == [nil, nil, Cartridge.new, nil, nil, Cartridge.new] | |
end | |
it "should be error when the target room of cylinder is not nil" do | |
->{ [2, 2].each { |pos| @rev.set_cartridge(pos) } } | |
.should raise_error(Revolver::CylinderError) | |
end | |
end | |
context "cylinder" do | |
it "should rotate for next when cocking" do | |
@rev.set_cartridge | |
@rev.cocking | |
@rev.cylinder.should == [Cartridge.new, nil, nil, nil, nil, nil] | |
end | |
end | |
context "trigger" do | |
it "should return 'Bang!'" do | |
@rev.set_cartridge | |
@rev.cocking | |
@rev.trigger.should == 'Bang!' | |
end | |
it "should be nil without cocking" do | |
@rev.set_cartridge | |
@rev.trigger.should be_nil | |
end | |
it "should work sequentially" do | |
6.times { @rev.set_cartridge } | |
@rev.cocking | |
8.times.map { @rev.trigger.tap{ @rev.cocking } }.should == ["Bang!", "Bang!", "Bang!", "Bang!", "Bang!", "Bang!", nil, nil] | |
end | |
end | |
context "hammer" do | |
it "should be false after triggering" do | |
@rev.set_cartridge | |
@rev.cocking | |
@rev.trigger | |
@rev.hammer.should be_false | |
end | |
end | |
context "spin_cylinder" do | |
it "should rotate the cylinder line randomly(fail sometimes)" do | |
@rev.set_cartridge | |
before_spin = @rev.cylinder.dup | |
@rev.spin_cylinder | |
@rev.cylinder.should_not == before_spin | |
end | |
end | |
end | |
describe Pistol do | |
before do | |
@pis = Pistol.new | |
end | |
context "chamber" do | |
it "should be error without a magazine" do | |
->{ @pis.chamber }.should raise_error(Gun::ChamberError) | |
end | |
it "should be empty with a empty magazine" do | |
@pis.set_magazine | |
@pis.chamber.should be_empty | |
end | |
it "should hava a cartridge when a magazine has a cartridge" do | |
@pis.set_magazine(Magazine.new(:cartridge => 1)) | |
@pis.chamber.should == [Cartridge.new] | |
end | |
end | |
context "set_magazine" do | |
it "should set a magazine" do | |
@pis.set_magazine | |
@pis.magazine.should == Magazine.new | |
end | |
it "should be error when a magazine is already set" do | |
->{ 2.times { @pis.set_magazine } }.should raise_error(Pistol::FullMagazineError) | |
end | |
end | |
context "pull_magazine" do | |
it "should pull a magazine" do | |
@pis.set_magazine | |
@pis.pull_magazine | |
@pis.magazine.should be_nil | |
end | |
it "should be error when a magazine is not set" do | |
->{ @pis.pull_magazine }.should raise_error(Pistol::NoMagazineError) | |
end | |
end | |
context "set_cartridge" do | |
it "should not work" do | |
->{ @pis.set_cartridge }.should raise_error(NoMethodError) | |
end | |
end | |
context "trigger" do | |
it "should return 'Bang!'" do | |
@pis.set_magazine(Magazine.new(:cartridge => 1)) | |
@pis.trigger.should == 'Bang!' | |
end | |
it "should be nil when no cartridge is in the magazine" do | |
@pis.set_magazine | |
@pis.trigger.should be_nil | |
end | |
it "should pop a cartridge from the magazine" do | |
@pis.set_magazine(Magazine.new(:cartridge => 4)) | |
2.times { @pis.trigger } | |
@pis.magazine.magazine.should == [Cartridge.new, Cartridge.new] | |
end | |
it "should work sequentially" do | |
@pis.set_magazine(Magazine.new(:cartridge => 4)) | |
6.times.map { @pis.trigger }.should == ["Bang!", "Bang!", "Bang!", "Bang!", nil, nil] | |
end | |
end | |
end |
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
require "term/ansicolor" | |
require_relative "gun" | |
String.send(:include, Term::ANSIColor) | |
def russian_roulette(fighters) | |
print "--- Welcome to Russian Roulette ---\n".green | |
print "Today's fighters are: " | |
print fighters.map { |f| f.magenta.underline }.join(", ") | |
print "\nLet's go!\n\n" | |
rev = Revolver.new | |
rev.set_cartridge | |
sleep 2 | |
fighters.shuffle.cycle do |fighter| | |
print "#{fighter}'s turn:\n".cyan | |
rev.cocking | |
rev.spin_cylinder | |
sleep 2 | |
unless result = rev.trigger | |
print " Nothing happened..\n\n".yellow | |
sleep 1 | |
else | |
print " #{result} ".yellow.on_red.blink | |
print " #{fighter} is dead.\n".blue | |
print "\n--- Game is over ---\n".green | |
exit | |
end | |
end | |
end | |
russian_roulette(ARGV) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment