ruby_ttt 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (7) hide show
  1. data/lib/ai.rb +66 -0
  2. data/lib/board.rb +145 -0
  3. data/lib/game.rb +59 -0
  4. data/lib/game_setup.rb +76 -0
  5. data/lib/player.rb +63 -0
  6. data/lib/ui.rb +118 -0
  7. metadata +9 -3
data/lib/ai.rb ADDED
@@ -0,0 +1,66 @@
1
+ POS_INF = 999
2
+ NEG_INF = -999
3
+ WIN = 1
4
+ LOSE = -1
5
+ TIE = 0
6
+
7
+ class AI
8
+
9
+ def computer_move(board, player)
10
+ test_board = board.dup
11
+ test_board.all_cells = board.all_cells.dup
12
+ get_best_move(test_board, player)
13
+ end
14
+
15
+ def get_best_move(board, player)
16
+ ranked_moves = rank_possible_moves(board, player)
17
+ move = ranked_moves.max_by {|cell, score| score}
18
+ move.first
19
+ end
20
+
21
+ private
22
+
23
+ def rank_possible_moves(board, player)
24
+ possible_moves = board.open_cells
25
+ possible_moves.each_key do |cell|
26
+ possible_moves[cell] = get_move_score(board, player, cell)
27
+ end
28
+ end
29
+
30
+ def get_move_score(board, player, cell)
31
+ board.add_marker(player.marker, cell)
32
+ best_score = apply_minimax(board, player, cell, depth=0, NEG_INF, POS_INF)
33
+ board.remove_marker(cell)
34
+ best_score
35
+ end
36
+
37
+ def get_score(board, player)
38
+ return WIN if board.winner?(player.marker) && player.current_player?
39
+ return LOSE if board.winner?(player.marker)
40
+ TIE
41
+ end
42
+
43
+ def apply_minimax(board, player, cell, depth, alpha, beta)
44
+ return get_score(board, player) if board.game_over?
45
+ if player.current_player?
46
+ maximizing_player = Maximizing.new(player)
47
+ alphabeta(board, maximizing_player, depth, alpha, beta)
48
+ else
49
+ minimizing_player = Minimizing.new(player)
50
+ alphabeta(board, minimizing_player, depth, alpha, beta)
51
+ end
52
+ end
53
+
54
+ def alphabeta(board, player, depth, alpha, beta)
55
+ board.open_cells.each_key do |cell|
56
+ board.add_marker(player.opponent.marker, cell)
57
+ score = (apply_minimax(board, player.opponent, cell, depth += 1, alpha, beta) / depth.to_f)
58
+ alpha = player.get_alpha(alpha, score)
59
+ beta = player.get_beta(beta, score)
60
+ board.remove_marker(cell)
61
+ break if alpha >= beta
62
+ end
63
+ player.return_value(alpha, beta)
64
+ end
65
+
66
+ end
data/lib/board.rb ADDED
@@ -0,0 +1,145 @@
1
+ MARKER_X = 'X'
2
+ MARKER_O = 'O'
3
+ class Board
4
+ attr_accessor :all_cells, :num_of_rows, :winning_lines
5
+ def initialize(num_of_rows)
6
+ @num_of_rows = num_of_rows
7
+ @all_cells = create_board_hash
8
+ @winning_lines = get_winning_lines
9
+ end
10
+
11
+ def create_board_hash
12
+ new_board = Hash.new
13
+ alpha = 'A'
14
+ numeric = 1
15
+ num_of_rows.times do
16
+ num_of_rows.times do
17
+ cellID = numeric.to_s + alpha
18
+ numeric += 1
19
+ new_board[cellID] = nil
20
+ end
21
+ alpha = alpha.next
22
+ numeric = 1
23
+ end
24
+ new_board
25
+ end
26
+
27
+ def get_winning_lines
28
+ lines = []
29
+ all_rows.each { |row| lines << row }
30
+ all_cols.each { |col| lines << col }
31
+ diagonals.each { |diagonal| lines << diagonal }
32
+ lines
33
+ end
34
+
35
+ def all_rows
36
+ rows = []
37
+ cellIDs = all_cells.keys
38
+ beg = 0
39
+ ending = num_of_rows - 1
40
+ until rows.length == num_of_rows
41
+ rows << cellIDs[beg..ending]
42
+ beg += num_of_rows
43
+ ending += num_of_rows
44
+ end
45
+ rows
46
+ end
47
+
48
+ def add_marker(marker, cell)
49
+ all_cells[cell] = marker
50
+ end
51
+
52
+ def winner?(marker)
53
+ board_markers = all_cells.select { |k,v| v == marker }.keys
54
+ winning_lines.each do |line|
55
+ return true if (line & board_markers).length == num_of_rows
56
+ end
57
+ false
58
+ end
59
+
60
+ def game_over?
61
+ !moves_remaining? || winner?(MARKER_X)|| winner?(MARKER_O)
62
+ end
63
+
64
+ def available_cell?(cell)
65
+ valid_cell?(cell) && all_cells[cell].nil?
66
+ end
67
+
68
+ def valid_cell?(cell)
69
+ all_cells.has_key?(cell)
70
+ end
71
+
72
+ def remove_marker(cell)
73
+ all_cells[cell] = nil
74
+ end
75
+
76
+ def moves_remaining?
77
+ all_cells.has_value?(nil)
78
+ end
79
+
80
+ def open_cells
81
+ all_cells.select { |k,v| v.nil? }
82
+ end
83
+
84
+ def empty?
85
+ open_cells.length == (num_of_rows * num_of_rows)
86
+ end
87
+
88
+ def random_cell
89
+ cells = open_cells.keys
90
+ cells_count = cells.length - 1
91
+ cells[rand(cells_count)]
92
+ end
93
+
94
+ private
95
+ def all_cols
96
+ cols = []
97
+ index = 0
98
+ num_of_rows.times do
99
+ cols << get_column(index)
100
+ index += 1
101
+ end
102
+ cols
103
+ end
104
+
105
+ def get_column(index)
106
+ column = []
107
+ cellIDs = all_cells.keys
108
+ num_of_rows.times do
109
+ column << cellIDs[index]
110
+ index += num_of_rows
111
+ end
112
+ column
113
+ end
114
+
115
+ def diagonals
116
+ diagonals = []
117
+ diagonals << diagonal_one
118
+ diagonals << diagonal_two
119
+ end
120
+
121
+ def diagonal_one
122
+ diagonal = []
123
+ alpha = 'A'
124
+ numeric = 1
125
+ num_of_rows.times do
126
+ diagonal << numeric.to_s + alpha
127
+ alpha = alpha.next
128
+ numeric += 1
129
+ end
130
+ diagonal
131
+ end
132
+
133
+ def diagonal_two
134
+ diagonal = []
135
+ alpha = 'A'
136
+ numeric = num_of_rows
137
+ num_of_rows.times do
138
+ diagonal << numeric.to_s + alpha
139
+ alpha = alpha.next
140
+ numeric -= 1
141
+ end
142
+ diagonal
143
+ end
144
+
145
+ end
data/lib/game.rb ADDED
@@ -0,0 +1,59 @@
1
+ require 'ai'
2
+ require 'board'
3
+ require 'player'
4
+ require 'ui'
5
+ require 'game_setup'
6
+ class Game
7
+ attr_accessor :board, :ui, :player_one, :player_two, :ai, :difficulty_level
8
+ def initialize(board, player_one, player_two, difficulty_level)
9
+ @board = board
10
+ @ui = UI.new(@board)
11
+ @player_one = player_one
12
+ @player_two = player_two
13
+ @ai = AI.new
14
+ @difficulty_level = difficulty_level
15
+ end
16
+
17
+ def play!
18
+ until board.game_over?
19
+ ui.display_board
20
+ move = get_next_move
21
+ board.available_cell?(move) ? advance_game(move, current_player) : invalid_move(move)
22
+ end
23
+ exit_game
24
+ end
25
+
26
+ def get_next_move
27
+ return ui.request_human_move if current_player.player_type == HUMAN_PLAYER
28
+ difficulty_level == HARD_LEVEL ? ai.computer_move(board, current_player) : board.random_cell
29
+ end
30
+
31
+ def advance_game(cell, player)
32
+ board.add_marker(player.marker, cell)
33
+ game_status_check
34
+ player.next_player_turn
35
+ ui.next_move_message(current_player) unless board.game_over?
36
+ end
37
+
38
+ def game_status_check
39
+ if board.winner?(current_player.marker)
40
+ ui.winning_game_message(current_player)
41
+ elsif !board.moves_remaining?
42
+ ui.tie_game_message
43
+ end
44
+ end
45
+
46
+ def invalid_move(cell)
47
+ board.valid_cell?(cell) ? ui.taken_cell_message(cell) : ui.bad_cell_message(cell)
48
+ end
49
+
50
+ def current_player
51
+ player_one.current_player? ? player_one : player_two
52
+ end
53
+
54
+ def exit_game
55
+ ui.display_board
56
+ ui.io.exit
57
+ end
58
+
59
+ end
data/lib/game_setup.rb ADDED
@@ -0,0 +1,76 @@
1
+ EASY_LEVEL = 'easy'
2
+ HARD_LEVEL = 'hard'
3
+ COMPUTER_PLAYER = 'computer'
4
+ HUMAN_PLAYER = 'human'
5
+ class GameSetup
6
+ attr_accessor :ui, :board, :player_one, :player_two
7
+ def initialize(board, player_one, player_two)
8
+ @board = board
9
+ @ui = UI.new(@board)
10
+ @player_one = player_one
11
+ @player_two = player_two
12
+ end
13
+
14
+ def start!
15
+ begin
16
+ set_opponents
17
+ get_player_type(player_one)
18
+ get_player_type(player_two)
19
+ level = get_difficulty_level
20
+ who_goes_first
21
+ Game.new(board, player_one, player_two, level).play!
22
+ rescue Interrupt
23
+ ui.early_exit_message
24
+ exit
25
+ end
26
+ end
27
+
28
+ def set_opponents
29
+ player_one.opponent = player_two
30
+ player_two.opponent = player_one
31
+ end
32
+
33
+ def get_player_type(player)
34
+ type = ui.request_player_type(player.marker)
35
+ validate_type(type, player) ? set_player_type(type, player) : invalid_type(type, player)
36
+ end
37
+
38
+ def get_difficulty_level
39
+ return nil unless player_one.player_type == COMPUTER_PLAYER || player_two.player_type == COMPUTER_PLAYER
40
+ level = ui.request_difficulty_level
41
+ validate_level(level) ? ui.level_assigned_message(level) : invalid_level(level)
42
+ end
43
+
44
+ def validate_type(type, player)
45
+ (type == HUMAN_PLAYER) || (type == COMPUTER_PLAYER)
46
+ end
47
+
48
+ def set_player_type(type, player)
49
+ player.player_type = type
50
+ ui.type_assigned_message(type, player.marker)
51
+ end
52
+
53
+ def invalid_type(type, player)
54
+ ui.invalid_input_message(type)
55
+ get_player_type(player)
56
+ end
57
+
58
+ def validate_level(level)
59
+ (level == HARD_LEVEL) || (level == EASY_LEVEL)
60
+ end
61
+
62
+ def invalid_level(level)
63
+ ui.invalid_input_message(level)
64
+ get_difficulty_level
65
+ end
66
+
67
+ def who_goes_first
68
+ rand(0..1) == 1 ? set_first_turn(player_one) : set_first_turn(player_two)
69
+ end
70
+
71
+ def set_first_turn(player)
72
+ player.turn = 1
73
+ ui.first_move_message(player)
74
+ end
75
+
76
+ end
data/lib/player.rb ADDED
@@ -0,0 +1,63 @@
1
+ class Player
2
+ attr_accessor :marker, :turn, :player_type, :opponent
3
+ def initialize(marker)
4
+ @marker = marker
5
+ @player_type = 'human'
6
+ @turn = 0
7
+ @opponent = nil
8
+ end
9
+
10
+ def next_player_turn
11
+ self.turn = 0
12
+ self.opponent.turn = 1
13
+ end
14
+
15
+ def current_player?
16
+ self.turn == 1
17
+ end
18
+
19
+ def get_alpha(alpha, score)
20
+ alpha
21
+ end
22
+
23
+ def get_beta(beta, score)
24
+ beta
25
+ end
26
+ end
27
+
28
+ class Minimizing < Player
29
+ attr_accessor :marker, :turn, :opponent
30
+ def initialize(player)
31
+ @marker = player.marker
32
+ @turn = player.turn
33
+ @opponent = player.opponent
34
+ end
35
+
36
+ def get_alpha(alpha, score)
37
+ score > alpha ? score : alpha
38
+ end
39
+
40
+ def return_value(alpha, beta)
41
+ alpha
42
+ end
43
+
44
+ end
45
+
46
+ class Maximizing < Player
47
+ attr_accessor :marker, :turn, :opponent
48
+ def initialize(player)
49
+ @marker = player.marker
50
+ @turn = player.turn
51
+ @opponent = player.opponent
52
+ end
53
+
54
+ def get_beta(beta, score)
55
+ score < beta ? score : beta
56
+ end
57
+
58
+ def return_value(alpha, beta)
59
+ beta
60
+ end
61
+
62
+ end
63
+
data/lib/ui.rb ADDED
@@ -0,0 +1,118 @@
1
+ class UI
2
+ attr_accessor :board, :io
3
+ def initialize(board)
4
+ @board = board
5
+ @io = Kernel
6
+ end
7
+
8
+ def request_player_type(marker)
9
+ player_type_message(marker)
10
+ io.gets.chomp.downcase
11
+ end
12
+
13
+ def request_difficulty_level
14
+ difficulty_level_message
15
+ io.gets.chomp.downcase
16
+ end
17
+
18
+ def request_human_move
19
+ standardize(io.gets.chomp)
20
+ end
21
+
22
+ def standardize(input)
23
+ input.split('').sort.join('').upcase
24
+ end
25
+
26
+ def difficulty_level_message
27
+ io.print "Select computer difficulty level: Enter 'easy' or 'hard.'\n"
28
+ end
29
+
30
+ def level_assigned_message(level)
31
+ io.print "You selected difficulty level #{level.upcase}.\n"
32
+ end
33
+
34
+ def invalid_input_message(input)
35
+ io.print " #{input} is not a valid option.\n"
36
+ end
37
+
38
+ def player_type_message(marker)
39
+ io.print "For player " + "'#{marker}'," + " enter 'human' or 'computer.'\n"
40
+ end
41
+
42
+ def type_assigned_message(type, marker)
43
+ io.print "Player " + "'#{marker}' " + "is #{type}.\n"
44
+ end
45
+
46
+ def print_board_numbers
47
+ num = 1
48
+ io.print " "
49
+ board.num_of_rows.times do
50
+ io.print "--#{num}-- "
51
+ num += 1
52
+ end
53
+ io.print "\n"
54
+ end
55
+
56
+ def print_divider
57
+ io.print " "
58
+ board.num_of_rows.times { io.print "------" }
59
+ io.print "\n"
60
+ end
61
+
62
+ def print_board_rows
63
+ alpha = 'A'
64
+ board.all_rows.each do |row|
65
+ show_row(alpha, row)
66
+ alpha = alpha.next
67
+ end
68
+ end
69
+
70
+ def show_row(letter, cells)
71
+ io.print "#{letter}"
72
+ cells.each { |cell| io.print " | " + show_marker(cell) }
73
+ io.print " | #{letter}\n"
74
+ print_divider
75
+ end
76
+
77
+ def show_marker(cell)
78
+ board.all_cells[cell].nil? ? ' ' : board.all_cells[cell]
79
+ end
80
+
81
+ def display_board
82
+ print_board_numbers
83
+ print_board_rows
84
+ print_board_numbers
85
+ end
86
+
87
+ def first_move_message(player)
88
+ io.print "\n\n************ New Game ************\n"
89
+ io.print "Player '#{player.marker}' goes first.\n"
90
+ end
91
+
92
+ def next_move_message(player)
93
+ io.print "Player '#{player.marker}': Enter open cell ID.\n"
94
+ end
95
+
96
+ def winning_game_message(player)
97
+ io.print "GAME OVER! Player '#{player.marker}' wins!\n"
98
+ end
99
+
100
+ def tie_game_message
101
+ io.print "GAME OVER! It's a tie!\n"
102
+ end
103
+
104
+ def taken_cell_message(cell)
105
+ io.print "#{cell} has already been taken!\n"
106
+ end
107
+
108
+ def bad_cell_message(cell)
109
+ io.print "#{cell} is not a valid cell ID!\n"
110
+ end
111
+
112
+ def early_exit_message
113
+ io.print "\nExiting Tic-Tac-Toe..."
114
+ io.print "...\n"
115
+ io.print "Goodbye!\n\n"
116
+ end
117
+
118
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_ttt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.0.8
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -34,13 +34,19 @@ email: tsauer@8thlight.com
34
34
  executables: []
35
35
  extensions: []
36
36
  extra_rdoc_files: []
37
- files: []
37
+ files:
38
+ - lib/ai.rb
39
+ - lib/board.rb
40
+ - lib/game.rb
41
+ - lib/game_setup.rb
42
+ - lib/player.rb
43
+ - lib/ui.rb
38
44
  homepage: http://rubygems.org/gems/ruby_ttt
39
45
  licenses: []
40
46
  post_install_message:
41
47
  rdoc_options: []
42
48
  require_paths:
43
- - .
49
+ - lib
44
50
  required_ruby_version: !ruby/object:Gem::Requirement
45
51
  none: false
46
52
  requirements: