connect_n_game 0.0.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.
@@ -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