tic_tac_toe_bfox 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/Gemfile +5 -0
- data/README.md +11 -0
- data/Rakefile +6 -0
- data/bin/setup +8 -0
- data/bin/tictactoe +6 -0
- data/lib/tictactoe/board.rb +36 -0
- data/lib/tictactoe/board_presenter_terminal.rb +17 -0
- data/lib/tictactoe/game.rb +129 -0
- data/lib/tictactoe/game_config.rb +71 -0
- data/lib/tictactoe/input_helper.rb +99 -0
- data/lib/tictactoe/io_terminal.rb +25 -0
- data/lib/tictactoe/move_generator.rb +70 -0
- data/lib/tictactoe/player.rb +12 -0
- data/lib/tictactoe/square.rb +30 -0
- data/lib/tictactoe/squares.rb +129 -0
- data/lib/tictactoe/squares_factory.rb +56 -0
- data/lib/tictactoe/version.rb +3 -0
- data/lib/tictactoe.rb +9 -0
- data/tictactoe.gemspec +35 -0
- metadata +107 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0b97a0ee48814d4adde4f74cd0a9218645ef8d67
|
4
|
+
data.tar.gz: be30925fff9cd631a7c7a31a2f6ea1a58af71e5f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a3bbebb23e84bc84d4e7bf23e2236b40cbc1d65541474414f6c5773228c3051b3516c76d90f2c25db5dc1e8669481eb64cc42496872e3f8cb134b51ef10c7aa3
|
7
|
+
data.tar.gz: 2c667d703865e7374250bf9386069759010d45130bbc06f1d8c829dee39dbfbb3c941974e02bf5548069dbfd8cd1b8a4940d7457080a2d44eb169fede6d56027
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# tic_tac_toe
|
2
|
+
|
3
|
+
**Instructions:**
|
4
|
+
|
5
|
+
1. In order to play the game, please type **ruby main.rb** into your console.
|
6
|
+
2. Follow the prompts to set your name and the value of your mark.
|
7
|
+
3. Choose whether you would like to play a computer (**"C"**) or a human (**"H"**).
|
8
|
+
4. If you choose to play a computer, further choose whether you would like to play
|
9
|
+
* an easy (**"E"**) computer: this computer selects randomly
|
10
|
+
* a difficult (**"D"**) computer: this computer is impossible to beat!
|
11
|
+
5. Choose how many squares per row you would like. Note that 9 is the maximum, unless you are playing a difficult computer, who prefers to play with a maximum of 3 squares per row.
|
data/Rakefile
ADDED
data/bin/setup
ADDED
data/bin/tictactoe
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
module TTT
|
2
|
+
class Board
|
3
|
+
attr_accessor :rows_and_cols, :squares
|
4
|
+
|
5
|
+
def initialize(args = {})
|
6
|
+
@rows_and_cols = args[:rows_and_cols]
|
7
|
+
@squares = args[:squares]
|
8
|
+
end
|
9
|
+
|
10
|
+
def change_square(display_value, new_value)
|
11
|
+
square_to_change = retrieve_square(display_value)
|
12
|
+
square_to_change.change_value(new_value)
|
13
|
+
end
|
14
|
+
|
15
|
+
def full?
|
16
|
+
return squares.full?
|
17
|
+
end
|
18
|
+
|
19
|
+
def won?
|
20
|
+
return squares.any_combination_won?
|
21
|
+
end
|
22
|
+
|
23
|
+
def display_values
|
24
|
+
return squares.display_values
|
25
|
+
end
|
26
|
+
|
27
|
+
def available_choices
|
28
|
+
return squares.available_choices
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def retrieve_square(display_value)
|
33
|
+
return squares.retrieve_square(display_value)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module TTT
|
2
|
+
class BoardPresenterTerminal
|
3
|
+
def present_board(board)
|
4
|
+
#board.display_values will return multidimensional array
|
5
|
+
display_values = board.display_values
|
6
|
+
display_values.each do |row|
|
7
|
+
row.each_with_index do |display_value, index|
|
8
|
+
#http://www.evc-cit.info/cit020/beginning-programming/chp_04/file_printf.html
|
9
|
+
printf "%2s", display_value
|
10
|
+
print " | " unless index == (row.size - 1)
|
11
|
+
end
|
12
|
+
print "\n"
|
13
|
+
puts "_"*(row.size * 5) unless row.equal? display_values.last
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require_relative 'io_terminal'
|
2
|
+
require_relative 'board_presenter_terminal'
|
3
|
+
require_relative 'game_config'
|
4
|
+
require_relative 'squares_factory'
|
5
|
+
|
6
|
+
module TTT
|
7
|
+
class Game
|
8
|
+
attr_accessor :board, :players, :board_presenter, :number_of_turns_taken, :config
|
9
|
+
attr_reader :io, :input_helper
|
10
|
+
|
11
|
+
def initialize(args = {})
|
12
|
+
@io = args.fetch(:io, IOTerminal.new)
|
13
|
+
@board_presenter = args.fetch(:board_presenter, BoardPresenterTerminal.new)
|
14
|
+
@input_helper = InputHelper.new(@io)
|
15
|
+
@number_of_turns_taken = 0
|
16
|
+
# below finds the user needed input needed for initial players and board configuration and stores in config object
|
17
|
+
@config = GameConfig.new(@input_helper)
|
18
|
+
@players = generate_players
|
19
|
+
@board = generate_board
|
20
|
+
end
|
21
|
+
|
22
|
+
#overriding so that io and input_helper always in harmony
|
23
|
+
def io=(new_io)
|
24
|
+
@io = new_io
|
25
|
+
self.input_helper = InputHelper.new(new_io)
|
26
|
+
end
|
27
|
+
|
28
|
+
def generate_players
|
29
|
+
player_1 = Player.new(value: config.player_1_value, name: config.player_1_name, type: :human)
|
30
|
+
player_2 = Player.new(value: config.player_2_value, name: config.player_2_name, type: config.player_2_type, difficulty_level: config.computer_difficulty_level)
|
31
|
+
[player_1, player_2]
|
32
|
+
end
|
33
|
+
|
34
|
+
def generate_board
|
35
|
+
number_of_rows_cols = config.number_of_rows_cols
|
36
|
+
Board.new(rows_and_cols: number_of_rows_cols, squares: SquaresFactory.build_empty_squares(number_of_rows_cols) )
|
37
|
+
end
|
38
|
+
|
39
|
+
def play
|
40
|
+
while !over?
|
41
|
+
print_board
|
42
|
+
square_choice = get_square_choice
|
43
|
+
#note that change_square moves current_player forward only if game is not won
|
44
|
+
change_square(square_choice, current_player.value)
|
45
|
+
end
|
46
|
+
|
47
|
+
if won?
|
48
|
+
print_board
|
49
|
+
io.present_with_new_line("#{current_player.name} wins!")
|
50
|
+
else
|
51
|
+
print_board
|
52
|
+
io.present_with_new_line("Draw!")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def get_square_choice
|
57
|
+
if current_player.type == :human
|
58
|
+
input_helper.get_square_to_change(current_player.name, available_choices)
|
59
|
+
elsif current_player.type == :computer
|
60
|
+
input_helper.computer_choosing_graphic
|
61
|
+
if current_player.difficulty_level == :difficult
|
62
|
+
perfect_move
|
63
|
+
elsif current_player.difficulty_level == :easy
|
64
|
+
random_move
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def change_square(display_value, new_value)
|
70
|
+
board.change_square(display_value, new_value)
|
71
|
+
if !won?
|
72
|
+
move_forward_one_turn
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def over?
|
77
|
+
board.full? || board.won?
|
78
|
+
end
|
79
|
+
|
80
|
+
def won?
|
81
|
+
board.won?
|
82
|
+
end
|
83
|
+
|
84
|
+
def winner
|
85
|
+
#each move is immediately proceeded by an increment to number_of_selections_made; therefore, need to rewind won to find winner
|
86
|
+
if !won?
|
87
|
+
return nil
|
88
|
+
end
|
89
|
+
current_player
|
90
|
+
end
|
91
|
+
|
92
|
+
def perfect_move
|
93
|
+
move_generator = MoveGenerator.new(self)
|
94
|
+
move_generator.minimax
|
95
|
+
move_generator.choice
|
96
|
+
end
|
97
|
+
|
98
|
+
def random_move
|
99
|
+
available_choices.sample
|
100
|
+
end
|
101
|
+
|
102
|
+
def available_choices
|
103
|
+
board.available_choices
|
104
|
+
end
|
105
|
+
|
106
|
+
def print_board
|
107
|
+
board_presenter.present_board(board)
|
108
|
+
end
|
109
|
+
|
110
|
+
def current_player
|
111
|
+
players[current_turn_player_index]
|
112
|
+
end
|
113
|
+
|
114
|
+
def move_forward_one_turn
|
115
|
+
self.number_of_turns_taken += 1
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
def number_of_players
|
121
|
+
players.size
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
def current_turn_player_index
|
126
|
+
(number_of_turns_taken % number_of_players)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module TTT
|
2
|
+
class GameConfig
|
3
|
+
attr_accessor :input_helper
|
4
|
+
|
5
|
+
attr_accessor :player_1_name
|
6
|
+
attr_accessor :player_1_value
|
7
|
+
attr_accessor :player_2_type
|
8
|
+
attr_accessor :player_2_name
|
9
|
+
attr_accessor :player_2_value
|
10
|
+
attr_accessor :computer_difficulty_level
|
11
|
+
attr_accessor :number_of_rows_cols
|
12
|
+
|
13
|
+
def initialize(input_helper)
|
14
|
+
@input_helper = input_helper
|
15
|
+
setup
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
def setup
|
20
|
+
set_player_1_values
|
21
|
+
set_player_2_values
|
22
|
+
set_board_rows_cols
|
23
|
+
end
|
24
|
+
|
25
|
+
def set_player_1_values
|
26
|
+
self.player_1_name = input_helper.get_player_1_name
|
27
|
+
self.player_1_value = input_helper.get_player_1_value(player_1_name)
|
28
|
+
end
|
29
|
+
|
30
|
+
def set_player_2_values
|
31
|
+
self.player_2_type = input_helper.get_player_2_type
|
32
|
+
if player_2_human?
|
33
|
+
self.player_2_name = input_helper.get_player_2_name
|
34
|
+
self.player_2_value = input_helper.get_player_2_value(player_1_value)
|
35
|
+
elsif player_2_computer?
|
36
|
+
self.player_2_name = "Computer"
|
37
|
+
self.player_2_value = get_computer_value
|
38
|
+
self.computer_difficulty_level = input_helper.get_computer_difficulty_level
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def set_board_rows_cols
|
43
|
+
#need this if else logic because if computer is difficult, it runs minimax, which is too slow to allow for more than 3 rows/cols
|
44
|
+
if computer_difficult?
|
45
|
+
self.number_of_rows_cols = input_helper.get_number_of_rows_cols_max_3
|
46
|
+
else
|
47
|
+
self.number_of_rows_cols = input_helper.get_number_of_rows_cols_max_9
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def player_2_human?
|
52
|
+
player_2_type == :human
|
53
|
+
end
|
54
|
+
|
55
|
+
def player_2_computer?
|
56
|
+
player_2_type == :computer
|
57
|
+
end
|
58
|
+
|
59
|
+
def computer_difficult?
|
60
|
+
computer_difficulty_level == :difficult
|
61
|
+
end
|
62
|
+
|
63
|
+
def get_computer_value
|
64
|
+
if player_2_value != "O"
|
65
|
+
"O"
|
66
|
+
else
|
67
|
+
"X"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module TTT
|
2
|
+
class InputHelper
|
3
|
+
attr_accessor :io
|
4
|
+
|
5
|
+
def initialize(io)
|
6
|
+
@io = io
|
7
|
+
end
|
8
|
+
|
9
|
+
def get_number_of_rows_cols_max_3
|
10
|
+
get_user_input("Please choose how many squares you would like in each row.", "Please choose number between 2 and 3.") do |input|
|
11
|
+
input.to_i >=2 && input.to_i <=3
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def get_number_of_rows_cols_max_9
|
16
|
+
get_user_input("Please choose how many squares you would like in each row.", "Please choose number between 2 and 3.") do |input|
|
17
|
+
input.to_i >=2 && input.to_i <=9
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def get_player_1_value(player_name = "Player 1")
|
22
|
+
user_choice = get_user_input("#{player_name}, please enter a value of either X or O", "Must be X or O. Please re-enter.") do |input|
|
23
|
+
input == 'x' || input == 'X' || input == 'o' || input == 'O'
|
24
|
+
end
|
25
|
+
user_choice.upcase
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_player_2_value(taken_value)
|
29
|
+
user_choice = get_user_input("Player 2, please enter your value", "Must not be #{taken_value}, please re-enter") do |input|
|
30
|
+
input.upcase != taken_value
|
31
|
+
end
|
32
|
+
user_choice.upcase
|
33
|
+
end
|
34
|
+
|
35
|
+
def get_player_1_name
|
36
|
+
user_choice = get_user_input("Player 1, please enter your name", "Please re-enter your name, using only letters") do |input|
|
37
|
+
input =~ /^[a-zA-Z]+$/
|
38
|
+
end
|
39
|
+
user_choice.capitalize
|
40
|
+
end
|
41
|
+
|
42
|
+
def get_player_2_name
|
43
|
+
user_choice = get_user_input("Player 2, please enter your name", "Please re-enter your name, using only letters") do |input|
|
44
|
+
input =~ /^[a-zA-Z]+$/
|
45
|
+
end
|
46
|
+
user_choice.capitalize
|
47
|
+
end
|
48
|
+
|
49
|
+
def get_player_2_type
|
50
|
+
user_choice = get_user_input("Please enter \"C\" if you would like to play the Computer. Enter \"H\" if you would like to play the human sitting next to you.", "Invalid character. Please enter either C(Computer) or H(Human).") do |input|
|
51
|
+
input == 'c' || input == 'C' || input == 'h' || input == 'H'
|
52
|
+
end
|
53
|
+
user_choice = user_choice.upcase
|
54
|
+
if user_choice == "C"
|
55
|
+
:computer
|
56
|
+
elsif user_choice == "H"
|
57
|
+
:human
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def get_computer_difficulty_level
|
62
|
+
user_choice = get_user_input("Please enter \"E\" if you would like to play an easy Computer. Enter \"D\" if you would like to play an extremely difficult computer.", "Invalid character. Please enter either E (Easy) or D (Difficult).") do |input|
|
63
|
+
input == 'd' || input == 'D' || input == 'e' || input == 'E'
|
64
|
+
end
|
65
|
+
user_choice = user_choice.upcase
|
66
|
+
if user_choice == "D"
|
67
|
+
:difficult
|
68
|
+
elsif user_choice == "E"
|
69
|
+
:easy
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def get_square_to_change(player_name, available_choices)
|
74
|
+
get_user_input("#{player_name}, please enter the number of the square that you would like to change.", "Invalid entry. Please try again.") do |input|
|
75
|
+
available_choices.include?(input)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def get_user_input(prompt, reprompt, &block_validation)
|
80
|
+
io.present_with_new_line(prompt)
|
81
|
+
user_choice = nil
|
82
|
+
while true
|
83
|
+
user_choice = io.receive
|
84
|
+
break if block_validation.call(user_choice)
|
85
|
+
puts reprompt
|
86
|
+
end
|
87
|
+
user_choice
|
88
|
+
end
|
89
|
+
|
90
|
+
def computer_choosing_graphic
|
91
|
+
io.present("Computer choosing.")
|
92
|
+
3.times do
|
93
|
+
sleep(0.3)
|
94
|
+
io.present(".")
|
95
|
+
end
|
96
|
+
io.present("\n")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module TTT
|
2
|
+
class IOTerminal
|
3
|
+
def present(prompt)
|
4
|
+
print prompt
|
5
|
+
end
|
6
|
+
|
7
|
+
def present_with_new_line(prompt)
|
8
|
+
puts prompt
|
9
|
+
end
|
10
|
+
|
11
|
+
def receive
|
12
|
+
result = gets.chomp
|
13
|
+
if is_int?(result)
|
14
|
+
return result.to_i
|
15
|
+
else
|
16
|
+
return result.chomp
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
def is_int?(figure)
|
22
|
+
Integer(figure) rescue false
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module TTT
|
2
|
+
class MoveGenerator
|
3
|
+
|
4
|
+
attr_accessor :game, :choice
|
5
|
+
|
6
|
+
def initialize(game)
|
7
|
+
@game = game
|
8
|
+
end
|
9
|
+
|
10
|
+
#returns int representing display value of random available square
|
11
|
+
def random_move
|
12
|
+
available_choices.sample
|
13
|
+
end
|
14
|
+
|
15
|
+
def minimax
|
16
|
+
scores = []
|
17
|
+
choices = []
|
18
|
+
if game.over?
|
19
|
+
return score(game)
|
20
|
+
end
|
21
|
+
|
22
|
+
available_choices.each do |available_choice|
|
23
|
+
new_game_state = Marshal.load(Marshal.dump(game))
|
24
|
+
new_game_state.change_square(available_choice, game.current_player.value)
|
25
|
+
#below needed because pry indicates possible_game_state's board not updating after make_move above
|
26
|
+
# possible_game_state.game_board = possible_board_state
|
27
|
+
choices.push(available_choice)
|
28
|
+
new_move_generator = MoveGenerator.new(new_game_state)
|
29
|
+
scores.push new_move_generator.minimax
|
30
|
+
end
|
31
|
+
|
32
|
+
if game.current_player == computer_player
|
33
|
+
|
34
|
+
#https://stackoverflow.com/questions/2149802/in-ruby-what-is-the-cleanest-way-of-obtaining-the-index-of-the-largest-value-in
|
35
|
+
max_score_index = scores.each_with_index.max[1]
|
36
|
+
@choice = choices[max_score_index]
|
37
|
+
return scores[max_score_index]
|
38
|
+
else
|
39
|
+
# This is the min calculation
|
40
|
+
min_score_index = scores.each_with_index.min[1]
|
41
|
+
@choice = choices[min_score_index]
|
42
|
+
return scores[min_score_index]
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
def available_choices
|
49
|
+
game.available_choices
|
50
|
+
end
|
51
|
+
|
52
|
+
def score(game)
|
53
|
+
if game.won? && game.winner == computer_player
|
54
|
+
return +1
|
55
|
+
elsif game.won? && game.winner == player_1
|
56
|
+
return -1
|
57
|
+
else
|
58
|
+
return 0
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def player_1
|
63
|
+
game.players[0]
|
64
|
+
end
|
65
|
+
|
66
|
+
def computer_player
|
67
|
+
game.players[1]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module TTT
|
2
|
+
class Player
|
3
|
+
attr_accessor :value, :name, :type, :difficulty_level
|
4
|
+
|
5
|
+
def initialize(args = {})
|
6
|
+
@value = args.fetch(:value, "X")
|
7
|
+
@name = args.fetch(:name, "Player 1")
|
8
|
+
@type = args.fetch(:type, :computer)
|
9
|
+
@difficulty_level = args.fetch(:difficulty_level, nil)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module TTT
|
2
|
+
class Square
|
3
|
+
attr_accessor :value, :display_value
|
4
|
+
attr_reader :row, :col
|
5
|
+
|
6
|
+
def initialize(args)
|
7
|
+
@value = args[:value]
|
8
|
+
@display_value = args[:display_value]
|
9
|
+
@row = args[:row]
|
10
|
+
@col = args[:col]
|
11
|
+
end
|
12
|
+
|
13
|
+
def change_value(new_value)
|
14
|
+
self.value = new_value
|
15
|
+
self.display_value = new_value
|
16
|
+
end
|
17
|
+
|
18
|
+
def empty?
|
19
|
+
return true unless full?
|
20
|
+
end
|
21
|
+
|
22
|
+
def full?
|
23
|
+
if value != nil
|
24
|
+
return true
|
25
|
+
else
|
26
|
+
return false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
module TTT
|
2
|
+
class Squares
|
3
|
+
attr_reader :collection_of_squares
|
4
|
+
|
5
|
+
#collection_of_squares is a multi-dimensional array
|
6
|
+
|
7
|
+
def initialize(args)
|
8
|
+
@collection_of_squares = args[:collection_of_squares]
|
9
|
+
end
|
10
|
+
|
11
|
+
def retrieve_square(display_value)
|
12
|
+
row = (display_value - 1) / number_of_rows_cols
|
13
|
+
col = (display_value - 1) % number_of_rows_cols
|
14
|
+
if row >= number_of_rows_cols || col >= number_of_rows_cols
|
15
|
+
return nil
|
16
|
+
else
|
17
|
+
return collection_of_squares[row][col]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def display_values
|
22
|
+
collection_of_squares.map do |row|
|
23
|
+
row.map do |square|
|
24
|
+
square.display_value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def available_choices
|
30
|
+
available_choices = Array.new
|
31
|
+
collection_of_squares.each do |row|
|
32
|
+
row.each do |square|
|
33
|
+
if square.empty?
|
34
|
+
available_choices.push(square.display_value)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
available_choices
|
39
|
+
end
|
40
|
+
|
41
|
+
def full?
|
42
|
+
collection_of_squares.each do |row|
|
43
|
+
return false if row.all? { |square| square.full? } == false
|
44
|
+
end
|
45
|
+
return true
|
46
|
+
end
|
47
|
+
|
48
|
+
def any_combination_won?
|
49
|
+
possible_winning_combinations.any? do |possible_winning_combination|
|
50
|
+
won?(possible_winning_combination)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def won?(array)
|
55
|
+
#https://stackoverflow.com/questions/3233278/how-do-i-test-if-all-items-in-an-array-are-identical
|
56
|
+
return false if array[0].value == nil
|
57
|
+
array.all? {|square| square.value == array[0].value}
|
58
|
+
end
|
59
|
+
|
60
|
+
def number_of_rows_cols
|
61
|
+
collection_of_squares.length
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
def cols
|
66
|
+
number_of_rows_cols = collection_of_squares.size
|
67
|
+
cols = Array.new
|
68
|
+
col = 0
|
69
|
+
while col < number_of_rows_cols
|
70
|
+
cols.push (
|
71
|
+
collection_of_squares.map do |row|
|
72
|
+
row[col]
|
73
|
+
end
|
74
|
+
)
|
75
|
+
col += 1
|
76
|
+
end
|
77
|
+
cols
|
78
|
+
end
|
79
|
+
|
80
|
+
#return collection_of_squares because set up as multidimensional array consisting of rows
|
81
|
+
def rows
|
82
|
+
collection_of_squares
|
83
|
+
end
|
84
|
+
|
85
|
+
def l_to_r_diag
|
86
|
+
number_of_rows_cols = collection_of_squares.size
|
87
|
+
diag = Array.new
|
88
|
+
row = 0
|
89
|
+
col = 0
|
90
|
+
while row < number_of_rows_cols
|
91
|
+
diag.push (
|
92
|
+
collection_of_squares[row][col]
|
93
|
+
)
|
94
|
+
row += 1
|
95
|
+
col += 1
|
96
|
+
end
|
97
|
+
diag
|
98
|
+
end
|
99
|
+
|
100
|
+
def r_to_l_diag
|
101
|
+
number_of_rows_cols = collection_of_squares.size
|
102
|
+
diag = Array.new
|
103
|
+
row = 0
|
104
|
+
col = number_of_rows_cols - 1
|
105
|
+
while row < number_of_rows_cols
|
106
|
+
diag.push (
|
107
|
+
collection_of_squares[row][col]
|
108
|
+
)
|
109
|
+
row += 1
|
110
|
+
col -= 1
|
111
|
+
end
|
112
|
+
diag
|
113
|
+
end
|
114
|
+
|
115
|
+
def possible_winning_combinations
|
116
|
+
winning_combinations = Array.new
|
117
|
+
winning_combinations.push(l_to_r_diag, r_to_l_diag)
|
118
|
+
|
119
|
+
rows.each do |row|
|
120
|
+
winning_combinations.push(row)
|
121
|
+
end
|
122
|
+
|
123
|
+
cols.each do |col|
|
124
|
+
winning_combinations.push(col)
|
125
|
+
end
|
126
|
+
winning_combinations
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require_relative 'squares'
|
2
|
+
require_relative 'square'
|
3
|
+
|
4
|
+
module TTT
|
5
|
+
module SquaresFactory
|
6
|
+
def self.build_custom_squares(config, squares_class = Squares)
|
7
|
+
|
8
|
+
rows_and_cols = config.length
|
9
|
+
|
10
|
+
#create multi-dimensional array
|
11
|
+
squares = Array.new(rows_and_cols) do
|
12
|
+
Array.new(rows_and_cols)
|
13
|
+
end
|
14
|
+
|
15
|
+
squares.each_with_index do |element, row|
|
16
|
+
element.each_index do |col|
|
17
|
+
squares[row][col] = create_custom_square(config[row][col][0],config[row][col][1], row, col)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
squares_class.new(collection_of_squares: squares)
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def self.create_custom_square(display_value, value, row, col, square_class = Square)
|
27
|
+
square_class.new(display_value: display_value,
|
28
|
+
value: value,
|
29
|
+
row: row,
|
30
|
+
col: col )
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.build_empty_squares(rows_and_cols, squares_class = Squares)
|
34
|
+
squares = Array.new(rows_and_cols) do
|
35
|
+
Array.new(rows_and_cols)
|
36
|
+
end
|
37
|
+
|
38
|
+
i = 1
|
39
|
+
squares.each_with_index do |element, row|
|
40
|
+
element.each_index do |col|
|
41
|
+
squares[row][col] = create_empty_square(i, row, col)
|
42
|
+
i += 1
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
squares_class.new(collection_of_squares: squares)
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.create_empty_square(display_value, row, col, square_class = Square)
|
50
|
+
square_class.new(display_value: display_value,
|
51
|
+
value: nil,
|
52
|
+
row: row,
|
53
|
+
col: col )
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/tictactoe.rb
ADDED
data/tictactoe.gemspec
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'tictactoe/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "tic_tac_toe_bfox"
|
8
|
+
spec.version = TTT::VERSION
|
9
|
+
spec.authors = ["Brett Fox"]
|
10
|
+
spec.email = ["brettfox11@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Command line tic tac toe}
|
13
|
+
spec.description = %q{In order to play the game, please type "tictactoe" at the command line.}
|
14
|
+
spec.homepage = "https://github.com/brett11/tic_tac_toe_2"
|
15
|
+
|
16
|
+
# # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
17
|
+
# # to allow pushing to a single host or delete this section to allow pushing to any host.
|
18
|
+
# if spec.respond_to?(:metadata)
|
19
|
+
# spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
|
20
|
+
# else
|
21
|
+
# raise "RubyGems 2.0 or newer is required to protect against " \
|
22
|
+
# "public gem pushes."
|
23
|
+
# end
|
24
|
+
|
25
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
26
|
+
f.match(%r{^(test|spec|features)/})
|
27
|
+
end
|
28
|
+
spec.bindir = "exe"
|
29
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
30
|
+
spec.require_paths = ["lib"]
|
31
|
+
|
32
|
+
spec.add_development_dependency "bundler", "~> 1.14"
|
33
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
34
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
35
|
+
end
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tic_tac_toe_bfox
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Brett Fox
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-09-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.14'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.14'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
description: In order to play the game, please type "tictactoe" at the command line.
|
56
|
+
email:
|
57
|
+
- brettfox11@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- ".rspec"
|
64
|
+
- ".travis.yml"
|
65
|
+
- Gemfile
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- bin/setup
|
69
|
+
- bin/tictactoe
|
70
|
+
- lib/tictactoe.rb
|
71
|
+
- lib/tictactoe/board.rb
|
72
|
+
- lib/tictactoe/board_presenter_terminal.rb
|
73
|
+
- lib/tictactoe/game.rb
|
74
|
+
- lib/tictactoe/game_config.rb
|
75
|
+
- lib/tictactoe/input_helper.rb
|
76
|
+
- lib/tictactoe/io_terminal.rb
|
77
|
+
- lib/tictactoe/move_generator.rb
|
78
|
+
- lib/tictactoe/player.rb
|
79
|
+
- lib/tictactoe/square.rb
|
80
|
+
- lib/tictactoe/squares.rb
|
81
|
+
- lib/tictactoe/squares_factory.rb
|
82
|
+
- lib/tictactoe/version.rb
|
83
|
+
- tictactoe.gemspec
|
84
|
+
homepage: https://github.com/brett11/tic_tac_toe_2
|
85
|
+
licenses: []
|
86
|
+
metadata: {}
|
87
|
+
post_install_message:
|
88
|
+
rdoc_options: []
|
89
|
+
require_paths:
|
90
|
+
- lib
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
requirements: []
|
102
|
+
rubyforge_project:
|
103
|
+
rubygems_version: 2.6.10
|
104
|
+
signing_key:
|
105
|
+
specification_version: 4
|
106
|
+
summary: Command line tic tac toe
|
107
|
+
test_files: []
|