tictactoe-core 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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