bashrw_ttt 0.1.0
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.
- data/bin/play.rb +10 -0
- data/lib/ttt/ai.rb +33 -0
- data/lib/ttt/ai_easy.rb +17 -0
- data/lib/ttt/ai_hard.rb +21 -0
- data/lib/ttt/ai_medium.rb +21 -0
- data/lib/ttt/ai_medium_back.rb +58 -0
- data/lib/ttt/board.rb +37 -0
- data/lib/ttt/config_helper.rb +53 -0
- data/lib/ttt/config_options.rb +27 -0
- data/lib/ttt/context.rb +214 -0
- data/lib/ttt/four_by_four.rb +62 -0
- data/lib/ttt/game.rb +94 -0
- data/lib/ttt/game_history.rb +37 -0
- data/lib/ttt/game_interactor.rb +126 -0
- data/lib/ttt/human.rb +12 -0
- data/lib/ttt/interfaces/cli/lib/cli/board_selection.rb +34 -0
- data/lib/ttt/interfaces/cli/lib/cli/cli_game.rb +186 -0
- data/lib/ttt/interfaces/cli/lib/cli/cli_presenter.rb +216 -0
- data/lib/ttt/interfaces/cli/lib/cli/clio.rb +17 -0
- data/lib/ttt/interfaces/cli/lib/cli/play_again.rb +38 -0
- data/lib/ttt/interfaces/cli/lib/cli/player_selection.rb +43 -0
- data/lib/ttt/interfaces/cli/lib/cli/selection.rb +8 -0
- data/lib/ttt/interfaces/cli/lib/cli/walk_history.rb +48 -0
- data/lib/ttt/interfaces/cli/spec/cli/board_selection_spec.rb +29 -0
- data/lib/ttt/interfaces/cli/spec/cli/cli_game_spec.rb +376 -0
- data/lib/ttt/interfaces/cli/spec/cli/cli_presenter_spec.rb +147 -0
- data/lib/ttt/interfaces/cli/spec/cli/play_again_spec.rb +39 -0
- data/lib/ttt/interfaces/cli/spec/cli/player_selection_spec.rb +62 -0
- data/lib/ttt/interfaces/cli/spec/cli/walk_history_spec.rb +115 -0
- data/lib/ttt/interfaces/cli/spec/spec_helper.rb +14 -0
- data/lib/ttt/interfaces/limelight/four_by_four/props.rb +1 -0
- data/lib/ttt/interfaces/limelight/four_by_four/styles.rb +74 -0
- data/lib/ttt/interfaces/limelight/four_by_four_finished/props.rb +1 -0
- data/lib/ttt/interfaces/limelight/four_by_four_finished/styles.rb +74 -0
- data/lib/ttt/interfaces/limelight/game_list/players/load_button.rb +10 -0
- data/lib/ttt/interfaces/limelight/game_list/props.rb +17 -0
- data/lib/ttt/interfaces/limelight/game_list/styles.rb +8 -0
- data/lib/ttt/interfaces/limelight/main_menu/players/exit_ttt.rb +3 -0
- data/lib/ttt/interfaces/limelight/main_menu/players/load_game.rb +3 -0
- data/lib/ttt/interfaces/limelight/main_menu/players/new_game.rb +3 -0
- data/lib/ttt/interfaces/limelight/main_menu/props.rb +17 -0
- data/lib/ttt/interfaces/limelight/new_game/players/board_type.rb +1 -0
- data/lib/ttt/interfaces/limelight/new_game/players/new_game.rb +5 -0
- data/lib/ttt/interfaces/limelight/new_game/players/player_type.rb +1 -0
- data/lib/ttt/interfaces/limelight/new_game/players/setup_button.rb +21 -0
- data/lib/ttt/interfaces/limelight/new_game/props.rb +21 -0
- data/lib/ttt/interfaces/limelight/new_game/styles.rb +23 -0
- data/lib/ttt/interfaces/limelight/partials/board_partial.rb +14 -0
- data/lib/ttt/interfaces/limelight/partials/menu_button_partial.rb +5 -0
- data/lib/ttt/interfaces/limelight/partials/move_history_partial.rb +6 -0
- data/lib/ttt/interfaces/limelight/partials/title_history_partial.rb +10 -0
- data/lib/ttt/interfaces/limelight/players/board.rb +3 -0
- data/lib/ttt/interfaces/limelight/players/game.rb +4 -0
- data/lib/ttt/interfaces/limelight/players/generic_move_history.rb +5 -0
- data/lib/ttt/interfaces/limelight/players/generic_player.rb +8 -0
- data/lib/ttt/interfaces/limelight/players/left_button.rb +3 -0
- data/lib/ttt/interfaces/limelight/players/main_menu.rb +3 -0
- data/lib/ttt/interfaces/limelight/players/right_button.rb +3 -0
- data/lib/ttt/interfaces/limelight/playscripts/game_playscript.rb +169 -0
- data/lib/ttt/interfaces/limelight/production.rb +14 -0
- data/lib/ttt/interfaces/limelight/spec/game_playscript_spec.rb +218 -0
- data/lib/ttt/interfaces/limelight/spec/main_menu/main_menu_spec.rb +38 -0
- data/lib/ttt/interfaces/limelight/spec/new_game/new_game_spec.rb +45 -0
- data/lib/ttt/interfaces/limelight/spec/spec_helper.rb +8 -0
- data/lib/ttt/interfaces/limelight/stages.rb +6 -0
- data/lib/ttt/interfaces/limelight/styles.rb +119 -0
- data/lib/ttt/interfaces/limelight/three_by_three/props.rb +1 -0
- data/lib/ttt/interfaces/limelight/three_by_three/styles.rb +39 -0
- data/lib/ttt/interfaces/limelight/three_by_three_by_three/props.rb +1 -0
- data/lib/ttt/interfaces/limelight/three_by_three_by_three/styles.rb +119 -0
- data/lib/ttt/interfaces/limelight/three_by_three_by_three_finished/props.rb +1 -0
- data/lib/ttt/interfaces/limelight/three_by_three_by_three_finished/styles.rb +119 -0
- data/lib/ttt/interfaces/limelight/three_by_three_finished/props.rb +1 -0
- data/lib/ttt/interfaces/limelight/three_by_three_finished/styles.rb +39 -0
- data/lib/ttt/interfaces/rails/app/controllers/application_controller.rb +3 -0
- data/lib/ttt/interfaces/rails/app/controllers/ttt_games_controller.rb +86 -0
- data/lib/ttt/interfaces/rails/app/helpers/application_helper.rb +2 -0
- data/lib/ttt/interfaces/rails/app/models/ttt_game.rb +34 -0
- data/lib/ttt/interfaces/rails/app/presenters/web_game_presenter.rb +159 -0
- data/lib/ttt/interfaces/rails/autotest/discover.rb +1 -0
- data/lib/ttt/interfaces/rails/config/application.rb +67 -0
- data/lib/ttt/interfaces/rails/config/boot.rb +6 -0
- data/lib/ttt/interfaces/rails/config/environment.rb +5 -0
- data/lib/ttt/interfaces/rails/config/environments/development.rb +37 -0
- data/lib/ttt/interfaces/rails/config/environments/production.rb +67 -0
- data/lib/ttt/interfaces/rails/config/environments/test.rb +37 -0
- data/lib/ttt/interfaces/rails/config/initializers/backtrace_silencers.rb +7 -0
- data/lib/ttt/interfaces/rails/config/initializers/inflections.rb +15 -0
- data/lib/ttt/interfaces/rails/config/initializers/mime_types.rb +5 -0
- data/lib/ttt/interfaces/rails/config/initializers/secret_token.rb +7 -0
- data/lib/ttt/interfaces/rails/config/initializers/session_store.rb +8 -0
- data/lib/ttt/interfaces/rails/config/initializers/wrap_parameters.rb +14 -0
- data/lib/ttt/interfaces/rails/config/routes.rb +49 -0
- data/lib/ttt/interfaces/rails/db/schema.rb +28 -0
- data/lib/ttt/interfaces/rails/db/seeds.rb +7 -0
- data/lib/ttt/interfaces/rails/spec/controllers/ttt_games_controller_spec.rb +211 -0
- data/lib/ttt/interfaces/rails/spec/models/ttt_game_spec.rb +76 -0
- data/lib/ttt/interfaces/rails/spec/presenters/web_game_presenter_spec.rb +52 -0
- data/lib/ttt/interfaces/rails/spec/spec_helper.rb +13 -0
- data/lib/ttt/interfaces/web_interface/html_generator.rb +76 -0
- data/lib/ttt/interfaces/web_interface/web_game_presenter.rb +0 -0
- data/lib/ttt/minimax.rb +46 -0
- data/lib/ttt/move_history.rb +10 -0
- data/lib/ttt/move_traverser.rb +49 -0
- data/lib/ttt/player.rb +15 -0
- data/lib/ttt/riak_db.rb +53 -0
- data/lib/ttt/setup.rb +59 -0
- data/lib/ttt/three_by_three.rb +35 -0
- data/lib/ttt/three_by_three_by_three.rb +37 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/ttt/ai_easy_spec.rb +15 -0
- data/spec/ttt/ai_hard_spec.rb +76 -0
- data/spec/ttt/ai_medium_spec.rb +65 -0
- data/spec/ttt/ai_spec.rb +50 -0
- data/spec/ttt/board_spec.rb +96 -0
- data/spec/ttt/config_helper_spec.rb +54 -0
- data/spec/ttt/context_spec.rb +398 -0
- data/spec/ttt/four_by_four_spec.rb +112 -0
- data/spec/ttt/game_history_spec.rb +41 -0
- data/spec/ttt/game_interactor_spec.rb +197 -0
- data/spec/ttt/game_spec.rb +246 -0
- data/spec/ttt/human_spec.rb +18 -0
- data/spec/ttt/move_history_spec.rb +13 -0
- data/spec/ttt/move_traverser_spec.rb +75 -0
- data/spec/ttt/player_spec.rb +13 -0
- data/spec/ttt/riak_db_spec.rb +64 -0
- data/spec/ttt/setup_spec.rb +63 -0
- data/spec/ttt/three_by_three_by_three_spec.rb +130 -0
- data/spec/ttt/three_by_three_spec.rb +110 -0
- metadata +238 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
module CLI
|
|
2
|
+
class CLIPresenter
|
|
3
|
+
attr_accessor :io
|
|
4
|
+
def initialize(input, output)
|
|
5
|
+
self.io = CLIO.new(input, output)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def output(args)
|
|
9
|
+
io.output args
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def padding
|
|
13
|
+
output "\n"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def input
|
|
17
|
+
io.input
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def welcome_prompt
|
|
21
|
+
output "Welcome to TTT!"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def command_list
|
|
25
|
+
output "\nPlease select an option from the following list:"
|
|
26
|
+
padding
|
|
27
|
+
output "1. Play TTT!\n\n"
|
|
28
|
+
output "2. Load game\n\n"
|
|
29
|
+
output "3. Quit\n\n"
|
|
30
|
+
padding
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def menu
|
|
34
|
+
welcome_prompt
|
|
35
|
+
command_list
|
|
36
|
+
selection = input.chomp
|
|
37
|
+
if valid_menu?(selection)
|
|
38
|
+
selection
|
|
39
|
+
else
|
|
40
|
+
error
|
|
41
|
+
menu
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def valid_menu?(selection)
|
|
46
|
+
selection.to_i >= 1 && selection.to_i <= 3
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def process_game_list(list)
|
|
50
|
+
clear
|
|
51
|
+
puts "Please choose one of the following games:\n\n"
|
|
52
|
+
list.each do |key|
|
|
53
|
+
output key
|
|
54
|
+
end
|
|
55
|
+
output "\n"
|
|
56
|
+
input.chomp
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def sort_list(list)
|
|
60
|
+
list.map(&:to_i).sort.map(&:to_s)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def no_games
|
|
64
|
+
output "No games were found."
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def player_type_prompt(num, players)
|
|
68
|
+
output "\nPlease select an option for Player #{num}:\n\n"
|
|
69
|
+
display_options_with_index(players)
|
|
70
|
+
input
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def select_game_prompt
|
|
74
|
+
output "\nPlease choose one of the following games:\n\n"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def player_prompt(board, history, player)
|
|
78
|
+
output_help(board)
|
|
79
|
+
output_move_history(history)
|
|
80
|
+
output_board(board)
|
|
81
|
+
output "\n#{player.prompt}"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def output_move_history(moves)
|
|
85
|
+
output "\n"
|
|
86
|
+
output "Move History:".rjust(10)
|
|
87
|
+
move_num = "#"
|
|
88
|
+
move_side = "side"
|
|
89
|
+
move_square = "square"
|
|
90
|
+
output "#{move_num} #{move_side.rjust(7)} #{move_square.rjust(7)}"
|
|
91
|
+
moves.each_with_index do |move, n|
|
|
92
|
+
output "#{n}. #{(move.side).to_s.rjust(5)} #{(move.move).to_s.rjust(5)}"
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def process_winner(board, history, player)
|
|
97
|
+
clear
|
|
98
|
+
output_move_history(history)
|
|
99
|
+
output_board(board)
|
|
100
|
+
winner_prompt(player)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def process_draw(board, history)
|
|
104
|
+
clear
|
|
105
|
+
output_move_history(history)
|
|
106
|
+
output_board(board)
|
|
107
|
+
draw_prompt
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def board_selection_prompt(boards)
|
|
111
|
+
clear
|
|
112
|
+
output "\nPlease select which board you'd like to play:\n"
|
|
113
|
+
display_options_with_index(boards)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def display_options_with_index(options)
|
|
117
|
+
options.each_with_index do |option, index|
|
|
118
|
+
output "#{index + 1}: #{option}\n\n"
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def clear
|
|
123
|
+
system('clear')
|
|
124
|
+
#output "\e[2J"
|
|
125
|
+
#output "\e[0;0H"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def ai_difficulty_prompt(ai_options)
|
|
129
|
+
clear
|
|
130
|
+
output "\nChoose a difficulty level for the computer:"
|
|
131
|
+
ai_options.inject(1) do |index, difficulty|
|
|
132
|
+
output "#{index}. #{difficulty.to_s.gsub(/TTT::AI/, '')}"
|
|
133
|
+
index += 1
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def post_game_msg
|
|
138
|
+
output "\nWould you like to review the game history (y or n)?"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def play_again_msg
|
|
142
|
+
output "\nPlay again (y or n)?"
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def walk_msg
|
|
146
|
+
output "\nEnter -1 to go back, 0 for main menu, or 1 to go forward"
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def output_board(board)
|
|
150
|
+
output "\n"
|
|
151
|
+
case board.length
|
|
152
|
+
when 9
|
|
153
|
+
output_three_by_three(board)
|
|
154
|
+
when 16
|
|
155
|
+
output_four_by_four(board)
|
|
156
|
+
when 27
|
|
157
|
+
output_3d(board)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def output_help(board)
|
|
162
|
+
clear
|
|
163
|
+
output "\nMove options:"
|
|
164
|
+
case board.length
|
|
165
|
+
when 9
|
|
166
|
+
regular_move_prompt
|
|
167
|
+
when 16
|
|
168
|
+
four_by_four_move_prompt
|
|
169
|
+
when 27
|
|
170
|
+
three_d_prompt
|
|
171
|
+
end
|
|
172
|
+
output "\n"
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def error
|
|
176
|
+
output "\nI'm sorry, I didn't understand. Please try again."
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def winner_prompt(winner)
|
|
180
|
+
output "\n#{winner} is the winner!"
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def draw_prompt
|
|
184
|
+
output "\nIt's a draw!"
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def output_three_by_three(board)
|
|
188
|
+
output("\n #{board[0]} | #{board[1]} | #{board[2]} \n-----------------\n #{board[3]} | #{board[4]} | #{board[5]} \n-----------------\n #{board[6]} | #{board[7]} | #{board[8]} ")
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def output_four_by_four(board)
|
|
192
|
+
output("\n #{board[0]} | #{board[1]} | #{board[2]} | #{board[3]} \n-----------------------\n #{board[4]} | #{board[5]} | #{board[6]} | #{board[7]} \n-----------------------\n #{board[8]} | #{board[9]} | #{board[10]} | #{board[11]} \n-----------------------\n #{board[12]} | #{board[13]} | #{board[14]} | #{board[15]} ")
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def output_3d(board)
|
|
196
|
+
output("\n #{board[0]} | #{board[1]} | #{board[2]} #{board[9]} | #{board[10]} | #{board[11]} #{board[18]} | #{board[19]} | #{board[20]} \n----------------- ----------------- -----------------\n #{board[3]} | #{board[4]} | #{board[5]} #{board[12]} | #{board[13]} | #{board[14]} #{board[21]} | #{board[22]} | #{board[23]} \n----------------- ----------------- -----------------\n #{board[6]} | #{board[7]} | #{board[8]} #{board[15]} | #{board[16]} | #{board[17]} #{board[24]} | #{board[25]} | #{board[26]} ")
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def three_d_prompt
|
|
200
|
+
output("\n 0 | 1 | 2 9 | 10 | 11 18 | 19 | 20 \n----------------- ----------------- -----------------\n 3 | 4 | 5 12 | 13 | 14 21 | 22 | 23 \n----------------- ----------------- -----------------\n 6 | 7 | 8 15 | 16 | 17 24 | 25 | 26 ")
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def regular_move_prompt
|
|
204
|
+
output("\n 0 | 1 | 2 \n-----------------\n 3 | 4 | 5 \n-----------------\n 6 | 7 | 8 ")
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def four_by_four_move_prompt
|
|
208
|
+
output("\n 0 | 1 | 2 | 3 \n-----------------------\n 4 | 5 | 6 | 7 \n-----------------------\n 8 | 9 | 10 | 11 \n-----------------------\n 12 | 13 | 14 | 15 ")
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def welcome_prompt
|
|
212
|
+
clear
|
|
213
|
+
output("Welcome to TTT!")
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module CLI
|
|
2
|
+
class CLIO
|
|
3
|
+
attr_accessor :outstream, :instream
|
|
4
|
+
def initialize(input, output)
|
|
5
|
+
self.outstream = output
|
|
6
|
+
self.instream = input
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def output(message)
|
|
10
|
+
outstream.puts message
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def input
|
|
14
|
+
instream.gets
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module CLI
|
|
2
|
+
class PlayAgain
|
|
3
|
+
|
|
4
|
+
def initialize(presenter)
|
|
5
|
+
self.presenter = presenter
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def play_again?
|
|
9
|
+
play_again_msg
|
|
10
|
+
process_reply input
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def process_reply reply
|
|
14
|
+
if valid_reply?(reply)
|
|
15
|
+
return false if reply.chomp == "n"
|
|
16
|
+
true
|
|
17
|
+
else
|
|
18
|
+
presenter.error
|
|
19
|
+
play_again?
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def input
|
|
24
|
+
presenter.input
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def valid_reply?(response)
|
|
28
|
+
response =~ (/(y|n)/i)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def play_again_msg
|
|
32
|
+
presenter.play_again_msg
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
attr_accessor :presenter
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require 'cli/selection'
|
|
2
|
+
|
|
3
|
+
module CLI
|
|
4
|
+
class PlayerSelection < Selection
|
|
5
|
+
attr_accessor :presenter, :players, :player1, :player2
|
|
6
|
+
def initialize(players, presenter)
|
|
7
|
+
self.presenter = presenter
|
|
8
|
+
self.players = players
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def process
|
|
12
|
+
process_player_type_input(1, presenter.player_type_prompt(1, players))
|
|
13
|
+
process_player_type_input(2, presenter.player_type_prompt(2, players))
|
|
14
|
+
self
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def process_player_type_input(num, selection)
|
|
18
|
+
if player_selection_valid? selection.chomp.to_i
|
|
19
|
+
set_player_string(num, selection.chomp.to_i)
|
|
20
|
+
else
|
|
21
|
+
presenter.error
|
|
22
|
+
process_player_type_input(num, presenter.player_type_prompt(num, players))
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def set_player_string(num, selection)
|
|
27
|
+
case num
|
|
28
|
+
when 1
|
|
29
|
+
self.player1 = _player(selection)
|
|
30
|
+
when 2
|
|
31
|
+
self.player2 = _player(selection)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def _player(selection)
|
|
36
|
+
players[selection - 1]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def player_selection_valid?(selection)
|
|
40
|
+
0 < selection.to_i && selection <= players.length
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module CLI
|
|
2
|
+
class WalkHistory
|
|
3
|
+
attr_accessor :presenter, :game, :move_traverser, :board
|
|
4
|
+
def initialize(presenter, game)
|
|
5
|
+
self.presenter = presenter
|
|
6
|
+
self.move_traverser = game.history.move_traverser
|
|
7
|
+
self.game = game
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def post_game_msg
|
|
11
|
+
presenter.post_game_msg
|
|
12
|
+
process(presenter.input.chomp)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def process(selection)
|
|
16
|
+
if selection =~ (/y|n/i)
|
|
17
|
+
walk_msg if selection =~ (/y/i)
|
|
18
|
+
else
|
|
19
|
+
presenter.error
|
|
20
|
+
post_game_msg
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def walk_msg
|
|
25
|
+
presenter.clear
|
|
26
|
+
presenter.output_board(build_board)
|
|
27
|
+
presenter.walk_msg
|
|
28
|
+
diff_index = presenter.input.chomp
|
|
29
|
+
case diff_index
|
|
30
|
+
when "-1" then walk_history(-1)
|
|
31
|
+
when "0" then return
|
|
32
|
+
when "1" then walk_history(1)
|
|
33
|
+
else
|
|
34
|
+
presenter.error
|
|
35
|
+
walk_msg
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def walk_history(diff_index)
|
|
40
|
+
move_traverser.adjust_move_index(diff_index)
|
|
41
|
+
walk_msg
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def build_board(board = game.board, move_index = move_traverser.move_index)
|
|
45
|
+
move_traverser.history_board_builder(board, move_index)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module CLI
|
|
4
|
+
describe BoardSelection do
|
|
5
|
+
let(:view) { CLIO.new(StringIO.new, StringIO.new) }
|
|
6
|
+
let(:boards) { TTT::Setup.new.players }
|
|
7
|
+
let(:presenter) { CLIPresenter.new(StringIO.new, StringIO.new) }
|
|
8
|
+
let(:boardio) { BoardSelection.new(boards, presenter) }
|
|
9
|
+
|
|
10
|
+
describe "#board_selection_input_valid?" do
|
|
11
|
+
it "returns true when input is within valid range of options" do
|
|
12
|
+
boardio.board_selection_input_valid?(1).should == true
|
|
13
|
+
boardio.board_selection_input_valid?(2).should == true
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "returns false when input is not within valid range of options" do
|
|
17
|
+
boardio.board_selection_input_valid?(6).should == false
|
|
18
|
+
boardio.board_selection_input_valid?(1.5).should == false
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe "#process" do
|
|
23
|
+
it "displays a generic error message if the user inputs invalid input" do
|
|
24
|
+
presenter.io.instream = StringIO.new("9\n1\n")
|
|
25
|
+
boardio.process
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'stringio'
|
|
3
|
+
require 'ttt/context'
|
|
4
|
+
|
|
5
|
+
module CLI
|
|
6
|
+
describe CLIGame do
|
|
7
|
+
let(:setup) { TTT::Setup.new }
|
|
8
|
+
let(:context) { TTT::Context.instance }
|
|
9
|
+
let(:players) { setup.players }
|
|
10
|
+
let(:boards) { setup.boards }
|
|
11
|
+
let(:db) { setup.new_db }
|
|
12
|
+
let(:presenter) { CLIPresenter.new(input, output) }
|
|
13
|
+
let(:output) { StringIO.new }
|
|
14
|
+
let(:input1) { StringIO.new("1\n1\n1\n0\n3\n1\n4\n2\nn\n") }
|
|
15
|
+
let(:input) { StringIO.new }
|
|
16
|
+
let(:game) { setup.new_game(player1: players.first, player2: players.first, board: boards.first) }
|
|
17
|
+
let(:ai_game) { setup.new_game(player1: players.last, player2: players.last, board: boards.first) }
|
|
18
|
+
|
|
19
|
+
before(:each) do
|
|
20
|
+
context.setup = TTT::Setup
|
|
21
|
+
@cli_game = described_class.new(context, input, output)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
describe "#play" do
|
|
25
|
+
it "displays the main game menu to the user" do
|
|
26
|
+
@cli_game.stub(:process_command).and_return(nil)
|
|
27
|
+
@cli_game.presenter.should_receive(:menu)
|
|
28
|
+
@cli_game.play
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "chooses the game made" do
|
|
32
|
+
@cli_game.presenter.stub(:menu).and_return(1)
|
|
33
|
+
@cli_game.should_receive(:process_command).with(1)
|
|
34
|
+
@cli_game.play
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
describe "#process_command" do
|
|
39
|
+
context "matches user game mode selection with the proper game play option" do
|
|
40
|
+
it "starts a new game when the user selects option 1" do
|
|
41
|
+
@cli_game.should_receive(:init_game)
|
|
42
|
+
@cli_game.process_command("1\n")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "prompts the user to load a previously played game when the user selects option 2" do
|
|
46
|
+
@cli_game.should_receive(:game_list)
|
|
47
|
+
@cli_game.process_command("2\n")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it "exits the game when the user selects option 3" do
|
|
51
|
+
@cli_game.should_receive(:exit)
|
|
52
|
+
@cli_game.process_command("3\n")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "reprompts the user with the main menu if invalid input is received" do
|
|
56
|
+
@cli_game.should_receive(:play)
|
|
57
|
+
@cli_game.process_command("aoivoijv\n")
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
describe "#init_game" do
|
|
63
|
+
before(:each) do
|
|
64
|
+
@cli_game.presenter.io.instream = StringIO.new("1\n1\n1\n")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "presents a welcome prompt to the user" do
|
|
68
|
+
@cli_game.presenter.should_receive(:welcome_prompt)
|
|
69
|
+
@cli_game.stub(:get_player_selection => nil)
|
|
70
|
+
@cli_game.stub(:get_board_selection => nil)
|
|
71
|
+
@cli_game.stub(:build_game => nil)
|
|
72
|
+
@cli_game.context.stub(:add_game => nil)
|
|
73
|
+
@cli_game.stub(:play_game => nil)
|
|
74
|
+
@cli_game.init_game
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it "asks the user to select player types for player 1 and player 2" do
|
|
78
|
+
@cli_game.should_receive(:get_player_selection)
|
|
79
|
+
@cli_game.stub(:get_board_selection => nil)
|
|
80
|
+
@cli_game.stub(:build_game => nil)
|
|
81
|
+
@cli_game.context.stub(:add_game => nil)
|
|
82
|
+
@cli_game.stub(:play_game => nil)
|
|
83
|
+
@cli_game.init_game
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it "asks the user to select the board type for the new game" do
|
|
87
|
+
@cli_game.stub(:get_player_selection => nil)
|
|
88
|
+
@cli_game.should_receive(:get_board_selection)
|
|
89
|
+
@cli_game.stub(:build_game => nil)
|
|
90
|
+
@cli_game.context.stub(:add_game => nil)
|
|
91
|
+
@cli_game.stub(:play_game => nil)
|
|
92
|
+
@cli_game.init_game
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it "builds a new game based on the player type and board type selections of the user" do
|
|
96
|
+
@cli_game.should_receive(:build_game)
|
|
97
|
+
@cli_game.context.stub(:add_game => nil)
|
|
98
|
+
@cli_game.stub(:play_game => nil)
|
|
99
|
+
@cli_game.init_game
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it "a new game is added to the datastore" do
|
|
103
|
+
@cli_game.context.should_receive(:add_game)
|
|
104
|
+
@cli_game.stub(:play_game => nil)
|
|
105
|
+
@cli_game.init_game
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it "begins to play the game after a new game has been successfully created" do
|
|
109
|
+
@cli_game.should_receive(:play_game)
|
|
110
|
+
@cli_game.init_game
|
|
111
|
+
@cli_game.id.should_not be_nil
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
describe "#get_player_selection" do
|
|
116
|
+
it "selects the player type for player1 and player2 based on the context.players list" do
|
|
117
|
+
context.players.length.times do |n|
|
|
118
|
+
@cli_game.presenter.io.instream = StringIO.new("#{n + 1}\n#{n + 1}\n")
|
|
119
|
+
players_selection = @cli_game.get_player_selection
|
|
120
|
+
players_selection.player1.should == context.players[n]
|
|
121
|
+
players_selection.player2.should == context.players[n]
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
describe "#get_board_selection" do
|
|
127
|
+
it "selects the board type based on the context.boards list" do
|
|
128
|
+
context.boards.length.times do |n|
|
|
129
|
+
@cli_game.presenter.io.instream = StringIO.new("#{n + 1}\n#{n + 1}\n")
|
|
130
|
+
board_selection = @cli_game.get_board_selection
|
|
131
|
+
board_selection.board.should == context.boards[n]
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
describe "#build_game" do
|
|
137
|
+
it "constructs a new game based on the player types and board type selected by the user" do
|
|
138
|
+
p1 = [*1..context.players.length].sample
|
|
139
|
+
p2 = [*1..context.players.length].sample
|
|
140
|
+
b = [*1..context.boards.length].sample
|
|
141
|
+
@cli_game.presenter.io.instream = StringIO.new("#{p1}\n#{p2}\n#{b}\n")
|
|
142
|
+
player_selection = @cli_game.get_player_selection
|
|
143
|
+
board_selection = @cli_game.get_board_selection
|
|
144
|
+
game = @cli_game.build_game(player_selection, board_selection)
|
|
145
|
+
game.player1.should_not be_nil
|
|
146
|
+
game.player2.should_not be_nil
|
|
147
|
+
game.board.should_not be_nil
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
describe "#play_game" do
|
|
152
|
+
before(:each) do
|
|
153
|
+
@cli_game.presenter.io.instream = StringIO.new("1\n1\n1\n0\n1\n4\n5\n8\n")
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
it "plays a game until the game is finished" do
|
|
157
|
+
@cli_game.should_receive(:game_over)
|
|
158
|
+
@cli_game.init_game
|
|
159
|
+
@cli_game.game.board.board.should == ['x', 'o', ' ', ' ', 'x', 'o', ' ', ' ', 'x']
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
describe "#game_over" do
|
|
164
|
+
it "prompts the user to walk the previously played game's history" do
|
|
165
|
+
@cli_game.should_receive(:walk_history)
|
|
166
|
+
@cli_game.stub(:play_again)
|
|
167
|
+
@cli_game.game_over
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
it "prompts the user to play again" do
|
|
171
|
+
@cli_game.should_receive(:play_again)
|
|
172
|
+
@cli_game.stub(:walk_history)
|
|
173
|
+
@cli_game.game_over
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
describe "#play_again" do
|
|
178
|
+
it "exits the program if the user doesn't want to play again" do
|
|
179
|
+
PlayAgain.stub_chain(:new, :play_again?).and_return(nil)
|
|
180
|
+
@cli_game.should_receive(:exit)
|
|
181
|
+
@cli_game.stub(:play => nil)
|
|
182
|
+
@cli_game.play_again
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
describe "#game" do
|
|
187
|
+
it "returns the current game from the datastore" do
|
|
188
|
+
@cli_game.presenter.io.instream = StringIO.new("1\n1\n1\n")
|
|
189
|
+
@cli_game.stub(:play_game => nil)
|
|
190
|
+
@cli_game.game.should == context.get_game(@cli_game.id)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
describe "#move_cycle" do
|
|
195
|
+
it "prompts the current player to move" do
|
|
196
|
+
@cli_game.id = context.add_game(game)
|
|
197
|
+
@cli_game.stub(:process_next_move => nil)
|
|
198
|
+
@cli_game.presenter.should_receive(:player_prompt).with(game.board.board, game.show_history, game.current_player)
|
|
199
|
+
@cli_game.move_cycle
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
it "receives the next move from the current_player" do
|
|
203
|
+
@cli_game.id = context.add_game(ai_game)
|
|
204
|
+
@cli_game.stub(:game => context.get_game(@cli_game.id))
|
|
205
|
+
@cli_game.presenter.stub(:player_prompt => nil)
|
|
206
|
+
@cli_game.should_receive(:process_next_move)
|
|
207
|
+
@cli_game.move_cycle
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
describe "#player_prompt" do
|
|
212
|
+
it "calls player_prompt on @presenter" do
|
|
213
|
+
@cli_game.id = context.add_game(game)
|
|
214
|
+
@cli_game.presenter.should_receive(:player_prompt)
|
|
215
|
+
@cli_game.player_prompt(context.get_game(@cli_game.id))
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
describe "#process_next_move" do
|
|
220
|
+
context "When an AI player move is processed" do
|
|
221
|
+
it "returns the AI player's move if the current player is an AI player" do
|
|
222
|
+
@cli_game.id = context.add_game(ai_game)
|
|
223
|
+
original_board = @cli_game.game.board.board
|
|
224
|
+
@cli_game.process_next_move
|
|
225
|
+
original_board.should_not == @cli_game.game.board.board
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
context "When a Human player move is processed" do
|
|
230
|
+
before(:each) do
|
|
231
|
+
@cli_game.id = context.add_game(game)
|
|
232
|
+
@cli_game.presenter.io.instream = StringIO.new("1\n")
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
it "gets the human player's move" do
|
|
236
|
+
@cli_game.process_next_move
|
|
237
|
+
@cli_game.game.board.board.should == [' ', 'x', ' ', ' ', ' ', ' ', ' ', ' ', ' ']
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
it "validates the human player's move" do
|
|
241
|
+
@cli_game.should_receive(:validate_move).with("1\n").and_return(true)
|
|
242
|
+
@cli_game.process_next_move
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
it "updates the game with the human player's move" do
|
|
246
|
+
@cli_game.game.board.board.should == [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ']
|
|
247
|
+
@cli_game.process_next_move
|
|
248
|
+
@cli_game.game.board.board.should == [' ', 'x', ' ', ' ', ' ', ' ', ' ', ' ', ' ']
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
it "evaluates the game after the move has been processed" do
|
|
253
|
+
@cli_game.id = context.add_game(game)
|
|
254
|
+
game = @cli_game.game
|
|
255
|
+
game.board.board = ['x', 'o', ' ', ' ', 'x', 'o', ' ', ' ', ' ']
|
|
256
|
+
context.save_game(@cli_game.id, game)
|
|
257
|
+
@cli_game.presenter.io.instream = StringIO.new("8\n") # win for 'x'
|
|
258
|
+
@cli_game.presenter.should_receive(:process_winner)
|
|
259
|
+
@cli_game.process_next_move
|
|
260
|
+
@cli_game.game.winner?.should == true
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
describe "#get_next_move" do
|
|
265
|
+
it "prompts the human player for input on the command line if the current player is human" do
|
|
266
|
+
@cli_game.id = context.add_game(game)
|
|
267
|
+
@cli_game.presenter.io.instream = StringIO.new("1\n")
|
|
268
|
+
@cli_game.presenter.should_receive(:input).and_return("1\n")
|
|
269
|
+
@cli_game.process_next_move
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
it "gets the AI player's move when the current player is an AI player" do
|
|
273
|
+
@cli_game.id = context.add_game(ai_game)
|
|
274
|
+
original_board = @cli_game.game.board.board
|
|
275
|
+
@cli_game.get_next_move
|
|
276
|
+
original_board.should_not == @cli_game.game.board.board
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
describe "#get_games" do
|
|
281
|
+
it "asks the context object for alist of games saved to the datastore" do
|
|
282
|
+
games = context.game_list
|
|
283
|
+
@cli_game.get_games
|
|
284
|
+
@cli_game.games.length.should == games.length
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
it "sets @games equal to nil if there are no games in the datastore" do
|
|
288
|
+
@cli_game.context.stub(:game_list => [])
|
|
289
|
+
@cli_game.get_games
|
|
290
|
+
@cli_game.games.should == nil
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
describe "#load_game" do
|
|
295
|
+
before(:each) do
|
|
296
|
+
@cli_game.stub(:game_list => nil)
|
|
297
|
+
end
|
|
298
|
+
it "prompts the user to select a game id from the game list" do
|
|
299
|
+
@cli_game.presenter.should_receive(:process_game_list).with(@cli_game.games).and_return("1\n")
|
|
300
|
+
@cli_game.context.stub(:get_game => nil)
|
|
301
|
+
@cli_game.load_game
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
it "loads a game with a given id" do
|
|
305
|
+
@cli_game.id = context.add_game(game)
|
|
306
|
+
@cli_game.presenter.stub(:process_game_list => @cli_game.id)
|
|
307
|
+
@cli_game.context.stub(:get_game => @cli_game.game)
|
|
308
|
+
@cli_game.should_receive(:resume_game)
|
|
309
|
+
@cli_game.load_game
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
describe "#no_games" do
|
|
314
|
+
it "prompts the users that no games exist in the datastore and returns to the main menu" do
|
|
315
|
+
@cli_game.presenter.should_receive(:no_games)
|
|
316
|
+
@cli_game.should_receive(:play)
|
|
317
|
+
@cli_game.no_games
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
it "sleeps for one second" do
|
|
321
|
+
@cli_game.should_receive(:sleep).with(1)
|
|
322
|
+
@cli_game.stub(:play => nil)
|
|
323
|
+
@cli_game.no_games
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
describe "#resume_game" do
|
|
328
|
+
it "prompts the player to resume playing an unfinished game loaded from the datastore" do
|
|
329
|
+
@cli_game.id = context.add_game(game)
|
|
330
|
+
@cli_game.presenter.should_receive(:player_prompt).with(@cli_game.game.board.board, @cli_game.game.show_history, @cli_game.game.current_player)
|
|
331
|
+
@cli_game.stub(:play_game => nil)
|
|
332
|
+
@cli_game.resume_game
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
it "displasy a loaded game's final state when a finished game is loaded from the db" do
|
|
336
|
+
@cli_game.id = context.add_game(game)
|
|
337
|
+
game = context.get_game(@cli_game.id)
|
|
338
|
+
game.board.board = %w(a b c d e f g h i)
|
|
339
|
+
@cli_game.context.save_game(@cli_game.id, game)
|
|
340
|
+
@cli_game.stub(:play_game => nil)
|
|
341
|
+
@cli_game.presenter.io.outstream.include? "It's a draw"
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
describe "#game_list" do
|
|
346
|
+
it "gets a list of games" do
|
|
347
|
+
@cli_game.should_receive(:get_games)
|
|
348
|
+
@cli_game.stub(:load_game)
|
|
349
|
+
@cli_game.stub(:no_games)
|
|
350
|
+
@cli_game.game_list
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
it "calls #load_game if there are games stored in the datastore" do
|
|
354
|
+
@cli_game.should_receive(:load_game)
|
|
355
|
+
@cli_game.game_list
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
it "calls #no_games if there are no games stored in the datastore" do
|
|
359
|
+
@cli_game.stub(:get_games => nil)
|
|
360
|
+
@cli_game.games = nil
|
|
361
|
+
@cli_game.should_receive(:no_games)
|
|
362
|
+
@cli_game.game_list
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
describe "#walk_history" do
|
|
367
|
+
it "makes a new WalkHistory object" do
|
|
368
|
+
@cli_game.id = context.add_game(game)
|
|
369
|
+
@cli_game.presenter.stub(:input => "n")
|
|
370
|
+
@cli_game.stub(:play_again => nil)
|
|
371
|
+
@cli_game.presenter.should_receive(:post_game_msg)
|
|
372
|
+
@cli_game.walk_history
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
end
|