Home/Blog/Programming/Design a Connect Four Game
Home/Blog/Programming/Design a Connect Four Game

Design a Connect Four Game

10 min read
Jun 13, 2024
content
Problem definition
Requirements collection
Class diagram
Components of the Connect Four game
The Player class
The WinConditionStrategy interface
The GameBoard class
The GameObserver interface
The Game class
The Toss class
The GameFactory class
The GameState class
Relationship between the classes
Association
Aggregation
Inheritance
Class diagram of the Connect Four game
Design patterns
Design principles (SOLID)
Code for the Connect Four game
The Player class
The WinConditionStrategy interface
The GameBoard class
The GameObserver interface
The Game class
The Toss class
The GameFactory class
The GameState class
The Connect4Game class
OOD interview guide
Conclusion
Next steps

Mastering the art of designing scalable and efficient systems is essential in object-oriented design (OOD) interviews. One common scenario presented to software engineers during these interviews is designing a game (e.g., Blackjack, Connect Four, Chess, Jigsaw puzzle, etc.). This seemingly straightforward problem encompasses various design principles and considerations, making it an excellent litmus test for a candidate’s OOD skills.

This blog delves into the intricacies of designing a Connect Four game using object-oriented principles, patterns, and best practices. We’ll leverage a comprehensive implementation that covers various game components, including the game board, player management, game logic, user interface, and more. Additionally, we’ll explore an OOD interview guide, providing insights and guidelines for effectively navigating low-level design interviews.

Problem definition#

Connect Four is a two-player strategy game where the objective is to be the first to connect four of one’s discs of the same color vertically, horizontally, or diagonally on a grid. Players take turns dropping colored discs into a vertical grid. The game continues until a player achieves the required four-in-a-row alignment, resulting in a win, or until the grid is filled, resulting in a draw. The game involves strategic planning and pattern recognition to outmaneuver the opponent and create winning formations.

The Connect Four game
The Connect Four game

Requirements collection#

Let’s define the requirements for the Connect Four game problem:

  • The game must support two players, each identified by a unique marker (X and O), each with a fixed number of disks (21).

  • Create a 6×7 grid game board where players can place markers. The board starts empty.

  • Players take turns dropping their markers into a chosen column, which should be the lowest available position in that column.

  • Players win if they connect four of their markers horizontally, vertically, or diagonally.

  • Players alternate turns throughout the game; the first turn should be through a coin toss.

  • The game ends when a player wins or the board is filled (resulting in a draw).

These core functionalities are needed to run the Connect Four game smoothly in an object-oriented design paradigm. As we proceed, these requirements will help shape the classes and interfaces we’ll construct to simulate the game.

Class diagram#

In the class diagram for the Connect Four game, we’ll identify and design classes, abstract classes, and interfaces based on the game’s requirements.

Components of the Connect Four game#

As we take a bottom-up approach to designing the Connect Four game, we’ll begin by identifying and designing the classes for smaller components like the game board, players, and win condition strategies. Subsequently, we’ll integrate these smaller components into the class representing the Connect Four game system.


Let’s look at the different components of a Connect Four game system.

The Player class #

The Player class represents a player in the game. It is characterized by two instance variables: name (the player’s name) and marker (the marker used by the player). It has getter methods for these instance variables.

The class diagram of the Player class
The class diagram of the Player class

The WinConditionStrategy interface#

The WinConditionStrategy interface represents a win condition check strategy. It declares a method, checkForWin, that takes a grid and a marker and checks if there’s a win condition for the marker in the grid.
Three classes (VerticalWinCondition, HorizontalWinCondition, and DiagonalWinCondition) implement this interface, each checking for a win in a different direction.

The class diagram of WinConditionStrategy and its child classes
The class diagram of WinConditionStrategy and its child classes

The GameBoard class #

The GameBoard class represents the game board in the Connect Four game. It has a 2D array grid representing the game grid and a list of WinConditionStrategy objects. It has several methods for handling game board-level operations, such as initializing the grid, checking for a win condition, checking if the board is full, making a move, and printing the board.

The class diagram of the GameBoard class
The class diagram of the GameBoard class

The GameObserver interface #

The GameObserver interface defines the update method for observer classes. The PlayerObserver class, which implements the GameObserver interface, has a Player instance variable and provides a player-specific update functionality.

The class diagram of GameObserver and its child class
The class diagram of GameObserver and its child class

The Game class #

