TypeScript Tutorial: Building a Simple Interactive Web-Based Game of Connect Four

Connect Four, a classic strategy game, has captivated players for decades. Its simple rules belie a surprising depth of strategic possibilities, making it an excellent project for learning TypeScript. In this tutorial, we will walk through the process of building a fully functional, interactive Connect Four game using TypeScript, HTML, and CSS. This project will not only solidify your understanding of TypeScript fundamentals but also introduce you to concepts like game logic, event handling, and DOM manipulation.

Why Build a Connect Four Game with TypeScript?

TypeScript offers several advantages for this project:

  • Type Safety: TypeScript’s static typing helps catch errors early in the development process, reducing debugging time and improving code quality.
  • Code Readability: TypeScript enhances code readability through features like interfaces and classes, making your code easier to understand and maintain.
  • Modern JavaScript: TypeScript compiles to JavaScript, allowing you to use modern JavaScript features while ensuring compatibility with older browsers.
  • Learning Opportunity: Building a game like Connect Four provides a practical context for learning TypeScript concepts, making the learning process more engaging and effective.

Setting Up the Project

Before we dive into the code, let’s set up our project. We’ll use a simple file structure:

  • index.html: The main HTML file.
  • style.css: The CSS file for styling the game.
  • src/: A directory to hold our TypeScript files.
  • src/index.ts: The main TypeScript file.
  • tsconfig.json: TypeScript configuration file.

First, create a new directory for your project and navigate into it using your terminal. Then, initialize a Node.js project:

npm init -y

Next, install TypeScript and a few other necessary packages:

npm install typescript --save-dev
npm install --save-dev @types/node
npm install --save-dev @types/dom

Now, create the tsconfig.json file in the root directory. This file configures the TypeScript compiler. Here’s a basic configuration:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"]
}

This configuration specifies that we want to compile our TypeScript code to ES5 JavaScript, use CommonJS modules, output the compiled files to a dist directory, and enable strict type checking. It also includes all files in the src directory.

HTML Structure (index.html)

Let’s create the basic HTML structure for our game. This file will contain the game board, player turn indicators, and any other UI elements.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Connect Four</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="container">
        <h1>Connect Four</h1>
        <div class="game-board"></div>
        <div class="game-info">
            <p id="player-turn">Player 1's turn</p>
            <button id="reset-button">Reset Game</button>
        </div>
    </div>
    <script src="dist/index.js"></script>
</body>
</html>

This HTML provides a basic structure. It includes a title, a game board (<div class="game-board">), player turn information (<p id="player-turn">), and a reset button (<button id="reset-button">). We also link to our CSS file and our compiled JavaScript file (dist/index.js).

CSS Styling (style.css)

Next, let’s add some basic styling to make our game visually appealing. Here’s a sample CSS file:

body {
    font-family: sans-serif;
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 100vh;
    background-color: #f0f0f0;
}

.container {
    text-align: center;
    background-color: #fff;
    padding: 20px;
    border-radius: 8px;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}

.game-board {
    display: grid;
    grid-template-columns: repeat(7, 60px);
    grid-template-rows: repeat(6, 60px);
    gap: 10px;
    margin-top: 20px;
}

.cell {
    width: 60px;
    height: 60px;
    border-radius: 50%;
    background-color: #4285f4;
    cursor: pointer;
}

.cell.player1 {
    background-color: #ea4335;
}

.cell.player2 {
    background-color: #34a853;
}

.game-info {
    margin-top: 20px;
}

#reset-button {
    padding: 10px 20px;
    font-size: 16px;
    background-color: #4285f4;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
}

This CSS sets up the basic layout and styling for the game board, cells, and other UI elements. You can customize the colors and styles to your preference.

TypeScript Implementation (src/index.ts)

Now, let’s write the core TypeScript logic for our Connect Four game. This is where we’ll define the game board, handle player turns, check for wins, and update the UI.


// Define constants
const ROWS = 6;
const COLS = 7;

// Define the game board as a 2D array.  0 = empty, 1 = player 1, 2 = player 2
let board: number[][] = [];

// Current player (1 or 2)
let currentPlayer: number = 1;

// Game over flag
let gameOver: boolean = false;

// Get references to DOM elements
const gameBoard = document.querySelector('.game-board') as HTMLElement;
const playerTurn = document.getElementById('player-turn') as HTMLElement;
const resetButton = document.getElementById('reset-button') as HTMLButtonElement;

