Online Demo : https://replit.com/@Jee-El/connectn?v=1
Add this line to your application's Gemfile :
gem 'connect_n'
Then run :
bundle
Or install it directly by running :
gem install connect_n
- 1. What I learnt
- 2. Contributing
- 3. ComputerPlayer's mechanism
- 4. Documentation
-
How to test my project with RSpec.
-
How starting a project by writing tests first helps organize the project.
Contributions of any kind are more than welcome!
Feel free to open a github issue or a pull request if you come across any typos or bugs, or if you find some parts of the API confusing, or if you would like to suggest a new feature :D!
It is made of a combination of minimax algorithm, alpha-beta pruning, and the heuristic function that is explained here : https://identity.pub/2019/10/16/minimax-connect4.html
ConnectN::Board.new(rows_amount: 6, cols_amount: 7, empty_disc: '⚪' -> ConnectN::Board
Returns a Board
instance with the dimensions rows_amount x cols_amount
, with each cell containing the value of empty_disc
.
Notes :
-
rows_amount
&cols_amount
must be aFloat
/Integer
>= 0 -
empty_disc
can be any object.
default_board = ConnectN::Board.new #=> 6x7 board
board = ConnectN::Board.new rows_amount: 9, cols_amount: 9 #=> 9x9 board
board.cell_at(row_num, col_num) -> object or nil
Returns the board cell at coordinates (row_num, col_num).
If row_num
(or resp. col_num
) is not in the range 0..rows_amount-1
(or resp. 0..cols_amount-1
), returns nil
.
Notes :
- The 1st cell, at (0, 0), is the one in the bottom left corner.
board = ConnectN::Board.new
bottom_left_corner_cell = board.cell_at(0, 0) #=> '⚪'
board.col_at(n) -> Array or nil
Returns the board's nth
column.
If n
is not in the range 0..cols_amount-1
, returns nil
.
Notes :
-
The 1st column, at (0), is the one on the far left.
-
The column elements are ordered bottom-to-top.
board = ConnectN::Board.new
far_left_col = board.col_at(0) #=> ['⚪', '⚪', '⚪', '⚪', '⚪', '⚪']
board.cols -> 2D Array
Returns the board's columns.
Notes :
-
The 1st column is the one on the far left.
-
Each column's elements are ordered bottom-to-top.
board = ConnectN::Board.new
cols = board.cols
board.draw -> nil
Outputs the board in a table-format to stdout and returns nil
.
board = ConnectN::Board.new
board.draw
board.drop_disc(disc, at_col:) -> Array
Modifies self
by dropping disc
at the board's column number at_col
.
Returns an array of length 3 [row_num, col_num, disc]
.
The 1st 2 elements represent the coordinates of the cell at which the disc was dropped, the third element is the dropped disc.
disc
can by any object.
at_col
must be in the range 0..cols_amount-1
.
Notes :
- If the given
at_col
refers to a filled column, an exception is raised.
board = ConnectN::Board.new
board.drop_disc('🔥', at_col: 0) #=> [0, 0, '🔥']
board.filled? -> true or false
Returns true if the board is filled, returns false otherwise.
board = ConnectN::Board.new
board.filled? #=> false
board.row_at(n) -> Array or nil
Returns the board's nth
row.
If n
is not in the range 0..cols_amount-1
, returns nil
.
Notes :
-
The 1st row, at (0), is the one in the bottom of the board.
-
The row elements are ordered left-to-right.
board = ConnectN::Board.new
bottom_row = board.row_at(0) #=> ['⚪', '⚪', '⚪', '⚪', '⚪', '⚪', '⚪']
board.rows -> 2D Array
Returns the board's rows, self
is not modified.
Notes :
-
The 1st row is the one in the bottom of the board.
-
Each row's elements are ordered left-to-right.
board = ConnectN::Board.new
rows = board.rows
board.valid_pick?(pick) -> true or false
Returns true
if pick
is a valid column number, i.e the column is not filled nor outside of the range 0..cols_amount-1
. self
is not modified.
board = ConnectN::Board.new
board.valid_pick?(3) #=> true
Notes :
-
Demo
's purpose is to show all features of the gem and to give you an idea on how you could use it to build your own custom connect_n game. -
You need to create a yaml file called
connect_n_saved_games.yaml
in the directory where you runDemo#launch
.
ConnectN::Demo.new -> ConnectN::Demo
demo.launch(yaml_fn) -> nil
Runs a game demo.
Notes :
yaml_fn
must exist and be a yaml file.
demo = ConnectN::Demo.new
demo.launch('saved_games.yaml)
ConnectN::Game.games(yaml_fn) -> Hash
Returns a deserialized hash from the given yaml_fn
whose keys are symbols representing the names of the saved games, while values are the corresponding game instances.
Notes :
yaml_fn
must be a YAML file.
ConnectN::Game.games('empty.yaml') #=> {}
ConnectN::Game.games('not_empty.yaml') #=> { test: game_obj }
ConnectN::Game.select_game_name(yaml_fn) -> Symbol
Lists the games saved in yaml_fn
for the user to select one & returns the selected game name as a symbol.
Notes :
yaml_fn
must contain at least one saved game, otherwise an exception is raised.
ConnectN::Game.select_game_name('empty.yaml') # Exception is raised
ConnectN::Game.select_game_name('not_empty.yaml') #=> works as intended
ConnectN::Game.load(name, yaml_fn) -> ConnectN::Game or nil
Returns the ConnectN::Game
instance named name
in yaml_fn
.
Returns nil if no such game exists.
Notes :
name
must be a String or Symbol
ConnectN::Game.load('test', 'my_games.yaml')
ConnectN::Game.load(:test, 'my_games.yaml')
ConnectN::Game.new(board:, first_player:, second_player:, min_to_win:) -> ConnectN::Game
Notes :
-
board
must be aConnectN::Board
instance. -
first_player
&second_player
can be instances ofPlayer
,HumanPlayer
, orComputerPlayer
. -
min_to_win
is the minimum number of connected similar discs to count as a win. Must be a positiveInteger
.
board = ConnectN::Board.new
player_1 = ConnectN::HumanPlayer.new(disc: 'A')
player_2 = ConnectN::HumanPlayer.new(disc: 'B')
game = ConnectN::Game.new(
board: board,
first_player: player_1,
second_player: player_2,
)
ConnectN::Game.resume(game) -> nil
Resumes the given game
and returns nil
.
ConnectN::Game.resume? -> true or false
Returns true
if the user wants to resume a saved game.
Returns false
otherwise.
ConnectN::Game.save(game, name, yaml_fn) -> Integer
Serializes the given game
as a Hash
of key name
& value game
to the given yaml_fn
.
Notes :
-
game
:ConnectN::Game
instance. -
name
:String
orSymbol
ConnectN::Game.save? -> true or false
Returns true
if the user wants to save the game.
Returns false
otherwise.
game.invalid_pick -> nil
Outputs the error message 'Invalid Column Number' on a red box to stdout.
game.play(yaml_fn = nil) -> nil
Starts the game.
Pass yaml_fn
if you intend to save the game in the middle of playing it.
board = ConnectN::Board.new
first_player = ConnectN::HumanPlayer.new
second_player = ConnectN::HumanPlayer.new
game = ConnectN::Game.new(
board: board,
first_player: first_player,
second_player: second_player,
)
game.play('my_games.yaml')
game.play_again? -> true or false
Returns true
if the user wants to play the game again.
Returns false
otherwise.
game.over(winner) -> nil
Outputs the winner's name on a green box to stdout.
If there is no winner, it similarly announces a tie.
game.over? -> true or false
Returns true
if a player has won or if it is a draw.
Returns false
otherwise.
game.welcome -> nil
Outputs, to stdout, a friendly message that explains the game to the user.
ConnectN::Player.new(name:, disc:) -> ConnectN::Player
Creates a Player
instance with the name name
and disc disc
.
Notes :
-
name
can be any object, aSymbol
orString
makes more sense, though. -
disc
can be any object, aSymbol
orString
makes more sense, though. -
Both
names
&disc
can be reassigned after creation.
ConnectN::HumanPlayer.new(name: 'Human', disc: '🔥', save_key: ':w') -> ConnectN::HumanPlayer
Notes :
-
To understand the use of
save_key
, see next section. -
HumanPlayer
inherits fromConnectN::Player
. -
The only difference between
Player::new
andHumanPlayer::new
are the default values.
human_player.pick -> Object
Prompts the user to enter a pick, i.e a column number.
If the value of human_player.save_key
is entered by the user, it is recognized as the user wanting to save the game. For example, it is used in Game#play
.
Returns the value of human_player.save_key
if the user wants to save the game.
Returns the Integer
entered by the user, minus one.
human_player = ConnectN::HumanPlayer.new
# User enters ':w'
human_player.pick #=> :w
# User enters '5'
human_player.pick #=> 4
# User enters 'random string'
human_player.pick #=> -1
ConnectN::ComputerPlayer.new(
name: 'Computer',
disc: '🧊',
min_to_win: 4,
difficulty: 0,
delay: 0,
board:,
opponent_disc:
) -> ConnectN::ComputerPlayer
-
min_to_win
: Must be the samemin_to_win
of theConnectN::Game
instance. -
difficulty
: anInteger
greater than or equal to 0. The higher it is, the harder it is to beat the computer. -
delay
: a/anFloat
/Integer
of how many seconds theConnectN::ComputerPlayer
instance should take to play its pick. -
board
: Must be the sameConnectN::Board
instance given to theConnectN::Game
instance. -
opponent_disc
: Must be thedisc
of the opponent player.
board = ConnectN::Board.new
player_1 = ConnectN::ComputerPlayer.new(
difficulty: 4,
delay: 2,
board: board,
opponent_disc: '🔥'
)
player_2 = ConnectN::ComputerPlayer.new(
difficulty: 4,
delay: 2,
board: board,
opponent_disc: player_1.disc
)
game = ConnectN::Game.new(
board: board,
first_player: player_1,
second_player: player_2,
)
game.play
computer_player.pick -> Integer
Returns a valid column number, i.e in the range 0..cols_amount-1
.
See this for more info on how it works.
ConnectN::Prompt.ask_for_cols_amount(
prompt: 'How many columns do you want in the board?',
default: 7
) -> Integer
Prompts the user, with a prompt consisting of the value of prompt
, to enter the amount of columns to be in the board.
default
's value is returned if the user does not enter any input and presses the enter key.
ConnectN::Prompt.ask_for_difficulty(prompt: 'Difficulty : ', levels: [*0..10], default: 5) -> Integer
Prompts the user, with a prompt consisting of the value of prompt
, followed by a slider that the user can slide to choose a difficulty level of the levels in levels
.
default
is the initial value of the slider.
ConnectN::Prompt.ask_for_disc(
prompt: 'Enter a character that will represent your disc : ',
default: '🔥',
error_msg: 'Please enter a single character.'
) -> String
Prompts the user, with a prompt consisting of the value of prompt
, to enter the character that will represent their disc.
default
's value is returned if the user does not enter any input and presses the enter key.
error_msg
is the error message displayed in case the user does not enter a single character.
ConnectN::Prompt.ask_for_min_to_win(
prompt: 'Minimum number of aligned similar discs necessary to win : ',
default: 4
) -> Integer
Prompts the user, with a prompt consisting of the value of prompt
, to enter the minimum amount of aligned discs that will count as a win.
default
's value is returned if the user does not enter any input and presses the enter key.
ConnectN::Prompt.ask_for_mode(prompt: 'Choose a game mode : ')
Prompts the user with a prompt consisting of the value of prompt
, followed by a select menu that has two options :
-
Single Player
-
Multiplayer
ConnectN::Prompt.ask_for_name(prompt: 'Enter your name : ', default: ENV['USER']) -> String
Prompts the user with a prompt consisting of the value of prompt
.
default
's value is returned if the user presses enter without entering anything.
ConnectN::Prompt.ask_for_pick(prompt: 'Please enter a column number : ') -> String
Prompts the user with a prompt consisting of the value of prompt
.
ConnectN::Prompt.ask_for_rows_amount(
prompt: 'How many rows do you want in the board?',
default: 6
) -> Integer
Same as ConnectN::Prompt.ask_for_cols_amount
but with different default values.
ConnectN::Prompt.starts?(prompt: 'Do you wanna play first?') -> true or false
Prompts the user with a yes/no prompt consisting of the value of prompt
.
true
is the default value returned if the user presses enter without entering anything.
win?(board, row_num, col_num, disc) -> true or false
Returns true if disc
getting dropped at the cell (row_num, col_num)
of board
makes a win.
Returns false otherwise.