The Game class is one of the main classes that handle the game’s overall logic. It has instance variables for player1, player2, currentPlayer, gameBoard, and a list of GameObserver. It includes methods to start the game, switch between players, notify updates to observers, and handle user inputs.

The class diagram of the Game class
The class diagram of the Game class

The Toss class #

The Toss class is used to decide which player starts the game.

The class diagram of the Toss class
The class diagram of the Toss class

The GameFactory class #

The GameFactory class supplies instance creation logic for Player, GameBoard, and Game classes.

The class diagram of the GameFactory class
The class diagram of the GameFactory class

The GameState class #

The GameState class represents the game’s current state, with variables representing the game board grid, the current player’s marker, and any relevant message.

The class diagram of the GameState class
The class diagram of the GameState class

Relationship between the classes#

Now, let’s examine the relationships between the classes defined in the Connect Four game system.

Association#

The class diagram exhibits the following association relationships:

  • The GameFactory has a one-way association with Player, Game, and GameBoard.

  • The GameObserver has a one-way association with GameState.

  • The Toss has a one-way association with Player.

  • The GameBoard has a one-way association with WinConditionStrategy.

The one-way association relationship between classes
The one-way association relationship between classes

Aggregation#

The class diagram has the following aggregation relationships:

  • The Game has an aggregation relationship with Player, GameBoard, and GameBoard.

The aggregation relationship between classes
The aggregation relationship between classes

Inheritance#

The following classes show an inheritance relationship:

  • The VerticalWinCondition, HorizontalWinCondition, and DiagonalWinCondition classes inherit from the WinConditionStrategy class.

  • PlayerObserver can be considered as a specific type of GameObserver. Therefore, this can also be seen as an inheritance relationship, with PlayerObserver as the subclass and GameObserver as the superclass.

Note: We have already discussed the inheritance relationship between classes in the class diagram section above.

Class diagram of the Connect Four game#

Here is the complete class diagram for our Connect Four game:

The class diagram of the Connect Four game
The class diagram of the Connect Four game

Design patterns #

In the implementation of the Connect Four game, several design patterns are utilized to enhance the structure, maintainability, and flexibility of the system:

Design Patterns

Description


Factory pattern

The GameFactory class implements the factory design pattern. It abstracts the process of creating objects of the Game, Player, and GameBoard classes, providing a simple interface for the client code (main method) to create them without knowing the implementation details.

Observer pattern

We’ve used the observer design pattern in the Connect Four game design. With this pattern, every time there’s an update in the game state, all the observers (in our case, players) get notified.


Strategy pattern

The WinConditionStrategy interface and its implementations constitute a strategy pattern in our design. This pattern allows us to choose the appropriate win check strategy at runtime based on horizontal, vertical, or diagonal directions.

Design principles (SOLID)#

The SOLID principles aim to make software designs more understandable, flexible, and maintainable. They consist of:

Design Principles

Description

Single responsibility principle (SRP)

Each class has a single well-defined responsibility. For instance, the Player class is responsible for player-related information only, GameBoard handles the game board operations, and Game controls the overall game flow.

Open-closed principle (OCP)

The classes are open for extension but closed for modification. The WinConditionStrategy interface allows adding new win condition strategies without modifying the existing game board class.

Liskov substitution principle (LSP)

This is prominent in the use of the GameObserver interface. The Game class can use any class implementing the GameObserver interface interchangeably, thus meeting the LSP.

Interface segregation principle (ISP)

We’ve used specific interfaces (e.g., GameObserver, WinConditionStrategy) instead of creating one large interface. Each class uses only those interfaces that apply to it.

Dependency inversion principle (DIP)

The Game class is a good example of the DIP in our design. Rather than the Game directly dependent on concrete Player classes, it depends on an abstraction (the GameObserver interface).

Code for the Connect Four game#

We’ve discussed the design aspects of the Connect Four game and identified the necessary classes and their relationships. Now, let’s look at the practical implementation of the Connect Four game using Java. This implementation will bring our design to life and allow us to simulate and play the game.


We have chosen Java for the implementation of the Connect Four game.

The Player class#

The Player class represents a player in the game, storing their name and marker.

class Player {
private final String name;
private final String marker;
public Player(String name, String marker) {
this.name = name;
this.marker = marker;
}
public String getName() {
return name;
}
public String getMarker() {
return marker;
}
}

The WinConditionStrategy interface#

The WinConditionStrategy interface defines a strategy for checking win conditions on the game board. The following are the three child classes of the WinConditionStrategy interface:

  • The HorizontalWinCondition class implements the WinConditionStrategy interface to check for horizontal win conditions on the game board.

  • The VerticalWinCondition class implements the WinConditionStrategy interface to check for vertical win conditions on the game board.

  • The DiagonalWinCondition class implements the WinConditionStrategy interface to check for diagonal win conditions on the game board.

