connect_n gem
Online Demo : https://replit.com/@Jee-El/connectn?v=1
Installation
Add this line to your application's Gemfile :
gem 'connect_n'Then run :
bundle
Or install it directly by running :
gem install connect_n
Contents
- 1. What I learnt
- 2. Contributing
- 3. ComputerPlayer's mechanism
- 4. Documentation
- 4.1 Board
- 4.1.1 ::new
- 4.1.2 #cell_at
- 4.1.3 #col_at
- 4.1.4 #cols
- 4.1.5 #draw
- 4.1.6 #drop_disc
- 4.1.7 #filled?
- 4.1.8 #row
- 4.1.9 #rows
- 4.1.10 #valid_pick?
- 4.2 Demo
- 4.2.1 ::new
- 4.2.2 #launch
- 4.3 Game
- 4.3.1 ::games
- 4.3.2 ::select_game_name
- 4.3.3 ::load
- 4.3.4 ::new
- 4.3.5 ::resume
- 4.3.6 ::resume?
- 4.3.7 ::save
- 4.3.8 ::save?
- 4.3.9 #invalid_pick
- 4.3.10 #play
- 4.3.11 #play_again?
- 4.3.12 #over
- 4.3.13 #over?
- 4.3.14 #welcome
- 4.4 Player
- 4.4.1 ::new
- 4.5 HumanPlayer
- 4.5.1 ::new
- 4.5.2 #pick
- 4.6 ComputerPlayer
- 4.6.1 ::new
- 4.6.2 #pick
- 4.7 Prompt
- 4.7.1 ::ask_for_cols_amount
- 4.7.2 ::ask_for_difficulty
- 4.7.3 ::ask_for_disc
- 4.7.4 ::ask_for_min_to_win
- 4.7.5 ::ask_for_mode
- 4.7.6 ::ask_for_name
- 4.7.7 ::ask_for_pick
- 4.7.8 ::ask_for_rows_amount
- 4.7.9 ::starts?
- 4.8 Winnable
- 4.8.1 #win?
- 4.1 Board
1. What I learnt
-
How to test my project with RSpec.
-
How starting a project by writing tests first helps organize the project.
2. Contributing
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!
3. ComputerPlayer's mechanism
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
4. Documentation
4.1 Board
4.1.1 ::new
ConnectN::Board.new(rows_amount: 6, cols_amount: 7, empty_disc: '⚪' -> ConnectN::BoardReturns a Board instance with the dimensions rows_amount x cols_amount, with each cell containing the value of empty_disc.
Notes :
-
rows_amount&cols_amountmust be aFloat/Integer>= 0 -
empty_disccan be any object.
default_board = ConnectN::Board.new #=> 6x7 board
board = ConnectN::Board.new rows_amount: 9, cols_amount: 9 #=> 9x9 board4.1.2 #cell_at
board.cell_at(row_num, col_num) -> object or nilReturns 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) #=> '⚪'4.1.3 #col_at
board.col_at(n) -> Array or nilReturns 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) #=> ['⚪', '⚪', '⚪', '⚪', '⚪', '⚪']4.1.4 #cols
board.cols -> 2D ArrayReturns 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.cols4.1.5 #draw
board.draw -> nilOutputs the board in a table-format to stdout and returns nil.
board = ConnectN::Board.new
board.draw4.1.6 #drop_disc
board.drop_disc(disc, at_col:) -> ArrayModifies 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_colrefers to a filled column, an exception is raised.
board = ConnectN::Board.new
board.drop_disc('🔥', at_col: 0) #=> [0, 0, '🔥']4.1.7 #filled?
board.filled? -> true or falseReturns true if the board is filled, returns false otherwise.
board = ConnectN::Board.new
board.filled? #=> false4.1.8 #row_at
board.row_at(n) -> Array or nilReturns 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) #=> ['⚪', '⚪', '⚪', '⚪', '⚪', '⚪', '⚪']4.1.9 #rows
board.rows -> 2D ArrayReturns 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.rows4.1.10 #valid_pick?
board.valid_pick?(pick) -> true or falseReturns 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) #=> true4.2 Demo
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.yamlin the directory where you runDemo#launch.
4.2.1 ::new
ConnectN::Demo.new -> ConnectN::Demo4.2.2 #launch
demo.launch(yaml_fn) -> nilRuns a game demo.
Notes :
-
yaml_fnmust exist and be a yaml file.
demo = ConnectN::Demo.new
demo.launch('saved_games.yaml)4.3 Game
4.3.1 ::games
ConnectN::Game.games(yaml_fn) -> HashReturns 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_fnmust be a YAML file.
ConnectN::Game.games('empty.yaml') #=> {}
ConnectN::Game.games('not_empty.yaml') #=> { test: game_obj }4.3.2 ::select_game_name
ConnectN::Game.select_game_name(yaml_fn) -> SymbolLists the games saved in yaml_fn for the user to select one & returns the selected game name as a symbol.
Notes :
-
yaml_fnmust 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 intended4.3.3 ::load
ConnectN::Game.load(name, yaml_fn) -> ConnectN::Game or nilReturns the ConnectN::Game instance named name in yaml_fn.
Returns nil if no such game exists.
Notes :
-
namemust be a String or Symbol
ConnectN::Game.load('test', 'my_games.yaml')
ConnectN::Game.load(:test, 'my_games.yaml')4.3.4 ::new
ConnectN::Game.new(board:, first_player:, second_player:, min_to_win:) -> ConnectN::GameNotes :
-
boardmust be aConnectN::Boardinstance. -
first_player&second_playercan be instances ofPlayer,HumanPlayer, orComputerPlayer. -
min_to_winis 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,
)4.3.5 ::resume
ConnectN::Game.resume(game) -> nilResumes the given game and returns nil.
4.3.6 ::resume?
ConnectN::Game.resume? -> true or falseReturns true if the user wants to resume a saved game.
Returns false otherwise.
4.3.7 ::save
ConnectN::Game.save(game, name, yaml_fn) -> IntegerSerializes the given game as a Hash of key name & value game to the given yaml_fn.
Notes :
-
game:ConnectN::Gameinstance. -
name:StringorSymbol
4.3.8 ::save?
ConnectN::Game.save? -> true or falseReturns true if the user wants to save the game.
Returns false otherwise.
4.3.9 #invalid_pick
game.invalid_pick -> nilOutputs the error message 'Invalid Column Number' on a red box to stdout.
4.3.10 #play
game.play(yaml_fn = nil) -> nilStarts 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')4.3.11 #play_again?
game.play_again? -> true or falseReturns true if the user wants to play the game again.
Returns false otherwise.
4.3.12 #over
game.over(winner) -> nilOutputs the winner's name on a green box to stdout.
If there is no winner, it similarly announces a tie.
4.3.13 #over?
game.over? -> true or falseReturns true if a player has won or if it is a draw.
Returns false otherwise.
4.3.14 #welcome
game.welcome -> nilOutputs, to stdout, a friendly message that explains the game to the user.
4.4 Player
4.4.1 ::new
ConnectN::Player.new(name:, disc:) -> ConnectN::PlayerCreates a Player instance with the name name and disc disc.
Notes :
-
namecan be any object, aSymbolorStringmakes more sense, though. -
disccan be any object, aSymbolorStringmakes more sense, though. -
Both
names&disccan be reassigned after creation.
4.5 HumanPlayer
4.5.1 ::new
ConnectN::HumanPlayer.new(name: 'Human', disc: '🔥', save_key: ':w') -> ConnectN::HumanPlayerNotes :
-
To understand the use of
save_key, see next section. -
HumanPlayerinherits fromConnectN::Player. -
The only difference between
Player::newandHumanPlayer::neware the default values.
4.5.2 #pick
human_player.pick -> ObjectPrompts 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 #=> -14.6 ComputerPlayer
4.6.1 ::new
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_winof theConnectN::Gameinstance. -
difficulty: anIntegergreater than or equal to 0. The higher it is, the harder it is to beat the computer. -
delay: a/anFloat/Integerof how many seconds theConnectN::ComputerPlayerinstance should take to play its pick. -
board: Must be the sameConnectN::Boardinstance given to theConnectN::Gameinstance. -
opponent_disc: Must be thediscof 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.play4.6.2 #pick
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.
4.7 Prompt
4.7.1 ::ask_for_cols_amount
ConnectN::Prompt.ask_for_cols_amount(
prompt: 'How many columns do you want in the board?',
default: 7
) -> IntegerPrompts 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.
4.7.2 ::ask_for_difficulty
ConnectN::Prompt.ask_for_difficulty(prompt: 'Difficulty : ', levels: [*0..10], default: 5) -> IntegerPrompts 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.
4.7.3 ::ask_for_disc
ConnectN::Prompt.ask_for_disc(
prompt: 'Enter a character that will represent your disc : ',
default: '🔥',
error_msg: 'Please enter a single character.'
) -> StringPrompts 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.
4.7.4 ::ask_for_min_to_win
ConnectN::Prompt.ask_for_min_to_win(
prompt: 'Minimum number of aligned similar discs necessary to win : ',
default: 4
) -> IntegerPrompts 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.
4.7.5 ::ask_for_mode
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
4.7.6 ::ask_for_name
ConnectN::Prompt.ask_for_name(prompt: 'Enter your name : ', default: ENV['USER']) -> StringPrompts the user with a prompt consisting of the value of prompt.
default's value is returned if the user presses enter without entering anything.
4.7.7 ::ask_for_pick
ConnectN::Prompt.ask_for_pick(prompt: 'Please enter a column number : ') -> StringPrompts the user with a prompt consisting of the value of prompt.
4.7.8 ::ask_for_rows_amount
ConnectN::Prompt.ask_for_rows_amount(
prompt: 'How many rows do you want in the board?',
default: 6
) -> IntegerSame as ConnectN::Prompt.ask_for_cols_amount but with different default values.
4.7.9 ::starts?
ConnectN::Prompt.starts?(prompt: 'Do you wanna play first?') -> true or falsePrompts 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.
4.8 Winnable
4.8.1 #win?
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.