tic_tac_toes 0.0.7 → 0.0.8
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/Gemfile.lock +1 -1
- data/Rakefile +2 -2
- data/bin/tic_tac_toes +18 -22
- data/lib/tic_tac_toes/command_line/menu.rb +55 -0
- data/lib/tic_tac_toes/command_line/prompt.rb +29 -0
- data/lib/tic_tac_toes/command_line/runner.rb +38 -0
- data/lib/tic_tac_toes/core/board.rb +64 -0
- data/lib/tic_tac_toes/core/board_factory.rb +11 -0
- data/lib/tic_tac_toes/core/game_state.rb +40 -0
- data/lib/tic_tac_toes/core/game_state_factory.rb +15 -0
- data/lib/tic_tac_toes/core/history.rb +29 -0
- data/lib/tic_tac_toes/core/io.rb +93 -0
- data/lib/tic_tac_toes/core/move_strategies/easy_ai.rb +13 -0
- data/lib/tic_tac_toes/core/move_strategies/hard_ai.rb +89 -0
- data/lib/tic_tac_toes/core/move_strategies/human.rb +20 -0
- data/lib/tic_tac_toes/core/move_strategies/medium_ai.rb +15 -0
- data/lib/tic_tac_toes/core/player.rb +22 -0
- data/lib/tic_tac_toes/core/player_factory.rb +30 -0
- data/lib/tic_tac_toes/core/rules.rb +66 -0
- data/lib/tic_tac_toes/core/strings.rb +106 -0
- data/lib/tic_tac_toes/database/pg_wrapper.rb +74 -0
- data/lib/tic_tac_toes/ui/adapter.rb +96 -0
- data/lib/tic_tac_toes/ui/serializer.rb +0 -0
- data/lib/tic_tac_toes/version.rb +1 -1
- data/spec/{command_line → tic_tac_toes/command_line}/menu_spec.rb +14 -14
- data/spec/{command_line → tic_tac_toes/command_line}/runner_spec.rb +7 -10
- data/spec/tic_tac_toes/{board_factory_spec.rb → core/board_factory_spec.rb} +3 -3
- data/spec/tic_tac_toes/{board_spec.rb → core/board_spec.rb} +23 -23
- data/spec/tic_tac_toes/{game_state_factory_spec.rb → core/game_state_factory_spec.rb} +3 -3
- data/spec/tic_tac_toes/core/game_state_spec.rb +129 -0
- data/spec/tic_tac_toes/{history_spec.rb → core/history_spec.rb} +3 -3
- data/spec/tic_tac_toes/{io_spec.rb → core/io_spec.rb} +7 -7
- data/spec/tic_tac_toes/core/move_strategies/easy_ai_spec.rb +19 -0
- data/spec/tic_tac_toes/core/move_strategies/hard_ai_spec.rb +121 -0
- data/spec/tic_tac_toes/{move_strategies → core/move_strategies}/human_spec.rb +3 -3
- data/spec/tic_tac_toes/core/move_strategies/medium_ai_spec.rb +21 -0
- data/spec/tic_tac_toes/{player_factory_spec.rb → core/player_factory_spec.rb} +7 -7
- data/spec/tic_tac_toes/{player_spec.rb → core/player_spec.rb} +5 -5
- data/spec/tic_tac_toes/{rules_spec.rb → core/rules_spec.rb} +34 -34
- data/spec/tic_tac_toes/{strings_spec.rb → core/strings_spec.rb} +8 -8
- data/spec/{database → tic_tac_toes/database}/pg_wrapper_spec.rb +3 -3
- data/spec/tic_tac_toes/test_board_generator.rb +17 -0
- data/spec/{ui → tic_tac_toes/ui}/adapter_spec.rb +13 -13
- data/spec/tic_tac_toes/ui/serializer_spec.rb +0 -0
- metadata +64 -61
- data/lib/command_line/menu.rb +0 -53
- data/lib/command_line/prompt.rb +0 -27
- data/lib/command_line/runner.rb +0 -37
- data/lib/database/pg_wrapper.rb +0 -72
- data/lib/tic_tac_toes/board.rb +0 -62
- data/lib/tic_tac_toes/board_factory.rb +0 -9
- data/lib/tic_tac_toes/game_state.rb +0 -27
- data/lib/tic_tac_toes/game_state_factory.rb +0 -14
- data/lib/tic_tac_toes/history.rb +0 -27
- data/lib/tic_tac_toes/io.rb +0 -91
- data/lib/tic_tac_toes/move_strategies/easy_ai.rb +0 -11
- data/lib/tic_tac_toes/move_strategies/hard_ai.rb +0 -87
- data/lib/tic_tac_toes/move_strategies/human.rb +0 -18
- data/lib/tic_tac_toes/move_strategies/medium_ai.rb +0 -13
- data/lib/tic_tac_toes/player.rb +0 -20
- data/lib/tic_tac_toes/player_factory.rb +0 -28
- data/lib/tic_tac_toes/rules.rb +0 -64
- data/lib/tic_tac_toes/strings.rb +0 -104
- data/lib/ui/adapter.rb +0 -94
- data/spec/test_board_generator.rb +0 -15
- data/spec/tic_tac_toes/game_state_spec.rb +0 -66
- data/spec/tic_tac_toes/move_strategies/easy_ai_spec.rb +0 -19
- data/spec/tic_tac_toes/move_strategies/hard_ai_spec.rb +0 -121
- data/spec/tic_tac_toes/move_strategies/medium_ai_spec.rb +0 -21
- /data/lib/{tasks → tic_tac_toes/tasks}/destroy_databases.rake +0 -0
- /data/lib/{tasks → tic_tac_toes/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: 085e4d7d1fd5316dd0a28ca154203ae9411e3c2f
|
4
|
+
data.tar.gz: ef98016568277620377b83e02aa0ea58827a379b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 688112f80e57889cbd0cf27e190f3f2a7a8e41dc6823ef9ee1d47bf622cd5f554c52b8684c4332c97d838ea923c5b32cb2f0687f92f9a2d3ff9169c5730ba308
|
7
|
+
data.tar.gz: ccf022fc972444762499b6ac6a9450dc91ae27be064d24692c11cbdfe232814ff6ad18425e98f56c052c6649aa8de7a0a3c56b1f664fea422ec3aee2db4af24a
|
data/Gemfile.lock
CHANGED
data/Rakefile
CHANGED
data/bin/tic_tac_toes
CHANGED
@@ -2,34 +2,30 @@
|
|
2
2
|
|
3
3
|
$LOAD_PATH << File.expand_path(File.dirname(__FILE__) + '/../lib')
|
4
4
|
|
5
|
-
require 'command_line/prompt'
|
6
|
-
require 'tic_tac_toes/io'
|
5
|
+
require 'tic_tac_toes/command_line/prompt'
|
6
|
+
require 'tic_tac_toes/core/io'
|
7
7
|
|
8
|
-
require 'tic_tac_toes/board_factory'
|
9
|
-
require 'tic_tac_toes/player_factory'
|
10
|
-
require 'command_line/menu'
|
8
|
+
require 'tic_tac_toes/core/board_factory'
|
9
|
+
require 'tic_tac_toes/core/player_factory'
|
10
|
+
require 'tic_tac_toes/command_line/menu'
|
11
11
|
|
12
|
-
require 'tic_tac_toes/
|
12
|
+
require 'tic_tac_toes/database/pg_wrapper'
|
13
|
+
require 'tic_tac_toes/core/history'
|
14
|
+
require 'tic_tac_toes/core/game_state_factory'
|
13
15
|
|
14
|
-
require '
|
15
|
-
require 'tic_tac_toes/history'
|
16
|
-
require 'tic_tac_toes/game_state_factory'
|
17
|
-
|
18
|
-
require 'command_line/runner'
|
16
|
+
require 'tic_tac_toes/command_line/runner'
|
19
17
|
|
20
18
|
database = "tic_tac_toes"
|
21
19
|
|
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)
|
20
|
+
prompt = TicTacToes::CommandLine::Prompt
|
21
|
+
io = TicTacToes::Core::IO.new(prompt)
|
28
22
|
|
29
|
-
|
30
|
-
|
31
|
-
|
23
|
+
board_factory = TicTacToes::Core::BoardFactory.new
|
24
|
+
player_factory = TicTacToes::Core::PlayerFactory.new(io)
|
25
|
+
menu = TicTacToes::CommandLine::Menu.new(io, board_factory, player_factory)
|
32
26
|
|
33
|
-
|
27
|
+
database_wrapper = TicTacToes::Database::PGWrapper.new(database)
|
28
|
+
history = TicTacToes::Core::History.new(database_wrapper)
|
29
|
+
game_state_factory = TicTacToes::Core::GameStateFactory.new(history)
|
34
30
|
|
35
|
-
CommandLine::Runner.new(io, menu, game_state_factory
|
31
|
+
TicTacToes::CommandLine::Runner.new(io, menu, game_state_factory).run
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'tic_tac_toes/core/rules'
|
2
|
+
|
3
|
+
module TicTacToes
|
4
|
+
module CommandLine
|
5
|
+
class Menu
|
6
|
+
def initialize(io, board_factory, player_factory)
|
7
|
+
@io = io
|
8
|
+
@board_factory = board_factory
|
9
|
+
@player_factory = player_factory
|
10
|
+
end
|
11
|
+
|
12
|
+
def get_board
|
13
|
+
@board_factory.generate_board(get_row_size)
|
14
|
+
end
|
15
|
+
|
16
|
+
def get_players
|
17
|
+
taken_tokens = []
|
18
|
+
human_token = get_token(:human, taken_tokens)
|
19
|
+
taken_tokens << human_token
|
20
|
+
computer_token = get_token(:computer, taken_tokens)
|
21
|
+
difficulty = get_difficulty
|
22
|
+
|
23
|
+
human_player = @player_factory.generate_human_player(human_token)
|
24
|
+
computer_player = @player_factory.generate_computer_player(computer_token, difficulty)
|
25
|
+
[human_player, computer_player]
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def get_row_size
|
31
|
+
loop do
|
32
|
+
row_size = @io.get_row_size
|
33
|
+
break row_size if Core::Rules.row_size_valid?(row_size)
|
34
|
+
@io.invalid_row_size_error
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def get_token(player, taken_tokens)
|
39
|
+
loop do
|
40
|
+
token = @io.get_token(player)
|
41
|
+
break token if Core::Rules.token_valid?(token, taken_tokens)
|
42
|
+
@io.invalid_token_error
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def get_difficulty
|
47
|
+
loop do
|
48
|
+
difficulty = @io.get_difficulty
|
49
|
+
break difficulty if Core::Rules.difficulty_valid?(difficulty)
|
50
|
+
@io.invalid_difficulty_error
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module TicTacToes
|
2
|
+
module CommandLine
|
3
|
+
module Prompt
|
4
|
+
def self.solicit_input
|
5
|
+
gets.chomp
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.display(message)
|
9
|
+
puts message
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.display_red(message)
|
13
|
+
puts red(message)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.red(message)
|
17
|
+
colorize(message, 31)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.blue(message)
|
21
|
+
colorize(message, 34)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.colorize(message, color_code)
|
25
|
+
"\e[#{color_code}m#{message}\e[0m"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module TicTacToes
|
2
|
+
module CommandLine
|
3
|
+
class Runner
|
4
|
+
def initialize(io, menu, game_state_factory)
|
5
|
+
@io = io
|
6
|
+
@menu = menu
|
7
|
+
@game_state_factory = game_state_factory
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
board, players = @menu.get_board, @menu.get_players
|
12
|
+
game_state = @game_state_factory.generate_game_state(board, players)
|
13
|
+
|
14
|
+
take_turn(game_state) until game_state.game_over?
|
15
|
+
end_game(game_state)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
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)
|
26
|
+
end
|
27
|
+
|
28
|
+
def end_game(game_state)
|
29
|
+
@io.draw_board(game_state.board)
|
30
|
+
|
31
|
+
winner = game_state.determine_winner
|
32
|
+
game_state.game_over(winner)
|
33
|
+
|
34
|
+
@io.game_over_notification(winner)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module TicTacToes
|
2
|
+
module Core
|
3
|
+
class Board
|
4
|
+
attr_reader :row_size, :size, :spaces
|
5
|
+
|
6
|
+
def initialize(row_size: 3)
|
7
|
+
@row_size = row_size
|
8
|
+
@size = @row_size**2
|
9
|
+
@spaces = Array.new(@size)
|
10
|
+
end
|
11
|
+
|
12
|
+
def place(player, space)
|
13
|
+
@spaces[space] = player if valid?(space)
|
14
|
+
end
|
15
|
+
|
16
|
+
def space(space)
|
17
|
+
@spaces[space]
|
18
|
+
end
|
19
|
+
|
20
|
+
def open_spaces
|
21
|
+
open_spaces = []
|
22
|
+
|
23
|
+
@spaces.each_with_index do |space, index|
|
24
|
+
open_spaces << index if space.nil?
|
25
|
+
end
|
26
|
+
|
27
|
+
open_spaces
|
28
|
+
end
|
29
|
+
|
30
|
+
def rows
|
31
|
+
@spaces.each_slice(@row_size).to_a
|
32
|
+
end
|
33
|
+
|
34
|
+
def columns
|
35
|
+
rows.transpose
|
36
|
+
end
|
37
|
+
|
38
|
+
def diagonals
|
39
|
+
back_diagonal, front_diagonal = [], []
|
40
|
+
|
41
|
+
rows.each_with_index do |row, index|
|
42
|
+
back_diagonal << row[index]
|
43
|
+
front_diagonal << row[@row_size - (index + 1)]
|
44
|
+
end
|
45
|
+
|
46
|
+
[back_diagonal, front_diagonal]
|
47
|
+
end
|
48
|
+
|
49
|
+
def full?
|
50
|
+
@spaces.all? { |space| !space.nil? }
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def valid?(space)
|
56
|
+
space_empty = @spaces[space].nil?
|
57
|
+
board_range = 0..(@size - 1)
|
58
|
+
on_the_board = board_range.include? space
|
59
|
+
|
60
|
+
space_empty && on_the_board
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'tic_tac_toes/core/rules'
|
2
|
+
|
3
|
+
module TicTacToes
|
4
|
+
module Core
|
5
|
+
class GameState
|
6
|
+
attr_reader :board, :players
|
7
|
+
|
8
|
+
def initialize(board, players, history)
|
9
|
+
@board = board
|
10
|
+
@players = players
|
11
|
+
@history = history
|
12
|
+
@rules = Rules
|
13
|
+
|
14
|
+
@history.record_board_size(@board.size)
|
15
|
+
end
|
16
|
+
|
17
|
+
def current_player
|
18
|
+
@players.first
|
19
|
+
end
|
20
|
+
|
21
|
+
def turn_over(move)
|
22
|
+
@history.record_move(move)
|
23
|
+
@players.rotate!
|
24
|
+
end
|
25
|
+
|
26
|
+
def game_over(winner)
|
27
|
+
@history.record_winner(winner)
|
28
|
+
@history.persist
|
29
|
+
end
|
30
|
+
|
31
|
+
def game_over?
|
32
|
+
@rules.game_over?(@board, @players)
|
33
|
+
end
|
34
|
+
|
35
|
+
def determine_winner
|
36
|
+
@rules.determine_winner(@board, @players)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'tic_tac_toes/core/game_state'
|
2
|
+
|
3
|
+
module TicTacToes
|
4
|
+
module Core
|
5
|
+
class GameStateFactory
|
6
|
+
def initialize(history)
|
7
|
+
@history = history
|
8
|
+
end
|
9
|
+
|
10
|
+
def generate_game_state(board, players)
|
11
|
+
GameState.new(board, players, @history)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module TicTacToes
|
2
|
+
module Core
|
3
|
+
class History
|
4
|
+
attr_reader :board_size, :moves, :winner
|
5
|
+
|
6
|
+
def initialize(database_wrapper)
|
7
|
+
@database_wrapper = database_wrapper
|
8
|
+
end
|
9
|
+
|
10
|
+
def record_board_size(size)
|
11
|
+
@board_size = size
|
12
|
+
end
|
13
|
+
|
14
|
+
def record_move(move)
|
15
|
+
@moves ||= []
|
16
|
+
@moves << move
|
17
|
+
end
|
18
|
+
|
19
|
+
def record_winner(winner)
|
20
|
+
winner = "Draw" if winner.nil?
|
21
|
+
@winner = winner
|
22
|
+
end
|
23
|
+
|
24
|
+
def persist
|
25
|
+
@database_wrapper.record_game_history(self)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'tic_tac_toes/core/strings'
|
2
|
+
|
3
|
+
module TicTacToes
|
4
|
+
module Core
|
5
|
+
class IO
|
6
|
+
def initialize(io_strategy)
|
7
|
+
@io_strategy = io_strategy
|
8
|
+
end
|
9
|
+
|
10
|
+
def get_row_size
|
11
|
+
row_size_solicitation
|
12
|
+
|
13
|
+
Integer(@io_strategy.solicit_input)
|
14
|
+
rescue ArgumentError
|
15
|
+
not_an_integer_error
|
16
|
+
get_row_size
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_token(player)
|
20
|
+
token_solicitation(player)
|
21
|
+
@io_strategy.solicit_input
|
22
|
+
end
|
23
|
+
|
24
|
+
def get_difficulty
|
25
|
+
difficulty_solicitation
|
26
|
+
@io_strategy.solicit_input.downcase.to_sym
|
27
|
+
end
|
28
|
+
|
29
|
+
def draw_board(board)
|
30
|
+
@io_strategy.display(Strings.board(board))
|
31
|
+
end
|
32
|
+
|
33
|
+
def invalid_row_size_error
|
34
|
+
@io_strategy.display_red(Strings::INVALID_ROW_SIZE)
|
35
|
+
end
|
36
|
+
|
37
|
+
def invalid_token_error
|
38
|
+
@io_strategy.display_red(Strings::INVALID_TOKEN)
|
39
|
+
end
|
40
|
+
|
41
|
+
def invalid_difficulty_error
|
42
|
+
@io_strategy.display_red(Strings::INVALID_DIFFICULTY)
|
43
|
+
end
|
44
|
+
|
45
|
+
def invalid_move_error
|
46
|
+
@io_strategy.display_red(Strings::INVALID_MOVE)
|
47
|
+
end
|
48
|
+
|
49
|
+
def thinking_notification
|
50
|
+
@io_strategy.display_red(Strings::THINKING)
|
51
|
+
end
|
52
|
+
|
53
|
+
def game_over_notification(winner)
|
54
|
+
winner = "Nobody" if winner.nil?
|
55
|
+
@io_strategy.display(Strings.game_over_notification(winner))
|
56
|
+
end
|
57
|
+
|
58
|
+
def move_solicitation
|
59
|
+
@io_strategy.display(Strings::MOVE_SOLICITATION)
|
60
|
+
end
|
61
|
+
|
62
|
+
def not_an_integer_error
|
63
|
+
@io_strategy.display_red(Strings::NOT_AN_INTEGER)
|
64
|
+
end
|
65
|
+
|
66
|
+
def solicit_input
|
67
|
+
@io_strategy.solicit_input
|
68
|
+
end
|
69
|
+
|
70
|
+
def red(message)
|
71
|
+
@io_strategy.red(message)
|
72
|
+
end
|
73
|
+
|
74
|
+
def blue(message)
|
75
|
+
@io_strategy.blue(message)
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def row_size_solicitation
|
81
|
+
@io_strategy.display(Strings::ROW_SIZE_SOLICITATION)
|
82
|
+
end
|
83
|
+
|
84
|
+
def token_solicitation(player)
|
85
|
+
@io_strategy.display(Strings.token_solicitation(player))
|
86
|
+
end
|
87
|
+
|
88
|
+
def difficulty_solicitation
|
89
|
+
@io_strategy.display(Strings::DIFFICULTY_SOLICITATION)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'tic_tac_toes/core/rules'
|
2
|
+
|
3
|
+
module TicTacToes
|
4
|
+
module Core
|
5
|
+
module MoveStrategies
|
6
|
+
module HardAI
|
7
|
+
def self.move(board, players)
|
8
|
+
return second_move(board) if nine_board_second_move?(board)
|
9
|
+
|
10
|
+
open_spaces = Hash[board.open_spaces.map { |space| [space, nil] }]
|
11
|
+
|
12
|
+
open_spaces.each do |space, score|
|
13
|
+
score = minimax(generate_board(players.first, space, board), :min, players)
|
14
|
+
open_spaces[space] = score
|
15
|
+
end
|
16
|
+
|
17
|
+
best_score = open_spaces.values.max
|
18
|
+
open_spaces.each { |space, score| return space if score == best_score }
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def self.minimax(board, current_player, players)
|
24
|
+
return score(board, players) if Rules.game_over?(board, players)
|
25
|
+
|
26
|
+
if current_player == :max
|
27
|
+
best_score = -1
|
28
|
+
board.open_spaces.each do |space|
|
29
|
+
score = minimax(generate_board(players.first, space, board), :min, players)
|
30
|
+
best_score = [best_score, score].max
|
31
|
+
end
|
32
|
+
best_score
|
33
|
+
|
34
|
+
elsif current_player == :min
|
35
|
+
best_score = 1
|
36
|
+
board.open_spaces.each do |space|
|
37
|
+
score = minimax(generate_board(players.last, space, board), :max, players)
|
38
|
+
best_score = [best_score, score].min
|
39
|
+
end
|
40
|
+
best_score
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.generate_board(player, space, board)
|
45
|
+
new_board = Marshal.load(Marshal.dump(board))
|
46
|
+
new_board.place(player, space)
|
47
|
+
new_board
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.score(board, players)
|
51
|
+
own_token, opponent_token = players.first.token, players.last.token
|
52
|
+
|
53
|
+
case Rules.determine_winner(board, players)
|
54
|
+
when own_token
|
55
|
+
1
|
56
|
+
when opponent_token
|
57
|
+
-1
|
58
|
+
else
|
59
|
+
0
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.nine_board_second_move?(board)
|
64
|
+
board.size == 9 && board.open_spaces.count == 8
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.second_move(board)
|
68
|
+
if nine_board_corner_occupied(board) || nine_board_side_occupied(board)
|
69
|
+
4
|
70
|
+
elsif nine_board_center_occupied(board)
|
71
|
+
0
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.nine_board_corner_occupied(board)
|
76
|
+
board.space(0) || board.space(2) || board.space(6) || board.space(8)
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.nine_board_side_occupied(board)
|
80
|
+
board.space(1) || board.space(3) || board.space(5) || board.space(7)
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.nine_board_center_occupied(board)
|
84
|
+
board.space(4) ? true : false
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module TicTacToes
|
2
|
+
module Core
|
3
|
+
module MoveStrategies
|
4
|
+
class Human
|
5
|
+
def initialize(io)
|
6
|
+
@io = io
|
7
|
+
end
|
8
|
+
|
9
|
+
def move(board, players)
|
10
|
+
@io.move_solicitation
|
11
|
+
|
12
|
+
Integer(@io.solicit_input)
|
13
|
+
rescue ArgumentError
|
14
|
+
@io.not_an_integer_error
|
15
|
+
move(board, players)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'tic_tac_toes/core/move_strategies/easy_ai'
|
2
|
+
require 'tic_tac_toes/core/move_strategies/hard_ai'
|
3
|
+
|
4
|
+
module TicTacToes
|
5
|
+
module Core
|
6
|
+
module MoveStrategies
|
7
|
+
module MediumAI
|
8
|
+
def self.move(board, players)
|
9
|
+
move_strategy = [EasyAI, HardAI, HardAI].sample
|
10
|
+
move_strategy.move(board, players)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module TicTacToes
|
2
|
+
module Core
|
3
|
+
class Player
|
4
|
+
attr_reader :move_strategy, :token, :needs_to_think
|
5
|
+
|
6
|
+
def initialize(move_strategy, token, needs_to_think, io)
|
7
|
+
@move_strategy = move_strategy
|
8
|
+
@token = token
|
9
|
+
@needs_to_think = needs_to_think
|
10
|
+
@io = io
|
11
|
+
end
|
12
|
+
|
13
|
+
def place_and_return_move(board, players)
|
14
|
+
loop do
|
15
|
+
space = @move_strategy.move(board, players)
|
16
|
+
break [@token, space] if board.place(self, space)
|
17
|
+
@io.invalid_move_error
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'tic_tac_toes/core/move_strategies/human'
|
2
|
+
require 'tic_tac_toes/core/move_strategies/easy_ai'
|
3
|
+
require 'tic_tac_toes/core/move_strategies/medium_ai'
|
4
|
+
require 'tic_tac_toes/core/move_strategies/hard_ai'
|
5
|
+
require 'tic_tac_toes/core/player'
|
6
|
+
|
7
|
+
module TicTacToes
|
8
|
+
module Core
|
9
|
+
class PlayerFactory
|
10
|
+
def initialize(io)
|
11
|
+
@io = io
|
12
|
+
end
|
13
|
+
|
14
|
+
AIS = {
|
15
|
+
easy: ::TicTacToes::Core::MoveStrategies::EasyAI,
|
16
|
+
medium: ::TicTacToes::Core::MoveStrategies::MediumAI,
|
17
|
+
hard: ::TicTacToes::Core::MoveStrategies::HardAI }
|
18
|
+
|
19
|
+
def generate_human_player(token)
|
20
|
+
needs_to_think = false
|
21
|
+
Player.new(TicTacToes::Core::MoveStrategies::Human.new(@io), token, needs_to_think, @io)
|
22
|
+
end
|
23
|
+
|
24
|
+
def generate_computer_player(token, difficulty)
|
25
|
+
needs_to_think = true
|
26
|
+
Player.new(AIS[difficulty], token, needs_to_think, @io)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|