// Function to initialize the game board
function initializeBoard(): void {
  board = [];
  for (let i = 0; i < ROWS; i++) {
    board[i] = [];
    for (let j = 0; j < COLS; j++) {
      board[i][j] = 0;
    }
  }
}

// Function to create the game board in the DOM
function createBoard(): void {
  for (let row = 0; row < ROWS; row++) {
    for (let col = 0; col < COLS; col++) {
      const cell = document.createElement('div');
      cell.classList.add('cell');
      cell.dataset.row = row.toString();
      cell.dataset.col = col.toString();
      cell.addEventListener('click', () => handleCellClick(col));
      gameBoard.appendChild(cell);
    }
  }
}

// Function to handle a cell click
function handleCellClick(col: number): void {
  if (gameOver) return;

  // Find the next available row in the selected column
  const row = getNextOpenRow(col);

  if (row !== -1) {
    placePiece(row, col, currentPlayer);
    if (checkWin(row, col)) {
      gameOver = true;
      playerTurn.textContent = `Player ${currentPlayer} wins!`;
    } else if (isBoardFull()) {
      gameOver = true;
      playerTurn.textContent = "It's a draw!";
    } else {
      switchPlayer();
    }
  }
}

// Function to get the next open row in a column
function getNextOpenRow(col: number): number {
  for (let row = ROWS - 1; row >= 0; row--) {
    if (board[row][col] === 0) {
      return row;
    }
  }
  return -1; // Column is full
}

// Function to place a piece on the board
function placePiece(row: number, col: number, player: number): void {
  board[row][col] = player;
  updateUI(row, col);
}

// Function to update the UI after a piece is placed
function updateUI(row: number, col: number): void {
  const cell = document.querySelector(`.cell[data-row="${row}"][data-col="${col}"]`) as HTMLElement;
  cell.classList.add(currentPlayer === 1 ? 'player1' : 'player2');
}

// Function to switch the current player
function switchPlayer(): void {
  currentPlayer = currentPlayer === 1 ? 2 : 1;
  playerTurn.textContent = `Player ${currentPlayer}'s turn`;
}

// Function to check if the board is full
function isBoardFull(): boolean {
  for (let col = 0; col < COLS; col++) {
    if (board[0][col] === 0) {
      return false;
    }
  }
  return true;
}

// Function to check for a win
function checkWin(row: number, col: number): boolean {
  // Check horizontal
  if (checkLine(row, col, 0, 1)) return true;

  // Check vertical
  if (checkLine(row, col, 1, 0)) return true;

  // Check positive diagonal
  if (checkLine(row, col, 1, 1)) return true;

  // Check negative diagonal
  if (checkLine(row, col, 1, -1)) return true;

  return false;
}

// Helper function to check a line for a win
function checkLine(row: number, col: number, rowDir: number, colDir: number): boolean {
  let count = 1;
  // Check in one direction
  for (let i = 1; i < 4; i++) {
    const r = row + rowDir * i;
    const c = col + colDir * i;
    if (r >= 0 && r < ROWS && c >= 0 && c < COLS && board[r][c] === currentPlayer) {
      count++;
    } else {
      break;
    }
  }

  // Check in the opposite direction
  for (let i = 1; i < 4; i++) {
    const r = row - rowDir * i;
    const c = col - colDir * i;
    if (r >= 0 && r < ROWS && c >= 0 && c < COLS && board[r][c] === currentPlayer) {
      count++;
    } else {
      break;
    }
  }

  return count >= 4;
}

// Function to reset the game
function resetGame(): void {
  gameOver = false;
  currentPlayer = 1;
  playerTurn.textContent = "Player 1's turn";
  initializeBoard();
  clearBoardUI();
}

// Function to clear the board UI
function clearBoardUI(): void {
  const cells = document.querySelectorAll('.cell');
  cells.forEach(cell => {
    cell.classList.remove('player1', 'player2');
  });
}

// Initialize the game when the page loads
function initGame(): void {
  initializeBoard();
  createBoard();
  resetButton.addEventListener('click', resetGame);
}

// Call initGame to start the game
initGame();

Let’s break down the code:

  • Constants and Variables: We define constants for the number of rows and columns, and variables to represent the game board (a 2D array), the current player, and the game over state.
  • DOM Element References: We get references to the game board, player turn indicator, and reset button elements in the HTML.
  • initializeBoard(): This function initializes the board array with all zeros, representing an empty board.
  • createBoard(): This function creates the visual representation of the game board in the DOM. It iterates through the rows and columns, creating a div element for each cell and adding event listeners for click events.
  • handleCellClick(col: number): This function is called when a cell is clicked. It determines the row to place the piece, calls placePiece(), checks for a win or draw, and switches the player.
  • getNextOpenRow(col: number): This function finds the next available row in a given column.
  • placePiece(row: number, col: number, player: number): This function updates the game board array with the player’s piece and calls updateUI() to update the visual representation.
  • updateUI(row: number, col: number): This function updates the UI by adding the appropriate CSS class (player1 or player2) to the clicked cell.
  • switchPlayer(): This function switches the current player.
  • isBoardFull(): This function checks if the board is full.
  • checkWin(row: number, col: number): This function checks if the current player has won by checking all possible winning lines (horizontal, vertical, and diagonals).
  • checkLine(row: number, col: number, rowDir: number, colDir: number): This helper function checks a specific line (defined by direction) for a win.
  • resetGame(): This function resets the game to its initial state.
  • clearBoardUI(): This function clears the visual representation of the board by removing the player classes from the cells.
  • initGame(): This function initializes the game by calling initializeBoard(), createBoard(), and attaching the reset button’s event listener. It is called when the page loads.

Compiling and Running the Game

To compile your TypeScript code, run the following command in your terminal:

tsc

This will compile the src/index.ts file and create a dist/index.js file. Now, open index.html in your browser. You should see the Connect Four game board, and you should be able to play the game.

Common Mistakes and Solutions

Here are some common mistakes and how to fix them:

  • Incorrect TypeScript Configuration: Double-check your tsconfig.json file. Ensure that the target is set to a compatible version (e.g., es5), and that the include property correctly includes your source files.
  • DOM Element Selection Errors: Make sure you have the correct IDs and class names in your HTML and that you are using document.getElementById() or document.querySelector() correctly to select the elements. Use type assertions (e.g., as HTMLElement) to help TypeScript understand the types of the selected elements.
  • Event Listener Issues: Ensure that your event listeners are correctly attached and that the event handler functions are properly defined. Check for any typos in the event listener names (e.g., 'click').
  • Logic Errors: Carefully review your game logic, especially the checkWin() and getNextOpenRow() functions, to ensure they are working correctly. Test your game thoroughly to identify and fix any bugs.
  • Type Errors: TypeScript will help you catch type errors. Pay attention to the error messages in your console and fix the type mismatches.

Key Takeaways

In this tutorial, we have learned how to build a basic Connect Four game using TypeScript, HTML, and CSS. We’ve covered the following key concepts:

  • Setting up a TypeScript project.
  • Creating an HTML structure for the game.
  • Styling the game with CSS.
  • Implementing game logic in TypeScript, including game board representation, player turns, win condition checks, and UI updates.
  • Handling user input through event listeners.
  • Using TypeScript’s type system to improve code quality and prevent errors.

FAQ

Here are some frequently asked questions about building a Connect Four game with TypeScript:

  1. How can I improve the game’s UI? You can enhance the UI by adding animations, more visually appealing graphics, and sound effects. Consider using a CSS framework like Bootstrap or Tailwind CSS for easier styling.
  2. How can I add an AI opponent? To add an AI opponent, you would need to implement an algorithm to evaluate the game board and determine the best move for the computer. Minimax is a common algorithm for this purpose.
  3. How can I deploy this game online? You can deploy your game online by hosting the HTML, CSS, and JavaScript files on a web server or using a platform like Netlify or GitHub Pages.
  4. What are some other game ideas for learning TypeScript? Other beginner-friendly game ideas include Tic-Tac-Toe, simple card games (like War), or a basic version of Tetris.

Connect Four, despite its simple premise, is a wonderful vehicle for learning and practicing fundamental programming skills. The project allows you to apply TypeScript’s strengths in a practical context, from type safety and code organization to handling user interactions and managing game state. Building this game not only provides a fun and engaging project but also equips you with essential skills that are transferable to more complex software development projects. The combination of TypeScript’s robustness and the inherent logic of Connect Four provides a solid foundation for further exploration in game development and software engineering. As you continue to build and refine your skills, remember that the most valuable lesson is the journey of learning and problem-solving, and the satisfaction of seeing your code come to life in a playable game.