tic_tac_toes 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +7 -0
  3. data/Gemfile.lock +1 -1
  4. data/README.md +2 -0
  5. data/Rakefile +2 -2
  6. data/bin/tic_tac_toes +21 -9
  7. data/lib/command_line/menu.rb +11 -12
  8. data/lib/command_line/{io.rb → prompt.rb} +1 -1
  9. data/lib/command_line/runner.rb +18 -18
  10. data/lib/tic_tac_toes/board_factory.rb +9 -0
  11. data/lib/tic_tac_toes/game_state.rb +27 -0
  12. data/lib/tic_tac_toes/game_state_factory.rb +14 -0
  13. data/lib/tic_tac_toes/history.rb +3 -3
  14. data/lib/tic_tac_toes/io.rb +91 -0
  15. data/lib/tic_tac_toes/move_strategies/easy_ai.rb +11 -0
  16. data/lib/tic_tac_toes/move_strategies/hard_ai.rb +59 -0
  17. data/lib/tic_tac_toes/move_strategies/human.rb +18 -0
  18. data/lib/tic_tac_toes/move_strategies/medium_ai.rb +13 -0
  19. data/lib/tic_tac_toes/player.rb +7 -7
  20. data/lib/tic_tac_toes/player_factory.rb +13 -9
  21. data/lib/tic_tac_toes/rules.rb +1 -1
  22. data/lib/tic_tac_toes/strings.rb +3 -2
  23. data/lib/tic_tac_toes/version.rb +1 -1
  24. data/spec/command_line/menu_spec.rb +40 -44
  25. data/spec/command_line/runner_spec.rb +23 -101
  26. data/spec/database/pg_wrapper_spec.rb +0 -1
  27. data/spec/test_board_generator.rb +15 -0
  28. data/spec/tic_tac_toes/board_factory_spec.rb +13 -0
  29. data/spec/tic_tac_toes/board_spec.rb +20 -25
  30. data/spec/tic_tac_toes/game_state_factory_spec.rb +15 -0
  31. data/spec/tic_tac_toes/game_state_spec.rb +66 -0
  32. data/spec/tic_tac_toes/history_spec.rb +3 -4
  33. data/spec/tic_tac_toes/io_spec.rb +163 -0
  34. data/spec/tic_tac_toes/move_strategies/easy_ai_spec.rb +19 -0
  35. data/spec/tic_tac_toes/move_strategies/hard_ai_spec.rb +95 -0
  36. data/spec/tic_tac_toes/move_strategies/human_spec.rb +31 -0
  37. data/spec/tic_tac_toes/move_strategies/medium_ai_spec.rb +21 -0
  38. data/spec/tic_tac_toes/player_factory_spec.rb +13 -16
  39. data/spec/tic_tac_toes/player_spec.rb +14 -16
  40. data/spec/tic_tac_toes/rules_spec.rb +31 -41
  41. data/spec/tic_tac_toes/strings_spec.rb +0 -1
  42. metadata +32 -19
  43. data/lib/tic_tac_toes/easy_ai.rb +0 -9
  44. data/lib/tic_tac_toes/hard_ai.rb +0 -57
  45. data/lib/tic_tac_toes/io_interface.rb +0 -96
  46. data/lib/tic_tac_toes/medium_ai.rb +0 -11
  47. data/spec/tic_tac_toes/easy_ai_spec.rb +0 -20
  48. data/spec/tic_tac_toes/hard_ai_spec.rb +0 -103
  49. data/spec/tic_tac_toes/io_interface_spec.rb +0 -175
  50. data/spec/tic_tac_toes/medium_ai_spec.rb +0 -22
  51. data/spec/tic_tac_toes/spec_helper.rb +0 -13
  52. /data/lib/{tic_tac_toes/tasks → tasks}/destroy_databases.rake +0 -0
  53. /data/lib/{tic_tac_toes/tasks → tasks}/set_up_databases.rake +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: eba6ff9bd4e9e32b11a8d56fec6a495e8c3b6f4c
