games_bfox 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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