pszals_ttt 0.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.
- checksums.yaml +7 -0
- data/lib/board.rb +135 -0
- data/lib/configuration.rb +68 -0
- data/lib/console_ui.rb +75 -0
- data/lib/game.rb +79 -0
- data/lib/player.rb +9 -0
- data/lib/runner.rb +29 -0
- data/lib/unbeatable_ai.rb +54 -0
- metadata +49 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e94776de494fef81265f1603ad67dbb72ac6d9fd
|
4
|
+
data.tar.gz: 6567cdf5a42a5f46dd674bc40e3179985b152dba
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2c97013a4575ddd44fe2ca9a3dbece254d3f8babf6862d3740f5950afb5b424a19ff2a0bf0ed2c8234f28728333125960b874206526f6c7467553eb81b14ac46
|
7
|
+
data.tar.gz: 26419f52c36fc0e988730b29322c7ddbe2fcb605c87abc6beeebf30ac862bb1ff44e0ff0a102ff6b41761b8f5e8a5411290aa46ec3df8cb71cc5ccfcfa5b6997
|
data/lib/board.rb
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
class Board
|
2
|
+
|
3
|
+
attr_accessor :width, :current_board, :player_1, :player_2
|
4
|
+
|
5
|
+
def initialize(player_1, player_2)
|
6
|
+
@width = 3
|
7
|
+
@current_board = squares_with_integers
|
8
|
+
@player_1 = player_1
|
9
|
+
@player_2 = player_2
|
10
|
+
end
|
11
|
+
|
12
|
+
def whose_turn
|
13
|
+
number_of_empty_squares%2 == 0 ? @player_2.marker : @player_1.marker
|
14
|
+
end
|
15
|
+
|
16
|
+
def get_opponent
|
17
|
+
number_of_empty_squares%2 != 0 ? @player_2.marker : @player_1.marker
|
18
|
+
end
|
19
|
+
|
20
|
+
def integer_board
|
21
|
+
(1..@width**2).to_a
|
22
|
+
end
|
23
|
+
|
24
|
+
def squares_with_integers
|
25
|
+
string_board = integer_board.map {|square| square.to_s}
|
26
|
+
end
|
27
|
+
|
28
|
+
def display_board
|
29
|
+
if @width == 3
|
30
|
+
@current_board.each_slice(@width).
|
31
|
+
map { |a,b,c| " #{a} | #{b} | #{c} \n" }.
|
32
|
+
join("---|---|---\n")
|
33
|
+
else
|
34
|
+
@current_board.each_slice(@width).
|
35
|
+
map { |a,b,c,d| " #{a} | #{b} | #{c} | #{d} \n" }.
|
36
|
+
join("---|---|---|---\n")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def reset_board
|
41
|
+
@current_board = squares_with_integers
|
42
|
+
end
|
43
|
+
|
44
|
+
def set_square(square, marker)
|
45
|
+
@current_board[square.to_i - 1] = marker
|
46
|
+
end
|
47
|
+
|
48
|
+
def undo_set_square(square)
|
49
|
+
set_square(square, square.to_s)
|
50
|
+
end
|
51
|
+
|
52
|
+
def square_empty?(square)
|
53
|
+
@current_board[square.to_i - 1] == square.to_s ? true : false
|
54
|
+
end
|
55
|
+
|
56
|
+
def list_of_open_squares
|
57
|
+
@current_board.select {|square| square_empty?(square.to_i)}
|
58
|
+
end
|
59
|
+
|
60
|
+
def number_of_empty_squares
|
61
|
+
list_of_open_squares.length
|
62
|
+
end
|
63
|
+
|
64
|
+
def board_open?
|
65
|
+
number_of_empty_squares >= 1? true : false
|
66
|
+
end
|
67
|
+
|
68
|
+
def number_of_filled_squares
|
69
|
+
@current_board.length - list_of_open_squares.length
|
70
|
+
end
|
71
|
+
|
72
|
+
def winner_on_board?
|
73
|
+
winning_marker = false
|
74
|
+
gather_winning_combinations.each do |combo|
|
75
|
+
marker = @current_board[combo[0]]
|
76
|
+
winner = []
|
77
|
+
positions = (0...@width).to_a
|
78
|
+
positions.each {|position| winner << @current_board[combo[position]]}
|
79
|
+
if winner.all? { |square| square == marker }
|
80
|
+
winning_marker = marker
|
81
|
+
end
|
82
|
+
end
|
83
|
+
return winning_marker
|
84
|
+
end
|
85
|
+
|
86
|
+
def game_over?
|
87
|
+
(!board_open? or winner_on_board?) ? true : false
|
88
|
+
end
|
89
|
+
|
90
|
+
def game_won?(marker)
|
91
|
+
winner_on_board? == marker ? true : false
|
92
|
+
end
|
93
|
+
|
94
|
+
def board_indices
|
95
|
+
(0...@width**2).to_a
|
96
|
+
end
|
97
|
+
|
98
|
+
def winning_rows
|
99
|
+
board_indices.each_slice(@width).reduce([]) {|rows, slice| rows << slice}
|
100
|
+
end
|
101
|
+
|
102
|
+
def generate_column(starting_index)
|
103
|
+
(0...@width).reduce([]) {|column, index|
|
104
|
+
column << (index*@width+starting_index)
|
105
|
+
}
|
106
|
+
end
|
107
|
+
|
108
|
+
def winning_columns
|
109
|
+
(0...@width).reduce([]) {|set_of_columns, index|
|
110
|
+
set_of_columns << generate_column(index)
|
111
|
+
}
|
112
|
+
end
|
113
|
+
|
114
|
+
def diagonal_down
|
115
|
+
(0...@width).reduce([]) {|diagonal, index|
|
116
|
+
diagonal << index*(@width + 1)
|
117
|
+
}
|
118
|
+
end
|
119
|
+
|
120
|
+
def diagonal_up
|
121
|
+
(0...@width).reduce([]) {|diagonal, index|
|
122
|
+
diagonal << (index + 1)*(@width - 1)
|
123
|
+
}
|
124
|
+
end
|
125
|
+
|
126
|
+
def gather_winning_combinations
|
127
|
+
combos = []
|
128
|
+
winning_rows.each {|row| combos << row } and
|
129
|
+
combos << diagonal_down and
|
130
|
+
combos << diagonal_up and
|
131
|
+
winning_columns.each {|column| combos << column}
|
132
|
+
combos
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'console_ui'
|
2
|
+
require 'board'
|
3
|
+
require 'game'
|
4
|
+
require 'unbeatable_ai'
|
5
|
+
require 'player'
|
6
|
+
|
7
|
+
class Configuration
|
8
|
+
|
9
|
+
attr_accessor :ai, :ui, :board_width
|
10
|
+
|
11
|
+
def initialize(user_interface)
|
12
|
+
@ui = user_interface
|
13
|
+
@ai = Unbeatable_AI.new
|
14
|
+
@board_width = false
|
15
|
+
end
|
16
|
+
|
17
|
+
def configure_opponent
|
18
|
+
@ui.ask_for_opponent
|
19
|
+
opponent_type = @ui.get_opponent
|
20
|
+
if opponent_type == 1
|
21
|
+
@ai.opponent = true
|
22
|
+
@ai
|
23
|
+
else
|
24
|
+
@ai.opponent = false
|
25
|
+
@ai
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_marker
|
30
|
+
@ui.ask_for_marker_type
|
31
|
+
marker = @ui.get_marker_type
|
32
|
+
return marker
|
33
|
+
end
|
34
|
+
|
35
|
+
def configure_player_1(marker)
|
36
|
+
if marker.length == 1 and marker =~ /[A-Za-z]/
|
37
|
+
player_1 = Player.new(marker)
|
38
|
+
else
|
39
|
+
@ui.marker_error
|
40
|
+
configure_player_1(get_marker)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def configure_player_2(marker)
|
45
|
+
if marker.length == 1 and marker =~ /[A-Za-z]/
|
46
|
+
player_2 = Player.new(marker)
|
47
|
+
else
|
48
|
+
@ui.marker_error
|
49
|
+
configure_player_2(get_marker)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def configure_board(player_1, player_2)
|
54
|
+
board = Board.new(player_1, player_2)
|
55
|
+
@ui.ask_for_width_of_board
|
56
|
+
board_width = @ui.get_width_of_board
|
57
|
+
if board_width == 3
|
58
|
+
board.width = 3
|
59
|
+
board
|
60
|
+
elsif board_width == 4
|
61
|
+
board.width = 4
|
62
|
+
board.current_board = board.squares_with_integers
|
63
|
+
board
|
64
|
+
else
|
65
|
+
configure_board(player_1, player_2)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/console_ui.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
class Console_UI
|
2
|
+
|
3
|
+
def ask_for_width_of_board
|
4
|
+
put_to_console("Enter 3 for a 3x3 board or 4 for a 4x4 board.")
|
5
|
+
end
|
6
|
+
|
7
|
+
def get_width_of_board
|
8
|
+
get_input
|
9
|
+
end
|
10
|
+
|
11
|
+
def ask_for_first_player
|
12
|
+
put_to_console("Enter 'X' or 'O.'")
|
13
|
+
end
|
14
|
+
|
15
|
+
def ask_for_marker_type
|
16
|
+
put_to_console("Enter a character A-Z to serve as your marker type")
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_marker_type
|
20
|
+
gets.chomp
|
21
|
+
end
|
22
|
+
|
23
|
+
def get_first_player
|
24
|
+
get_input
|
25
|
+
end
|
26
|
+
|
27
|
+
def ask_for_opponent
|
28
|
+
put_to_console("Enter 1 for computer opponent or any key for human opponent")
|
29
|
+
end
|
30
|
+
|
31
|
+
def get_opponent
|
32
|
+
get_input
|
33
|
+
end
|
34
|
+
|
35
|
+
def puts_turn(marker)
|
36
|
+
put_to_console("It is #{marker}'s turn.")
|
37
|
+
end
|
38
|
+
|
39
|
+
def ask_for_square_to_mark
|
40
|
+
put_to_console('Pick an empty square to mark: ')
|
41
|
+
end
|
42
|
+
|
43
|
+
def marker_error
|
44
|
+
put_to_console("Input error.")
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_square_to_mark
|
48
|
+
get_input
|
49
|
+
end
|
50
|
+
|
51
|
+
def puts_winner(winning_marker)
|
52
|
+
put_to_console("Player #{winning_marker} wins!")
|
53
|
+
end
|
54
|
+
|
55
|
+
def puts_tie
|
56
|
+
put_to_console("Tie Game!")
|
57
|
+
end
|
58
|
+
|
59
|
+
def ask_to_restart
|
60
|
+
put_to_console("Enter 1 to restart or any key to exit.")
|
61
|
+
end
|
62
|
+
|
63
|
+
def print_board(board)
|
64
|
+
put_to_console(board)
|
65
|
+
end
|
66
|
+
|
67
|
+
def put_to_console(output)
|
68
|
+
puts output
|
69
|
+
end
|
70
|
+
|
71
|
+
def get_input
|
72
|
+
gets.chomp.to_i
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
data/lib/game.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'runner'
|
2
|
+
|
3
|
+
class Game
|
4
|
+
|
5
|
+
attr_accessor :player_1, :player_2
|
6
|
+
attr_reader :ui, :board, :ai, :unbeatable_ai, :runner
|
7
|
+
|
8
|
+
def initialize(board, ui, ai, player_1, player_2)
|
9
|
+
@player_1 = player_1
|
10
|
+
@player_2 = player_2
|
11
|
+
@board = board
|
12
|
+
@ui = ui
|
13
|
+
@ai = ai
|
14
|
+
@unbeatable_ai = ai
|
15
|
+
@runner = Runner.new(ui)
|
16
|
+
end
|
17
|
+
|
18
|
+
def declare_turn(marker)
|
19
|
+
@ui.puts_turn(marker)
|
20
|
+
@ui.print_board(@board.display_board)
|
21
|
+
@ui.ask_for_square_to_mark
|
22
|
+
end
|
23
|
+
|
24
|
+
def select_square
|
25
|
+
square = nil
|
26
|
+
marker = @board.whose_turn
|
27
|
+
declare_turn(marker)
|
28
|
+
|
29
|
+
if marker == player_2.marker and @ai.opponent == true
|
30
|
+
square = @unbeatable_ai.make_move(@board, marker)
|
31
|
+
else
|
32
|
+
square = @ui.get_square_to_mark
|
33
|
+
end
|
34
|
+
|
35
|
+
place_marker(square, marker)
|
36
|
+
end
|
37
|
+
|
38
|
+
def place_marker(square, marker)
|
39
|
+
|
40
|
+
if @board.square_empty?(square) == false
|
41
|
+
@ui.marker_error
|
42
|
+
select_square
|
43
|
+
end
|
44
|
+
|
45
|
+
@board.set_square(square, marker)
|
46
|
+
find_winner
|
47
|
+
end
|
48
|
+
|
49
|
+
def find_winner
|
50
|
+
winner = @board.winner_on_board?
|
51
|
+
open_board = @board.board_open?
|
52
|
+
|
53
|
+
if winner == false and open_board
|
54
|
+
select_square
|
55
|
+
elsif winner != false
|
56
|
+
game_over(@ui.puts_winner(winner))
|
57
|
+
else
|
58
|
+
game_over(@ui.puts_tie)
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
def game_over(final_game_message)
|
64
|
+
@ui.print_board(@board.display_board)
|
65
|
+
final_game_message
|
66
|
+
@ui.ask_to_restart
|
67
|
+
choice = @ui.get_input
|
68
|
+
restart?(choice)
|
69
|
+
end
|
70
|
+
|
71
|
+
def restart?(input)
|
72
|
+
input == 1 ? @runner.call : exit
|
73
|
+
end
|
74
|
+
|
75
|
+
def play_game
|
76
|
+
select_square
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
data/lib/player.rb
ADDED
data/lib/runner.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'board'
|
2
|
+
require 'game'
|
3
|
+
require 'configuration'
|
4
|
+
|
5
|
+
class Runner
|
6
|
+
|
7
|
+
def initialize(ui)
|
8
|
+
@ui = ui
|
9
|
+
end
|
10
|
+
|
11
|
+
def call
|
12
|
+
configure_game
|
13
|
+
play_game
|
14
|
+
end
|
15
|
+
|
16
|
+
def configure_game
|
17
|
+
configuration = Configuration.new(@ui)
|
18
|
+
ai = configuration.configure_opponent
|
19
|
+
player_1 = configuration.configure_player_1(configuration.get_marker)
|
20
|
+
player_2 = configuration.configure_player_2(configuration.get_marker)
|
21
|
+
board = configuration.configure_board(player_1, player_2)
|
22
|
+
@game = Game.new(board, @ui, ai, player_1, player_2)
|
23
|
+
end
|
24
|
+
|
25
|
+
def play_game
|
26
|
+
@game.play_game
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'board'
|
2
|
+
|
3
|
+
class Unbeatable_AI
|
4
|
+
|
5
|
+
attr_accessor :opponent
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@opponent = false
|
9
|
+
end
|
10
|
+
|
11
|
+
def score_board(board, marker)
|
12
|
+
if !board.board_open? and !board.winner_on_board?
|
13
|
+
return 0
|
14
|
+
elsif board.game_won?(marker)
|
15
|
+
1.0
|
16
|
+
else
|
17
|
+
-1.0
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def minimax(board, marker, depth, alpha, beta)
|
22
|
+
opponent = board.get_opponent
|
23
|
+
score = 0
|
24
|
+
best_score = -1.0/0
|
25
|
+
if board.game_over?
|
26
|
+
return score_board(board, marker) / depth
|
27
|
+
else
|
28
|
+
new_alpha = alpha
|
29
|
+
board.list_of_open_squares.each do |square|
|
30
|
+
board.set_square(square, marker)
|
31
|
+
score = -minimax(board, opponent, depth + 1, -new_alpha, -beta)
|
32
|
+
board.undo_set_square(square)
|
33
|
+
best_score = score if score > best_score
|
34
|
+
end
|
35
|
+
return best_score
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def make_move(board, marker)
|
40
|
+
best_square = nil
|
41
|
+
best_score = -1.0/0
|
42
|
+
opponent = board.get_opponent
|
43
|
+
board.list_of_open_squares.each do |square|
|
44
|
+
board.set_square(square, marker)
|
45
|
+
score = -minimax(board, opponent, 1, -1.0/0, 1.0/0)
|
46
|
+
board.undo_set_square(square)
|
47
|
+
if score > best_score
|
48
|
+
best_score = score
|
49
|
+
best_square = square
|
50
|
+
end
|
51
|
+
end
|
52
|
+
return best_square
|
53
|
+
end
|
54
|
+
end
|
metadata
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pszals_ttt
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Philip Szalwinski
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-07-25 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: my tic tac toe game engine
|
14
|
+
email: pszalwinski@gmail.com
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- lib/board.rb
|
20
|
+
- lib/configuration.rb
|
21
|
+
- lib/console_ui.rb
|
22
|
+
- lib/game.rb
|
23
|
+
- lib/player.rb
|
24
|
+
- lib/runner.rb
|
25
|
+
- lib/unbeatable_ai.rb
|
26
|
+
homepage:
|
27
|
+
licenses: []
|
28
|
+
metadata: {}
|
29
|
+
post_install_message:
|
30
|
+
rdoc_options: []
|
31
|
+
require_paths:
|
32
|
+
- lib
|
33
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
34
|
+
requirements:
|
35
|
+
- - '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - '>='
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
requirements: []
|
44
|
+
rubyforge_project:
|
45
|
+
rubygems_version: 2.0.4
|
46
|
+
signing_key:
|
47
|
+
specification_version: 4
|
48
|
+
summary: a simple tic tac toe game
|
49
|
+
test_files: []
|