tictactoe-core 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.travis.yml +9 -0
  3. data/Gemfile +3 -0
  4. data/README.md +2 -0
  5. data/Rakefile +34 -0
  6. data/lib/tictactoe.rb +1 -0
  7. data/lib/tictactoe/ai/ab_minimax.rb +78 -0
  8. data/lib/tictactoe/ai/ab_negamax.rb +45 -0
  9. data/lib/tictactoe/ai/perfect_intelligence.rb +35 -0
  10. data/lib/tictactoe/ai/random_chooser.rb +21 -0
  11. data/lib/tictactoe/ai/tree.rb +44 -0
  12. data/lib/tictactoe/boards/board_type_factory.rb +15 -0
  13. data/lib/tictactoe/boards/four_by_four_board.rb +28 -0
  14. data/lib/tictactoe/boards/three_by_three_board.rb +27 -0
  15. data/lib/tictactoe/game.rb +102 -0
  16. data/lib/tictactoe/players/computer.rb +21 -0
  17. data/lib/tictactoe/players/factory.rb +21 -0
  18. data/lib/tictactoe/sequence.rb +24 -0
  19. data/lib/tictactoe/state.rb +64 -0
  20. data/runtests.sh +1 -0
  21. data/spec/performance_spec.rb +16 -0
  22. data/spec/properties_spec.rb +27 -0
  23. data/spec/rake_rspec.rb +42 -0
  24. data/spec/regression_spec.rb +29 -0
  25. data/spec/reproducible_random.rb +16 -0
  26. data/spec/spec_helper.rb +6 -0
  27. data/spec/test_run.rb +19 -0
  28. data/spec/tictactoe/ai/ab_minimax_spec.rb +511 -0
  29. data/spec/tictactoe/ai/ab_negamax_spec.rb +199 -0
  30. data/spec/tictactoe/ai/perfect_intelligence_spec.rb +122 -0
  31. data/spec/tictactoe/ai/random_choser_spec.rb +50 -0
  32. data/spec/tictactoe/boards/board_type_factory_spec.rb +16 -0
  33. data/spec/tictactoe/boards/four_by_four_board_spec.rb +30 -0
  34. data/spec/tictactoe/boards/three_by_three_board_spec.rb +30 -0
  35. data/spec/tictactoe/game_spec.rb +288 -0
  36. data/spec/tictactoe/players/computer_spec.rb +42 -0
  37. data/spec/tictactoe/players/factory_spec.rb +48 -0
  38. data/spec/tictactoe/sequence_spec.rb +20 -0
  39. data/spec/tictactoe/state_spec.rb +136 -0
  40. data/tictactoe-core-0.1.0.gem +0 -0
  41. data/tictactoe-core.gemspec +15 -0
  42. metadata +84 -0