4
- data.tar.gz: 3b3a75a1cce99eaeb7a7f98c816f5b48cc348244
3
+ metadata.gz: de5c575f7ff03180dda1f07d8f1930cc56bde5ca
4
+ data.tar.gz: 7dc353433569c7d289055a4893d37b9fcf845af3
5
5
  SHA512:
6
- metadata.gz: 28447b247bb23d735ccf63dbccacb1a65a5179e382a54fc50ed25ff005d6354552db44b548af93fd26253a2a0055910bcec8798151265a05a177f294483a04f7
7
- data.tar.gz: 9c276bb9a57b910eb7e8a6fac87f5f1036551923c7e682ec19532010fa5cb1fbc6eb68a7001d21b97a3390e7779be9a5f6a519c4967461d86e0b66cb49c77217
6
+ metadata.gz: 435ed2f2a0e102b82302d1ee4fc4c0a617d9e51767fb9976c3fecd2223d210a70915bab7b41047b62a83309fa7c4b3f97533fb90918f5948ad44b3e3350d92da
7
+ data.tar.gz: 4c1b8cfc7d68129cb5516d83807e5660c9bf3a4e23c89a558921cdf0d72b33b59fd9ca1e18f125a8f58a77e00d2420eedd68576cdfb62248b989453a85db0ed5
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - "2.1.1"
4
+ script:
5
+ - bundle exec rake set_up_databases
6
+ - bundle exec rspec
7
+ - bundle exec rake destroy_databases
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- tic_tac_toes (0.0.1)
4
+ tic_tac_toes (0.0.2)
5
5
  pg
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ [![Build Status](https://travis-ci.org/bspatafora/tic_tac_toes.svg?branch=master)](https://travis-ci.org/bspatafora/tic_tac_toes)
2
+
1
3
  # TicTacToes
2
4
 
3
5
  The game Tic-tac-toe, featuring an unbeatable AI.
data/Rakefile CHANGED
@@ -1,4 +1,4 @@
1
1
  require 'bundler/gem_tasks'
2
2
 
3
- load './lib/tic_tac_toes/tasks/set_up_databases.rake'
4
- load './lib/tic_tac_toes/tasks/destroy_databases.rake'
3
+ load './lib/tasks/set_up_databases.rake'
4
+ load './lib/tasks/destroy_databases.rake'
data/bin/tic_tac_toes CHANGED
@@ -2,22 +2,34 @@
2
2
 
3
3
  $LOAD_PATH << File.expand_path(File.dirname(__FILE__) + '/../lib')
4
4
 
5
- require 'command_line/io'
5
+ require 'command_line/prompt'
6
+ require 'tic_tac_toes/io'
7
+
8
+ require 'tic_tac_toes/board_factory'
9
+ require 'tic_tac_toes/player_factory'
6
10
  require 'command_line/menu'
7
- require 'database/pg_wrapper'
8
- require 'tic_tac_toes/io_interface'
11
+
9
12
  require 'tic_tac_toes/rules'
13
+
14
+ require 'database/pg_wrapper'
10
15
  require 'tic_tac_toes/history'
16
+ require 'tic_tac_toes/game_state_factory'
11
17
 
12
18
  require 'command_line/runner'
13
19
 
14
20
  database = "tic_tac_toes"
15
21
 
16
- io = CommandLine::IO
17
- io_interface = TicTacToes::IOInterface.new(io)
18
- menu = CommandLine::Menu.new(io_interface)
22
+ prompt = CommandLine::Prompt
23
+ io = TicTacToes::IO.new(prompt)
24
+
25
+ board_factory = TicTacToes::BoardFactory.new
26
+ player_factory = TicTacToes::PlayerFactory.new(io)
27
+ menu = CommandLine::Menu.new(io, board_factory, player_factory)
28
+
29
+ database_wrapper = Database::PGWrapper.new(database)
30
+ history = TicTacToes::History.new(database_wrapper)
31
+ game_state_factory = TicTacToes::GameStateFactory.new(history)
32
+
19
33
  rules = TicTacToes::Rules
20
- database_interface = Database::PGWrapper.new(database)
21
- history = TicTacToes::History.new(database_interface)
22
34
 
23
- CommandLine::Runner.new(io_interface, menu, rules, history).run
35
+ CommandLine::Runner.new(io, menu, game_state_factory, rules).run
@@ -1,16 +1,15 @@
1
- require 'tic_tac_toes/board'
2
- require 'tic_tac_toes/player_factory'
3
1
  require 'tic_tac_toes/rules'
4
2
 
5
3
  module CommandLine
6
4
  class Menu
7
- def initialize(io_interface)
8
- @io_interface = io_interface
9
- @player_factory = TicTacToes::PlayerFactory.new(io_interface)
5
+ def initialize(io, board_factory, player_factory)
6
+ @io = io
7
+ @board_factory = board_factory
8
+ @player_factory = player_factory
10
9
  end
11
10
 
12
11
  def get_board
13
- TicTacToes::Board.new(row_size: get_row_size)
12
+ @board_factory.generate_board(get_row_size)
14
13
  end
15
14
 
16
15
  def get_players
@@ -29,25 +28,25 @@ module CommandLine
29
28
 
30
29
  def get_row_size
31
30
  loop do
32
- row_size = @io_interface.get_row_size
31
+ row_size = @io.get_row_size
33
32
  break row_size if TicTacToes::Rules.row_size_valid?(row_size)
34
- @io_interface.invalid_row_size_error
33
+ @io.invalid_row_size_error
35
34
  end
36
35
  end
37
36
 
38
37
  def get_token(player, taken_tokens)
39
38
  loop do
40
- token = @io_interface.get_token(player)
39
+ token = @io.get_token(player)
41
40
  break token if TicTacToes::Rules.token_valid?(token, taken_tokens)
42
- @io_interface.invalid_token_error
41
+ @io.invalid_token_error
43
42
  end
44
43
  end
45
44
 
46
45
  def get_difficulty
47
46
  loop do
48
- difficulty = @io_interface.get_difficulty
47
+ difficulty = @io.get_difficulty
49
48
  break difficulty if TicTacToes::Rules.difficulty_valid?(difficulty)
50
- @io_interface.invalid_difficulty_error
49
+ @io.invalid_difficulty_error
51
50
  end
52
51
  end
53
52
  end
@@ -1,5 +1,5 @@
1
1
  module CommandLine
2
- module IO
2
+ module Prompt
3
3
  def self.solicit_input
4
4
  gets.chomp
5
5
  end
@@ -1,37 +1,37 @@
1
1
  module CommandLine
2
2
  class Runner
3
- def initialize(io_interface, menu, rules, history)
4
- @io_interface = io_interface
3
+ def initialize(io, menu, game_state_factory, rules)
4
+ @io = io
5
5
  @menu = menu
6
+ @game_state_factory = game_state_factory
6
7
  @rules = rules
7
- @history = history
8
8
  end
9
9
 
10
10
  def run
11
11
  board, players = @menu.get_board, @menu.get_players
12
- @history.record_board_size(board.size)
12
+ game_state = @game_state_factory.generate_game_state(board, players)
13
13
 
14
- take_turn(board, players) until @rules.game_over?(board, players)
15
- end_game(board, players)
14
+ take_turn(game_state) until @rules.game_over?(game_state.board, game_state.players)
15
+ end_game(game_state)
16
16
  end
17
17
 
18
- def take_turn(board, players)
19
- @io_interface.draw_board(board)
20
- @io_interface.thinking_notification if players.first.needs_to_think
18
+ private
21
19
 
22
- move = players.first.make_move(board, players)
23
- @history.record_move(move)
24
- players.rotate!
20
+ def take_turn(game_state)
21
+ @io.draw_board(game_state.board)
22
+ @io.thinking_notification if game_state.current_player.needs_to_think
23
+
24
+ move = game_state.current_player.place_and_return_move(game_state.board, game_state.players)
25
+ game_state.turn_over(move)
25
26
  end
26
27
 
27
- def end_game(board, players)
28
- @io_interface.draw_board(board)
28
+ def end_game(game_state)
29
+ @io.draw_board(game_state.board)
29
30
 
30
- winner = @rules.determine_winner(board, players)
31
+ winner = @rules.determine_winner(game_state.board, game_state.players)
32
+ game_state.game_over(winner)
31
33
 
32
- @history.record_winner(winner)
33
- @history.persist
34
- @io_interface.game_over_notification(winner)
34
+ @io.game_over_notification(winner)
35
35
  end
36
36
  end
37
37
  end
@@ -0,0 +1,9 @@
1
+ require 'tic_tac_toes/board'
2
+
3
+ module TicTacToes
4
+ class BoardFactory
5
+ def generate_board(row_size)
6
+ TicTacToes::Board.new(row_size: row_size)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,27 @@
1
+ module TicTacToes
2
+ class GameState
3
+ attr_reader :board, :players
4
+
5
+ def initialize(board, players, history)
6
+ @board = board
7
+ @players = players
8
+ @history = history
9
+
10
+ @history.record_board_size(@board.size)
11
+ end
12
+
13
+ def current_player
14
+ @players.first
15
+ end
16
+
17
+ def turn_over(move)
18
+ @history.record_move(move)
19
+ @players.rotate!
20
+ end
21
+
22
+ def game_over(winner)
23
+ @history.record_winner(winner)
24
+ @history.persist
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,14 @@
1
+ require 'tic_tac_toes/game_state'
2
+ require 'tic_tac_toes/history'
3
+
4
+ module TicTacToes
5
+ class GameStateFactory
6
+ def initialize(history)
7
+ @history = history
8
+ end
9
+
10
+ def generate_game_state(board, players)
11
+ TicTacToes::GameState.new(board, players, @history)
12
+ end
13
+ end
14
+ end
@@ -2,8 +2,8 @@ module TicTacToes
2
2
  class History
3
3
  attr_reader :board_size, :moves, :winner
4
4
 
5
- def initialize(database_interface)
6
- @database_interface = database_interface
5
+ def initialize(database_wrapper)
6
+ @database_wrapper = database_wrapper
7
7
  end
8
8
 
9
9
  def record_board_size(size)
@@ -21,7 +21,7 @@ module TicTacToes
21
21
  end
22
22
 
23
23
  def persist
24
- @database_interface.record_game_history(self)
24
+ @database_wrapper.record_game_history(self)
25
25
  end
26
26
  end
27
27
  end
@@ -0,0 +1,91 @@
1
+ require 'tic_tac_toes/strings'
2
+
3
+ module TicTacToes
4
+ class IO
5
+ def initialize(io_strategy)
6
+ @io_strategy = io_strategy
7
+ end
8
+
9
+ def get_row_size
10
+ row_size_solicitation
11
+
12
+ Integer(@io_strategy.solicit_input)
13
+ rescue ArgumentError
14
+ not_an_integer_error
15
+ get_row_size
16
+ end
17
+
18
+ def get_token(player)
19
+ token_solicitation(player)
20
+ @io_strategy.solicit_input
21
+ end
22
+
23
+ def get_difficulty
24
+ difficulty_solicitation
25
+ @io_strategy.solicit_input.downcase.to_sym
26
+ end
27
+
28
+ def draw_board(board)
29
+ @io_strategy.display(Strings.board(board))
30
+ end
31
+
32
+ def invalid_row_size_error
33
+ @io_strategy.display_red(Strings::INVALID_ROW_SIZE)
34
+ end
35
+
36
+ def invalid_token_error
37
+ @io_strategy.display_red(Strings::INVALID_TOKEN)
38
+ end
39
+
40
+ def invalid_difficulty_error
41
+ @io_strategy.display_red(Strings::INVALID_DIFFICULTY)
42
+ end
43
+
44
+ def invalid_move_error
45
+ @io_strategy.display_red(Strings::INVALID_MOVE)
46
+ end
47
+
48
+ def thinking_notification
49
+ @io_strategy.display_red(Strings::THINKING)
50
+ end
51
+
52
+ def game_over_notification(winner)
53
+ winner = "Nobody" if winner.nil?
54
+ @io_strategy.display(Strings.game_over_notification(winner))
55
+ end
56
+
57
+ def move_solicitation
58
+ @io_strategy.display(Strings::MOVE_SOLICITATION)
59
+ end
60
+
61
+ def not_an_integer_error
62
+ @io_strategy.display_red(Strings::NOT_AN_INTEGER)
63
+ end
64
+
65
+ def solicit_input
66
+ @io_strategy.solicit_input
67
+ end
68
+
69
+ def red(message)
70
+ @io_strategy.red(message)
71
+ end
72
+
73
+ def blue(message)
74
+ @io_strategy.blue(message)
75
+ end
76
+
77
+ private
78
+
79
+ def row_size_solicitation
80
+ @io_strategy.display(Strings::ROW_SIZE_SOLICITATION)
81
+ end
82
+
83
+ def token_solicitation(player)
84
+ @io_strategy.display(Strings.token_solicitation(player))
85
+ end
86
+
87
+ def difficulty_solicitation
88
+ @io_strategy.display(Strings::DIFFICULTY_SOLICITATION)
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,11 @@
1
+ require 'tic_tac_toes/board'
2
+
3
+ module TicTacToes
4
+ module MoveStrategies
5
+ module EasyAI
6
+ def self.move(board, players)
7
+ board.open_spaces.sample
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,59 @@
1
+ require 'tic_tac_toes/rules'
2
+
3
+ module TicTacToes
4
+ module MoveStrategies
5
+ module HardAI
6
+ def self.move(board, players)
7
+ open_spaces = Hash[board.open_spaces.map { |space| [space, nil] }]
8
+
9
+ open_spaces.each do |space, score|
10
+ score = minimax(generate_board(players.first, space, board), :min, players)
11
+ open_spaces[space] = score
12
+ end
13
+
14
+ best_score = open_spaces.values.max
15
+ open_spaces.each { |space, score| return space if score == best_score }
16
+ end
17
+
18
+ def self.minimax(board, current_player, players)
19
+ return score(board, players) if Rules.game_over?(board, players)
20
+
21
+ if current_player == :max
22
+ best_score = -1
23
+ board.open_spaces.each do |space|
24
+ score = minimax(generate_board(players.first, space, board), :min, players)
25
+ best_score = [best_score, score].max
26
+ end
27
+ best_score
28
+
29
+ elsif current_player == :min
30
+ best_score = 1
31
+ board.open_spaces.each do |space|
32
+ score = minimax(generate_board(players.last, space, board), :max, players)
33
+ best_score = [best_score, score].min
34
+ end
35
+ best_score
36
+ end
37
+ end
38
+
39
+ def self.generate_board(player, space, board)
40
+ new_board = Marshal.load(Marshal.dump(board))
41
+ new_board.place(player, space)
42
+ new_board
43
+ end
44
+
45
+ def self.score(board, players)
46
+ own_token, opponent_token = players.first.token, players.last.token
47
+
48
+ case Rules.determine_winner(board, players)
49
+ when own_token
50
+ 1
51
+ when opponent_token
52
+ -1
53
+ else
54
+ 0
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,18 @@
1
+ module TicTacToes
2
+ module MoveStrategies
3
+ class Human
4
+ def initialize(io)
5
+ @io = io
6
+ end
7
+
8
+ def move(board, players)
9
+ @io.move_solicitation
10
+
11
+ Integer(@io.solicit_input)
12
+ rescue ArgumentError
13
+ @io.not_an_integer_error
14
+ move(board, players)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ require 'tic_tac_toes/move_strategies/easy_ai'
2
+ require 'tic_tac_toes/move_strategies/hard_ai'
3
+
4
+ module TicTacToes
5
+ module MoveStrategies
6
+ module MediumAI
7
+ def self.move(board, players)
8
+ move_strategy = [EasyAI, HardAI, HardAI].sample
9
+ move_strategy.move(board, players)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,19 +1,19 @@
1
1
  module TicTacToes
2
2
  class Player
3
- attr_reader :decider, :token, :needs_to_think
3
+ attr_reader :move_strategy, :token, :needs_to_think
4
4
 
5
- def initialize(decider, token, needs_to_think, io_interface)
6
- @decider = decider
5
+ def initialize(move_strategy, token, needs_to_think, io)
6
+ @move_strategy = move_strategy
7
7
  @token = token
8
8
  @needs_to_think = needs_to_think
9
- @io_interface = io_interface
9
+ @io = io
10
10
  end
11
11
 
12
- def make_move(board, players)
12
+ def place_and_return_move(board, players)
13
13
  loop do
14
- space = @decider.make_move(board, players)
14
+ space = @move_strategy.move(board, players)
15
15
  break [@token, space] if board.place(self, space)
16
- @io_interface.invalid_move_error
16
+ @io.invalid_move_error
17
17
  end
18
18
  end
19
19
  end
@@ -1,24 +1,28 @@
1
- require 'tic_tac_toes/easy_ai'
2
- require 'tic_tac_toes/hard_ai'
3
- require 'tic_tac_toes/medium_ai'
1
+ require 'tic_tac_toes/move_strategies/human'
2
+ require 'tic_tac_toes/move_strategies/easy_ai'
3
+ require 'tic_tac_toes/move_strategies/medium_ai'
4
+ require 'tic_tac_toes/move_strategies/hard_ai'
4
5
  require 'tic_tac_toes/player'
5
6
 
6
7
  module TicTacToes
7
8
  class PlayerFactory
8
- AI_DIFFICULTIES = { easy: EasyAI, medium: MediumAI, hard: HardAI }
9
-
10
- def initialize(io_interface)
11
- @io_interface = io_interface
9
+ def initialize(io)
10
+ @io = io
12
11
  end
13
12
 
13
+ AIS = {
14
+ easy: ::TicTacToes::MoveStrategies::EasyAI,
15
+ medium: ::TicTacToes::MoveStrategies::MediumAI,
16
+ hard: ::TicTacToes::MoveStrategies::HardAI }
17
+
14
18
  def generate_human_player(token)
15
19
  needs_to_think = false
16
- Player.new(@io_interface, token, needs_to_think, @io_interface)
20
+ Player.new(TicTacToes::MoveStrategies::Human.new(@io), token, needs_to_think, @io)
17
21
  end
18
22
 
19
23
  def generate_computer_player(token, difficulty)
20
24
  needs_to_think = true
21
- Player.new(AI_DIFFICULTIES[difficulty], token, needs_to_think, @io_interface)
25
+ Player.new(AIS[difficulty], token, needs_to_think, @io)
22
26
  end
23
27
  end
24
28
  end
@@ -16,7 +16,7 @@ module TicTacToes
16
16
  end
17
17
 
18
18
  def self.difficulty_valid?(difficulty)
19
- PlayerFactory::AI_DIFFICULTIES.include? difficulty
19
+ PlayerFactory::AIS.include? difficulty
20
20
  end
21
21
 
22
22
  def self.game_over?(board, players)
@@ -1,3 +1,4 @@
1
+ require 'command_line/prompt'
1
2
  require 'tic_tac_toes/rules'
2
3
 
3
4
  module TicTacToes
@@ -71,9 +72,9 @@ module TicTacToes
71
72
 
72
73
  def self.get_colored_token(player)
73
74
  if player.needs_to_think
74
- return CommandLine::IO.red(player.token)
75
+ return CommandLine::Prompt.red(player.token)
75
76
  else
76
- return CommandLine::IO.blue(player.token)
77
+ return CommandLine::Prompt.blue(player.token)
77
78
  end
78
79
  end
79
80
 
@@ -1,3 +1,3 @@
1
1
  module TicTacToes
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end