diff --git a/Gemfile.lock b/Gemfile.lock index bc400ce..b0b2aaa 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,6 +2,7 @@ GEM remote: https://rubygems.org/ specs: SgfParser (2.0.0) + ray (0.2.1) PLATFORMS java @@ -9,3 +10,4 @@ PLATFORMS DEPENDENCIES SgfParser + ray diff --git a/lib/weiqi.rb b/lib/weiqi.rb index 61f8edf..27984c5 100644 --- a/lib/weiqi.rb +++ b/lib/weiqi.rb @@ -1,3 +1,4 @@ require_relative "weiqi/board" require_relative "weiqi/gnu_go" +require_relative "weiqi/scheduler" require_relative "weiqi/game" diff --git a/lib/weiqi/game.rb b/lib/weiqi/game.rb index 183efe8..f3ca1e3 100644 --- a/lib/weiqi/game.rb +++ b/lib/weiqi/game.rb @@ -1,12 +1,10 @@ -require "thread" - module Weiqi class Game - def initialize(engine) + def initialize(engine, scheduler=Weiqi::AsyncScheduler.new) self.engine = engine self.observers = [] self.history = [] - self.mutex = Mutex.new + self.scheduler = scheduler end def observe(&block) @@ -27,17 +25,13 @@ def quit private - attr_accessor :engine, :observers, :history, :mutex + attr_accessor :engine, :observers, :history, :scheduler def move - return if mutex.locked? - - Thread.new do - mutex.synchronize do - notify_observers(yield) + scheduler.run do + notify_observers(yield) - notify_observers(engine.play_white) - end + notify_observers(engine.play_white) end end diff --git a/lib/weiqi/scheduler.rb b/lib/weiqi/scheduler.rb new file mode 100644 index 0000000..5518356 --- /dev/null +++ b/lib/weiqi/scheduler.rb @@ -0,0 +1,21 @@ +require "thread" + +module Weiqi + class SequentialScheduler + def run + yield + end + end + + class AsyncScheduler + def initialize + @mutex = Mutex.new + end + + def run(&block) + return if @mutex.locked? + + @mutex.synchronize { yield } + end + end +end diff --git a/test/game_test.rb b/test/game_test.rb new file mode 100644 index 0000000..20c96b7 --- /dev/null +++ b/test/game_test.rb @@ -0,0 +1,42 @@ +require "minitest/autorun" +require "mocha/mini_test" + +require_relative "../lib/weiqi" +require_relative "helpers/game_session" + +describe "Game" do + let(:engine) { mock("Engine") } + let(:game) { Weiqi::Game.new(engine, Weiqi::SequentialScheduler.new) } + let(:session) { GameSession.new(engine, game) } + let(:transcript) { [] } + + it "must be able to end game end by passing" do + assert_output "PASS!\nGame over. Final score 0\n" do + session.black_passes + .white_passes + .final_score(0) + .game_ends + end + end + + it "must be able to end game by resignation" do + assert_output "PASS!\nYou win! The computer resigned\n" do + session.black_passes + .white_resigns + .game_ends + end + end + + it "must be able to notify observers of moves" do + moves = [] + game.observe { |board| moves << board.last_move } + + session.black_plays(3,3) + .white_plays(3,4) + .black_plays(4,4) + .white_plays(5,5) + .verify_results + + moves.must_equal([[3,3],[3,4],[4,4],[5,5]]) + end +end diff --git a/test/helpers/game_session.rb b/test/helpers/game_session.rb new file mode 100644 index 0000000..e23c5ec --- /dev/null +++ b/test/helpers/game_session.rb @@ -0,0 +1,69 @@ +class GameSession + def initialize(mock_engine, game) + @moves = Mocha::Sequence.new('moves') + @engine = mock_engine + @game = game + + @actions = [] + end + + def black_passes + @engine.expects(:pass_black).in_sequence(@moves).returns(move("PASS")) + + @actions << lambda { @game.pass } + + self + end + + def black_plays(x,y) + @engine.expects(:play_black).in_sequence(@moves).returns(move([x,y])) + + @actions << lambda { @game.play(x, y) } + + self + end + + def white_plays(x,y) + @engine.expects(:play_white).in_sequence(@moves).returns(move([x,y])) + + self + end + + def white_passes + @engine.expects(:play_white).in_sequence(@moves).returns(move("PASS")) + + self + end + + def white_resigns + @engine.expects(:play_white).in_sequence(@moves).returns(move("resign")) + + self + end + + def final_score(num) + @engine.expects(:final_score).returns(num) + + self + end + + def game_ends + @engine.expects(:quit) + + verify_results + + self + end + + def verify_results + @actions.each { |a| a.call } + + self + end + + private + + def move(data) + Struct.new(:last_move).new(data) + end +end