interface WinConditionStrategy {
boolean checkForWin(String[][] grid, String marker);
}
class VerticalWinCondition implements WinConditionStrategy {
@Override
public boolean checkForWin(String[][] grid, String marker) {
for (int col = 0; col < grid[0].length; col++) {
for (int row = 0; row < grid.length - 3; row++) {
if (marker.equals(grid[row][col]) &&
marker.equals(grid[row + 1][col]) &&
marker.equals(grid[row + 2][col]) &&
marker.equals(grid[row + 3][col])) {
return true;
}
}
}
return false;
}
}
class HorizontalWinCondition implements WinConditionStrategy {
@Override
public boolean checkForWin(String[][] grid, String marker) {
for (int row = 0; row < grid.length; row++) {
for (int col = 0; col < grid[row].length - 3; col++) {
if (marker.equals(grid[row][col]) &&
marker.equals(grid[row][col + 1]) &&
marker.equals(grid[row][col + 2]) &&
marker.equals(grid[row][col + 3])) {
return true;
}
}
}
return false;
}
}
class DiagonalWinCondition implements WinConditionStrategy {
@Override
public boolean checkForWin(String[][] grid, String marker) {
// Check for descending diagonal win
for (int row = 0; row < grid.length - 3; row++) {
for (int col = 0; col < grid[row].length - 3; col++) {
if (marker.equals(grid[row][col]) &&
marker.equals(grid[row + 1][col + 1]) &&
marker.equals(grid[row + 2][col + 2]) &&
marker.equals(grid[row + 3][col + 3])) {
return true;
}
}
}
// Check for ascending diagonal win
for (int row = 3; row < grid.length; row++) {
for (int col = 0; col < grid[row].length - 3; col++) {
if (marker.equals(grid[row][col]) &&
marker.equals(grid[row - 1][col + 1]) &&
marker.equals(grid[row - 2][col + 2]) &&
marker.equals(grid[row - 3][col + 3])) {
return true;
}
}
}
return false;
}
}

The GameBoard class#

The GameBoard class represents the game board, including its dimensions, grid, and functionalities like making moves, checking for wins, and printing the board.

class GameBoard {
private final int rows = 6;
private final int columns = 7;
private final String[][] grid = new String[rows][columns];
private final List<WinConditionStrategy> winConditions;
private boolean gameOver;
private String winMessage;
public GameBoard(List<WinConditionStrategy> winConditions) {
this.winConditions = winConditions;
initializeGrid();
}
private void initializeGrid() {
for (int i = 0; i < rows; i++) {
Arrays.fill(grid[i], "-");
}
}
public boolean checkForWin(String marker) {
return winConditions.stream()
.anyMatch(strategy -> strategy.checkForWin(grid, marker));
}
public boolean isBoardFull() {
return !Arrays.stream(grid).flatMap(Arrays::stream).anyMatch(cell -> cell.equals("-"));
}
public boolean makeMove(int column, String marker) {
if (isValidMove(column)) {
int row = findEmptyRow(column);
grid[row][column] = marker;
return true;
}
return false;
}
private boolean isValidMove(int column) {
return column >= 0 && column < columns && grid[0][column].equals("-");
}
private int findEmptyRow(int column) {
for (int i = rows - 1; i >= 0; i--) {
if (grid[i][column].equals("-")) {
return i;
}
}
throw new IllegalArgumentException("Column " + (column + 1) + " is full.");
}
public void printBoard() {
for (String[] row : grid) {
System.out.println(Arrays.toString(row));
}
}
public int getRows() {
return rows;
}
public int getColumns() {
return columns;
}
public String[][] getGrid() {
return grid;
}
public boolean isGameOver() {
return gameOver;
}
public void setGameOver(boolean gameOver) {
this.gameOver = gameOver;
}
public String getWinMessage() {
return winMessage;
}
public void setWinMessage(String winMessage) {
this.winMessage = winMessage;
}
}

The GameObserver interface#

The GameObserver interface defines a contract for classes that want to observe the state of the game. Classes implementing this interface must provide an update method that takes a GameState object as an argument. The PlayerObserver class implements the GameObserver interface and observes the game state for a specific player.

interface GameObserver {
void update(GameState state, int nextMove);
}
class PlayerObserver implements GameObserver {
private final Player player;
public PlayerObserver(Player player) {
this.player = player;
}
@Override
public void update(GameState state, int nextMove) {
// Implement player-specific updates based on the game state and next move
// For example, display a message to the player
System.out.println("Game state updated for " + player.getName() + ". Next move: " + nextMove);
}
}

The Game class#

class Game {
private final Player player1;
private final Player player2;
private final GameBoard gameBoard;
private Player currentPlayer;
private final Scanner scanner;
private final List<GameObserver> observers;
public Game(Player player1, Player player2, GameBoard gameBoard) {
this.player1 = player1;
this.player2 = player2;
this.gameBoard = gameBoard;
this.currentPlayer = Toss.performToss(player1, player2);
this.scanner = new Scanner(System.in);
this.observers = new ArrayList<>();
this.observers.add(new PlayerObserver(player1));
this.observers.add(new PlayerObserver(player2));
}
public void addObserver(GameObserver observer) {
this.observers.add(observer);
}
public void play() {
System.out.println("Game Started. " + currentPlayer.getName() + " begins.");
boolean gameRunning = true;
while (gameRunning) {
gameBoard.printBoard();
System.out.println(currentPlayer.getName() + "'s turn. Enter column number (1-" + gameBoard.getColumns() + "): ");
int column = scanner.nextInt() - 1;
if (gameBoard.makeMove(column, currentPlayer.getMarker())) {
notifyObservers(column);
if (gameBoard.checkForWin(currentPlayer.getMarker())) {
gameBoard.printBoard();
System.out.println(currentPlayer.getName() + " wins!");
gameBoard.setGameOver(true);
} else if (gameBoard.isBoardFull()) {
gameBoard.printBoard();
System.out.println("The game is a draw!");
gameBoard.setGameOver(true);
} else {
switchPlayer();
}
} else {
System.out.println("Invalid move. Try again.");
}
}
scanner.close();
}
private void switchPlayer() {
currentPlayer = (currentPlayer == player1) ? player2 : player1;
}
private void notifyObservers(int nextMove) {
for (GameObserver observer : observers) {
observer.update(new GameState(), nextMove);
}
}
}

The Toss class#

The Toss class provides a method to perform a coin toss to decide which player goes first.

class Toss {
public static Player performToss(Player player1, Player player2) {
Random random = new Random();
return random.nextBoolean() ? player1 : player2;
}
}

The GameFactory class#

The GameFactory class provides static factory methods for creating players, game boards, and games.

class GameFactory {
public static Player createPlayer(String name, String marker) {
return new Player(name, marker);
}
public static GameBoard createGameBoard(List<WinConditionStrategy> winConditions) {
return new GameBoard(winConditions);
}
public static Game createGame(Player player1, Player player2, GameBoard gameBoard) {
return new Game(player1, player2, gameBoard);
}
}

The GameState class#

The GameState class represents the state of the game, including the current game board and other relevant information.

class GameState {
private String[][] grid;
private String currentPlayerMarker;
private String message;
// Constructor, getters, and setters
}

The Connect4Game class#

The Connect4Game class is the entry point for the Connect Four game. It sets up the players, win conditions, and game board and initiates the game loop.

public class Connect4Game {
public static void main(String[] args) {
// Create players
Player player1 = GameFactory.createPlayer("Player 1", "X");
Player player2 = GameFactory.createPlayer("Player 2", "O");
// Create win conditions
List<WinConditionStrategy> winConditions = new ArrayList<>();
winConditions.add(new HorizontalWinCondition());
winConditions.add(new VerticalWinCondition());
winConditions.add(new DiagonalWinCondition());
// Create the game board
GameBoard gameBoard = GameFactory.createGameBoard(winConditions);
// Create a game
Game game = GameFactory.createGame(player1, player2, gameBoard);
// Start the game
game.play();
}
}

The above-implemented Connect Four game system offers comprehensive functionalities for players to engage in matches, make moves, and determine win-or-draw outcomes. Employing modular design patterns like Strategy and Factory ensures flexibility to adapt to various game rules and board configurations. This design establishes a robust foundation for a scalable and adaptable Connect Four game solution, allowing customization and enhancement as needed. You can modify and further improve the design according to your specific preferences and additional features you may want to incorporate.

OOD interview guide#

