connect_n 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|