connect_n_game 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,39 @@
1
+ # coding: utf-8
2
+
3
+ module ConnectNGame
4
+
5
+ #The EchoMoves echoes moves when possible.
6
+ class BasicMoves < Player
7
+
8
+ #Build the player
9
+ def initialize(name = "Basic")
10
+ super(name, "Minimum tactical analysis.", :silicon)
11
+ end
12
+
13
+ #Make a move. This bot picks the move with the highest score.
14
+ #<br>Parameters
15
+ #* game - the game being played.
16
+ #* piece - the piece to be played, 1 or 2.
17
+ #<br>Returns
18
+ #* A move, 1 .. rack.width
19
+ def make_move(game, piece)
20
+ (game.rack.weights.each_with_index.map do |weight, index|
21
+ channel = index + 1
22
+ [weight + game.rack.score_move(channel, piece), channel]
23
+ end).sort.show_weights("Scan").last[1]
24
+ end
25
+
26
+ #The thrill of victory.
27
+ def winners_comments
28
+ "#{name} says 'A genius in my own mind!'"
29
+ end
30
+
31
+ #The agony of defeat
32
+ def losers_comments
33
+ "#{name} says 'Hmmm... What did I miss?'"
34
+ end
35
+
36
+ end
37
+
38
+ Player.players << BasicMoves.new
39
+ end
@@ -0,0 +1,45 @@
1
+ # coding: utf-8
2
+
3
+ module ConnectNGame
4
+
5
+ #The EchoMoves echoes moves when possible.
6
+ class EchoMoves < Player
7
+
8
+ #Build the player
9
+ def initialize(name = "Echo")
10
+ super(name, "Really unoriginal.", :silicon)
11
+ end
12
+
13
+ #Make a move. This bot parrots the previous move when it can,
14
+ #and moves randomly when it cannot.
15
+ #<br>Parameters
16
+ #* game - the game being played.
17
+ #* _piece - the piece to be played, 1 or 2. (Not used here)
18
+ #<br>Returns
19
+ #* A move, 1 .. rack.width
20
+ def make_move(game, _piece)
21
+ channel = game.last_move
22
+
23
+ unless channel && !game.rack.channel_full?(channel)
24
+ begin
25
+ channel = rand(1..(game.rack.width))
26
+ end while game.rack.channel_full?(channel)
27
+ end
28
+
29
+ channel
30
+ end
31
+
32
+ #The thrill of victory.
33
+ def winners_comments
34
+ "#{name} says 'How did this happen?'"
35
+ end
36
+
37
+ #The agony of defeat
38
+ def losers_comments
39
+ "#{name} says 'Yes! I came in second!'"
40
+ end
41
+
42
+ end
43
+
44
+ Player.players << EchoMoves.new
45
+ end
@@ -0,0 +1,40 @@
1
+ # coding: utf-8
2
+
3
+ module ConnectNGame
4
+
5
+ #The JustRandom player simply make random moves.
6
+ class JustRandom < Player
7
+
8
+ #Build the player
9
+ def initialize(name = "Random")
10
+ super(name, "Moves randomly.", :silicon)
11
+ end
12
+
13
+ #Make a move. This bot moves randomly.
14
+ #<br>Parameters
15
+ #* game - the game being played.
16
+ #* _piece - the piece to be played, 1 or 2. (Not used here)
17
+ #<br>Returns
18
+ #* A move, 1 .. rack.width
19
+ def make_move(game, _piece)
20
+ begin
21
+ channel = rand(1..(game.rack.width))
22
+ end while game.rack.channel_full?(channel)
23
+
24
+ channel
25
+ end
26
+
27
+ #The thrill of victory.
28
+ def winners_comments
29
+ "#{name} says 'It was all up to pure skill!'"
30
+ end
31
+
32
+ #The agony of defeat
33
+ def losers_comments
34
+ "#{name} says 'No comment.'"
35
+ end
36
+
37
+ end
38
+
39
+ Player.players << JustRandom.new
40
+ end
@@ -0,0 +1,41 @@
1
+ # coding: utf-8
2
+
3
+ module ConnectNGame
4
+
5
+ #The EchoMoves echoes moves when possible.
6
+ class MiddleMoves < Player
7
+
8
+ #Build the player
9
+ def initialize(name = "Middle")
10
+ super(name, "Moves toward the middle", :silicon)
11
+ end
12
+
13
+ #Make a move. This bot prefers to play the middle.
14
+ #<br>Parameters
15
+ #* game - the game being played.
16
+ #* _piece - the piece to be played, 1 or 2. (Not used here)
17
+ #<br>Returns
18
+ #* A move, 1 .. rack.width
19
+ def make_move(game, _piece)
20
+ weights = game.rack.weights.each_with_index.to_a
21
+
22
+ weights.sort.reverse_each do |weight, index|
23
+ channel = index + 1
24
+ return channel unless game.rack.channel_full?(channel)
25
+ end
26
+ end
27
+
28
+ #The thrill of victory.
29
+ def winners_comments
30
+ "#{name} says 'Slow and steady wins the race!'"
31
+ end
32
+
33
+ #The agony of defeat
34
+ def losers_comments
35
+ "#{name} says 'I lost to the lunatic fringe!'"
36
+ end
37
+
38
+ end
39
+
40
+ Player.players << MiddleMoves.new
41
+ end
@@ -0,0 +1,67 @@
1
+ # coding: utf-8
2
+
3
+ module ConnectNGame
4
+
5
+ #The EchoMoves echoes moves when possible.
6
+ class Prudent < Player
7
+
8
+ #Build the player
9
+ def initialize(name = "Prudent")
10
+ super(name, "Minimum tactical analysis with some defense.", :silicon)
11
+ end
12
+
13
+ #Make a move. This bot picks the move with the highest score.
14
+ #<br>Parameters
15
+ #* game - the game being played.
16
+ #* piece - the piece to be played, 1 or 2.
17
+ #<br>Returns
18
+ #* A move, 1 .. rack.width
19
+ def make_move(game, piece)
20
+ #Compute the moves of the first ply.
21
+ first_ply = (game.rack.weights.each_with_index.map do |weight, index|
22
+ channel = index + 1
23
+ [weight + game.rack.score_move(channel, piece), channel]
24
+ end).sort.show_weights("Scan 1")
25
+
26
+ #If we're done, stop.
27
+ return first_ply.last[1] if first_ply.last[0] >= game.rack.order
28
+
29
+ #Factor in the behavior of the opponent.
30
+ (0...(first_ply.length)).each do |index|
31
+ copy = game.rack.clone.deep_clone
32
+
33
+ copy.rack[first_ply[index][1]-1] << piece
34
+ first_ply[index][0] -= check_opponent(copy, (piece % 2) + 1)
35
+ end
36
+
37
+ first_ply.sort.show_weights("Scan 2").last[1]
38
+ end
39
+
40
+ #Check for the opponent's best moves at this level
41
+ def check_opponent(rack, piece)
42
+ threshold = rack.order - 1
43
+ result = 0
44
+
45
+ (1..rack.width).each do |channel|
46
+ score = rack.score_move(channel, piece)
47
+ score *= 2 if score > threshold
48
+ result += score if score >= threshold
49
+ end
50
+
51
+ result
52
+ end
53
+
54
+ #The thrill of victory.
55
+ def winners_comments
56
+ "#{name} says 'Good moves must give way to better!'"
57
+ end
58
+
59
+ #The agony of defeat
60
+ def losers_comments
61
+ "#{name} says 'How could I have missed this?'"
62
+ end
63
+
64
+ end
65
+
66
+ Player.players << Prudent.new
67
+ end
@@ -0,0 +1,9 @@
1
+ # coding: utf-8
2
+
3
+ module ConnectNGame
4
+
5
+ #The abstract \UI (User Interface) class of the connect_n_game.
6
+ class UI
7
+ end
8
+
9
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+
3
+ #A small utility module
4
+ module Utl
5
+ #An array with some letters in it and a place holder at zero.
6
+ Letters = %w(x A B C D E F G H I J K L M N O P Q R S T U V W X Y Z)
7
+
8
+ #Convert a channel name to its number.
9
+ #<br>Parameters
10
+ #* letter - the letter that was played.
11
+ #<br>Returns
12
+ #* The channel number (1..26) or nil if invalid.
13
+ def self.name_to_channel(letter)
14
+ Letters.find_index(letter[0].upcase)
15
+ end
16
+
17
+ #Convert a channel number to its name.
18
+ #<br>Parameters
19
+ #* channel - the channel number (1..26),
20
+ #<br>Returns
21
+ #* The channel letter.
22
+ def self.channel_to_name(channel)
23
+ Letters[channel]
24
+ end
25
+
26
+ end
@@ -0,0 +1,5 @@
1
+
2
+ module ConnectNGame
3
+ #The version of the Connect N \Game.
4
+ VERSION = "0.0.1"
5
+ end
@@ -0,0 +1,53 @@
1
+ # coding: utf-8
2
+
3
+ require_relative '../lib/connect_n_game'
4
+ gem 'minitest'
5
+ require 'minitest/autorun'
6
+
7
+ #Test the standard fOOrth library.
8
+ class GameTester < Minitest::Test
9
+
10
+ #Test that this test was run!
11
+ def test_dummy
12
+ assert_equal(ConnectNGame::VERSION.class, String)
13
+ end
14
+
15
+ #Test that it knows how to play.
16
+ def test_taking_turns
17
+ pl1 = ConnectNGame::Player.new("A", "OK", :silicon)
18
+ pl2 = ConnectNGame::Player.new("B", "GOOD", :carbon)
19
+
20
+ game = ConnectNGame::Game.new(pl1, pl2).game_initialize
21
+
22
+ assert_equal(4, game.rack.order)
23
+ assert_equal(pl1, game.current_player)
24
+ assert_equal(:continue, game.next_move)
25
+ assert_equal(1, game.turn)
26
+
27
+ assert_equal(pl2, game.current_player)
28
+ assert_equal(:continue, game.next_move)
29
+ assert_equal(2, game.turn)
30
+
31
+ assert_equal(pl1, game.current_player)
32
+ assert_equal(:continue, game.next_move)
33
+ assert_equal(3, game.turn)
34
+
35
+ assert_equal(pl2, game.current_player)
36
+ assert_equal(:continue, game.next_move)
37
+ assert_equal(4, game.turn)
38
+
39
+ assert_equal(pl1, game.current_player)
40
+ assert_equal(:continue, game.next_move)
41
+ assert_equal(5, game.turn)
42
+
43
+ assert_equal(pl2, game.current_player)
44
+ assert_equal(:continue, game.next_move)
45
+ assert_equal(6, game.turn)
46
+
47
+ assert_equal(pl1, game.current_player)
48
+ assert_equal(:victory, game.next_move)
49
+ assert_equal(7, game.turn)
50
+ end
51
+
52
+
53
+ end
@@ -0,0 +1,20 @@
1
+ # coding: utf-8
2
+
3
+ require_relative '../lib/connect_n_game'
4
+ gem 'minitest'
5
+ require 'minitest/autorun'
6
+
7
+ #Test the standard fOOrth library.
8
+ class ConnectNGameTester < Minitest::Test
9
+
10
+ #Test that all the pieces are in the box!
11
+ def test_modules_and_classes_exist
12
+ assert_equal(ConnectNGame.class, Module)
13
+ assert_equal(ConnectNGame::VERSION.class, String)
14
+ assert_equal(ConnectNGame::Game.class, Class)
15
+ assert_equal(ConnectNGame::Player.class, Class)
16
+ assert_equal(ConnectNGame::UI.class, Class)
17
+ end
18
+
19
+
20
+ end
@@ -0,0 +1,55 @@
1
+ # coding: utf-8
2
+
3
+ require_relative '../lib/connect_n_game'
4
+ gem 'minitest'
5
+ require 'minitest/autorun'
6
+
7
+ #Test the standard fOOrth library.
8
+ class PlayerTester < Minitest::Test
9
+
10
+ #Test that this test was run!
11
+ def test_dummy
12
+ assert_equal(String, ConnectNGame::VERSION.class)
13
+ end
14
+
15
+ def test_abstract_behaviours
16
+ #It should have the attributes of name, description, and type.
17
+ atp = ConnectNGame::Player.new("Ted", "Cool", :carbon)
18
+ assert_equal("Ted", atp.name)
19
+ assert_equal("Cool", atp.description)
20
+ assert_equal(:carbon, atp.type)
21
+ assert(atp.carbon?)
22
+ refute(atp.silicon?)
23
+
24
+ #It should reject invalid player types.
25
+ assert_raises { ConnectNGame::Player.new("Ted", "Cool", :germanium) }
26
+ end
27
+
28
+ #We should be able to sort players alphabetically by name.
29
+ def test_that_it_is_sortable
30
+ aop = []
31
+ aop << ConnectNGame::Player.new("Ted", "Cool", :carbon)
32
+ aop << ConnectNGame::Player.new("apple", "Cooler", :silicon)
33
+ aop << ConnectNGame::Player.new("Ed", "Coolest", :carbon)
34
+
35
+ aop.sort! {|a,b| a.name.casecmp(b.name) }
36
+
37
+ assert_equal("apple", aop[0].name)
38
+ assert(aop[0].silicon?)
39
+ refute(aop[0].carbon?)
40
+
41
+ assert_equal("Ed", aop[1].name)
42
+ refute(aop[1].silicon?)
43
+ assert(aop[1].carbon?)
44
+
45
+ assert_equal("Ted", aop[2].name)
46
+ refute(aop[2].silicon?)
47
+ assert(aop[2].carbon?)
48
+ end
49
+
50
+ #Test that it loads up available players.
51
+ def test_player_loading
52
+ assert_equal(Array, ConnectNGame::Player.players.class)
53
+ end
54
+
55
+ end
@@ -0,0 +1,222 @@
1
+ # coding: utf-8
2
+
3
+ require_relative '../lib/connect_n_game'
4
+ gem 'minitest'
5
+ require 'minitest/autorun'
6
+
7
+ #Test the standard fOOrth library.
8
+ class RackTester < Minitest::Test
9
+
10
+ #Test that this test was run!
11
+ def test_dummy
12
+ assert_equal(ConnectNGame::VERSION.class, String)
13
+ end
14
+
15
+ #Test that we can create racks.
16
+ def test_creating_racks
17
+ assert_raises { ConnectNGame::Rack.new(3) }
18
+
19
+ tr = ConnectNGame::Rack.new(4)
20
+ assert_equal(4, tr.order)
21
+ assert_equal(6, tr.depth)
22
+ assert_equal(7, tr.width)
23
+ assert_equal([[],[],[],[],[],[],[]], tr.rack)
24
+ assert_equal(%w(A B C D E F G), tr.channel_names)
25
+ assert_equal([0.0078125, 0.03125, 0.125, 0.5, 0.25, 0.0625,
26
+ 0.015625], tr.weights)
27
+
28
+ tr = ConnectNGame::Rack.new(5)
29
+ assert_equal(5, tr.order)
30
+ assert_equal(7, tr.depth)
31
+ assert_equal(9, tr.width)
32
+ assert_equal([[],[],[],[],[],[],[],[],[]], tr.rack)
33
+ assert_equal(%w(A B C D E F G H I), tr.channel_names)
34
+ assert_equal([0.001953125, 0.0078125, 0.03125, 0.125, 0.5,
35
+ 0.25, 0.0625, 0.015625, 0.00390625], tr.weights)
36
+
37
+ tr = ConnectNGame::Rack.new(6)
38
+ assert_equal(6, tr.order)
39
+ assert_equal(9, tr.depth)
40
+ assert_equal(11, tr.width)
41
+ assert_equal([[],[],[],[],[],[],[],[],[],[],[]], tr.rack)
42
+ assert_equal(%w(A B C D E F G H I J K), tr.channel_names)
43
+ assert_equal([0.00048828125, 0.001953125, 0.0078125, 0.03125,
44
+ 0.125, 0.5, 0.25, 0.0625, 0.015625, 0.00390625,
45
+ 0.0009765625], tr.weights)
46
+
47
+ tr = ConnectNGame::Rack.new(7)
48
+ assert_equal(7, tr.order)
49
+ assert_equal(10, tr.depth)
50
+ assert_equal(11, tr.width)
51
+ assert_equal([[],[],[],[],[],[],[],[],[],[],[]], tr.rack)
52
+ assert_equal(%w(A B C D E F G H I J K), tr.channel_names)
53
+ assert_equal([0.00048828125, 0.001953125, 0.0078125, 0.03125,
54
+ 0.125, 0.5, 0.25, 0.0625, 0.015625, 0.00390625,
55
+ 0.0009765625], tr.weights)
56
+
57
+ tr = ConnectNGame::Rack.new(8)
58
+ assert_equal(8, tr.order)
59
+ assert_equal(12, tr.depth)
60
+ assert_equal(13, tr.width)
61
+ assert_equal([[],[],[],[],[],[],[],[],[],[],[],[],[]], tr.rack)
62
+ assert_equal(%w(A B C D E F G H I J K L M), tr.channel_names)
63
+ assert_equal([0.0001220703125, 0.00048828125, 0.001953125,
64
+ 0.0078125, 0.03125, 0.125, 0.5, 0.25, 0.0625,
65
+ 0.015625, 0.00390625, 0.0009765625,
66
+ 0.000244140625], tr.weights)
67
+
68
+ assert_raises { ConnectNGame::Rack.new(9) }
69
+ end
70
+
71
+ #Test that it can retrieve channels
72
+ def test_get_channel
73
+ (4..8).each do |order|
74
+ tr = ConnectNGame::Rack.new(order)
75
+
76
+ (1..(tr.width)).each do |ch|
77
+ assert_equal([], tr.get_channel(ch))
78
+ end
79
+ end
80
+ end
81
+
82
+ #Test that it knows when a channel is full
83
+ def test_channel_and_rack_full
84
+ (4..8).each do |order|
85
+ tr = ConnectNGame::Rack.new(order)
86
+
87
+ #The channels are being filled.
88
+ (1..tr.depth).each do | _depth |
89
+
90
+ refute(tr.rack_full?)
91
+
92
+ (1..tr.width).each do | channel |
93
+
94
+ refute(tr.channel_full?(channel))
95
+
96
+ tr.get_channel(channel) << 1
97
+ end
98
+ end
99
+
100
+ #Now all the channels should be full.
101
+ (1..tr.width).each do | channel |
102
+ assert(tr.channel_full?(channel))
103
+ end
104
+
105
+ assert(tr.rack_full?)
106
+ end
107
+ end
108
+
109
+ #Test that we can query individual cells
110
+ def test_get_cell
111
+ tr = ConnectNGame::Rack.new(4)
112
+ assert_equal(nil, tr.get_cell(1,1))
113
+ assert_equal(nil, tr.get_cell(1,2))
114
+ assert_equal(nil, tr.get_cell(1,3))
115
+
116
+ tr.get_channel(1) << 1
117
+ assert_equal(1, tr.get_cell(1,1))
118
+ assert_equal(nil, tr.get_cell(1,2))
119
+ assert_equal(nil, tr.get_cell(1,3))
120
+
121
+ tr.get_channel(1) << 2
122
+ assert_equal(1, tr.get_cell(1,1))
123
+ assert_equal(2, tr.get_cell(1,2))
124
+ assert_equal(nil, tr.get_cell(1,3))
125
+
126
+ tr.get_channel(1) << 1
127
+ assert_equal(1, tr.get_cell(1,1))
128
+ assert_equal(2, tr.get_cell(1,2))
129
+ assert_equal(1, tr.get_cell(1,3))
130
+ end
131
+
132
+ #Test that we can play a channel.
133
+ def test_play_channel
134
+ tr = ConnectNGame::Rack.new(4)
135
+
136
+ assert_equal(1, tr.play_channel(1, 1))
137
+ assert_equal(2, tr.play_channel(1, 1))
138
+ assert_equal(3, tr.play_channel(1, 1))
139
+ assert_equal(4, tr.play_channel(1, 1))
140
+ assert_equal(5, tr.play_channel(1, 1))
141
+ assert_equal(6, tr.play_channel(1, 1))
142
+
143
+ #Playing a full channel has no effect or score.
144
+ assert_equal(6, tr.get_channel(1).length)
145
+ assert_equal(-9, tr.play_channel(1, 1))
146
+ assert_equal(6, tr.get_channel(1).length)
147
+ end
148
+
149
+ #Test getting the free row for a channel
150
+ def test_channel_to_row
151
+ tr = ConnectNGame::Rack.new(4)
152
+
153
+ assert_equal(1, tr.channel_to_row(1))
154
+
155
+ tr.play_channel(1,1)
156
+ assert_equal(2, tr.channel_to_row(1))
157
+
158
+ tr.play_channel(1,1)
159
+ assert_equal(3, tr.channel_to_row(1))
160
+
161
+ tr.play_channel(1,1)
162
+ assert_equal(4, tr.channel_to_row(1))
163
+
164
+ tr.play_channel(1,1)
165
+ assert_equal(5, tr.channel_to_row(1))
166
+
167
+ tr.play_channel(1,1)
168
+ assert_equal(6, tr.channel_to_row(1))
169
+
170
+ tr.play_channel(1,1)
171
+ assert_equal(nil, tr.channel_to_row(1))
172
+ end
173
+
174
+ #Test that we can count cells around a position.
175
+ def test_score_move
176
+ tr = ConnectNGame::Rack.new(4)
177
+ #.......
178
+ #.......
179
+ #.......
180
+ #.......
181
+
182
+ assert_equal(1, tr.play_channel(3,1))
183
+ #.......
184
+ #.......
185
+ #.......
186
+ #..X....
187
+
188
+ assert_equal(2, tr.play_channel(4,1))
189
+ #.......
190
+ #.......
191
+ #.......
192
+ #..XX...
193
+
194
+ assert_equal(2, tr.play_channel(4,1))
195
+ #.......
196
+ #.......
197
+ #...X...
198
+ #..XX...
199
+
200
+ assert_equal(2, tr.play_channel(3,1))
201
+ #.......
202
+ #.......
203
+ #..XX...
204
+ #..XX...
205
+
206
+ assert_equal(3, tr.play_channel(3,1))
207
+ #.......
208
+ #..X....
209
+ #..XX...
210
+ #..XX...
211
+
212
+ assert_equal(4, tr.play_channel(3,1))
213
+ #..X....
214
+ #..X....
215
+ #..XX...
216
+ #..XX...
217
+
218
+ #It should not hang either!
219
+ assert_raises { tr.score_move(3, nil) }
220
+ end
221
+
222
+ end