connect_n 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/connect_n/board/board.rb +92 -0
- data/lib/connect_n/demo/demo.rb +138 -0
- data/lib/connect_n/game/game.rb +116 -0
- data/lib/connect_n/player/computer_player/computer_player.rb +124 -0
- data/lib/connect_n/player/human_player/human_player.rb +21 -0
- data/lib/connect_n/player/player.rb +12 -0
- data/lib/connect_n/prompt/prompt.rb +7 -0
- data/lib/connect_n/winnable/winnable.rb +66 -0
- data/lib/connect_n.rb +10 -0
- metadata +81 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f576942d7bcaf43312e72ace215cb8f92fea00e6f615387151bd2f674c826267
|
4
|
+
data.tar.gz: 947c671cb8af51ca156c2fdd3d58d66dae4c0db3994fd4e82f91e3aa13dbb0b4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7ecfb976380fd669a3094a7825e91ed3d312a7ce557c84e6eaca4d2d6b5bfbb67df35357fcfbdfe943ee45b8c1f338f74fc26d01ed5d9c62963ca383e5a43062
|
7
|
+
data.tar.gz: 79c4840a51f839a87a0e8aaa9111e5dc7702628cddfca2c5fb93f255b9b144bd88dfe5f13be2f0ca73121b0fc1a9a49ec5c0a68a62216f06f0d0f48d52ddd089
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ConnectN
|
4
|
+
class Board
|
5
|
+
attr_reader :table, :empty_disc, :cols_amount, :rows_amount
|
6
|
+
|
7
|
+
def initialize(rows_amount: 6, cols_amount: 7, empty_disc: '⚪')
|
8
|
+
@empty_disc = empty_disc
|
9
|
+
|
10
|
+
@rows_amount = rows_amount
|
11
|
+
|
12
|
+
@cols_amount = cols_amount
|
13
|
+
|
14
|
+
@table = Array.new(rows_amount) { Array.new(cols_amount) { empty_disc } }
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize_copy(original_board)
|
18
|
+
super
|
19
|
+
@empty_disc = @empty_disc.clone
|
20
|
+
@table = @table.clone.map(&:clone)
|
21
|
+
end
|
22
|
+
|
23
|
+
def drop_disc(disc, at_col:)
|
24
|
+
row_num = col_at(at_col).index(empty_disc)
|
25
|
+
row_at(row_num)[at_col] = disc
|
26
|
+
[row_num, at_col, disc]
|
27
|
+
end
|
28
|
+
|
29
|
+
def valid_pick?(pick)
|
30
|
+
valid_col?(pick) and col_at(pick).include?(empty_disc)
|
31
|
+
end
|
32
|
+
|
33
|
+
def filled?
|
34
|
+
!table.flatten.include?(empty_disc)
|
35
|
+
end
|
36
|
+
|
37
|
+
def draw
|
38
|
+
table.each{ |row| draw_border || draw_row(row) }
|
39
|
+
draw_border
|
40
|
+
draw_col_nums
|
41
|
+
end
|
42
|
+
|
43
|
+
def cell_at(row_num, col_num)
|
44
|
+
return unless valid_cell?(row_num, col_num)
|
45
|
+
|
46
|
+
row_at(row_num)[col_num]
|
47
|
+
end
|
48
|
+
|
49
|
+
def cols = table.transpose.map(&:reverse)
|
50
|
+
|
51
|
+
def rows = table
|
52
|
+
|
53
|
+
def col_at(n)
|
54
|
+
return unless valid_col?(n)
|
55
|
+
|
56
|
+
table.transpose[n].reverse
|
57
|
+
end
|
58
|
+
|
59
|
+
def row_at(n)
|
60
|
+
return unless valid_row?(n)
|
61
|
+
|
62
|
+
n = rows_amount - 1 - n
|
63
|
+
table[n]
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def draw_border
|
69
|
+
cols_amount.times { print '+----' } and puts '+'
|
70
|
+
end
|
71
|
+
|
72
|
+
def draw_row(row)
|
73
|
+
puts '| ' + row.join(' | ') + ' |'
|
74
|
+
end
|
75
|
+
|
76
|
+
def draw_col_nums
|
77
|
+
print '|'
|
78
|
+
(1..cols_amount).each do |num|
|
79
|
+
num.even? ? print(' ') : print(' ')
|
80
|
+
print num
|
81
|
+
num.odd? ? print(' |') : print(' |')
|
82
|
+
end
|
83
|
+
puts
|
84
|
+
end
|
85
|
+
|
86
|
+
def valid_cell?(row_num, col_num) = valid_row?(row_num) && valid_col?(col_num)
|
87
|
+
|
88
|
+
def valid_row?(n) = n.between?(0, rows_amount - 1)
|
89
|
+
|
90
|
+
def valid_col?(n) = n.between?(0, cols_amount - 1)
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../player/human_player/human_player'
|
4
|
+
require_relative '../player/computer_player/computer_player'
|
5
|
+
require_relative '../game/game'
|
6
|
+
require_relative '../board/board'
|
7
|
+
|
8
|
+
module ConnectN
|
9
|
+
class Demo
|
10
|
+
attr_reader :parameters, :game
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@parameters = { human_players: [] }
|
14
|
+
end
|
15
|
+
|
16
|
+
def launch
|
17
|
+
if !Game.games('connect_n_saved_games.yaml').empty? && Game.resume?
|
18
|
+
game_name = Game.select_game_name
|
19
|
+
@game = Game.load game_name, 'connect_n_saved_games.yaml'
|
20
|
+
return Game.resume game
|
21
|
+
end
|
22
|
+
|
23
|
+
setup_parameters
|
24
|
+
@game = if parameters[:mode] == 'multiplayer'
|
25
|
+
multiplayer_game
|
26
|
+
else
|
27
|
+
single_player_game
|
28
|
+
end
|
29
|
+
game.play('connect_n_saved_games.yaml')
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def setup_parameters
|
35
|
+
parameters[:human_players].push [human_name, disc]
|
36
|
+
|
37
|
+
parameters[:cols_amount] = cols_amount
|
38
|
+
parameters[:rows_amount] = rows_amount
|
39
|
+
parameters[:min_to_win] = min_to_win
|
40
|
+
|
41
|
+
parameters[:mode] = mode
|
42
|
+
|
43
|
+
if parameters[:mode] == 'multiplayer'
|
44
|
+
parameters[:human_players].push [human_name => disc]
|
45
|
+
else
|
46
|
+
parameters[:difficulty] = difficulty
|
47
|
+
parameters[:human_starts?] = human_starts?
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def cols_amount
|
52
|
+
PROMPT.ask 'How many columns do you want in the board?', convert: :int, default: 7
|
53
|
+
end
|
54
|
+
|
55
|
+
def rows_amount
|
56
|
+
PROMPT.ask 'How many rows do you want in the board?', convert: :int, default: 6
|
57
|
+
end
|
58
|
+
|
59
|
+
def min_to_win
|
60
|
+
PROMPT.ask(
|
61
|
+
'Minimum number of aligned similar discs necessary to win : ',
|
62
|
+
convert: :int,
|
63
|
+
default: 4
|
64
|
+
)
|
65
|
+
end
|
66
|
+
|
67
|
+
def difficulty
|
68
|
+
PROMPT.slider 'Difficulty : ', [*0..10], default: 0
|
69
|
+
end
|
70
|
+
|
71
|
+
def mode
|
72
|
+
PROMPT.select('Choose a game mode : ', ['Single Player', 'Multiplayer']).downcase
|
73
|
+
end
|
74
|
+
|
75
|
+
def human_starts?
|
76
|
+
PROMPT.yes? 'Do you wanna play first?'
|
77
|
+
end
|
78
|
+
|
79
|
+
def disc
|
80
|
+
PROMPT.ask 'Enter a character that will represent your disc : ', default: '🔥' do |q|
|
81
|
+
q.validate(/^.?$/)
|
82
|
+
q.messages[:valid?] = 'Please enter a single character.'
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def human_name
|
87
|
+
PROMPT.ask 'Enter your name : ', default: ENV['USER']
|
88
|
+
end
|
89
|
+
|
90
|
+
def multiplayer_game
|
91
|
+
human_players = multiplayer_players
|
92
|
+
board = Board.new(
|
93
|
+
cols_amount: parameters[:cols_amount],
|
94
|
+
rows_amount: parameters[:rows_amount]
|
95
|
+
)
|
96
|
+
Game.new(
|
97
|
+
board: board,
|
98
|
+
first_player: players.first,
|
99
|
+
second_player: players.last,
|
100
|
+
min_to_win: parameters[:min_to_win]
|
101
|
+
)
|
102
|
+
end
|
103
|
+
|
104
|
+
def single_player_game
|
105
|
+
board = Board.new(
|
106
|
+
cols_amount: parameters[:cols_amount],
|
107
|
+
rows_amount: parameters[:rows_amount]
|
108
|
+
)
|
109
|
+
players = single_player_players(board)
|
110
|
+
Game.new(
|
111
|
+
board: board,
|
112
|
+
first_player: players.first,
|
113
|
+
second_player: players.last,
|
114
|
+
min_to_win: parameters[:min_to_win]
|
115
|
+
)
|
116
|
+
end
|
117
|
+
|
118
|
+
def multiplayer_players
|
119
|
+
parameters[:human_players].map do |name, disc|
|
120
|
+
HumanPlayer.new(name: name, disc: disc)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def single_player_players(board)
|
125
|
+
name, disc = parameters[:human_players].first
|
126
|
+
players = [
|
127
|
+
HumanPlayer.new(name: name, disc: disc),
|
128
|
+
ComputerPlayer.new(
|
129
|
+
board: board,
|
130
|
+
opponent_disc: disc,
|
131
|
+
min_to_win: parameters[:min_to_win],
|
132
|
+
difficulty: parameters[:difficulty]
|
133
|
+
)
|
134
|
+
]
|
135
|
+
parameters[:human_starts?] ? players : players.rotate
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'tty-box'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
require_relative '../player/human_player/human_player'
|
7
|
+
require_relative '../player/computer_player/computer_player'
|
8
|
+
require_relative '../board/board'
|
9
|
+
require_relative '../winnable/winnable'
|
10
|
+
|
11
|
+
module ConnectN
|
12
|
+
class Game
|
13
|
+
include Winnable
|
14
|
+
|
15
|
+
attr_reader :board, :players, :min_to_win
|
16
|
+
|
17
|
+
PERMITTED_CLASSES = [Symbol, Game, Board, HumanPlayer, ComputerPlayer]
|
18
|
+
|
19
|
+
def initialize(
|
20
|
+
board:,
|
21
|
+
first_player:,
|
22
|
+
second_player:,
|
23
|
+
min_to_win: 4
|
24
|
+
)
|
25
|
+
@board = board
|
26
|
+
@players = [first_player, second_player]
|
27
|
+
@min_to_win = min_to_win
|
28
|
+
end
|
29
|
+
|
30
|
+
def play(yaml_fn = nil)
|
31
|
+
welcome
|
32
|
+
loop do
|
33
|
+
current_player = players.first
|
34
|
+
|
35
|
+
pick = current_player.pick
|
36
|
+
|
37
|
+
break self.class.save(self, self.class.name_game, yaml_fn) if self.class.save? pick
|
38
|
+
|
39
|
+
next invalid_pick unless board.valid_pick? pick
|
40
|
+
|
41
|
+
row_num, col_num, disc = board.drop_disc(current_player.disc, at_col: pick)
|
42
|
+
|
43
|
+
clear_display
|
44
|
+
board.draw
|
45
|
+
|
46
|
+
break over(current_player) if over?(board, row_num, col_num, disc)
|
47
|
+
|
48
|
+
players.rotate!
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def over?(board, row, col, disc) = win?(board, row, col, disc) || board.filled?
|
53
|
+
|
54
|
+
def play_again? = PROMPT.yes? 'Would you like to play again?'
|
55
|
+
|
56
|
+
def self.save?(input) = input == ':w'
|
57
|
+
|
58
|
+
def self.resume? = PROMPT.yes? 'Do you want to resume a game?'
|
59
|
+
|
60
|
+
def self.resume(game) = game.play
|
61
|
+
|
62
|
+
def self.load(name, yaml_fn) = games(yaml_fn)[name.to_sym]
|
63
|
+
|
64
|
+
def self.games(yaml_fn)
|
65
|
+
YAML.safe_load_file(
|
66
|
+
yaml_fn,
|
67
|
+
permitted_classes: PERMITTED_CLASSES,
|
68
|
+
aliases: true
|
69
|
+
) || {}
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.save(game, name, yaml_fn)
|
73
|
+
games = games(yaml_fn)
|
74
|
+
games[name.to_sym] = game
|
75
|
+
dumped_games = YAML.dump(games)
|
76
|
+
File.write(yaml_fn, dumped_games)
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.name_game = PROMPT.ask 'Name your game : '
|
80
|
+
|
81
|
+
def self.select_game_name(yaml_fn)
|
82
|
+
games = games(yaml_fn).keys.map.with_index(1) { "#{_2} -> #{_1}" }
|
83
|
+
PROMPT.select 'Choose a saved game : ', games, convert: :sym
|
84
|
+
end
|
85
|
+
|
86
|
+
def welcome
|
87
|
+
text = <<~TEXT
|
88
|
+
Welcome to Connect Four
|
89
|
+
#{' '}
|
90
|
+
To play, Enter a number from 1
|
91
|
+
to #{board.cols_amount}
|
92
|
+
The number corresponds to the
|
93
|
+
column order starting from the
|
94
|
+
left.
|
95
|
+
Enter anything to proceed.
|
96
|
+
TEXT
|
97
|
+
puts TTY::Box.frame text, padding: 2, align: :center
|
98
|
+
board.draw
|
99
|
+
end
|
100
|
+
|
101
|
+
def invalid_pick
|
102
|
+
PROMPT.error 'Invalid Column Number'
|
103
|
+
end
|
104
|
+
|
105
|
+
def over(winner)
|
106
|
+
phrase = board.filled? ? 'It is a tie!' : "#{winner.name} has won!"
|
107
|
+
puts TTY::Box.sucess(phrase)
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def clear_display
|
113
|
+
puts "\e[1;1H\e[2J"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../player'
|
4
|
+
require_relative '../../winnable/winnable'
|
5
|
+
|
6
|
+
module ConnectN
|
7
|
+
class ComputerPlayer < Player
|
8
|
+
include Winnable
|
9
|
+
|
10
|
+
attr_accessor :difficulty, :delay
|
11
|
+
attr_reader :opponent_disc, :min_to_win
|
12
|
+
|
13
|
+
def initialize(
|
14
|
+
name: 'Computer',
|
15
|
+
disc: '🧊',
|
16
|
+
min_to_win: 4,
|
17
|
+
difficulty: 0,
|
18
|
+
delay: 0,
|
19
|
+
board:,
|
20
|
+
opponent_disc:
|
21
|
+
)
|
22
|
+
super(name: name, disc: disc)
|
23
|
+
@min_to_win = min_to_win
|
24
|
+
@difficulty = difficulty
|
25
|
+
@delay = delay
|
26
|
+
@board = board
|
27
|
+
@opponent_disc = opponent_disc
|
28
|
+
@scores = { disc => 10000, opponent_disc => -10000 }
|
29
|
+
end
|
30
|
+
|
31
|
+
def pick
|
32
|
+
sleep delay
|
33
|
+
best_score = -Float::INFINITY
|
34
|
+
best_pick = nil
|
35
|
+
@board.cols_amount.times do |pick|
|
36
|
+
next unless @board.valid_pick?(pick)
|
37
|
+
|
38
|
+
board_copy = @board.clone
|
39
|
+
row_num, col_num = board_copy.drop_disc(disc, at_col: pick)
|
40
|
+
score = minimax(board_copy, disc, row_num, col_num)
|
41
|
+
if score >= best_score
|
42
|
+
best_score = score
|
43
|
+
best_pick = pick
|
44
|
+
end
|
45
|
+
end
|
46
|
+
best_pick
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
attr_reader :scores
|
52
|
+
|
53
|
+
def minimax(
|
54
|
+
current_board,
|
55
|
+
current_disc,
|
56
|
+
row_num,
|
57
|
+
col_num,
|
58
|
+
moves_counter = 0,
|
59
|
+
alpha = -Float::INFINITY,
|
60
|
+
beta = Float::INFINITY,
|
61
|
+
depth = difficulty,
|
62
|
+
maximizing: false
|
63
|
+
)
|
64
|
+
return calculate_win_score(current_disc, moves_counter) if win?(current_board, row_num, col_num, current_disc)
|
65
|
+
|
66
|
+
return 0 if current_board.filled?
|
67
|
+
|
68
|
+
return heuristic(current_board, current_disc) if depth <= 0
|
69
|
+
|
70
|
+
if maximizing
|
71
|
+
score = -Float::INFINITY
|
72
|
+
@board.cols_amount.times do |pick|
|
73
|
+
next unless current_board.valid_pick?(pick)
|
74
|
+
|
75
|
+
board_copy = current_board.clone
|
76
|
+
row_num, col_num = board_copy.drop_disc(disc, at_col: pick)
|
77
|
+
score = [
|
78
|
+
score,
|
79
|
+
minimax(board_copy, disc, row_num, col_num, moves_counter + 1, alpha, beta, depth - 1, maximizing: false)
|
80
|
+
].max
|
81
|
+
break if score >= beta
|
82
|
+
|
83
|
+
alpha = [alpha, score].max
|
84
|
+
end
|
85
|
+
else
|
86
|
+
score = Float::INFINITY
|
87
|
+
@board.cols_amount.times do |pick|
|
88
|
+
next unless current_board.valid_pick?(pick)
|
89
|
+
|
90
|
+
board_copy = current_board.clone
|
91
|
+
row_num, col_num = board_copy.drop_disc(opponent_disc, at_col: pick)
|
92
|
+
score = [
|
93
|
+
score,
|
94
|
+
minimax(board_copy, opponent_disc, row_num, col_num, moves_counter + 1, alpha, beta, depth - 1, maximizing: true)
|
95
|
+
].min
|
96
|
+
break if score <= alpha
|
97
|
+
|
98
|
+
beta = [beta, score].min
|
99
|
+
end
|
100
|
+
end
|
101
|
+
score
|
102
|
+
end
|
103
|
+
|
104
|
+
def calculate_win_score(disc, moves_counter)
|
105
|
+
score = scores[disc] * @board.cols_amount * @board.rows_amount
|
106
|
+
score / moves_counter.to_f
|
107
|
+
end
|
108
|
+
|
109
|
+
def heuristic(board, disc)
|
110
|
+
value = 0
|
111
|
+
board.rows.each do |row|
|
112
|
+
row.each_cons(min_to_win).each do |set_of_n|
|
113
|
+
disc_count = set_of_n.count disc
|
114
|
+
opponent_disc_count = set_of_n.count opponent_disc
|
115
|
+
next if [disc_count, opponent_disc_count].none?(&:zero?)
|
116
|
+
|
117
|
+
value += disc_count
|
118
|
+
value -= opponent_disc_count
|
119
|
+
end
|
120
|
+
end
|
121
|
+
value
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'tty-prompt'
|
4
|
+
|
5
|
+
require_relative '../player'
|
6
|
+
|
7
|
+
module ConnectN
|
8
|
+
class HumanPlayer < Player
|
9
|
+
attr_accessor :save_key
|
10
|
+
|
11
|
+
def initialize(name: 'Human', disc: '🔥', save_key: ':w')
|
12
|
+
@save_key = save_key
|
13
|
+
super name: name, disc: disc
|
14
|
+
end
|
15
|
+
|
16
|
+
def pick
|
17
|
+
input = PROMPT.ask('Please enter a column number : ')
|
18
|
+
input == save_key ? input : input.to_i - 1
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ConnectN
|
4
|
+
module Winnable
|
5
|
+
def win?(board, row_num, col_num, disc)
|
6
|
+
return true if vertical_win?(board, row_num, col_num, disc)
|
7
|
+
|
8
|
+
# k = -1 => backward diagonal \
|
9
|
+
# k = 0 => horizontal --
|
10
|
+
# k = 1 => forward diagonal /
|
11
|
+
(-1..1).any? do |k|
|
12
|
+
l = l_discs(board, row_num, col_num, disc, k)
|
13
|
+
r = r_discs(board, row_num, col_num, disc, k)
|
14
|
+
l + r + 1 >= min_to_win
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def vertical_win?(board, row_num, col_num, disc)
|
21
|
+
v = v_discs(board, row_num, col_num, disc)
|
22
|
+
v + 1 >= min_to_win
|
23
|
+
end
|
24
|
+
|
25
|
+
# Count similar discs on the left side
|
26
|
+
def l_discs(board, row_num, col_num, disc, k)
|
27
|
+
range = (1...min_to_win)
|
28
|
+
amount = range.find { |i| board.cell_at(row_num - k * i, col_num - i) != disc }
|
29
|
+
|
30
|
+
# amount can be nil if there are more similar discs than min_to_win
|
31
|
+
# in which case it should count as a win
|
32
|
+
amount ||= min_to_win
|
33
|
+
|
34
|
+
# Subtract the dropped disc
|
35
|
+
amount -= 1
|
36
|
+
end
|
37
|
+
|
38
|
+
# Count similar discs on the right side
|
39
|
+
def r_discs(board, row_num, col_num, disc, k)
|
40
|
+
range = (1...min_to_win)
|
41
|
+
amount = range.find { |i| board.cell_at(row_num + k * i, col_num + i) != disc }
|
42
|
+
|
43
|
+
# amount can be nil if there are more similar discs than min_to_win
|
44
|
+
# in which case it should count as a win
|
45
|
+
amount ||= min_to_win
|
46
|
+
|
47
|
+
# Subtract the dropped disc
|
48
|
+
amount -= 1
|
49
|
+
end
|
50
|
+
|
51
|
+
# Count similar discs forming a vertical line
|
52
|
+
def v_discs(board, row_num, col_num, disc)
|
53
|
+
range = (1...min_to_win)
|
54
|
+
amount = range.find do |i|
|
55
|
+
board.cell_at(row_num - i, col_num) != disc
|
56
|
+
end
|
57
|
+
|
58
|
+
# amount can be nil if there are more similar discs than min_to_win
|
59
|
+
# in which case it should count as a win
|
60
|
+
amount ||= min_to_win
|
61
|
+
|
62
|
+
# Subtract the dropped disc
|
63
|
+
amount -= 1
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/connect_n.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'connect_n/prompt/prompt'
|
4
|
+
require_relative 'connect_n/player/human_player/human_player'
|
5
|
+
require_relative 'connect_n/player/computer_player/computer_player'
|
6
|
+
require_relative 'connect_n/demo/demo'
|
7
|
+
require_relative 'connect_n/game/game'
|
8
|
+
require_relative 'connect_n/board/board'
|
9
|
+
|
10
|
+
module ConnectN; end
|
metadata
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: connect_n
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Lhoussaine (Jee-El) Ghallou
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-01-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: tty-prompt
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.23.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.23.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: tty-box
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.7.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.7.0
|
41
|
+
description: A more general version of connect-4 where you try to connect N similar
|
42
|
+
discs. It comes with several features and a friendly API that allows you to customize
|
43
|
+
the game however you want!
|
44
|
+
email: ''
|
45
|
+
executables: []
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- lib/connect_n.rb
|
50
|
+
- lib/connect_n/board/board.rb
|
51
|
+
- lib/connect_n/demo/demo.rb
|
52
|
+
- lib/connect_n/game/game.rb
|
53
|
+
- lib/connect_n/player/computer_player/computer_player.rb
|
54
|
+
- lib/connect_n/player/human_player/human_player.rb
|
55
|
+
- lib/connect_n/player/player.rb
|
56
|
+
- lib/connect_n/prompt/prompt.rb
|
57
|
+
- lib/connect_n/winnable/winnable.rb
|
58
|
+
homepage: https://rubygems.org/gems/connect_n
|
59
|
+
licenses:
|
60
|
+
- MIT
|
61
|
+
metadata: {}
|
62
|
+
post_install_message:
|
63
|
+
rdoc_options: []
|
64
|
+
require_paths:
|
65
|
+
- lib
|
66
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
requirements: []
|
77
|
+
rubygems_version: 3.4.1
|
78
|
+
signing_key:
|
79
|
+
specification_version: 4
|
80
|
+
summary: Connect-N!
|
81
|
+
test_files: []
|