games_bfox 0.2.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.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +5 -0
  5. data/Gemfile +4 -0
  6. data/README.md +36 -0
  7. data/Rakefile +6 -0
  8. data/exe/mastermind +8 -0
  9. data/exe/setup +8 -0
  10. data/exe/tictactoe +8 -0
  11. data/games.gemspec +36 -0
  12. data/lib/games/mastermind/board.rb +43 -0
  13. data/lib/games/mastermind/board_builder.rb +9 -0
  14. data/lib/games/mastermind/board_presenter_terminal.rb +69 -0
  15. data/lib/games/mastermind/colors.rb +14 -0
  16. data/lib/games/mastermind/game.rb +64 -0
  17. data/lib/games/mastermind/game_config.rb +48 -0
  18. data/lib/games/mastermind/game_resetter.rb +19 -0
  19. data/lib/games/mastermind/game_state_changer.rb +41 -0
  20. data/lib/games/mastermind/guess_evaluator.rb +85 -0
  21. data/lib/games/mastermind/input_helper.rb +54 -0
  22. data/lib/games/mastermind/move_generator.rb +46 -0
  23. data/lib/games/mastermind/peg.rb +15 -0
  24. data/lib/games/mastermind/pegs.rb +50 -0
  25. data/lib/games/mastermind/pegs_factory.rb +26 -0
  26. data/lib/games/mastermind/players_factory.rb +15 -0
  27. data/lib/games/mastermind.rb +13 -0
  28. data/lib/games/shared/array_iterator.rb +28 -0
  29. data/lib/games/shared/game.rb +155 -0
  30. data/lib/games/shared/input_helper.rb +74 -0
  31. data/lib/games/shared/io_terminal.rb +25 -0
  32. data/lib/games/shared/player.rb +15 -0
  33. data/lib/games/shared/version.rb +3 -0
  34. data/lib/games/tictactoe/board.rb +40 -0
  35. data/lib/games/tictactoe/board_builder.rb +10 -0
  36. data/lib/games/tictactoe/board_presenter_terminal.rb +28 -0
  37. data/lib/games/tictactoe/game.rb +60 -0
  38. data/lib/games/tictactoe/game_config.rb +73 -0
  39. data/lib/games/tictactoe/game_resetter.rb +16 -0
  40. data/lib/games/tictactoe/game_state_changer.rb +17 -0
  41. data/lib/games/tictactoe/input_helper.rb +80 -0
  42. data/lib/games/tictactoe/minimax.rb +81 -0
  43. data/lib/games/tictactoe/move_generator.rb +68 -0
  44. data/lib/games/tictactoe/player.rb +11 -0
  45. data/lib/games/tictactoe/players_factory.rb +11 -0
  46. data/lib/games/tictactoe/square.rb +30 -0
  47. data/lib/games/tictactoe/squares.rb +129 -0
  48. data/lib/games/tictactoe/squares_factory.rb +56 -0
  49. data/lib/games/tictactoe.rb +13 -0
  50. data/lib/games/version.rb +3 -0
  51. data/lib/games.rb +5 -0
  52. metadata +173 -0