Object-oriented design (OOD) interviews assess candidates’ ability to solve complex problems using object-oriented principles and design patterns. Here’s a comprehensive guide to help you prepare for such interviews:

  • Understand OOD concepts: Learn abstraction, encapsulation, inheritance, and polymorphism for scalable and maintainable systems.

  • Practice problem-solving: Solve coding problems and design object models for scenarios like parking lots or banking systems.

  • Know design patterns: Expert Singleton, Factory, Strategy, Observer, and State patterns; apply them contextually.

  • System design skills: Design systems considering scalability, performance, and extensibility, and discuss decision-making based on constraints.

  • Effective communication: Clearly articulate problem-solving approaches and designs and be receptive to feedback.

  • Review past projects: Reflect on previous projects’ patterns (during discussions with interviewers), discussing encountered challenges.

  • Stay updated: Read about design patterns and best practices to stay informed about OOD and software design trends.

  • Mock interviews: Conduct realistic mock interviews to receive feedback on problem-solving and communication skills. You can visit the Educative AI-based mock interview to prepare for FAANG/MAANG object-oriented design, system design, API design, and coding interviews.

  • Continuous learning: Engage with the software development community and seek continuous improvement through practice.

By following this guide and dedicating time to practice, you can build confidence to excel in OOD interviews and tackle challenging design problems effectively.

Conclusion #

The Connect Four game design presented here exemplifies the application of object-oriented design principles and patterns to create a scalable and efficient gaming system. Through the utilization of modular design patterns like Factory, Strategy, and Observer, the system ensures flexibility to accommodate various game rules and configurations. This design lays a robust foundation for a versatile Connect Four game solution, allowing for customization and further enhancement. By mastering these design principles and practicing problem-solving in similar scenarios, candidates can better prepare for object-oriented design interviews and effectively demonstrate their proficiency in complex systems.

Next steps#

If you’re looking to broaden your understanding and delve deeper into object-oriented design, design patterns, and SOLID principles, consider exploring the following courses as an excellent starting point and very helpful for your object-oriented design interviews:

Grokking the Low Level Design Interview Using OOD Principles

Cover
Grokking the Low Level Design Interview Using OOD Principles

With hundreds of potential problems to design, preparing for the object-oriented design (OOD) interview can feel like a daunting task. However, with a strategic approach, OOD interview prep doesn’t have to take more than a few weeks. In this course, you’ll learn the fundamentals of object-oriented design with an extensive set of real-world problems to help you prepare for the OOD part of a typical software engineering interview process at major tech companies like Apple, Google, Meta, Microsoft, and Amazon. By the end of this course, you will get an understanding of the essential object-oriented concepts like design principles and patterns to ace the OOD interview. You will develop the ability to efficiently breakdown an interview design problem into multiple parts using a bottom-up approach. You will be familiar with the scope of each interview problem by accurately defining the requirements and presenting its solution using class, use case, sequence, and activity diagrams.

50hrs
Intermediate
8 Playgrounds
4 Quizzes

The Easiest Way to Learn Design Patterns in C#

Cover
The Easiest Way to Learn Design Patterns in C#

A deep understanding of design patterns and the ability to apply them to relevant design challenges is key to writing good code. They ensure the code is easy to understand and maintain, can be extended in the face of evolving requirements, and can be reused as needed. This course will help you learn design patterns by providing context for using each design pattern and mapping it to common real-world problems that developers can relate to. It also provides hands-on code examples in C# to help apply the learned concepts. The course starts by explaining the SOLID design principles for writing good code followed by discussing the classical behavioral, creational, and structural design patterns in detail. The course also focuses on their strengths and weaknesses and the recurring challenges in software design that these patterns address. After taking this course, you can use your knowledge of design patterns and best practices to create reliable, scalable, and maintainable software projects.

22hrs
Intermediate
33 Playgrounds
2 Quizzes

Master Software Design Patterns and Architecture in C++

Cover
Master Software Design Patterns and Architecture in C++

Software engineering researchers and practitioners noticed that parts of software projects could often be solved using approaches that were discovered earlier to solve similar problems. This led to the documentation of software design and architectural patterns, which are known solution approaches to a class of problems. This course starts by putting software patterns into perspective by answering the question, “What is a software pattern, and how is it different from an algorithm?” You will then learn about the two classes of patterns—software design and architecture patterns. Next, you’ll learn common C++ idioms. Finally, you will learn about software patterns for concurrency. By the end of the course, you will have useful and practical tools to improve the quality of your code and productivity. You’ll also be able to analyze a given part of your software project, pick the right pattern for the job, and apply it.

30hrs
Intermediate
96 Playgrounds
10 Quizzes


Written By:
Saif Ali
Join 2.5 million developers at
Explore the catalog

Free Resources