tic_tac_toes 0.0.2 → 0.0.3
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 +4 -4
- data/.travis.yml +7 -0
- data/Gemfile.lock +1 -1
- data/README.md +2 -0
- data/Rakefile +2 -2
- data/bin/tic_tac_toes +21 -9
- data/lib/command_line/menu.rb +11 -12
- data/lib/command_line/{io.rb → prompt.rb} +1 -1
- data/lib/command_line/runner.rb +18 -18
- data/lib/tic_tac_toes/board_factory.rb +9 -0
- data/lib/tic_tac_toes/game_state.rb +27 -0
- data/lib/tic_tac_toes/game_state_factory.rb +14 -0
- data/lib/tic_tac_toes/history.rb +3 -3
- data/lib/tic_tac_toes/io.rb +91 -0
- data/lib/tic_tac_toes/move_strategies/easy_ai.rb +11 -0
- data/lib/tic_tac_toes/move_strategies/hard_ai.rb +59 -0
- data/lib/tic_tac_toes/move_strategies/human.rb +18 -0
- data/lib/tic_tac_toes/move_strategies/medium_ai.rb +13 -0
- data/lib/tic_tac_toes/player.rb +7 -7
- data/lib/tic_tac_toes/player_factory.rb +13 -9
- data/lib/tic_tac_toes/rules.rb +1 -1
- data/lib/tic_tac_toes/strings.rb +3 -2
- data/lib/tic_tac_toes/version.rb +1 -1
- data/spec/command_line/menu_spec.rb +40 -44
- data/spec/command_line/runner_spec.rb +23 -101
- data/spec/database/pg_wrapper_spec.rb +0 -1
- data/spec/test_board_generator.rb +15 -0
- data/spec/tic_tac_toes/board_factory_spec.rb +13 -0
- data/spec/tic_tac_toes/board_spec.rb +20 -25
- data/spec/tic_tac_toes/game_state_factory_spec.rb +15 -0
- data/spec/tic_tac_toes/game_state_spec.rb +66 -0
- data/spec/tic_tac_toes/history_spec.rb +3 -4
- data/spec/tic_tac_toes/io_spec.rb +163 -0
- data/spec/tic_tac_toes/move_strategies/easy_ai_spec.rb +19 -0
- data/spec/tic_tac_toes/move_strategies/hard_ai_spec.rb +95 -0
- data/spec/tic_tac_toes/move_strategies/human_spec.rb +31 -0
- data/spec/tic_tac_toes/move_strategies/medium_ai_spec.rb +21 -0
- data/spec/tic_tac_toes/player_factory_spec.rb +13 -16
- data/spec/tic_tac_toes/player_spec.rb +14 -16
- data/spec/tic_tac_toes/rules_spec.rb +31 -41
- data/spec/tic_tac_toes/strings_spec.rb +0 -1
- metadata +32 -19
- data/lib/tic_tac_toes/easy_ai.rb +0 -9
- data/lib/tic_tac_toes/hard_ai.rb +0 -57
- data/lib/tic_tac_toes/io_interface.rb +0 -96
- data/lib/tic_tac_toes/medium_ai.rb +0 -11
- data/spec/tic_tac_toes/easy_ai_spec.rb +0 -20
- data/spec/tic_tac_toes/hard_ai_spec.rb +0 -103
- data/spec/tic_tac_toes/io_interface_spec.rb +0 -175
- data/spec/tic_tac_toes/medium_ai_spec.rb +0 -22
- data/spec/tic_tac_toes/spec_helper.rb +0 -13
- /data/lib/{tic_tac_toes/tasks → tasks}/destroy_databases.rake +0 -0
- /data/lib/{tic_tac_toes/tasks → tasks}/set_up_databases.rake +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: de5c575f7ff03180dda1f07d8f1930cc56bde5ca
|
4
|
+
data.tar.gz: 7dc353433569c7d289055a4893d37b9fcf845af3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 435ed2f2a0e102b82302d1ee4fc4c0a617d9e51767fb9976c3fecd2223d210a70915bab7b41047b62a83309fa7c4b3f97533fb90918f5948ad44b3e3350d92da
|
7
|
+
data.tar.gz: 4c1b8cfc7d68129cb5516d83807e5660c9bf3a4e23c89a558921cdf0d72b33b59fd9ca1e18f125a8f58a77e00d2420eedd68576cdfb62248b989453a85db0ed5
|
data/.travis.yml
ADDED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
data/Rakefile
CHANGED
data/bin/tic_tac_toes
CHANGED
@@ -2,22 +2,34 @@
|
|
2
2
|
|
3
3
|
$LOAD_PATH << File.expand_path(File.dirname(__FILE__) + '/../lib')
|
4
4
|
|
5
|
-
require 'command_line/
|
5
|
+
require 'command_line/prompt'
|
6
|
+
require 'tic_tac_toes/io'
|
7
|
+
|
8
|
+
require 'tic_tac_toes/board_factory'
|
9
|
+
require 'tic_tac_toes/player_factory'
|
6
10
|
require 'command_line/menu'
|
7
|
-
|
8
|
-
require 'tic_tac_toes/io_interface'
|
11
|
+
|
9
12
|
require 'tic_tac_toes/rules'
|
13
|
+
|
14
|
+
require 'database/pg_wrapper'
|
10
15
|
require 'tic_tac_toes/history'
|
16
|
+
require 'tic_tac_toes/game_state_factory'
|
11
17
|
|
12
18
|
require 'command_line/runner'
|
13
19
|
|
14
20
|
database = "tic_tac_toes"
|
15
21
|
|
16
|
-
|
17
|
-
|
18
|
-
|
22
|
+
prompt = CommandLine::Prompt
|
23
|
+
io = TicTacToes::IO.new(prompt)
|
24
|
+
|
25
|
+
board_factory = TicTacToes::BoardFactory.new
|
26
|
+
player_factory = TicTacToes::PlayerFactory.new(io)
|
27
|
+
menu = CommandLine::Menu.new(io, board_factory, player_factory)
|
28
|
+
|
29
|
+
database_wrapper = Database::PGWrapper.new(database)
|
30
|
+
history = TicTacToes::History.new(database_wrapper)
|
31
|
+
game_state_factory = TicTacToes::GameStateFactory.new(history)
|
32
|
+
|
19
33
|
rules = TicTacToes::Rules
|
20
|
-
database_interface = Database::PGWrapper.new(database)
|
21
|
-
history = TicTacToes::History.new(database_interface)
|
22
34
|
|
23
|
-
CommandLine::Runner.new(
|
35
|
+
CommandLine::Runner.new(io, menu, game_state_factory, rules).run
|
data/lib/command_line/menu.rb
CHANGED
@@ -1,16 +1,15 @@
|
|
1
|
-
require 'tic_tac_toes/board'
|
2
|
-
require 'tic_tac_toes/player_factory'
|
3
1
|
require 'tic_tac_toes/rules'
|
4
2
|
|
5
3
|
module CommandLine
|
6
4
|
class Menu
|
7
|
-
def initialize(
|
8
|
-
@
|
9
|
-
@
|
5
|
+
def initialize(io, board_factory, player_factory)
|
6
|
+
@io = io
|
7
|
+
@board_factory = board_factory
|
8
|
+
@player_factory = player_factory
|
10
9
|
end
|
11
10
|
|
12
11
|
def get_board
|
13
|
-
|
12
|
+
@board_factory.generate_board(get_row_size)
|
14
13
|
end
|
15
14
|
|
16
15
|
def get_players
|
@@ -29,25 +28,25 @@ module CommandLine
|
|
29
28
|
|
30
29
|
def get_row_size
|
31
30
|
loop do
|
32
|
-
row_size = @
|
31
|
+
row_size = @io.get_row_size
|
33
32
|
break row_size if TicTacToes::Rules.row_size_valid?(row_size)
|
34
|
-
@
|
33
|
+
@io.invalid_row_size_error
|
35
34
|
end
|
36
35
|
end
|
37
36
|
|
38
37
|
def get_token(player, taken_tokens)
|
39
38
|
loop do
|
40
|
-
token = @
|
39
|
+
token = @io.get_token(player)
|
41
40
|
break token if TicTacToes::Rules.token_valid?(token, taken_tokens)
|
42
|
-
@
|
41
|
+
@io.invalid_token_error
|
43
42
|
end
|
44
43
|
end
|
45
44
|
|
46
45
|
def get_difficulty
|
47
46
|
loop do
|
48
|
-
difficulty = @
|
47
|
+
difficulty = @io.get_difficulty
|
49
48
|
break difficulty if TicTacToes::Rules.difficulty_valid?(difficulty)
|
50
|
-
@
|
49
|
+
@io.invalid_difficulty_error
|
51
50
|
end
|
52
51
|
end
|
53
52
|
end
|
data/lib/command_line/runner.rb
CHANGED
@@ -1,37 +1,37 @@
|
|
1
1
|
module CommandLine
|
2
2
|
class Runner
|
3
|
-
def initialize(
|
4
|
-
@
|
3
|
+
def initialize(io, menu, game_state_factory, rules)
|
4
|
+
@io = io
|
5
5
|
@menu = menu
|
6
|
+
@game_state_factory = game_state_factory
|
6
7
|
@rules = rules
|
7
|
-
@history = history
|
8
8
|
end
|
9
9
|
|
10
10
|
def run
|
11
11
|
board, players = @menu.get_board, @menu.get_players
|
12
|
-
@
|
12
|
+
game_state = @game_state_factory.generate_game_state(board, players)
|
13
13
|
|
14
|
-
take_turn(
|
15
|
-
end_game(
|
14
|
+
take_turn(game_state) until @rules.game_over?(game_state.board, game_state.players)
|
15
|
+
end_game(game_state)
|
16
16
|
end
|
17
17
|
|
18
|
-
|
19
|
-
@io_interface.draw_board(board)
|
20
|
-
@io_interface.thinking_notification if players.first.needs_to_think
|
18
|
+
private
|
21
19
|
|
22
|
-
|
23
|
-
@
|
24
|
-
|
20
|
+
def take_turn(game_state)
|
21
|
+
@io.draw_board(game_state.board)
|
22
|
+
@io.thinking_notification if game_state.current_player.needs_to_think
|
23
|
+
|
24
|
+
move = game_state.current_player.place_and_return_move(game_state.board, game_state.players)
|
25
|
+
game_state.turn_over(move)
|
25
26
|
end
|
26
27
|
|
27
|
-
def end_game(
|
28
|
-
@
|
28
|
+
def end_game(game_state)
|
29
|
+
@io.draw_board(game_state.board)
|
29
30
|
|
30
|
-
winner = @rules.determine_winner(board, players)
|
31
|
+
winner = @rules.determine_winner(game_state.board, game_state.players)
|
32
|
+
game_state.game_over(winner)
|
31
33
|
|
32
|
-
@
|
33
|
-
@history.persist
|
34
|
-
@io_interface.game_over_notification(winner)
|
34
|
+
@io.game_over_notification(winner)
|
35
35
|
end
|
36
36
|
end
|
37
37
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module TicTacToes
|
2
|
+
class GameState
|
3
|
+
attr_reader :board, :players
|
4
|
+
|
5
|
+
def initialize(board, players, history)
|
6
|
+
@board = board
|
7
|
+
@players = players
|
8
|
+
@history = history
|
9
|
+
|
10
|
+
@history.record_board_size(@board.size)
|
11
|
+
end
|
12
|
+
|
13
|
+
def current_player
|
14
|
+
@players.first
|
15
|
+
end
|
16
|
+
|
17
|
+
def turn_over(move)
|
18
|
+
@history.record_move(move)
|
19
|
+
@players.rotate!
|
20
|
+
end
|
21
|
+
|
22
|
+
def game_over(winner)
|
23
|
+
@history.record_winner(winner)
|
24
|
+
@history.persist
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'tic_tac_toes/game_state'
|
2
|
+
require 'tic_tac_toes/history'
|
3
|
+
|
4
|
+
module TicTacToes
|
5
|
+
class GameStateFactory
|
6
|
+
def initialize(history)
|
7
|
+
@history = history
|
8
|
+
end
|
9
|
+
|
10
|
+
def generate_game_state(board, players)
|
11
|
+
TicTacToes::GameState.new(board, players, @history)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/tic_tac_toes/history.rb
CHANGED
@@ -2,8 +2,8 @@ module TicTacToes
|
|
2
2
|
class History
|
3
3
|
attr_reader :board_size, :moves, :winner
|
4
4
|
|
5
|
-
def initialize(
|
6
|
-
@
|
5
|
+
def initialize(database_wrapper)
|
6
|
+
@database_wrapper = database_wrapper
|
7
7
|
end
|
8
8
|
|
9
9
|
def record_board_size(size)
|
@@ -21,7 +21,7 @@ module TicTacToes
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def persist
|
24
|
-
@
|
24
|
+
@database_wrapper.record_game_history(self)
|
25
25
|
end
|
26
26
|
end
|
27
27
|
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'tic_tac_toes/strings'
|
2
|
+
|
3
|
+
module TicTacToes
|
4
|
+
class IO
|
5
|
+
def initialize(io_strategy)
|
6
|
+
@io_strategy = io_strategy
|
7
|
+
end
|
8
|
+
|
9
|
+
def get_row_size
|
10
|
+
row_size_solicitation
|
11
|
+
|
12
|
+
Integer(@io_strategy.solicit_input)
|
13
|
+
rescue ArgumentError
|
14
|
+
not_an_integer_error
|
15
|
+
get_row_size
|
16
|
+
end
|
17
|
+
|
18
|
+
def get_token(player)
|
19
|
+
token_solicitation(player)
|
20
|
+
@io_strategy.solicit_input
|
21
|
+
end
|
22
|
+
|
23
|
+
def get_difficulty
|
24
|
+
difficulty_solicitation
|
25
|
+
@io_strategy.solicit_input.downcase.to_sym
|
26
|
+
end
|
27
|
+
|
28
|
+
def draw_board(board)
|
29
|
+
@io_strategy.display(Strings.board(board))
|
30
|
+
end
|
31
|
+
|
32
|
+
def invalid_row_size_error
|
33
|
+
@io_strategy.display_red(Strings::INVALID_ROW_SIZE)
|
34
|
+
end
|
35
|
+
|
36
|
+
def invalid_token_error
|
37
|
+
@io_strategy.display_red(Strings::INVALID_TOKEN)
|
38
|
+
end
|
39
|
+
|
40
|
+
def invalid_difficulty_error
|
41
|
+
@io_strategy.display_red(Strings::INVALID_DIFFICULTY)
|
42
|
+
end
|
43
|
+
|
44
|
+
def invalid_move_error
|
45
|
+
@io_strategy.display_red(Strings::INVALID_MOVE)
|
46
|
+
end
|
47
|
+
|
48
|
+
def thinking_notification
|
49
|
+
@io_strategy.display_red(Strings::THINKING)
|
50
|
+
end
|
51
|
+
|
52
|
+
def game_over_notification(winner)
|
53
|
+
winner = "Nobody" if winner.nil?
|
54
|
+
@io_strategy.display(Strings.game_over_notification(winner))
|
55
|
+
end
|
56
|
+
|
57
|
+
def move_solicitation
|
58
|
+
@io_strategy.display(Strings::MOVE_SOLICITATION)
|
59
|
+
end
|
60
|
+
|
61
|
+
def not_an_integer_error
|
62
|
+
@io_strategy.display_red(Strings::NOT_AN_INTEGER)
|
63
|
+
end
|
64
|
+
|
65
|
+
def solicit_input
|
66
|
+
@io_strategy.solicit_input
|
67
|
+
end
|
68
|
+
|
69
|
+
def red(message)
|
70
|
+
@io_strategy.red(message)
|
71
|
+
end
|
72
|
+
|
73
|
+
def blue(message)
|
74
|
+
@io_strategy.blue(message)
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def row_size_solicitation
|
80
|
+
@io_strategy.display(Strings::ROW_SIZE_SOLICITATION)
|
81
|
+
end
|
82
|
+
|
83
|
+
def token_solicitation(player)
|
84
|
+
@io_strategy.display(Strings.token_solicitation(player))
|
85
|
+
end
|
86
|
+
|
87
|
+
def difficulty_solicitation
|
88
|
+
@io_strategy.display(Strings::DIFFICULTY_SOLICITATION)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'tic_tac_toes/rules'
|
2
|
+
|
3
|
+
module TicTacToes
|
4
|
+
module MoveStrategies
|
5
|
+
module HardAI
|
6
|
+
def self.move(board, players)
|
7
|
+
open_spaces = Hash[board.open_spaces.map { |space| [space, nil] }]
|
8
|
+
|
9
|
+
open_spaces.each do |space, score|
|
10
|
+
score = minimax(generate_board(players.first, space, board), :min, players)
|
11
|
+
open_spaces[space] = score
|
12
|
+
end
|
13
|
+
|
14
|
+
best_score = open_spaces.values.max
|
15
|
+
open_spaces.each { |space, score| return space if score == best_score }
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.minimax(board, current_player, players)
|
19
|
+
return score(board, players) if Rules.game_over?(board, players)
|
20
|
+
|
21
|
+
if current_player == :max
|
22
|
+
best_score = -1
|
23
|
+
board.open_spaces.each do |space|
|
24
|
+
score = minimax(generate_board(players.first, space, board), :min, players)
|
25
|
+
best_score = [best_score, score].max
|
26
|
+
end
|
27
|
+
best_score
|
28
|
+
|
29
|
+
elsif current_player == :min
|
30
|
+
best_score = 1
|
31
|
+
board.open_spaces.each do |space|
|
32
|
+
score = minimax(generate_board(players.last, space, board), :max, players)
|
33
|
+
best_score = [best_score, score].min
|
34
|
+
end
|
35
|
+
best_score
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.generate_board(player, space, board)
|
40
|
+
new_board = Marshal.load(Marshal.dump(board))
|
41
|
+
new_board.place(player, space)
|
42
|
+
new_board
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.score(board, players)
|
46
|
+
own_token, opponent_token = players.first.token, players.last.token
|
47
|
+
|
48
|
+
case Rules.determine_winner(board, players)
|
49
|
+
when own_token
|
50
|
+
1
|
51
|
+
when opponent_token
|
52
|
+
-1
|
53
|
+
else
|
54
|
+
0
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module TicTacToes
|
2
|
+
module MoveStrategies
|
3
|
+
class Human
|
4
|
+
def initialize(io)
|
5
|
+
@io = io
|
6
|
+
end
|
7
|
+
|
8
|
+
def move(board, players)
|
9
|
+
@io.move_solicitation
|
10
|
+
|
11
|
+
Integer(@io.solicit_input)
|
12
|
+
rescue ArgumentError
|
13
|
+
@io.not_an_integer_error
|
14
|
+
move(board, players)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'tic_tac_toes/move_strategies/easy_ai'
|
2
|
+
require 'tic_tac_toes/move_strategies/hard_ai'
|
3
|
+
|
4
|
+
module TicTacToes
|
5
|
+
module MoveStrategies
|
6
|
+
module MediumAI
|
7
|
+
def self.move(board, players)
|
8
|
+
move_strategy = [EasyAI, HardAI, HardAI].sample
|
9
|
+
move_strategy.move(board, players)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/tic_tac_toes/player.rb
CHANGED
@@ -1,19 +1,19 @@
|
|
1
1
|
module TicTacToes
|
2
2
|
class Player
|
3
|
-
attr_reader :
|
3
|
+
attr_reader :move_strategy, :token, :needs_to_think
|
4
4
|
|
5
|
-
def initialize(
|
6
|
-
@
|
5
|
+
def initialize(move_strategy, token, needs_to_think, io)
|
6
|
+
@move_strategy = move_strategy
|
7
7
|
@token = token
|
8
8
|
@needs_to_think = needs_to_think
|
9
|
-
@
|
9
|
+
@io = io
|
10
10
|
end
|
11
11
|
|
12
|
-
def
|
12
|
+
def place_and_return_move(board, players)
|
13
13
|
loop do
|
14
|
-
space = @
|
14
|
+
space = @move_strategy.move(board, players)
|
15
15
|
break [@token, space] if board.place(self, space)
|
16
|
-
@
|
16
|
+
@io.invalid_move_error
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
@@ -1,24 +1,28 @@
|
|
1
|
-
require 'tic_tac_toes/
|
2
|
-
require 'tic_tac_toes/
|
3
|
-
require 'tic_tac_toes/medium_ai'
|
1
|
+
require 'tic_tac_toes/move_strategies/human'
|
2
|
+
require 'tic_tac_toes/move_strategies/easy_ai'
|
3
|
+
require 'tic_tac_toes/move_strategies/medium_ai'
|
4
|
+
require 'tic_tac_toes/move_strategies/hard_ai'
|
4
5
|
require 'tic_tac_toes/player'
|
5
6
|
|
6
7
|
module TicTacToes
|
7
8
|
class PlayerFactory
|
8
|
-
|
9
|
-
|
10
|
-
def initialize(io_interface)
|
11
|
-
@io_interface = io_interface
|
9
|
+
def initialize(io)
|
10
|
+
@io = io
|
12
11
|
end
|
13
12
|
|
13
|
+
AIS = {
|
14
|
+
easy: ::TicTacToes::MoveStrategies::EasyAI,
|
15
|
+
medium: ::TicTacToes::MoveStrategies::MediumAI,
|
16
|
+
hard: ::TicTacToes::MoveStrategies::HardAI }
|
17
|
+
|
14
18
|
def generate_human_player(token)
|
15
19
|
needs_to_think = false
|
16
|
-
Player.new(@
|
20
|
+
Player.new(TicTacToes::MoveStrategies::Human.new(@io), token, needs_to_think, @io)
|
17
21
|
end
|
18
22
|
|
19
23
|
def generate_computer_player(token, difficulty)
|
20
24
|
needs_to_think = true
|
21
|
-
Player.new(
|
25
|
+
Player.new(AIS[difficulty], token, needs_to_think, @io)
|
22
26
|
end
|
23
27
|
end
|
24
28
|
end
|
data/lib/tic_tac_toes/rules.rb
CHANGED
data/lib/tic_tac_toes/strings.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'command_line/prompt'
|
1
2
|
require 'tic_tac_toes/rules'
|
2
3
|
|
3
4
|
module TicTacToes
|
@@ -71,9 +72,9 @@ module TicTacToes
|
|
71
72
|
|
72
73
|
def self.get_colored_token(player)
|
73
74
|
if player.needs_to_think
|
74
|
-
return CommandLine::
|
75
|
+
return CommandLine::Prompt.red(player.token)
|
75
76
|
else
|
76
|
-
return CommandLine::
|
77
|
+
return CommandLine::Prompt.blue(player.token)
|
77
78
|
end
|
78
79
|
end
|
79
80
|
|
data/lib/tic_tac_toes/version.rb
CHANGED