@@ -0,0 +1,50 @@
1
+ module MM
2
+ class Pegs
3
+ attr_reader :collection_of_pegs
4
+
5
+ #collection_of_pegs is a multi-dimensional array
6
+
7
+ def initialize(args)
8
+ @collection_of_pegs = args[:collection_of_pegs]
9
+ end
10
+
11
+ def retrieve_peg(row, col)
12
+ if row >= number_of_rows || col >= number_of_cols
13
+ return nil
14
+ else
15
+ return collection_of_pegs[row][col]
16
+ end
17
+ end
18
+
19
+ def current_row(number_of_turns_taken)
20
+ collection_of_pegs[number_of_turns_taken]
21
+ end
22
+
23
+ def display_values
24
+ # reversed so that game appears to be filling itself in from top to bottom
25
+ collection_of_pegs.reverse.map do |row|
26
+ row.map do |peg|
27
+ peg.display_value
28
+ end
29
+ end
30
+ end
31
+
32
+ def result_values
33
+ collection_of_pegs.reverse.map do |row|
34
+ row.map do |peg|
35
+ peg.result_value
36
+ end
37
+ end
38
+ end
39
+
40
+ private
41
+ def number_of_rows
42
+ collection_of_pegs.length
43
+ end
44
+
45
+ def number_of_cols
46
+ collection_of_pegs[0].length
47
+ end
48
+ end
49
+ end
50
+
@@ -0,0 +1,26 @@
1
+ require_relative 'pegs'
2
+ require_relative 'peg'
3
+
4
+ module MM
5
+ module PegsFactory
6
+ def self.build_empty_pegs(rows = 12, cols = 4, pegs_class = Pegs)
7
+ pegs = Array.new(rows) do
8
+ Array.new(cols)
9
+ end
10
+
11
+ pegs.each_with_index do |element, row|
12
+ element.each_index do |col|
13
+ pegs[row][col] = create_empty_peg(row, col)
14
+ end
15
+ end
16
+
17
+ pegs_class.new(collection_of_pegs: pegs)
18
+ end
19
+
20
+ def self.create_empty_peg(row, col, display_value = nil, peg_class = Peg)
21
+ peg_class.new(display_value: display_value,
22
+ row: row,
23
+ col: col )
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,15 @@
1
+ require_relative '../shared/player'
2
+
3
+ module MM
4
+ class PlayersFactory
5
+ #returns an array in case we would like to extend the game to allow for multiple players
6
+ def generate_players(config)
7
+ if config.code_setter == :computer
8
+ player_1 = Shared::Player.new(name: config.player_1_name)
9
+ else
10
+ player_1 = Shared::Player.new(name: config.player_1_name, type: :computer)
11
+ end
12
+ [player_1]
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ Dir[File.join(File.expand_path(File.dirname(__FILE__)), 'shared', '*.rb')].each {|file| require file }
2
+ Dir[File.join(File.expand_path(File.dirname(__FILE__)), 'mastermind', '*.rb')].each {|file| require file }
3
+
4
+ class Mastermind
5
+ def self.run
6
+ io = Shared::IOTerminal.new
7
+ board_presenter = MM::BoardPresenterTerminal.new
8
+
9
+ game = MM::Game.new(game_module: MM, io: io, board_presenter: board_presenter)
10
+ # game.setup
11
+ game.play
12
+ end
13
+ end
@@ -0,0 +1,28 @@
1
+ #from Design Patterns by Russ Olsen pg 128-129
2
+ module Shared
3
+ class ArrayIterator
4
+ def initialize(array)
5
+ @array = array
6
+ @index = 0
7
+ end
8
+
9
+ def has_next?
10
+ @index < @array.length
11
+ end
12
+
13
+ def item
14
+ @array[@index]
15
+ end
16
+
17
+ def peek
18
+ @array[@index]
19
+ end
20
+
21
+ def next_item
22
+ value = @array[@index]
23
+ @index += 1
24
+ value
25
+ end
26
+ end
27
+ end
28
+
@@ -0,0 +1,155 @@
1
+ Dir[File.join(File.dirname(__FILE__), '*.rb')].each {|file| require file }
2
+ Dir[File.join(File.expand_path("..", File.dirname(__FILE__)), 'tictactoe', '*.rb')].each {|file| require file unless file == File.join(File.expand_path("..", File.dirname(__FILE__)), 'tictactoe', 'game.rb')}
3
+ Dir[File.join(File.expand_path("..", File.dirname(__FILE__)), 'mastermind', '*.rb')].each {|file| require file unless file == File.join(File.expand_path("..", File.dirname(__FILE__)), 'mastermind', 'game.rb')}
4
+
5
+ module Shared
6
+ class Game
7
+ attr_accessor :board, :board_presenter, :board_builder
8
+ attr_accessor :players, :players_factory
9
+ attr_accessor :config, :game_state_changer
10
+ attr_accessor :number_of_turns_taken
11
+ attr_accessor :game_resetter
12
+ attr_accessor :move_generator
13
+ attr_reader :io, :input_helper, :game_module
14
+
15
+ def initialize(args = {})
16
+ @game_module = args.fetch(:game_module, nil)
17
+ @io = args.fetch(:io, IOTerminal.new)
18
+ @board_presenter = args.fetch(:board_presenter, nil)
19
+
20
+ @input_helper = game_module::InputHelper.new(@io)
21
+ @board_builder = game_module::BoardBuilder.new
22
+ @game_state_changer = game_module::GameStateChanger.new
23
+ @game_resetter = game_module::GameResetter.new
24
+ @move_generator = game_module::MoveGenerator.new
25
+ @players_factory = game_module::PlayersFactory.new
26
+ @config = game_module::GameConfig.new(@input_helper)
27
+
28
+ @number_of_turns_taken = 0
29
+ end
30
+
31
+ def one_time_setup
32
+ #setup gets necessary info from user and stores it in config object
33
+ config.one_time_setup
34
+ end
35
+
36
+ def every_time_setup
37
+ config.every_time_setup
38
+ self.players = players_factory.generate_players(config)
39
+ self.board = board_builder.generate_empty_board(config)
40
+ local_setup
41
+ end
42
+
43
+ #to be implemented by subclasses
44
+ def local_setup
45
+ end
46
+
47
+ #overriding so that io and input_helper always in harmony
48
+ def io=(new_io)
49
+ @io = new_io
50
+ self.input_helper = game_module::InputHelper.new(new_io)
51
+ end
52
+
53
+ def play
54
+ one_time_setup
55
+ while true
56
+ every_time_setup
57
+ while !over?
58
+ print_board
59
+ move = move_generator.get_player_choice(self)
60
+ game_state_changer.change_game_state(move, self)
61
+ end
62
+
63
+ #need to print_final_iteration of board
64
+ print_board
65
+
66
+ if won?
67
+ winning_prompt
68
+ elsif over_with_no_winner?
69
+ no_winner_prompt
70
+ end
71
+
72
+ game_resetter.reset_game(self)
73
+ new_game_starting_graphic
74
+ end
75
+ end
76
+
77
+ def current_player
78
+ players[current_turn_player_index]
79
+ end
80
+
81
+ def current_player_name
82
+ current_player.name
83
+ end
84
+
85
+ def current_player_human?
86
+ current_player.type == :human
87
+ end
88
+
89
+ def current_player_computer?
90
+ current_player.type == :computer
91
+ end
92
+
93
+ def move_forward_one_turn
94
+ self.number_of_turns_taken += 1
95
+ end
96
+
97
+ def generate_empty_board(config)
98
+ board_builder.generate_empty_board(config)
99
+ end
100
+
101
+ def change_game_state(available_choice, game)
102
+ game_state_changer.change_game_state(available_choice, game)
103
+ end
104
+
105
+ def winner
106
+ #each move is immediately proceeded by an increment to number_of_selections_made; therefore, need to rewind won to find winner
107
+ if !won?
108
+ return nil
109
+ end
110
+ current_player
111
+ end
112
+
113
+ def display_values
114
+ board.display_values
115
+ end
116
+
117
+ private
118
+
119
+ def over?
120
+ raise 'Called abstract method: over?'
121
+ end
122
+
123
+ def over_with_no_winner?
124
+ raise 'Called abstract method: draw?'
125
+ end
126
+
127
+ def won?
128
+ raise 'Called abstract method: won?'
129
+ end
130
+
131
+ def print_board
132
+ board_presenter.present_board(board)
133
+ end
134
+
135
+ def number_of_players
136
+ players.size
137
+ end
138
+
139
+ def current_turn_player_index
140
+ (number_of_turns_taken % number_of_players)
141
+ end
142
+
143
+ def winning_prompt
144
+ input_helper.winning_prompt(current_player.name)
145
+ end
146
+
147
+ def no_winner_prompt
148
+ input_helper.no_winner_prompt
149
+ end
150
+
151
+ def new_game_starting_graphic
152
+ input_helper.new_game_starting_graphic
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,74 @@
1
+ module Shared
2
+ class InputHelper
3
+ attr_accessor :io
4
+
5
+ def initialize(io)
6
+ @io = io
7
+ end
8
+
9
+ def get_game
10
+ user_choice = get_user_input("Please enter \"M\" if you would like to play Mastermind. Enter \"T\" if you would like to play Tic Tac Toe.", "Invalid character. Please enter either M (Mastermind) or T (Tic Tac Toe).") do |input|
11
+ input == 'm' || input == 'M' || input == 't' || input == 'T'
12
+ end
13
+ user_choice = user_choice.upcase
14
+ if user_choice == "M"
15
+ :mastermind
16
+ elsif user_choice == "T"
17
+ :tictactoe
18
+ end
19
+ end
20
+
21
+ def get_player_1_name
22
+ user_choice = get_user_input("Player 1, please enter your name", "Please re-enter your name, using only letters") do |input|
23
+ input =~ /^[a-zA-Z]+$/
24
+ end
25
+ user_choice.capitalize
26
+ end
27
+
28
+ def get_user_input(prompt, reprompt, &block_validation)
29
+ io.present_with_new_line(prompt)
30
+ user_choice = nil
31
+ while true
32
+ user_choice = io.receive
33
+ if user_choice.to_s.upcase == "EXIT"
34
+ #http://blog.honeybadger.io/how-to-exit-a-ruby-program/
35
+ exit(0)
36
+ end
37
+ #breaks out of input loop if input is valid (the block validation makes sure some rule is true)
38
+ break if block_validation.call(user_choice)
39
+ puts reprompt
40
+ end
41
+ user_choice
42
+ end
43
+
44
+ def computer_choosing_graphic
45
+ io.present("Computer choosing.")
46
+ 3.times do
47
+ sleep(0.3)
48
+ io.present(".")
49
+ end
50
+ io.present("\n")
51
+ end
52
+
53
+ def new_game_starting_graphic
54
+ io.present("New game starting in 3")
55
+ i = 2
56
+ 2.times do
57
+ marching_dots
58
+ io.present("#{i.to_s}")
59
+ i = i - 1
60
+ end
61
+ marching_dots
62
+ io.present("\n")
63
+ end
64
+
65
+ def marching_dots
66
+ sleep(0.2)
67
+ io.present(".")
68
+ sleep(0.2)
69
+ io.present(".")
70
+ sleep(0.1)
71
+ io.present(".")
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,25 @@
1
+ module Shared
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,15 @@
1
+ module Shared
2
+ class Player
3
+ attr_accessor :value, :name, :type, :difficulty_level
4
+
5
+ def initialize(args = {})
6
+ @name = args.fetch(:name, "Player 1")
7
+ @type = args.fetch(:type, :human)
8
+ @difficulty_level = args.fetch(:difficulty_level, nil)
9
+ post_initialize(args)
10
+ end
11
+
12
+ def post_initialize(args)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,3 @@
1
+ module Shared
2
+ VERSION = "0.3.0"
3
+ end
@@ -0,0 +1,40 @@
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
+ def number_of_rows
32
+ rows_and_cols
33
+ end
34
+
35
+ private
36
+ def retrieve_square(display_value)
37
+ return squares.retrieve_square(display_value)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,10 @@
1
+ require_relative 'squares_factory'
2
+
3
+ module TTT
4
+ class BoardBuilder
5
+ def generate_empty_board(config)
6
+ number_of_rows_cols = config.number_of_rows_cols
7
+ Board.new(rows_and_cols: number_of_rows_cols, squares: SquaresFactory.build_empty_squares(number_of_rows_cols) )
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,28 @@
1
+ module TTT
2
+ class BoardPresenterTerminal
3
+ attr_accessor :board
4
+
5
+ def present_board(board)
6
+ @board = board
7
+ #board.display_values will return multidimensional array
8
+ display_values.each_with_index do |row, row_number|
9
+ row.each_with_index do |display_value, index|
10
+ #http://www.evc-cit.info/cit020/beginning-programming/chp_04/file_printf.html
11
+ printf "%2s", display_value
12
+ print " | " unless index == (row.size - 1)
13
+ end
14
+ print "\n"
15
+ #row_number starts at 0
16
+ puts "_"*(row.size * 5) unless row_number.equal? (number_of_rows - 1)
17
+ end
18
+ end
19
+
20
+ def display_values
21
+ board.display_values
22
+ end
23
+
24
+ def number_of_rows
25
+ board.number_of_rows
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,60 @@
1
+ require_relative '../shared/game'
2
+
3
+ module TTT
4
+ class Game < Shared::Game
5
+ attr_accessor :won_flag
6
+
7
+ def local_setup
8
+ self.won_flag = false
9
+ end
10
+
11
+
12
+ def over?
13
+ won_flag || over_with_no_winner?
14
+ end
15
+
16
+ def over_with_no_winner?
17
+ board.full?
18
+ end
19
+
20
+ def won?
21
+ if won_flag
22
+ return true
23
+ end
24
+
25
+ if board.won?
26
+ self.won_flag = true
27
+ else
28
+ false
29
+ end
30
+ end
31
+
32
+ def available_choices
33
+ board.available_choices
34
+ end
35
+
36
+ def player_1_value
37
+ players[0].value
38
+ end
39
+
40
+ def player_2_value
41
+ players[1].value
42
+ end
43
+
44
+ def current_player_value
45
+ current_player.value
46
+ end
47
+
48
+ def current_player_difficult_computer?
49
+ current_player_computer? && current_player.difficulty_level == :difficult
50
+ end
51
+
52
+ def current_player_easy_computer?
53
+ current_player_computer? && current_player.difficulty_level == :easy
54
+ end
55
+
56
+ def change_square(display_value, current_player_value)
57
+ board.change_square(display_value, current_player_value)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,73 @@
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
+ end
16
+
17
+ def one_time_setup
18
+ set_player_1_values
19
+ set_player_2_values
20
+ set_board_rows_cols
21
+ end
22
+
23
+ def every_time_setup
24
+
25
+ end
26
+
27
+ def set_player_1_values
28
+ self.player_1_name = input_helper.get_player_1_name
29
+ self.player_1_value = input_helper.get_player_1_value(player_1_name)
30
+ end
31
+
32
+ def set_player_2_values
33
+ self.player_2_type = input_helper.get_player_2_type
34
+ if player_2_human?
35
+ self.player_2_name = input_helper.get_player_2_name
36
+ self.player_2_value = input_helper.get_player_2_value(player_1_value)
37
+ elsif player_2_computer?
38
+ self.player_2_name = "Computer"
39
+ self.player_2_value = get_computer_value
40
+ self.computer_difficulty_level = input_helper.get_computer_difficulty_level
41
+ end
42
+ end
43
+
44
+ def set_board_rows_cols
45
+ #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
46
+ if computer_difficult?
47
+ self.number_of_rows_cols = input_helper.get_number_of_rows_cols_max_3
48
+ else
49
+ self.number_of_rows_cols = input_helper.get_number_of_rows_cols_max_9
50
+ end
51
+ end
52
+
53
+ def player_2_human?
54
+ player_2_type == :human
55
+ end
56
+
57
+ def player_2_computer?
58
+ player_2_type == :computer
59
+ end
60
+
61
+ def computer_difficult?
62
+ computer_difficulty_level == :difficult
63
+ end
64
+
65
+ def get_computer_value
66
+ if player_1_value != "O"
67
+ "O"
68
+ else
69
+ "X"
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,16 @@
1
+ module TTT
2
+ class GameResetter
3
+ attr_accessor :game
4
+
5
+ def reset_game(game)
6
+ @game = game
7
+ reset_board
8
+ game.number_of_turns_taken = 0
9
+ game.won_flag = false
10
+ end
11
+
12
+ def reset_board
13
+ game.board = game.generate_empty_board(game.config)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ module TTT
2
+ class GameStateChanger
3
+ attr_accessor :game
4
+
5
+ def change_game_state(player_choice, game)
6
+ @game = game
7
+ change_square(player_choice)
8
+ end
9
+
10
+ def change_square(display_value)
11
+ game.change_square(display_value, game.current_player_value)
12
+ if !game.won?
13
+ game.move_forward_one_turn
14
+ end
15
+ end
16
+ end
17
+ end