ttt-core 1.0.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 (57) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +36 -0
  3. data/.rspec +2 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +2 -0
  7. data/Gemfile +8 -0
  8. data/Gemfile.lock +61 -0
  9. data/README.md +29 -0
  10. data/Rakefile +8 -0
  11. data/bin/run.sh +21 -0
  12. data/lib/ai_player.rb +117 -0
  13. data/lib/board.rb +87 -0
  14. data/lib/board_factory.rb +8 -0
  15. data/lib/game.rb +27 -0
  16. data/lib/player.rb +16 -0
  17. data/lib/player_factory.rb +38 -0
  18. data/lib/player_options.rb +48 -0
  19. data/lib/player_symbols.rb +16 -0
  20. data/lib/replay_option.rb +3 -0
  21. data/lib/ttt-core/version.rb +3 -0
  22. data/spec/ai_player_spec.rb +143 -0
  23. data/spec/board_factory_spec.rb +10 -0
  24. data/spec/board_spec.rb +125 -0
  25. data/spec/game_spec.rb +58 -0
  26. data/spec/player_options_spec.rb +33 -0
  27. data/spec/player_symbols_spec.rb +32 -0
  28. data/spec/replay_option_spec.rb +8 -0
  29. data/spec/spec_helper.rb +107 -0
  30. data/ttt-core-0.0.1/.gitignore +36 -0
  31. data/ttt-core-0.0.1/.rspec +2 -0
  32. data/ttt-core-0.0.1/.ruby-gemset +1 -0
  33. data/ttt-core-0.0.1/.ruby-version +1 -0
  34. data/ttt-core-0.0.1/.travis.yml +2 -0
  35. data/ttt-core-0.0.1/Gemfile +10 -0
  36. data/ttt-core-0.0.1/README.md +21 -0
  37. data/ttt-core-0.0.1/Rakefile +7 -0
  38. data/ttt-core-0.0.1/bin/run.sh +21 -0
  39. data/ttt-core-0.0.1/lib/ai_player.rb +117 -0
  40. data/ttt-core-0.0.1/lib/board.rb +87 -0
  41. data/ttt-core-0.0.1/lib/board_factory.rb +8 -0
  42. data/ttt-core-0.0.1/lib/game.rb +27 -0
  43. data/ttt-core-0.0.1/lib/player.rb +16 -0
  44. data/ttt-core-0.0.1/lib/player_factory.rb +38 -0
  45. data/ttt-core-0.0.1/lib/player_options.rb +48 -0
  46. data/ttt-core-0.0.1/lib/player_symbols.rb +16 -0
  47. data/ttt-core-0.0.1/lib/replay_option.rb +3 -0
  48. data/ttt-core-0.0.1/spec/ai_player_spec.rb +143 -0
  49. data/ttt-core-0.0.1/spec/board_factory_spec.rb +10 -0
  50. data/ttt-core-0.0.1/spec/board_spec.rb +125 -0
  51. data/ttt-core-0.0.1/spec/game_spec.rb +58 -0
  52. data/ttt-core-0.0.1/spec/player_options_spec.rb +33 -0
  53. data/ttt-core-0.0.1/spec/player_symbols_spec.rb +32 -0
  54. data/ttt-core-0.0.1/spec/replay_option_spec.rb +8 -0
  55. data/ttt-core-0.0.1/spec/spec_helper.rb +107 -0
  56. data/ttt-core.gemspec +22 -0
  57. metadata +150 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 592a2b99d813d6eff7554da5722ef2ce5f960470