@@ -0,0 +1,288 @@
1
+ require 'tictactoe/game'
2
+
3
+ RSpec.describe Tictactoe::Game do
4
+ class Human
5
+ attr_reader :mark
6
+
7
+ def initialize(mark, moves)
8
+ @mark = mark
9
+ @moves = moves
10
+ end
11
+
12
+ def get_move(state)
13
+ @moves.pop
14
+ end
15
+ end
16
+
17
+ let(:moves){[]}
18
+
19
+ def create(board_size, x_type, o_type)
20
+ game = described_class.new(board_size, x_type, o_type)
21
+ game.register_human_factory(lambda{|mark| Human.new(mark, moves)})
22
+ game
23
+ end
24
+
25
+ def human_tick_playing_to(game, loc)
26
+ moves << loc
27
+ game.tick()
28
+ end
29
+
30
+ def computer_tick(game)
31
+ game.tick()
32
+ end
33
+
34
+ def expect_amount_of_marks(game, mark, expected_count)
35
+ actual_count = game.marks.select{|m| m == mark}.count
36
+ expect(actual_count).to eq(expected_count)
37
+ end
38
+
39
+ it 'is not finished' do
40
+ game = create(3, :human, :human)
41
+ expect(game.is_finished?).to eq(false)
42
+ end
43
+
44
+ describe 'can be observed' do
45
+ it 'initial game size 3' do
46
+ game = create(3, :human, :human)
47
+ expect(game.marks).to eq([
48
+ nil, nil, nil,
49
+ nil, nil, nil,
50
+ nil, nil, nil
51
+ ])
52
+ end
53
+
54
+ it 'initial game size 4' do
55
+ game = create(4, :human, :human)
56
+ expect(game.marks).to eq([
57
+ nil, nil, nil, nil,
58
+ nil, nil, nil, nil,
59
+ nil, nil, nil, nil,
60
+ nil, nil, nil, nil
61
+ ])
62
+ end
63
+
64
+ it 'first play of x on size 3' do
65
+ game = create(3, :human, :human)
66
+ human_tick_playing_to(game, 0)
67
+ expect(game.marks).to eq([
68
+ :x, nil, nil,
69
+ nil, nil, nil,
70
+ nil, nil, nil
71
+ ])
72
+ end
73
+
74
+ it 'first play of o on size 3' do
75
+ game = create(3, :human, :human)
76
+ human_tick_playing_to(game, 0)
77
+ human_tick_playing_to(game, 1)
78
+ expect(game.marks).to eq([
79
+ :x, :o, nil,
80
+ nil, nil, nil,
81
+ nil, nil, nil
82
+ ])
83
+ end
84
+ end
85
+
86
+ describe 'available' do
87
+ it 'returns all the moves when no move has been made' do
88
+ game = create(3, :human, :human)
89
+ expect(game.available).to eq([
90
+ 0, 1, 2,
91
+ 3, 4, 5,
92
+ 6, 7, 8
93
+ ])
94
+ end
95
+
96
+ it 'returns all the available moves for when there is one move made on 3 by 3' do
97
+ game = create(3, :human, :human)
98
+ human_tick_playing_to(game, 0)
99
+ expect(game.available).to eq([
100
+ 1, 2,
101
+ 3, 4, 5,
102
+ 6, 7, 8
103
+ ])
104
+ end
105
+
106
+ it 'returns all the available moves for when there is one move made on 4 by 4' do
107
+ game = create(4, :human, :human)
108
+ human_tick_playing_to(game, 15)
109
+ expect(game.available).to eq([
110
+ 0, 1, 2, 3,
111
+ 4, 5, 6, 7,
112
+ 8, 9,10,11,
113
+ 12,13,14
114
+ ])
115
+ end
116
+ end
117
+
118
+ describe 'is not finished if no player has a line' do
119
+ it do
120
+ game = create(4, :human, :human)
121
+ human_tick_playing_to(game, 0)
122
+ human_tick_playing_to(game, 4)
123
+ human_tick_playing_to(game, 1)
124
+ human_tick_playing_to(game, 5)
125
+ human_tick_playing_to(game, 2)
126
+ human_tick_playing_to(game, 6)
127
+ expect(game.is_finished?).to eq(false)
128
+ end
129
+
130
+ it do
131
+ game = create(3, :human, :human)
132
+ human_tick_playing_to(game, 0)
133
+ human_tick_playing_to(game, 3)
134
+ human_tick_playing_to(game, 1)
135
+ human_tick_playing_to(game, 4)
136
+ expect(game.is_finished?).to eq(false)
137
+ end
138
+ end
139
+
140
+ describe 'is finished when a player has a line' do
141
+ it do
142
+ game = create(3, :human, :human)
143
+ human_tick_playing_to(game, 0)
144
+ human_tick_playing_to(game, 3)
145
+ human_tick_playing_to(game, 1)
146
+ human_tick_playing_to(game, 4)
147
+ human_tick_playing_to(game, 2)
148
+ expect(game.is_finished?).to eq(true)
149
+ expect(game.winner).to eq(:x)
150
+ end
151
+
152
+ it do
153
+ game = create(3, :human, :human)
154
+ human_tick_playing_to(game, 3)
155
+ human_tick_playing_to(game, 0)
156
+ human_tick_playing_to(game, 4)
157
+ human_tick_playing_to(game, 1)
158
+ human_tick_playing_to(game, 8)
159
+ human_tick_playing_to(game, 2)
160
+ expect(game.is_finished?).to eq(true)
161
+ expect(game.winner).to eq(:o)
162
+ end
163
+
164
+ it do
165
+ game = create(4, :human, :human)
166
+ human_tick_playing_to(game, 0)
167
+ human_tick_playing_to(game, 4)
168
+ human_tick_playing_to(game, 1)
169
+ human_tick_playing_to(game, 5)
170
+ human_tick_playing_to(game, 2)
171
+ human_tick_playing_to(game, 6)
172
+ human_tick_playing_to(game, 3)
173
+ expect(game.is_finished?).to eq(true)
174
+ expect(game.winner).to eq(:x)
175
+ end
176
+ end
177
+
178
+ describe 'is finished when the board is full' do
179
+ it do
180
+ game = create(3, :human, :human)
181
+ human_tick_playing_to(game, 0)
182
+ human_tick_playing_to(game, 1)
183
+ human_tick_playing_to(game, 2)
184
+ human_tick_playing_to(game, 5)
185
+ human_tick_playing_to(game, 3)
186
+ human_tick_playing_to(game, 6)
187
+ human_tick_playing_to(game, 4)
188
+ human_tick_playing_to(game, 8)
189
+ human_tick_playing_to(game, 7)
190
+ expect(game.marks).to eq([
191
+ :x, :o, :x,
192
+ :x, :x, :o,
193
+ :o, :x, :o
194
+ ])
195
+ expect(game.is_finished?).to eq(true)
196
+ end
197
+ end
198
+
199
+ describe 'cannot play twice to the same move' do
200
+ it 'second play' do
201
+ game = create(3, :human, :human)
202
+ human_tick_playing_to(game, 0)
203
+ human_tick_playing_to(game, 0)
204
+ expect(game.marks).to eq([
205
+ :x, nil, nil,
206
+ nil, nil, nil,
207
+ nil, nil, nil
208
+ ])
209
+ end
210
+ end
211
+
212
+ describe 'cannot be played when finished' do
213
+ it do
214
+ game = create(3, :human, :human)
215
+
216
+ human_tick_playing_to(game, 0)
217
+ human_tick_playing_to(game, 3)
218
+ human_tick_playing_to(game, 1)
219
+ human_tick_playing_to(game, 4)
220
+ human_tick_playing_to(game, 2)
221
+
222
+ human_tick_playing_to(game, 5)
223
+
224
+ expect(game.marks).to eq([
225
+ :x, :x, :x,
226
+ :o, :o, nil,
227
+ nil, nil, nil
228
+ ])
229
+ end
230
+ end
231
+
232
+ describe 'if the human has no move ignores the ticks' do
233
+ it do
234
+ game = create(3, :human, :human)
235
+ human_tick_playing_to(game, nil)
236
+ human_tick_playing_to(game, nil)
237
+ human_tick_playing_to(game, nil)
238
+ expect_amount_of_marks(game, :x, 0)
239
+ end
240
+
241
+ it do
242
+ game = create(3, :human, :human)
243
+ human_tick_playing_to(game, nil)
244
+ human_tick_playing_to(game, nil)
245
+ human_tick_playing_to(game, nil)
246
+ human_tick_playing_to(game, 0)
247
+ expect_amount_of_marks(game, :x, 1)
248
+ expect_amount_of_marks(game, :o, 0)
249
+ end
250
+
251
+ it do
252
+ game = create(3, :human, :human)
253
+ human_tick_playing_to(game, 0)
254
+ human_tick_playing_to(game, nil)
255
+ human_tick_playing_to(game, nil)
256
+ human_tick_playing_to(game, nil)
257
+ expect_amount_of_marks(game, :x, 1)
258
+ expect_amount_of_marks(game, :o, 0)
259
+ end
260
+ end
261
+
262
+ describe 'the computer plays' do
263
+ it 'the first turn' do
264
+ game = create(3, :computer, :human)
265
+ computer_tick(game)
266
+ expect_amount_of_marks(game, :x, 1)
267
+ expect_amount_of_marks(game, :o, 0)
268
+ end
269
+
270
+ it 'the second turn' do
271
+ game = create(3, :human, :computer)
272
+ human_tick_playing_to(game, 0)
273
+ computer_tick(game)
274
+ expect_amount_of_marks(game, :x, 1)
275
+ expect_amount_of_marks(game, :o, 1)
276
+ end
277
+
278
+ it 'four consecutive turns' do
279
+ game = create(3, :computer, :computer)
280
+ computer_tick(game)
281
+ computer_tick(game)
282
+ computer_tick(game)
283
+ computer_tick(game)
284
+ expect_amount_of_marks(game, :x, 2)
285
+ expect_amount_of_marks(game, :o, 2)
286
+ end
287
+ end
288
+ end
@@ -0,0 +1,42 @@
1
+ require 'tictactoe/players/computer'
2
+
3
+ RSpec.describe Tictactoe::Players::Computer do
4
+ class IntelligenceSpy
5
+ attr_reader :received_state, :received_mark
6
+
7
+ def initialize(moves)
8
+ @moves = moves
9
+ end
10
+
11
+ def desired_moves(state, mark)
12
+ @received_state = state
13
+ @received_mark = mark
14
+ @moves
15
+ end
16
+ end
17
+
18
+ class ChooseFirst
19
+ def choose_one(list)
20
+ list.first
21
+ end
22
+ end
23
+
24
+ def create(mark, intelligence = IntelligenceSpy.new([1, 2, 3]), chooser = ChooseFirst.new)
25
+ described_class.new(mark, intelligence, chooser)
26
+ end
27
+
28
+ it 'has a mark' do
29
+ computer = create(:x)
30
+ expect(computer.mark).to eq(:x)
31
+
32
+ end
33
+
34
+ it 'can make a move' do
35
+ intelligence = IntelligenceSpy.new([1, 2, 3])
36
+ computer = create(:x, intelligence, ChooseFirst.new)
37
+
38
+ expect(computer.get_move(:state)).to eq (1)
39
+ expect(intelligence.received_state).to eq (:state)
40
+ expect(intelligence.received_mark).to eq (:x)
41
+ end
42
+ end
@@ -0,0 +1,48 @@
1
+ require 'tictactoe/players/factory'
2
+
3
+ RSpec.describe Tictactoe::Players::Factory do
4
+ class ComputerFake
5
+ attr_reader :mark
6
+
7
+ def initialize(mark)
8
+ @mark = mark
9
+ end
10
+ end
11
+
12
+ let(:computer_factory) do
13
+ lambda{|mark| ComputerFake.new(mark)}
14
+ end
15
+ let(:factory) do
16
+ factory = described_class.new()
17
+ factory.register(:computer, computer_factory)
18
+ factory
19
+ end
20
+
21
+ it 'creates a computer player' do
22
+ computer_player = factory.create(:computer, :x)
23
+
24
+ expect(computer_player).to be_a(ComputerFake)
25
+ expect(computer_player.mark).to eq(:x)
26
+ end
27
+
28
+ class HumanFake
29
+ attr_reader :mark
30
+
31
+ def initialize(mark)
32
+ @mark = mark
33
+ end
34
+ end
35
+
36
+ it 'creates a human computer after being registered' do
37
+ human_factory = lambda{|mark| HumanFake.new(mark)}
38
+ factory.register(:human, human_factory)
39
+ human_player = factory.create(:human, :o)
40
+
41
+ expect(human_player).to be_a(HumanFake)
42
+ expect(human_player.mark).to eq(:o)
43
+ end
44
+
45
+ it 'raises a message when no factory is available for that type' do
46
+ expect{factory.create(:unknown_type, :o)}.to raise_error("No factory has been defined for type: unknown_type")
47
+ end
48
+ end
@@ -0,0 +1,20 @@
1
+ require 'tictactoe/sequence'
2
+
3
+ RSpec.describe Tictactoe::Sequence do
4
+ it 'has the first value' do
5
+ expect(described_class.new([:x, :o]).first.value).to eq(:x)
6
+ end
7
+
8
+ it 'has the second value' do
9
+ expect(described_class.new([:x, :o]).first.next.value).to eq(:o)
10
+ end
11
+
12
+ it 'cycles when reaches the end' do
13
+ expect(described_class.new([:x, :o]).first.next.next.value).to eq(:x)
14
+ end
15
+
16
+ it 'can handle 3 nodes' do
17
+ expect(described_class.new([0, 1, 2]).first.next.next.value).to eq(2)
18
+ expect(described_class.new([0, 1, 2]).first.next.next.next.value).to eq(0)
19
+ end
20
+ end
@@ -0,0 +1,136 @@
1
+ require 'spec_helper'
2
+ require 'tictactoe/state'
3
+ require 'tictactoe/boards/three_by_three_board'
4
+
5
+ RSpec.describe Tictactoe::State do
6
+ def look_at(state, location)
7
+ state.marks[location]
8
+ end
9
+
10
+ def expect_state(marks)
11
+ expect(@state.marks).to eq(marks)
12
+ end
13
+
14
+ def expect_finished(expected)
15
+ finished = @state.when_finished{true} || false
16
+ expect(finished).to eq(expected)
17
+ end
18
+
19
+ def expect_winner(expected)
20
+ actual_winner = @state.when_finished{|winner| winner}
21
+ expect(actual_winner).to eq(expected)
22
+ end
23
+
24
+ describe "given a 3x3 board" do
25
+ before(:each) do
26
+ @board = Tictactoe::Boards::ThreeByThreeBoard.new
27
+ @state = described_class.new(@board)
28
+ end
29
+
30
+ it "can make a move" do
31
+ next_state = @state.make_move(3, :mark)
32
+ expect(look_at(next_state, 3)).to eq(:mark)
33
+ expect(next_state.available_moves).not_to include(3)
34
+ expect(next_state.played_moves).to eq(1)
35
+ end
36
+
37
+ it "is immutable" do
38
+ @state.make_move(2, :mark)
39
+ expect(look_at(@state, 2)).to eq(nil)
40
+ expect(@state.available_moves).to include(2)
41
+ end
42
+
43
+ it 'can access to the board marks' do
44
+ expect(@state.marks).to eq([
45
+ nil, nil, nil,
46
+ nil, nil, nil,
47
+ nil, nil, nil,
48
+ ])
49
+ end
50
+
51
+ describe "with no moves" do
52
+ it "should have an empty state" do
53
+ expect_state([nil, nil, nil, nil, nil, nil, nil, nil, nil])
54
+ end
55
+
56
+ it "should not be finished" do
57
+ expect_finished(false)
58
+ end
59
+
60
+ it "should have no winner" do
61
+ expect_winner(nil)
62
+ end
63
+ end
64
+
65
+ describe "with the first move" do
66
+ before(:each) do
67
+ @loc = 0
68
+ @state = @state.make_move(@loc, :X)
69
+ end
70
+
71
+ it "should contain that move" do
72
+ expect(look_at(@state, @loc)).to eq(:X)
73
+ end
74
+ end
75
+
76
+ Tictactoe::Boards::ThreeByThreeBoard.new.lines.each do |line|
77
+ describe "with a line for player a" do
78
+ before(:each) do
79
+ line.each do |l|
80
+ @state = @state.make_move(l, :X)
81
+ end
82
+ end
83
+
84
+ it "should be finished" do
85
+ expect_finished(true)
86
+ end
87
+
88
+ it "should have won" do
89
+ expect_winner(:X)
90
+ end
91
+ end
92
+ end
93
+
94
+ def set_state(*marks)
95
+ marks.each_with_index do |mark, location|
96
+ @state = @state.make_move(location, mark)
97
+ end
98
+ end
99
+
100
+ describe "with three moves not in line for player a" do
101
+ before(:each) do
102
+ set_state(
103
+ :X, :X, nil,
104
+ :X, nil, nil,
105
+ nil, nil, nil
106
+ )
107
+ end
108
+
109
+ it "should not be finished" do
110
+ expect_finished(false)
111
+ end
112
+
113
+ it "should have no winner" do
114
+ expect_winner(nil)
115
+ end
116
+ end
117
+
118
+ describe "with a full board but no winner" do
119
+ before(:each) do
120
+ set_state(
121
+ :O, :X, :X,
122
+ :X, :X, :O,
123
+ :O, :O, :X
124
+ )
125
+ end
126
+
127
+ it "should be finished" do
128
+ expect_finished(true)
129
+ end
130
+
131
+ it "should have no winner" do
132
+ expect_winner(nil)
133
+ end
134
+ end
135
+ end
136
+ end