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::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 boardboard.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) #=> 'βͺ'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) #=> ['βͺ', 'βͺ', 'βͺ', 'βͺ', 'βͺ', 'βͺ']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.colsboard.draw -> nilOutputs the board in a table-format to stdout and returns nil.
board = ConnectN::Board.new
board.drawboard.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, 'π₯']board.filled? -> true or falseReturns true if the board is filled, returns false otherwise.
board = ConnectN::Board.new
board.filled? #=> falseboard.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) #=> ['βͺ', 'βͺ', 'βͺ', 'βͺ', 'βͺ', 'βͺ', 'βͺ']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.rowsboard.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) #=> trueNotes :
-
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.
ConnectN::Demo.new -> ConnectN::Demodemo.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)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 }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 intendedConnectN::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')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,
)ConnectN::Game.resume(game) -> nilResumes the given game and returns nil.
ConnectN::Game.resume? -> true or falseReturns true if the user wants to resume a saved game.
Returns false otherwise.
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
ConnectN::Game.save? -> true or falseReturns true if the user wants to save the game.
Returns false otherwise.
game.invalid_pick -> nilOutputs the error message 'Invalid Column Number' on a red box to stdout.
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')game.play_again? -> true or falseReturns true if the user wants to play the game again.
Returns false otherwise.
game.over(winner) -> nilOutputs the winner's name on a green box to stdout.
If there is no winner, it similarly announces a tie.
game.over? -> true or falseReturns true if a player has won or if it is a draw.
Returns false otherwise.
game.welcome -> nilOutputs, to stdout, a friendly message that explains the game to the user.
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.
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.
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 #=> -1ConnectN::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.playcomputer_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
) -> 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.
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.
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.
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.
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']) -> 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.
ConnectN::Prompt.ask_for_pick(prompt: 'Please enter a column number : ') -> StringPrompts 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
) -> IntegerSame as ConnectN::Prompt.ask_for_cols_amount but with different default values.
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.
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.