tic_tac_toes 0.0.7 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- 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
|