Project

connect_n

0.0
No release in over a year
A more general version of connect-4 where you try to connect N similar discs. It comes with several features and a friendly API that allows you to customize the game however you want!
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Runtime

~> 0.7.0
~> 0.23.1
 Project Readme

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?

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::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 a Float/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

4.1.2 #cell_at

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) #=> '⚪'

4.1.3 #col_at

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) #=> ['⚪', '⚪', '⚪', '⚪', '⚪', '⚪']

4.1.4 #cols

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

4.1.5 #draw

board.draw -> nil

Outputs the board in a table-format to stdout and returns nil.

board = ConnectN::Board.new
board.draw

4.1.6 #drop_disc

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, '🔥']

4.1.7 #filled?

board.filled? -> true or false

Returns true if the board is filled, returns false otherwise.

board = ConnectN::Board.new
board.filled? #=> false

4.1.8 #row_at

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) #=> ['⚪', '⚪', '⚪', '⚪', '⚪', '⚪', '⚪']

4.1.9 #rows

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

4.1.10 #valid_pick?

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

4.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.yaml in the directory where you run Demo#launch.

4.2.1 ::new

ConnectN::Demo.new -> ConnectN::Demo

4.2.2 #launch

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)

4.3 Game

4.3.1 ::games

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 }

4.3.2 ::select_game_name

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

4.3.3 ::load

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')

4.3.4 ::new

ConnectN::Game.new(board:, first_player:, second_player:, min_to_win:) -> ConnectN::Game

Notes :

  • board must be a ConnectN::Board instance.

  • first_player & second_player can be instances of Player, HumanPlayer, or ComputerPlayer.

  • min_to_win is the minimum number of connected similar discs to count as a win. Must be a positive Integer.

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) -> nil

Resumes the given game and returns nil.

4.3.6 ::resume?

ConnectN::Game.resume? -> true or false

Returns true if the user wants to resume a saved game.

Returns false otherwise.

4.3.7 ::save

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 or Symbol

4.3.8 ::save?

ConnectN::Game.save? -> true or false

Returns true if the user wants to save the game.

Returns false otherwise.

4.3.9 #invalid_pick

game.invalid_pick -> nil

Outputs the error message 'Invalid Column Number' on a red box to stdout.

4.3.10 #play

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')

4.3.11 #play_again?

game.play_again? -> true or false

Returns true if the user wants to play the game again.

Returns false otherwise.

4.3.12 #over

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.

4.3.13 #over?

game.over? -> true or false

Returns true if a player has won or if it is a draw.

Returns false otherwise.

4.3.14 #welcome

game.welcome -> nil

Outputs, 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::Player

Creates a Player instance with the name name and disc disc.

Notes :

  • name can be any object, a Symbol or String makes more sense, though.

  • disc can be any object, a Symbol or String makes more sense, though.

  • Both names & disc can be reassigned after creation.

4.5 HumanPlayer

4.5.1 ::new

ConnectN::HumanPlayer.new(name: 'Human', disc: '🔥', save_key: ':w') -> ConnectN::HumanPlayer

Notes :

  • To understand the use of save_key, see next section.

  • HumanPlayer inherits from ConnectN::Player.

  • The only difference between Player::new and HumanPlayer::new are the default values.

4.5.2 #pick

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

4.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 same min_to_win of the ConnectN::Game instance.

  • difficulty : an Integer greater than or equal to 0. The higher it is, the harder it is to beat the computer.

  • delay : a/an Float/Integer of how many seconds the ConnectN::ComputerPlayer instance should take to play its pick.

  • board : Must be the same ConnectN::Board instance given to the ConnectN::Game instance.

  • opponent_disc : Must be the disc 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

4.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
) -> 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.

4.7.2 ::ask_for_difficulty

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.

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.'
) -> 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.

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
) -> 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.

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']) -> 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.

4.7.7 ::ask_for_pick

ConnectN::Prompt.ask_for_pick(prompt: 'Please enter a column number : ') -> String

Prompts 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
) -> Integer

Same 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 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.

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.