ang_ttt_gem 0.0.1
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.
- data/lib/ang_ttt_gem.rb +14 -0
- data/lib/ang_ttt_gem/ai.rb +87 -0
- data/lib/ang_ttt_gem/board.rb +69 -0
- data/lib/ang_ttt_gem/computer_player.rb +17 -0
- data/lib/ang_ttt_gem/game.rb +74 -0
- data/lib/ang_ttt_gem/human_player.rb +14 -0
- data/lib/ang_ttt_gem/message.rb +26 -0
- data/lib/ang_ttt_gem/player.rb +10 -0
- data/lib/ang_ttt_gem/scoring.rb +26 -0
- data/lib/ang_ttt_gem/validate.rb +22 -0
- metadata +55 -0
data/lib/ang_ttt_gem.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
$: << File.expand_path(File.dirname(__FILE__) + '/ang_ttt_gem/')
|
2
|
+
# require "board"
|
3
|
+
# class Hola
|
4
|
+
# def self.hi
|
5
|
+
# puts "Hello world!"
|
6
|
+
# board = Board.new
|
7
|
+
# puts $: << File.expand_path(File.dirname(__FILE__) + '/ang_ttt_gem/')
|
8
|
+
# # if board.nil?
|
9
|
+
# # puts "there is no board"
|
10
|
+
# # else
|
11
|
+
# # puts "there is a board"
|
12
|
+
# # end
|
13
|
+
# end
|
14
|
+
#end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require "scoring"
|
2
|
+
require "board"
|
3
|
+
|
4
|
+
class Ai
|
5
|
+
|
6
|
+
def initialize(player, board)
|
7
|
+
@board = board
|
8
|
+
@scoring = Scoring.new
|
9
|
+
@max_mark = player.mark
|
10
|
+
end
|
11
|
+
|
12
|
+
def find_opponent_mark
|
13
|
+
state = @board.current_state
|
14
|
+
min_mark = state.reject {|c| c =~ /^#{@max_mark}|\s{1}$/ }
|
15
|
+
@min_mark = min_mark[0]
|
16
|
+
end
|
17
|
+
|
18
|
+
def select_optimal_start_move
|
19
|
+
start_moves = [0, 2, 4, 6, 8]
|
20
|
+
best_move = start_moves.sample
|
21
|
+
end
|
22
|
+
|
23
|
+
def random_move
|
24
|
+
move = @board.available_spaces.sample
|
25
|
+
end
|
26
|
+
|
27
|
+
def find_best_move
|
28
|
+
if @board.available_spaces.count == 9
|
29
|
+
best_move = select_optimal_start_move
|
30
|
+
else
|
31
|
+
find_opponent_mark
|
32
|
+
move_reference, best_score = max_move
|
33
|
+
best_move = move_reference
|
34
|
+
end
|
35
|
+
best_move
|
36
|
+
end
|
37
|
+
|
38
|
+
def max_move
|
39
|
+
best_move = nil
|
40
|
+
best_score = nil
|
41
|
+
@board.available_spaces.each do |move|
|
42
|
+
@board.set(move, @max_mark)
|
43
|
+
if state_is_terminal?
|
44
|
+
score = evaluate_the_board
|
45
|
+
else
|
46
|
+
move_reference, score = min_move
|
47
|
+
end
|
48
|
+
@board.undo_move(move)
|
49
|
+
if best_score.nil? || score > best_score
|
50
|
+
best_score = score
|
51
|
+
best_move = move
|
52
|
+
end
|
53
|
+
end
|
54
|
+
return best_move, best_score
|
55
|
+
end
|
56
|
+
|
57
|
+
def min_move
|
58
|
+
best_move = nil
|
59
|
+
best_score = nil
|
60
|
+
@board.available_spaces.each do |move|
|
61
|
+
@board.set(move, @min_mark)
|
62
|
+
if state_is_terminal?
|
63
|
+
score = evaluate_the_board
|
64
|
+
else
|
65
|
+
move_reference, score = max_move
|
66
|
+
end
|
67
|
+
@board.undo_move(move)
|
68
|
+
if best_score.nil? || score < best_score
|
69
|
+
best_score = score
|
70
|
+
best_move = move
|
71
|
+
end
|
72
|
+
end
|
73
|
+
return best_move, best_score
|
74
|
+
end
|
75
|
+
|
76
|
+
def state_is_terminal?
|
77
|
+
@scoring.winner?(@board) || @scoring.draw?(@board)
|
78
|
+
end
|
79
|
+
|
80
|
+
def evaluate_the_board
|
81
|
+
decision = nil
|
82
|
+
decision = 1 if @scoring.winner?(@board) && @scoring.winning_mark(@board) == @max_mark
|
83
|
+
decision = -1 if @scoring.winner?(@board) && @scoring.winning_mark(@board) != @max_mark
|
84
|
+
decision = 0 if @scoring.draw?(@board)
|
85
|
+
decision
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
class Board
|
2
|
+
|
3
|
+
def initialize
|
4
|
+
@cells = Array.new(9) {" "}
|
5
|
+
end
|
6
|
+
|
7
|
+
def get(cell_number)
|
8
|
+
@cells[cell_number]
|
9
|
+
end
|
10
|
+
|
11
|
+
def set(cell_number, mark)
|
12
|
+
@cells[cell_number] = mark
|
13
|
+
end
|
14
|
+
|
15
|
+
def undo_move(cell_number)
|
16
|
+
@cells[cell_number] = " "
|
17
|
+
end
|
18
|
+
|
19
|
+
def available_spaces
|
20
|
+
available_spaces = @cells.each_with_index.select { |i, idx| i =~ / / }
|
21
|
+
available_spaces = available_spaces.map{|i| i[1] }
|
22
|
+
available_spaces
|
23
|
+
end
|
24
|
+
|
25
|
+
def cell_occupied?(cell_number)
|
26
|
+
get(cell_number.to_i - 1) != " "
|
27
|
+
end
|
28
|
+
|
29
|
+
def current_state
|
30
|
+
current_state = @cells.map {|c| c}
|
31
|
+
end
|
32
|
+
|
33
|
+
def clear_all_spaces
|
34
|
+
mark = " "
|
35
|
+
@cells.each_with_index do |cell, index|
|
36
|
+
set(index, mark)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def rows
|
41
|
+
[ @cells[0..2],
|
42
|
+
@cells[3..5],
|
43
|
+
@cells[6..8] ]
|
44
|
+
end
|
45
|
+
|
46
|
+
def columns
|
47
|
+
rows.transpose
|
48
|
+
end
|
49
|
+
|
50
|
+
def diagonal_forward
|
51
|
+
[ @cells[2],
|
52
|
+
@cells[4],
|
53
|
+
@cells[6] ]
|
54
|
+
end
|
55
|
+
|
56
|
+
def diagonal_back
|
57
|
+
[ @cells[0],
|
58
|
+
@cells[4],
|
59
|
+
@cells[8] ]
|
60
|
+
end
|
61
|
+
|
62
|
+
def possible_winning_combinations
|
63
|
+
possible_winning_combinations = []
|
64
|
+
rows.collect{|row| possible_winning_combinations << row }
|
65
|
+
columns.collect{|column| possible_winning_combinations << column }
|
66
|
+
possible_winning_combinations << diagonal_forward
|
67
|
+
possible_winning_combinations << diagonal_back
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "player"
|
2
|
+
require "ai"
|
3
|
+
require "board"
|
4
|
+
|
5
|
+
class ComputerPlayer < Player
|
6
|
+
|
7
|
+
attr_accessor :ai
|
8
|
+
|
9
|
+
def initialize(mark)
|
10
|
+
self.set_mark(mark)
|
11
|
+
end
|
12
|
+
|
13
|
+
def get_move(board)
|
14
|
+
ai = Ai.new(self, board)
|
15
|
+
cell_number = ai.find_best_move
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require "board"
|
2
|
+
require "scoring"
|
3
|
+
require "validate"
|
4
|
+
require "human_player"
|
5
|
+
require "computer_player"
|
6
|
+
|
7
|
+
class Game
|
8
|
+
|
9
|
+
attr_reader :players, :board
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@board = Board.new
|
13
|
+
@scoring = Scoring.new
|
14
|
+
@validate = Validate.new
|
15
|
+
@players = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def create_computer_player(mark)
|
19
|
+
@players << ComputerPlayer.new(mark)
|
20
|
+
end
|
21
|
+
|
22
|
+
def create_human_player(mark)
|
23
|
+
@players << HumanPlayer.new(mark)
|
24
|
+
end
|
25
|
+
|
26
|
+
def get_player_move(player)
|
27
|
+
index = player - 1
|
28
|
+
@players[index].get_move(@board)
|
29
|
+
end
|
30
|
+
|
31
|
+
def move_valid?(move)
|
32
|
+
(0..8).include?(move)
|
33
|
+
end
|
34
|
+
|
35
|
+
def make_move_player(player, move)
|
36
|
+
index = player - 1
|
37
|
+
mark = @players[index].mark
|
38
|
+
return false unless move_valid?(move)
|
39
|
+
@board.set(move, mark)
|
40
|
+
prepare_display_state
|
41
|
+
end
|
42
|
+
|
43
|
+
def square_taken?(cell_number)
|
44
|
+
@board.cell_occupied?(cell_number)
|
45
|
+
end
|
46
|
+
|
47
|
+
def prepare_display_state
|
48
|
+
board_display_state = []
|
49
|
+
current_state = @board.current_state
|
50
|
+
cell_numbers = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
|
51
|
+
index = 0
|
52
|
+
current_state.each do |cell|
|
53
|
+
board_display_state << cell_numbers[index] if cell == " "
|
54
|
+
board_display_state << current_state[index] unless cell == " "
|
55
|
+
index += 1
|
56
|
+
end
|
57
|
+
board_display_state
|
58
|
+
end
|
59
|
+
|
60
|
+
def is_over?
|
61
|
+
@scoring.winner?(@board) || @scoring.draw?(@board)
|
62
|
+
end
|
63
|
+
|
64
|
+
def result
|
65
|
+
message_key = :draw
|
66
|
+
@players.each_with_index do |player, i|
|
67
|
+
num = i + 1
|
68
|
+
if @scoring.winning_mark(@board) == player.mark
|
69
|
+
message_key = "player_#{num}_win".to_sym
|
70
|
+
end
|
71
|
+
end
|
72
|
+
message_key
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class Message
|
2
|
+
|
3
|
+
def initialize
|
4
|
+
|
5
|
+
@message = {
|
6
|
+
welcome: "Welcome to Tic Tac Toe! You will create 2 players. The first player you create will go first.\n",
|
7
|
+
create_player: "Create a player.\n",
|
8
|
+
determine_player_type: "Do you want the player to be human or computer? (H/C)\n",
|
9
|
+
select_player_mark: "Please select a single letter to represent the player.\n",
|
10
|
+
invalid_selection: "That is an invalid selection, please make a valid selection.\n",
|
11
|
+
select_square: "Please select an open square.\n",
|
12
|
+
player_1_win: "Player 1 is the winner!\n",
|
13
|
+
player_2_win: "Player 2 is the winner!\n",
|
14
|
+
draw: "It's a draw.\n",
|
15
|
+
play_again?: "Would you like to play again? (Y/N)\n",
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def passed(*keys)
|
20
|
+
message = String.new
|
21
|
+
keys.each do |key|
|
22
|
+
message << @message[key]
|
23
|
+
end
|
24
|
+
message
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "board"
|
2
|
+
|
3
|
+
class Scoring
|
4
|
+
|
5
|
+
def winner?(board)
|
6
|
+
winner = false
|
7
|
+
board.possible_winning_combinations.each do |combo|
|
8
|
+
if combo.uniq.length == 1 && combo[0] != " "
|
9
|
+
winner = true
|
10
|
+
end
|
11
|
+
end
|
12
|
+
winner
|
13
|
+
end
|
14
|
+
|
15
|
+
def winning_mark(board)
|
16
|
+
board.possible_winning_combinations.each do |combo|
|
17
|
+
if combo.uniq.length == 1 && combo[0] != " "
|
18
|
+
return combo[0]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def draw?(board)
|
24
|
+
winner?(board) == false && board.available_spaces.count == 0
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Validate
|
2
|
+
|
3
|
+
def player_input(input)
|
4
|
+
return false unless input =~ /[Hh,Cc]/
|
5
|
+
return true
|
6
|
+
end
|
7
|
+
|
8
|
+
def mark_input(input)
|
9
|
+
return false unless input =~ /[a-zA-Z]/
|
10
|
+
return true
|
11
|
+
end
|
12
|
+
|
13
|
+
def number_input(input)
|
14
|
+
return false unless input =~ /[1-9]/
|
15
|
+
return true
|
16
|
+
end
|
17
|
+
|
18
|
+
def play_again_input(input)
|
19
|
+
return false unless input =~ /[Nn,Yy]/
|
20
|
+
return true
|
21
|
+
end
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ang_ttt_gem
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Angeleah Daidone
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-08-28 00:00:00.000000000Z
|
13
|
+
dependencies: []
|
14
|
+
description: A Tic Tac Toe game made by Angeleah
|
15
|
+
email: angeleah@angeleah.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- lib/ang_ttt_gem/ai.rb
|
21
|
+
- lib/ang_ttt_gem/board.rb
|
22
|
+
- lib/ang_ttt_gem/computer_player.rb
|
23
|
+
- lib/ang_ttt_gem/game.rb
|
24
|
+
- lib/ang_ttt_gem/human_player.rb
|
25
|
+
- lib/ang_ttt_gem/message.rb
|
26
|
+
- lib/ang_ttt_gem/player.rb
|
27
|
+
- lib/ang_ttt_gem/scoring.rb
|
28
|
+
- lib/ang_ttt_gem/validate.rb
|
29
|
+
- lib/ang_ttt_gem.rb
|
30
|
+
homepage: https://github.com/angeleah/tic_tac_toe_by_angeleah
|
31
|
+
licenses: []
|
32
|
+
post_install_message:
|
33
|
+
rdoc_options: []
|
34
|
+
require_paths:
|
35
|
+
- lib
|
36
|
+
- spec
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ! '>='
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
44
|
+
none: false
|
45
|
+
requirements:
|
46
|
+
- - ! '>='
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
requirements: []
|
50
|
+
rubyforge_project:
|
51
|
+
rubygems_version: 1.8.10
|
52
|
+
signing_key:
|
53
|
+
specification_version: 3
|
54
|
+
summary: A Tic Tac Toe Gem
|
55
|
+
test_files: []
|