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,54 @@
1
+ # coding: utf-8
2
+ #Display and select player options.
3
+
4
+ module ConnectNGame
5
+
6
+ class CLI
7
+
8
+ #Select all new players
9
+ def select_new_players
10
+ @players = []
11
+ top_up_players
12
+ end
13
+
14
+ #Make sure we have two players
15
+ def top_up_players
16
+ pick_a_player while @players.length < 2
17
+ end
18
+
19
+ #Have the user pick a player.
20
+ def pick_a_player
21
+ begin
22
+ show_players
23
+ print "\nEnter player #{@players.length+1} name: "
24
+ input = gets.strip
25
+ player = find_player(input)
26
+ puts "invalid entry #{input.inspect}" unless player
27
+ end until player
28
+
29
+ @players << player
30
+ end
31
+
32
+ #Display the available players
33
+ def show_players
34
+ puts
35
+ puts "Player Selection: "
36
+
37
+ width = (Player.players.map do |player|
38
+ player.name.length
39
+ end).max
40
+
41
+ Player.players.each do |player|
42
+ puts " #{player.name.ljust(width+1)} #{player.description}"
43
+ end
44
+ end
45
+
46
+ #Find the selected player.
47
+ def find_player(arg)
48
+ Player.players.find do |player|
49
+ player.name.downcase == arg.downcase
50
+ end
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,13 @@
1
+ # coding: utf-8
2
+
3
+ require_relative 'connect_n_game/util'
4
+ require_relative 'connect_n_game/exceptions'
5
+ require_relative 'connect_n_game/game'
6
+ require_relative 'connect_n_game/player'
7
+ require_relative 'connect_n_game/ui'
8
+ require_relative 'connect_n_game/version'
9
+
10
+ #The Connect N \Game main module
11
+ module ConnectNGame
12
+ #WIP
13
+ end
@@ -0,0 +1,8 @@
1
+ # coding: utf-8
2
+
3
+ module ConnectNGame
4
+
5
+ #An invalid move was made.
6
+ class GameInvalidMove < StandardError; end
7
+
8
+ end
@@ -0,0 +1,93 @@
1
+ # coding: utf-8
2
+
3
+ require_relative 'game/rack.rb'
4
+
5
+ module ConnectNGame
6
+
7
+ #The \Game class of the connect_n_game.
8
+ #<br>Responsibility
9
+ #* This class is responsible for the overall operation of the game. It holds
10
+ # the playing "rack", and the players. It also keeps a log of moves,
11
+ # determines if the game has been won or if a stalemate has occurred.
12
+ #<br>Notes
13
+ #* The \Game class does NOT interact with the outside world. That is the
14
+ # job of the UI object.
15
+ class Game
16
+
17
+ #The \game playing surface
18
+ attr_reader :rack
19
+
20
+ #Whose playing?
21
+ attr_reader :players
22
+
23
+ #The play log.
24
+ attr_reader :log
25
+
26
+ #The current player number.
27
+ attr_reader :current
28
+
29
+ #The last played turn number.
30
+ attr_reader :turn
31
+
32
+ #Create an instance of a \game object.
33
+ #<br>Parameters
34
+ #* player_ex - The player who moves first
35
+ #* payer_oh - The player who moves next
36
+ #* game_size - The size of the game connection (4..8). Default: 4
37
+ #<br>Returns
38
+ #* An instance of a \Game.
39
+ def initialize(player_ex, player_oh, game_size=4)
40
+ @game_size = game_size
41
+ #Set up player related data.
42
+ @players = { 1 => player_ex, 2 => player_oh }
43
+ end
44
+
45
+ #What player moves next?
46
+ #<br>Returns
47
+ #* An instance of a \Player.
48
+ def current_player
49
+ players[current]
50
+ end
51
+
52
+ def previous_player
53
+ players[(@current % 2) + 1]
54
+ end
55
+
56
+ #Get ready to start a game
57
+ def game_initialize
58
+ #Set up game play data.
59
+ @turn = 0
60
+ @current = 1
61
+ @rack = Rack.new(@game_size)
62
+ @log = []
63
+
64
+ self
65
+ end
66
+
67
+ #Play the next move!
68
+ #<br>Returns
69
+ #* :victory, :stalemate, :continue, or :invalid_move
70
+ def next_move
71
+ channel = current_player.make_move(self, current)
72
+ score = rack.play_channel(channel, current)
73
+ @log << channel
74
+ @turn += 1
75
+
76
+ if score >= rack.order
77
+ :victory
78
+ elsif rack.rack_full?
79
+ :stalemate
80
+ else
81
+ @current = (@current % 2) + 1
82
+ :continue
83
+ end
84
+ end
85
+
86
+ #What was the last move?
87
+ def last_move
88
+ log[-1]
89
+ end
90
+
91
+ end
92
+
93
+ end
@@ -0,0 +1,180 @@
1
+ # coding: utf-8
2
+
3
+ module ConnectNGame
4
+
5
+ #The class of game racks. That is a matrix of cells
6
+ class Rack
7
+
8
+ #The number in a row needed for victory.
9
+ attr_reader :order
10
+
11
+ #The depth of the playable area.
12
+ attr_reader :depth
13
+
14
+ #The width of the playable area.
15
+ attr_reader :width
16
+
17
+ #The raw playable area.
18
+ attr_reader :rack
19
+
20
+ #The names of the channels
21
+ attr_reader :channel_names
22
+
23
+ #The column weight values.
24
+ attr_reader :weights
25
+
26
+ #Create a rack of the appropriate order.
27
+ #<br>Parameters
28
+ #* order - The order of the game, that is the winning
29
+ # number of pieces in a row.
30
+ def initialize(order)
31
+ unless (4..8).include?(order)
32
+ fail "Invalid game dimension #{order} not (4 .. 8)"
33
+ end
34
+
35
+ @order = order
36
+ @depth = @order + (order >> 1)
37
+ @width = @depth + (@depth.odd? ? 2 : 1)
38
+
39
+ @weights = [0.5]
40
+ weight = 4
41
+
42
+ (@width/2).times do
43
+ @weights << 1.0/weight
44
+ weight *= 2
45
+ @weights.insert(0, 1.0/weight)
46
+ weight *= 2
47
+ end
48
+
49
+ @channel_names = %w(A B C D E F G H I J K L M).first(width)
50
+
51
+ @rack = Array.new(@width) { [ ] }
52
+ end
53
+
54
+ #Get the required play channel
55
+ #<br>Parameters
56
+ #* channel - The channel number 1 .. width
57
+ #<br>Returns
58
+ #* The requested channel (array) or nil for invalid channels.
59
+ def get_channel(channel)
60
+ rack[channel-1]
61
+ end
62
+
63
+ #Is the selected channel full?
64
+ #<br>Parameters
65
+ #* channel - The channel number 1 .. width
66
+ #<br>Returns
67
+ #* true if full else false.
68
+ def channel_full?(channel)
69
+ get_channel(channel).length >= depth
70
+ end
71
+
72
+ #Is the rack full?
73
+ #<br>Returns
74
+ #* true if full (or invalid) else false.
75
+ def rack_full?
76
+ rack.each do |channel|
77
+ return false if channel.length < depth
78
+ end
79
+
80
+ true
81
+ end
82
+
83
+ #Get the specified cell.
84
+ #<br>Parameters
85
+ #* channel - The channel number 1 .. width
86
+ #* row - The row number 1 .. depth
87
+ #<br>Returns
88
+ #* The contents of the cell or nil
89
+ def get_cell(channel, row)
90
+ return nil unless valid_channel?(channel)
91
+ return nil unless valid_row?(row)
92
+
93
+ rack[channel-1][row-1]
94
+ end
95
+
96
+ #Play a specified channel.
97
+ #<br>Parameters
98
+ #* channel - The channel number 1 .. width
99
+ #* piece - The piece to be played.
100
+ #<br>Returns
101
+ #* The score of the move or raises \GameInvalidMove exception.
102
+ def play_channel(channel, piece)
103
+ score = score_move(channel, piece)
104
+ rack[channel-1] << piece if score > 0
105
+ score
106
+ end
107
+
108
+ #Determine the score obtained for moving to a specified channel
109
+ #<br>Parameters
110
+ #* channel - The channel number 1 .. width
111
+ #* piece - The piece to be played.
112
+ #<br>Returns
113
+ #* The score for that play 0 .. n
114
+ def score_move(channel, piece)
115
+ return -9 if channel_full?(channel)
116
+
117
+ row = channel_to_row(channel)
118
+
119
+ ([[0,1], [1,1], [1,0], [1,-1]].map do |delta|
120
+ dx, dy = delta
121
+ count_pieces(channel, row, dx, dy, piece) + 1 +
122
+ count_pieces(channel, row, -dx, -dy, piece)
123
+ end).max
124
+ end
125
+
126
+ #Count the pieces along the designated path.
127
+ #<br>Parameters
128
+ #* channel - The starting channel
129
+ #* row - The starting row
130
+ #* dx - The channel step value
131
+ #* dy - The row step value
132
+ #* piece - The piece we are looking for.
133
+ #<br>Returns
134
+ #* The score for that play 0 .. n
135
+ def count_pieces(channel, row, dx, dy, piece)
136
+ result = 0
137
+
138
+ while result < width
139
+ channel, row = channel+dx, row+dy
140
+
141
+ return result unless piece == get_cell(channel, row)
142
+
143
+ result += 1
144
+ end
145
+
146
+ fail "Looping error"
147
+ end
148
+
149
+ #Get the free row for the specified channel.
150
+ #<br>Parameters
151
+ #* channel - The channel number 1 .. width
152
+ #<br>Returns
153
+ #* The row number or nil.
154
+ def channel_to_row(channel)
155
+ channel_full?(channel) ? nil : rack[channel-1].length + 1
156
+ end
157
+
158
+ #Is this a valid channel?
159
+ def valid_channel?(channel)
160
+ (1..width).include?(channel)
161
+ end
162
+
163
+ #Is this a valid row?
164
+ def valid_row?(row)
165
+ (1..depth).include?(row)
166
+ end
167
+
168
+ #Clone the internal array.
169
+ def deep_clone
170
+ @rack = @rack.clone
171
+
172
+ (0...@width).each do |index|
173
+ @rack[index] = @rack[index].clone
174
+ end
175
+
176
+ self
177
+ end
178
+ end
179
+
180
+ end
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+
3
+ module ConnectNGame
4
+
5
+ #The Human player simply makes carbon based moves.
6
+ class Human < Player
7
+
8
+ #Build the player
9
+ def initialize(name = "Human")
10
+ super(name, "An actual player.", :carbon)
11
+ end
12
+
13
+ #Move generation is part of the user interface.
14
+
15
+ #The thrill of victory.
16
+ def winners_comments
17
+ "Congratulations #{name}! You're our winner today!"
18
+ end
19
+
20
+ #The agony of defeat
21
+ def losers_comments
22
+ "Too bad #{name}, you lose. Hang your head in shame."
23
+ end
24
+
25
+ end
26
+
27
+ Player.players << Human.new
28
+ Player.players << Human.new("Bruce")
29
+ Player.players << Human.new("Sheila")
30
+ end
@@ -0,0 +1,70 @@
1
+ # coding: utf-8
2
+
3
+ module ConnectNGame
4
+
5
+ #The abstract Player class of the connect_n_game.
6
+ #<br>Responsibility
7
+ #* This mostly abstract class is responsible for specifying the shared
8
+ # behavior of all the sorts of players, both human and automaton. This is
9
+ # where moves are generated, victory celebrated, and defeat mourned.
10
+ class Player
11
+ #Set up class instance data.
12
+ @players = []
13
+
14
+ class << self
15
+ #The list of available players.
16
+ attr_reader :players
17
+ end
18
+
19
+ # The name of this player.
20
+ attr_reader :name
21
+
22
+ # The description of this player.
23
+ attr_reader :description
24
+
25
+ #The type of this player: :carbon or :silicon
26
+ attr_reader :type
27
+
28
+ #Do the common player setup
29
+ #<br>Parameters
30
+ #* name - The name of the player.
31
+ #* description - A witty description of the player.
32
+ #* type - :carbon or :silicon
33
+ #<br>Returns
34
+ #* An instance of \Player
35
+ def initialize(name, description, type)
36
+ fail "Invalid type #{type}" unless [:carbon, :silicon].include?(type)
37
+ @name, @description, @type = name, description, type
38
+ end
39
+
40
+ #Is this an automaton?
41
+ def silicon?
42
+ type == :silicon
43
+ end
44
+
45
+ #Is this an organic life-form?
46
+ def carbon?
47
+ type == :carbon
48
+ end
49
+
50
+ #Make a move. This is dummy code for testing only.
51
+ #<br>Parameters
52
+ #* _game - the game being played - not used here.
53
+ #* piece - the piece to be played, 1 or 2.
54
+ #<br>Returns
55
+ #* A move, 1 .. rack.width
56
+ def make_move(_game, piece)
57
+ piece
58
+ end
59
+
60
+ end
61
+
62
+ end
63
+
64
+ require_relative 'human' #Humans come first!!!
65
+
66
+ #Load up the players!
67
+ Dir[File.dirname(__FILE__) + '/players/*.rb'].each {|file| require file }
68
+
69
+ #Sort them
70
+ ConnectNGame::Player.players.sort! {|a,b| a.name.casecmp(b.name) }