tictactien-gem 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "rspec", "~> 2.3.0"
10
+ gem "bundler", "~> 1.0.0"
11
+ gem "jeweler", "~> 1.6.2"
12
+ gem "rcov", ">= 0"
13
+ # gem "rake", "0.8.7"
14
+ gem "rake"
15
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,29 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.2)
5
+ git (1.2.5)
6
+ jeweler (1.6.2)
7
+ bundler (~> 1.0)
8
+ git (>= 1.2.5)
9
+ rake
10
+ rake (0.9.2)
11
+ rcov (0.9.9)
12
+ rspec (2.3.0)
13
+ rspec-core (~> 2.3.0)
14
+ rspec-expectations (~> 2.3.0)
15
+ rspec-mocks (~> 2.3.0)
16
+ rspec-core (2.3.1)
17
+ rspec-expectations (2.3.0)
18
+ diff-lcs (~> 1.1.2)
19
+ rspec-mocks (2.3.0)
20
+
21
+ PLATFORMS
22
+ ruby
23
+
24
+ DEPENDENCIES
25
+ bundler (~> 1.0.0)
26
+ jeweler (~> 1.6.2)
27
+ rake
28
+ rcov
29
+ rspec (~> 2.3.0)
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Tien H. Nguyen
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,19 @@
1
+ = tictactien-gem
2
+
3
+ Description goes here.
4
+
5
+ == Contributing to tictactien-gem
6
+
7
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
8
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
9
+ * Fork the project
10
+ * Start a feature/bugfix branch
11
+ * Commit and push until you are happy with your contribution
12
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
13
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2011 Tien H. Nguyen. See LICENSE.txt for
18
+ further details.
19
+
data/Rakefile ADDED
@@ -0,0 +1,49 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "tictactien-gem"
18
+ gem.homepage = "http://github.com/nht007/tictactien-gem"
19
+ gem.license = "MIT"
20
+ gem.summary = "Tic-Tac-Toe game in Ruby"
21
+ gem.description = "Tic-Tac-Toe game in Ruby..."
22
+ gem.email = "nht007@gmail.com"
23
+ gem.authors = ["Tien H. Nguyen"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rspec/core'
29
+ require 'rspec/core/rake_task'
30
+ RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ spec.pattern = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
35
+ spec.pattern = 'spec/**/*_spec.rb'
36
+ spec.rcov = true
37
+ end
38
+
39
+ task :default => :spec
40
+
41
+ require 'rake/rdoctask'
42
+ Rake::RDocTask.new do |rdoc|
43
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
44
+
45
+ rdoc.rdoc_dir = 'rdoc'
46
+ rdoc.title = "tictactien-gem #{version}"
47
+ rdoc.rdoc_files.include('README*')
48
+ rdoc.rdoc_files.include('lib/**/*.rb')
49
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.3.2
data/lib/ai.rb ADDED
@@ -0,0 +1,2 @@
1
+ class Ai
2
+ end
data/lib/board.rb ADDED
@@ -0,0 +1,114 @@
1
+ class Board
2
+ attr_accessor :grid
3
+
4
+ def initialize
5
+ @grid = [[nil, nil, nil],
6
+ [nil, nil, nil],
7
+ [nil, nil, nil]]
8
+ end
9
+
10
+ def clone
11
+ board = Board.new
12
+ board.grid = [[@grid[0][0], @grid[0][1], @grid[0][2]],
13
+ [@grid[1][0], @grid[1][1], @grid[1][2]],
14
+ [@grid[2][0], @grid[2][1], @grid[2][2]]]
15
+ return board
16
+ end
17
+
18
+ # checks for empty(nil) spaces on the grid
19
+ def available_spaces
20
+ spaces = []
21
+ @grid.each_with_index do |column, col_index|
22
+ column.each_with_index do |space, row_index|
23
+ spaces << [col_index, row_index] if space == nil
24
+ end
25
+ end
26
+ spaces
27
+ end
28
+
29
+ def validate_move(location)
30
+ if location.first && location.last
31
+ return @grid[location.first][location.last].nil?
32
+ end
33
+ end
34
+
35
+ # adds a piece to the grid
36
+ # indexes start at 0 from the top left corner of the grid
37
+ def add_piece(player_token, location)
38
+ if validate_move(location)
39
+ @grid[location.first][location.last] = player_token
40
+ return true
41
+ else
42
+ return false
43
+ end
44
+ end
45
+
46
+ # determines if there is a winning player and returns that player token
47
+ def calculate_win
48
+ return diagonal_win if diagonal_win
49
+ return horizontal_win if horizontal_win
50
+ return vertical_win if vertical_win
51
+ return 'nobody' if cat_game?
52
+ end
53
+
54
+ def print
55
+ "\n#{@grid[0]}\n#{@grid[1]}\n#{@grid[2]}\n"
56
+ end
57
+
58
+ private
59
+
60
+ def diagonal_win
61
+ # top left to bottom right
62
+ if @grid[0][0] == @grid[1][1] && @grid[1][1] == @grid[2][2] && @grid[0][0]
63
+ return @grid[0][0]
64
+ end
65
+
66
+ # bottom left to top right
67
+ if @grid[0][2] == @grid[1][1] && @grid[1][1] == @grid[2][0] && @grid[0][2]
68
+ return @grid[0][2]
69
+ end
70
+ end
71
+
72
+ def horizontal_win
73
+ # top row
74
+ if @grid[0][0] == @grid[0][1] && @grid[0][1] == @grid[0][2] && @grid[0][0]
75
+ return @grid[0][0]
76
+ end
77
+
78
+ # middle row
79
+ if @grid[1][0] == @grid[1][1] && @grid[1][1] == @grid[1][2] && @grid[1][0]
80
+ return @grid[1][0]
81
+ end
82
+
83
+ # bottom row
84
+ if @grid[2][0] == @grid[2][1] && @grid[2][1] == @grid[2][2] && @grid[2][0]
85
+ return @grid[2][0]
86
+ end
87
+ end
88
+
89
+ def vertical_win
90
+ # top row
91
+ if @grid[0][0] == @grid[1][0] && @grid[1][0] == @grid[2][0] && @grid[0][0]
92
+ return @grid[0][0]
93
+ end
94
+
95
+ # middle row
96
+ if @grid[0][1] == @grid[1][1] && @grid[1][1] == @grid[2][1] && @grid[0][1]
97
+ return @grid[0][1]
98
+ end
99
+
100
+ # bottom row
101
+ if @grid[0][2] == @grid[1][2] && @grid[1][2] == @grid[2][2] && @grid[0][2]
102
+ return @grid[0][2]
103
+ end
104
+ end
105
+
106
+ def cat_game?
107
+ @grid.each do |row|
108
+ row.each do |space|
109
+ return false if not space
110
+ end
111
+ end
112
+ return true
113
+ end
114
+ end
@@ -0,0 +1,79 @@
1
+ class ConsoleGame < Game
2
+ def start
3
+ @players[:one] = choose_player('x')
4
+ @players[:two] = choose_player('o')
5
+
6
+ @game_state = GameState.new(@players[:one])
7
+
8
+ game_loop
9
+ end
10
+
11
+ private
12
+
13
+ def game_loop
14
+ running = true
15
+
16
+ while running
17
+ puts @game_state.board.print
18
+
19
+ winner = @game_state.board.calculate_win
20
+ if winner
21
+ puts "Player '#{winner}' wins!"
22
+ running = false
23
+ break
24
+ end
25
+
26
+ move = ask_move(@game_state.board, @game_state.active_player.token)
27
+ @game_state.perform_move(move)
28
+ switch_active_player
29
+ end
30
+ end
31
+
32
+ def choose_player(token)
33
+ while true
34
+ puts "Please choose the player type for '#{token}':\n1. Human\n2. CPU\n"
35
+ input = $stdin.gets.strip
36
+
37
+ if input == "1"
38
+ return PlayerFactory.create_player(PlayerFactory::HUMAN, token)
39
+
40
+ elsif input == "2"
41
+ return choose_ai(token)
42
+ break
43
+ else
44
+ puts "Invalid input. Please try again."
45
+ end
46
+ end
47
+ end
48
+
49
+ def choose_ai(token)
50
+ while true
51
+ puts "Please choose the AI type for '#{token}':\n1. Perfect\n2. Random\n"
52
+ ai_input = $stdin.gets.strip
53
+
54
+ if ai_input == "1"
55
+ return PlayerFactory.create_player(PlayerFactory::PERFECT_CPU, token)
56
+ elsif ai_input == "2"
57
+ return PlayerFactory.create_player(PlayerFactory::RANDOM_CPU, token)
58
+ else
59
+ puts "Invalid input. Please try again."
60
+ end
61
+ end
62
+ end
63
+
64
+ def ask_move(board, token)
65
+ row, col = nil, nil
66
+
67
+ while !board.validate_move([row, col])
68
+ puts "Player '#{token}', choose a row (1-3).\n"
69
+ row_input = $stdin.gets.strip
70
+ row = row_input.to_i if ('1'..'3').include? row_input
71
+
72
+ puts "Player '#{token}', choose a column (1-3).\n"
73
+ col_input = $stdin.gets.strip
74
+ col = col_input.to_i if ('1'..'3').include? col_input
75
+ end
76
+
77
+ return [row-1, col-1]
78
+ end
79
+ end
data/lib/cpu_player.rb ADDED
@@ -0,0 +1,17 @@
1
+ # require 'player'
2
+
3
+ class CpuPlayer < Player
4
+ attr_accessor :ai
5
+
6
+ def initialize(token, ai)
7
+ @token = token
8
+ @ai = ai
9
+ end
10
+
11
+ def perform_move(game_state, move_param = nil)
12
+ move = @ai.calculate_move(game_state.board, self)
13
+
14
+ # board.add_piece(@token, move)
15
+ game_state.update(move)
16
+ end
17
+ end
data/lib/game.rb ADDED
@@ -0,0 +1,25 @@
1
+ # require 'board'
2
+
3
+ class Game
4
+ attr_accessor :players, :game_state
5
+
6
+ def initialize
7
+ @players = {:one => nil, :two => nil}
8
+ end
9
+
10
+ def switch_active_player
11
+ if @game_state.active_player == @players[:one]
12
+ @game_state.active_player = @players[:two]
13
+ elsif @game_state.active_player == @players[:two]
14
+ @game_state.active_player = @players[:one]
15
+ end
16
+ end
17
+
18
+ def check_win
19
+ @game_state.check_win
20
+ end
21
+
22
+ def active_player_class
23
+ @game_state.active_player_class
24
+ end
25
+ end
data/lib/game_state.rb ADDED
@@ -0,0 +1,24 @@
1
+ class GameState
2
+ attr_accessor :board, :active_player
3
+
4
+ def initialize(board=Board.new, active_player)
5
+ @board = board
6
+ @active_player = active_player
7
+ end
8
+
9
+ def perform_move(move)
10
+ @active_player.perform_move(self, move)
11
+ end
12
+
13
+ def update(move)
14
+ return @board.add_piece(@active_player.token, move)
15
+ end
16
+
17
+ def check_win
18
+ @board.calculate_win
19
+ end
20
+
21
+ def active_player_class
22
+ @active_player.class.inspect
23
+ end
24
+ end
@@ -0,0 +1,9 @@
1
+ # require 'player'
2
+
3
+ class HumanPlayer < Player
4
+ def perform_move(game_state, move)
5
+ # location = Game.ask_move(board, @token)
6
+ # board.add_piece(@token, location)
7
+ game_state.update(move)
8
+ end
9
+ end
data/lib/minimax.rb ADDED
@@ -0,0 +1,132 @@
1
+ class Minimax
2
+ attr_accessor :board, :last_move, :current_token, :root_token, :score
3
+
4
+ def initialize(board, last_move, current_token, root_token)
5
+ @board = board
6
+ @last_move = last_move
7
+ @current_token = current_token
8
+ @root_token = root_token
9
+ @children = []
10
+ end
11
+
12
+ def build_tree
13
+ if game_ended
14
+ @score = game_ended
15
+ return
16
+ end
17
+
18
+ moves = @board.available_spaces
19
+
20
+ moves.each do |move|
21
+ next_board = @board.clone
22
+ next_board.add_piece(@current_token, move)
23
+
24
+ @children << Minimax.new(next_board, move, switch_token(@current_token), @root_token)
25
+ end
26
+
27
+ score_list = []
28
+ @children.each do |child|
29
+ child.build_tree
30
+ score_list << child.score
31
+ end
32
+
33
+ # calculate score for non-leaf nodes
34
+ if @current_token == @root_token
35
+ @score = score_list.max
36
+ else
37
+ @score = score_list.min
38
+ end
39
+
40
+ end
41
+
42
+ def game_ended
43
+ if diagonal_win
44
+ diagonal_win == @root_token ? 1 : -1
45
+
46
+ elsif horizontal_win
47
+ horizontal_win == @root_token ? 1 : -1
48
+
49
+ elsif vertical_win
50
+ vertical_win == @root_token ? 1 : -1
51
+
52
+ elsif cat_game? # cat game
53
+ 0
54
+ end
55
+ end
56
+
57
+ def get_next_move
58
+ move_list = []
59
+ @children.each do |child|
60
+ move_list << [child.last_move, child.score]
61
+ end
62
+
63
+ move = move_list.max_by {|move| move.last}
64
+
65
+ return move.first
66
+ end
67
+
68
+ private
69
+
70
+ def switch_token(token)
71
+ if token == 'x'
72
+ return 'o'
73
+ elsif token == 'o'
74
+ return 'x'
75
+ end
76
+ end
77
+
78
+ def diagonal_win
79
+ # top left to bottom right
80
+ if @board.grid[0][0] == @board.grid[1][1] && @board.grid[1][1] == @board.grid[2][2] && @board.grid[0][0]
81
+ return @board.grid[0][0]
82
+ end
83
+
84
+ # bottom left to top right
85
+ if @board.grid[0][2] == @board.grid[1][1] && @board.grid[1][1] == @board.grid[2][0] && @board.grid[0][2]
86
+ return @board.grid[0][2]
87
+ end
88
+ end
89
+
90
+ def horizontal_win
91
+ # top row
92
+ if @board.grid[0][0] == @board.grid[0][1] && @board.grid[0][1] == @board.grid[0][2] && @board.grid[0][0]
93
+ return @board.grid[0][0]
94
+ end
95
+
96
+ # middle row
97
+ if @board.grid[1][0] == @board.grid[1][1] && @board.grid[1][1] == @board.grid[1][2] && @board.grid[1][0]
98
+ return @board.grid[1][0]
99
+ end
100
+
101
+ # bottom row
102
+ if @board.grid[2][0] == @board.grid[2][1] && @board.grid[2][1] == @board.grid[2][2] && @board.grid[2][0]
103
+ return @board.grid[2][0]
104
+ end
105
+ end
106
+
107
+ def vertical_win
108
+ # top row
109
+ if @board.grid[0][0] == @board.grid[1][0] && @board.grid[1][0] == @board.grid[2][0] && @board.grid[0][0]
110
+ return @board.grid[0][0]
111
+ end
112
+
113
+ # middle row
114
+ if @board.grid[0][1] == @board.grid[1][1] && @board.grid[1][1] == @board.grid[2][1] && @board.grid[0][1]
115
+ return @board.grid[0][1]
116
+ end
117
+
118
+ # bottom row
119
+ if @board.grid[0][2] == @board.grid[1][2] && @board.grid[1][2] == @board.grid[2][2] && @board.grid[0][2]
120
+ return @board.grid[0][2]
121
+ end
122
+ end
123
+
124
+ def cat_game?
125
+ @board.grid.each do |row|
126
+ row.each do |space|
127
+ return false if not space
128
+ end
129
+ end
130
+ return true
131
+ end
132
+ end
data/lib/perfect_ai.rb ADDED
@@ -0,0 +1,14 @@
1
+ # require 'ai'
2
+ # require 'minimax'
3
+
4
+ class PerfectAi < Ai
5
+ def calculate_move(board, current_player)
6
+ if board.available_spaces.count == 9
7
+ return [0,0]
8
+ end
9
+
10
+ minimax = Minimax.new(board, nil, current_player.token, current_player.token)
11
+ minimax.build_tree
12
+ minimax.get_next_move
13
+ end
14
+ end
data/lib/player.rb ADDED
@@ -0,0 +1,7 @@
1
+ class Player
2
+ attr_accessor :token
3
+
4
+ def initialize(token)
5
+ @token = token
6
+ end
7
+ end
@@ -0,0 +1,20 @@
1
+ # require 'human_player'
2
+ # require 'cpu_player'
3
+ # require 'random_ai'
4
+ # require 'perfect_ai'
5
+
6
+ class PlayerFactory
7
+ HUMAN = 0
8
+ RANDOM_CPU = 1
9
+ PERFECT_CPU = 2
10
+
11
+ def self.create_player(player_type, token)
12
+ if player_type == HUMAN
13
+ return HumanPlayer.new(token)
14
+ elsif player_type == RANDOM_CPU
15
+ return CpuPlayer.new(token, RandomAi.new)
16
+ elsif player_type == PERFECT_CPU
17
+ return CpuPlayer.new(token, PerfectAi.new)
18
+ end
19
+ end
20
+ end
data/lib/rails_game.rb ADDED
@@ -0,0 +1,36 @@
1
+ class RailsGame < Game
2
+ def choose_player_one(type)
3
+ choose_player(type, 'x')
4
+ end
5
+
6
+ def choose_player_two(type)
7
+ choose_player(type, 'o')
8
+ end
9
+
10
+ def start
11
+ @game_state = GameState.new(@players[:one])
12
+ end
13
+
14
+ def update(move)
15
+ # switch_active_player if @game_state.update(move)
16
+ switch_active_player if @game_state.perform_move(move)
17
+ end
18
+
19
+ def print_board
20
+ @game_state.board.print
21
+ end
22
+
23
+ private
24
+
25
+ def choose_player(type, token)
26
+ symbol = token == 'x' ? :one : :two
27
+
28
+ if type == "Human"
29
+ @players[symbol] = PlayerFactory.create_player(PlayerFactory::HUMAN, token)
30
+ elsif type == "RandomCPU"
31
+ @players[symbol] = PlayerFactory.create_player(PlayerFactory::RANDOM_CPU, token)
32
+ elsif type == "PerfectCPU"
33
+ @players[symbol] = PlayerFactory.create_player(PlayerFactory::PERFECT_CPU, token)
34
+ end
35
+ end
36
+ end
data/lib/random_ai.rb ADDED
@@ -0,0 +1,7 @@
1
+ # require 'ai'
2
+
3
+ class RandomAi < Ai
4
+ def calculate_move(board, current_player)
5
+ board.available_spaces.sample
6
+ end
7
+ end
@@ -0,0 +1,35 @@
1
+ require 'board'
2
+ require 'game'
3
+ require 'rails_game'
4
+ require 'console_game'
5
+ require 'ai'
6
+ require 'player'
7
+ require 'human_player'
8
+ require 'cpu_player'
9
+ require 'minimax'
10
+ require 'perfect_ai'
11
+ require 'random_ai'
12
+ require 'player_factory'
13
+ require 'game_state'
14
+
15
+ module Tictactien
16
+ def self.new_game
17
+ RailsGame.new
18
+ end
19
+ # HUMAN = PlayerFactory::HUMAN
20
+ # RANDOM_CPU = PlayerFactory::RANDOM_CPU
21
+ # PERFECT_CPU = PlayerFactory::PERFECT_CPU
22
+ #
23
+ # def self.create_player(player_type, token)
24
+ # PlayerFactory.create_player(player_type, token)
25
+ # end
26
+ #
27
+ # def self.create_new_game(active_player)
28
+ # board = Board.new
29
+ # GameState.new(board, active_player)
30
+ # end
31
+
32
+ # def self.add_piece(board, player_token, location)
33
+ # board.add_piece(player_token, location)
34
+ # end
35
+ end
data/spec/ai_spec.rb ADDED
@@ -0,0 +1,5 @@
1
+ #require '../lib/ai.rb'
2
+
3
+ describe "Ai" do
4
+
5
+ end