4
+ data.tar.gz: 65262df5515d534718ed484cab124e32f847fb22
5
+ SHA512:
6
+ metadata.gz: e1eb6b836b09ba27c3c944a4ae5064242f8595e20955dbb0516405ad22bccebec1247e3a1a32c2999b32a06050cb7bbf3dc1a88bdedb059da668643bf9596f99
7
+ data.tar.gz: ce091661e1f82bd04dca7b1fa02a128a63361cba5fdb90bfd35b477aab073d85a475fddf55fb58ff0776de7001cf9ddb7f9771f3a872a455fe0ffa3c46184fca
data/.gitignore ADDED
@@ -0,0 +1,36 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /test/tmp/
9
+ /test/version_tmp/
10
+ /tmp/
11
+ .coveralls.yml
12
+
13
+ ## Specific to RubyMotion:
14
+ .dat*
15
+ .repl_history
16
+ build/
17
+
18
+ ## Documentation cache and generated files:
19
+ /.yardoc/
20
+ /_yardoc/
21
+ /doc/
22
+ /rdoc/
23
+
24
+ ## Environment normalisation:
25
+ /.bundle/
26
+ /vendor/bundle
27
+ /lib/bundler/man/
28
+
29
+ # for a library or gem, you might want to ignore these files since the code is
30
+ # intended to run in multiple environments; otherwise, check them in:
31
+ Gemfile.lock
32
+ # .ruby-version
33
+ # .ruby-gemset
34
+
35
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
36
+ .rvmrc
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ ttt
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.2.1
data/.travis.yml ADDED
@@ -0,0 +1,2 @@
1
+ language: ruby
2
+
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :test do
4
+ gem 'rake', '10.5.0'
5
+ gem 'simplecov', '0.11.1'
6
+ gem 'coveralls', '0.8.10', :require => false
7
+ gem 'rspec', '3.4.0', :require => 'spec'
8
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,61 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ coveralls (0.8.10)
5
+ json (~> 1.8)
6
+ rest-client (>= 1.6.8, < 2)
7
+ simplecov (~> 0.11.0)
8
+ term-ansicolor (~> 1.3)
9
+ thor (~> 0.19.1)
10
+ tins (~> 1.6.0)
11
+ diff-lcs (1.2.5)
12
+ docile (1.1.5)
13
+ domain_name (0.5.25)
14
+ unf (>= 0.0.5, < 1.0.0)
15
+ http-cookie (1.0.2)
16
+ domain_name (~> 0.5)
17
+ json (1.8.3)
18
+ mime-types (2.99)
19
+ netrc (0.11.0)
20
+ rake (10.5.0)
21
+ rest-client (1.8.0)
22
+ http-cookie (>= 1.0.2, < 2.0)
23
+ mime-types (>= 1.16, < 3.0)
24
+ netrc (~> 0.7)
25
+ rspec (3.4.0)
26
+ rspec-core (~> 3.4.0)
27
+ rspec-expectations (~> 3.4.0)
28
+ rspec-mocks (~> 3.4.0)
29
+ rspec-core (3.4.1)
30
+ rspec-support (~> 3.4.0)
31
+ rspec-expectations (3.4.0)
32
+ diff-lcs (>= 1.2.0, < 2.0)
33
+ rspec-support (~> 3.4.0)
34
+ rspec-mocks (3.4.1)
35
+ diff-lcs (>= 1.2.0, < 2.0)
36
+ rspec-support (~> 3.4.0)
37
+ rspec-support (3.4.1)
38
+ simplecov (0.11.1)
39
+ docile (~> 1.1.0)
40
+ json (~> 1.8)
41
+ simplecov-html (~> 0.10.0)
42
+ simplecov-html (0.10.0)
43
+ term-ansicolor (1.3.2)
44
+ tins (~> 1.0)
45
+ thor (0.19.1)
46
+ tins (1.6.0)
47
+ unf (0.1.4)
48
+ unf_ext
49
+ unf_ext (0.0.7.1)
50
+
51
+ PLATFORMS
52
+ ruby
53
+
54
+ DEPENDENCIES
55
+ coveralls (= 0.8.10)
56
+ rake (= 10.5.0)
57
+ rspec (= 3.4.0)
58
+ simplecov (= 0.11.1)
59
+
60
+ BUNDLED WITH
61
+ 1.11.2
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Apprenticeship-RubyTicTacToe
2
+
3
+ Tic Tac Toe Core Game implemented in Ruby
4
+
5
+ [![Master Build Status](https://travis-ci.org/gemcfadyen/Apprenticeship-RubyTicTacToe.svg?branch=master)](https://travis-ci.org/gemcfadyen/Apprenticeship-RubyTicTacToe) [![Master Coverage Status](https://coveralls.io/repos/gemcfadyen/Apprenticeship-RubyTicTacToe/badge.svg?branch=master&service=github)](https://coveralls.io/github/gemcfadyen/Apprenticeship-RubyTicTacToe?branch=master)
6
+
7
+ To run the application clone the project into a folder
8
+
9
+ >> git clone git@github.com:gemcfadyen/Apprenticeship-RubyTicTacToe.git
10
+
11
+ To recreate the gem, do
12
+ >> gem build ttt-core.gemspec
13
+
14
+ To install locally, do
15
+ >> gem install ./ttt-core-0.0.1.gem
16
+
17
+ This will install the gem to the ttt gemset.
18
+ To use this gem, add the following to your Gemfile:
19
+
20
+ `gem ttt-core, '0.0.1'`
21
+
22
+ (update the version as appropriate)
23
+
24
+ Ensure your project will look at the ttt gemset path. Run `gem env` and check if the ttt gemset is listed under GEM PATHS.
25
+
26
+ If it is not, you can configure it by running
27
+ >> rvm 2.2.1@ttt
28
+
29
+ Run `gem env` again, and check that the gemset is listed under the GEM PATH.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ begin
2
+ require "bundler/gem_tasks"
3
+ require 'rspec/core/rake_task'
4
+ RSpec::Core::RakeTask.new(:spec)
5
+ rescue LoadError
6
+ end
7
+
8
+ task :default => :spec
data/bin/run.sh ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ ##Setup Load Path
4
+ $LOAD_PATH << File.expand_path("../../lib", __FILE__)
5
+
6
+ require 'command_line_app'
7
+ require 'command_line_ui'
8
+ require 'board_factory'
9
+ require 'command_line_player_factory'
10
+ require 'prompt_writer'
11
+ require 'prompt_reader'
12
+
13
+ writer = PromptWriter.new($stdout)
14
+ reader = PromptReader.new($stdin)
15
+ command_line_ui = CommandLineUI.new(writer, reader)
16
+
17
+ board_factory = BoardFactory.new
18
+ player_factory = CommandLinePlayerFactory.new
19
+
20
+ command_line_app = CommandLineApp.new(command_line_ui, board_factory, player_factory)
21
+ command_line_app.start
data/lib/ai_player.rb ADDED
@@ -0,0 +1,117 @@
1
+ require 'player_symbols'
2
+ require 'player'
3
+
4
+ class AiPlayer
5
+ include Player
6
+
7
+ def initialize(symbol)
8
+ super(symbol)
9
+ end
10
+
11
+ def choose_move(board)
12
+ minimax(board, true, board.vacant_indices.size, ALPHA, BETA).first[1]
13
+ end
14
+
15
+ def ready?
16
+ true
17
+ end
18
+
19
+ def minimax(board, is_max_player, depth, alpha, beta)
20
+ best_score_so_far = initial_score(is_max_player)
21
+
22
+ if round_is_over(board, depth)
23
+ return score(board, depth)
24
+ end
25
+
26
+ board.vacant_indices.each do |i|
27
+ new_board = board.make_move(i, current_players_symbol(is_max_player))
28
+ result = minimax(new_board, !is_max_player, depth - 1, alpha, beta)
29
+ best_score_so_far = update_score(is_max_player, i, best_score_so_far, score_from(result))
30
+
31
+ alpha = update_alpha(is_max_player, best_score_so_far, alpha)
32
+ beta = update_beta(is_max_player, best_score_so_far, beta)
33
+
34
+ if alpha > beta
35
+ break
36
+ end
37
+ end
38
+
39
+ best_score_so_far
40
+ end
41
+
42
+
43
+ private
44
+
45
+ ALPHA = -2
46
+ BETA = 2
47
+
48
+ def update_alpha(is_max_player, best_score_so_far, alpha)
49
+ if is_max_player && score_from(best_score_so_far) > alpha
50
+ alpha = score_from(best_score_so_far)
51
+ end
52
+ alpha
53
+ end
54
+
55
+ def score_from(score)
56
+ score.first.first
57
+ end
58
+
59
+ def update_beta(is_max_player, best_score_so_far, beta)
60
+ if !is_max_player && score_from(best_score_so_far) < beta
61
+ beta = score_from(best_score_so_far)
62
+ end
63
+ beta
64
+ end
65
+
66
+ def round_is_over(board, depth)
67
+ @winner = board.winning_symbol
68
+ !@winner.nil? || depth == 0
69
+ end
70
+
71
+ def score(board, depth)
72
+ if @winner.nil?
73
+ return {0 => nil}
74
+ end
75
+
76
+ if maximizing_player_won?(@winner)
77
+ return {(1 + depth) => nil}
78
+ end
79
+
80
+ if minimizing_player_won?(@winner)
81
+ return {(-1 - depth) => nil}
82
+ end
83
+ end
84
+
85
+ def maximizing_player_won?(winners_symbol)
86
+ symbols_are_equal?(winners_symbol, game_symbol)
87
+ end
88
+
89
+ def minimizing_player_won?(winners_symbol)
90
+ symbols_are_equal?(winners_symbol, PlayerSymbols::opponent(game_symbol))
91
+ end
92
+
93
+ def symbols_are_equal?(symbol1, symbol2)
94
+ symbol1 == symbol2
95
+ end
96
+
97
+ def initial_score(is_max_player)
98
+ if is_max_player
99
+ best_score_so_far = {ALPHA => nil}
100
+ else
101
+ best_score_so_far = {BETA => nil}
102
+ end
103
+ end
104
+
105
+ def current_players_symbol(is_max_player)
106
+ is_max_player == true ? game_symbol : PlayerSymbols::opponent(game_symbol)
107
+ end
108
+
109
+ def update_score(is_max_player, move, score, result_score)
110
+ if is_max_player && (result_score >= score_from(score))
111
+ return {result_score => move}
112
+ elsif !is_max_player && (result_score < score_from(score))
113
+ return {result_score => move}
114
+ end
115
+ return score
116
+ end
117
+ end
data/lib/board.rb ADDED
@@ -0,0 +1,87 @@
1
+ class Board
2
+
3
+ def initialize(symbols = Array.new(9))
4
+ @grid = symbols
5
+ end
6
+
7
+ def empty?
8
+ grid.all?(&:nil?)
9
+ end
10
+
11
+ def make_move(index, symbol)
12
+ copy_of_grid = grid.dup
13
+ copy_of_grid[index] = symbol
14
+ Board.new(copy_of_grid)
15
+ end
16
+
17
+ def free_spaces?
18
+ grid.include?(nil)
19
+ end
20
+
21
+ def get_symbol_at(position)
22
+ grid.at(position)
23
+ end
24
+
25
+ def winning_combination?
26
+ not_nil_row(find_winning_row_from(all_rows))
27
+ end
28
+
29
+ def winning_symbol
30
+ @winning_row = find_winning_row_from(all_rows)
31
+ @winning_row.nil? ? nil : @winning_row.first
32
+ end
33
+
34
+ def vacant_indices
35
+ grid.each_index.select{|v| grid[v].nil?}
36
+ end
37
+
38
+ def grid_for_display
39
+ rows
40
+ end
41
+
42
+ private
43
+
44
+ attr_reader :grid
45
+
46
+ def find_winning_row_from(rows)
47
+ @all_rows ||= rows.find do |row|
48
+ all_cells_match = row.all? {|cell| cell == row.first}
49
+ all_cells_match && not_nil_symbol(row.first)
50
+ end
51
+ end
52
+
53
+ def not_nil_row(row)
54
+ !row.nil?
55
+ end
56
+
57
+ def all_rows
58
+ @all ||= rows + columns + diagonals
59
+ end
60
+
61
+ def not_nil_symbol(cell)
62
+ !cell.nil?
63
+ end
64
+
65
+ def rows
66
+ [
67
+ [grid.at(0), grid.at(1), grid.at(2)],
68
+ [grid.at(3), grid.at(4), grid.at(5)],
69
+ [grid.at(6), grid.at(7), grid.at(8)]
70
+ ]
71
+ end
72
+
73
+ def columns
74
+ [
75
+ [grid.at(0), grid.at(3), grid.at(6)],
76
+ [grid.at(1), grid.at(4), grid.at(7)],
77
+ [grid.at(2), grid.at(5), grid.at(8)]
78
+ ]
79
+ end
80
+
81
+ def diagonals
82
+ [
83
+ [grid.at(0),grid.at(4), grid.at(8)],
84
+ [grid.at(2), grid.at(4), grid.at(6)]
85
+ ]
86
+ end
87
+ end
@@ -0,0 +1,8 @@
1
+ require 'board'
2
+
3
+ class BoardFactory
4
+
5
+ def create_board
6
+ Board.new
7
+ end
8
+ end
data/lib/game.rb ADDED
@@ -0,0 +1,27 @@
1
+ class Game
2
+
3
+ def initialize(board, players)
4
+ @board = board
5
+ @players = players
6
+ end
7
+
8
+ def play
9
+ while game_in_progress?
10
+ @board = board.make_move(current_player.choose_move(board), current_player.game_symbol)
11
+ players.reverse!
12
+ end
13
+ board
14
+ end
15
+
16
+ private
17
+
18
+ attr_reader :board, :players
19
+
20
+ def game_in_progress?
21
+ current_player.ready? && board.free_spaces? && !board.winning_combination?
22
+ end
23
+
24
+ def current_player
25
+ players.first
26
+ end
27
+ end
data/lib/player.rb ADDED
@@ -0,0 +1,16 @@
1
+ module Player
2
+
3
+ def initialize(symbol)
4
+ @symbol = symbol
5
+ end
6
+
7
+ def game_symbol
8
+ symbol
9
+ end
10
+
11
+ private
12
+
13
+ attr_reader :symbol
14
+
15
+ end
16
+
@@ -0,0 +1,38 @@
1
+ require 'player_symbols'
2
+ require 'player_options'
3
+ require 'ai_player'
4
+
5
+ class PlayerFactory
6
+ def create_players(player_option, command_line_ui)
7
+ if player_option == PlayerOptions::HUMAN_VS_HUMAN
8
+ human_vs_human(command_line_ui)
9
+ elsif player_option == PlayerOptions::HUMAN_VS_AI
10
+ human_vs_ai(command_line_ui)
11
+ else
12
+ ai_vs_human(command_line_ui)
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def human_vs_human(command_line_ui)
19
+ [
20
+ create_human(command_line_ui, PlayerSymbols::X),
21
+ create_human(command_line_ui, PlayerSymbols::O)
22
+ ]
23
+ end
24
+
25
+ def human_vs_ai(command_line_ui)
26
+ [
27
+ create_human(command_line_ui, PlayerSymbols::X),
28
+ AiPlayer.new(PlayerSymbols::O)
29
+ ]
30
+ end
31
+
32
+ def ai_vs_human(command_line_ui)
33
+ [
34
+ AiPlayer.new(PlayerSymbols::X),
35
+ create_human(command_line_ui, PlayerSymbols::O)
36
+ ]
37
+ end
38
+ end
@@ -0,0 +1,48 @@
1
+ class PlayerOptions
2
+ HUMAN_VS_HUMAN = "Human vs Human"
3
+ HUMAN_VS_AI = "Human vs Ai"
4
+ AI_VS_HUMAN = "Ai vs Human"
5
+
6
+ ID_TO_PLAYER_TYPE = {
7
+ 1 => HUMAN_VS_HUMAN,
8
+ 2 => HUMAN_VS_AI,
9
+ 3 => AI_VS_HUMAN
10
+ }
11
+
12
+ def self.valid_ids
13
+ ID_TO_PLAYER_TYPE.keys
14
+ end
15
+
16
+ def self.get_player_type_for_id(id)
17
+ game_value_of_player = ID_TO_PLAYER_TYPE.fetch(id)
18
+ end
19
+
20
+ def self.display_player_options
21
+ player_options_for_display = ID_TO_PLAYER_TYPE.each_pair.map do |id, option|
22
+ open_bracket + id.to_s + close_bracket + space + option
23
+ end
24
+ player_options_for_display.join(comma + space)
25
+ end
26
+
27
+ def self.all
28
+ ID_TO_PLAYER_TYPE
29
+ end
30
+
31
+ private
32
+
33
+ def self.open_bracket
34
+ "("
35
+ end
36
+
37
+ def self.close_bracket
38
+ ")"
39
+ end
40
+
41
+ def self.space
42
+ " "
43
+ end
44
+
45
+ def self.comma
46
+ ","
47
+ end
48
+ end
@@ -0,0 +1,16 @@
1
+ class PlayerSymbols
2
+ X = :X
3
+ O = :O
4
+
5
+ def self.opponent(symbol)
6
+ symbol == X ? O : X
7
+ end
8
+
9
+ def self.all
10
+ [X.to_s, O.to_s]
11
+ end
12
+
13
+ def self.to_symbol(value)
14
+ value == X.to_s ? X : O
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ class ReplayOption
2
+ Y = "Y"
3
+ end
@@ -0,0 +1,3 @@
1
+ module TTTCore
2
+ VERSION = "1.0.0"
3
+ end