Skip to content

Instantly share code, notes, and snippets.

@melborne
Created December 17, 2011 14:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save melborne/1490370 to your computer and use it in GitHub Desktop.
Save melborne/1490370 to your computer and use it in GitHub Desktop.
Revolver for Russian Roulette
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
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
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