Chess Web Programming: Part Eight: Chess.com API

Working with the Chess.com API

My journey into programming chess applications started with Chessboard Magic, a personal project that turned into a platform with over 34 unique chess-based applications. This blog continues my Chess Web Programming series, diving into one of the most exciting aspects of modern development: working with APIs. In this installment, we’ll explore the Chess.com API, learn how to interact with it, and create a React application to fetch and display games from any Chess.com user in a structured and beginner-friendly way.

Recap of the Previous Parts

Before we dive into the Chess.com API, here’s a quick recap of the earlier blogs in the series:

  1. Getting Started: Using react-chessboard and chess.js to set up a functional chessboard.
  2. Integrating Stockfish: Enhancing the application by suggesting the best move using Stockfish.
  3. Deploying Your Application: A guide to hosting your chess application online.
  4. Customizing the Chessboard: Styling and implementing features like custom piece sets and square highlighting.
  5. Game Review: Adding features like move highlighting and navigation for game analysis.
  6. Essential Resources: Tools, libraries, and learning materials to support chess application development.
  7. Lichess API: Learning how to use the Lichess.org API.

Note: This blog assumes that you’ve followed the previous parts of the series, particularly Blog 1, which covers setting up the foundational tools and concepts. You should already have the necessary software installed (e.g., Node.js, Visual Studio Code) and a basic understanding of React web programming. If you haven’t reviewed Blog 1 yet, we recommend starting there to ensure you’re set up for success.

What Are APIs?

APIs (Application Programming Interfaces) act as intermediaries that allow software applications to communicate with one another. They provide a set of rules and protocols for accessing a service or application’s features or data. For example, when you check the weather on your phone, the app fetches the data from a server using APIs. Similarly, we’ll use the Chess.com API to retrieve chess game data.

What Is a RESTful API?

A RESTful API (Representational State Transfer API) is a type of API that adheres to specific architectural principles. It uses standard HTTP methods such as GETPOSTPUT, and DELETE to manage resources (like game data or user profiles).
RESTful APIs:

  • Are stateless, meaning every request from the client to the server must contain all the information needed to process the request.
  • Use endpoints (specific URLs) to access different resources.

For instance, the chess.com API uses endpoints to provide access to specific types of data. Here’s an example:

  • Endpointhttps://api.chess.com/pub/player/{username}/games/{year}/{month}
  • Purpose: Fetches games played by a given Chess.com user.

In this blog, we’ll focus on the Games endpoint to fetch and display a user’s game history.

What Is HTTP?
HTTP (HyperText Transfer Protocol) is the foundation of communication on the web. It defines how messages are formatted and transmitted between clients (like a browser or application) and servers. For example, when your application sends a request to the Chess.com API, it uses HTTP to communicate. Common HTTP methods include:

  • GET: Retrieve data from a server.
  • POST: Send data to a server.
  • PUT: Update data on a server.
  • DELETE: Remove data from a server.

The Chess.com API

The Chess.com API is a RESTful API that provides developers access to various chess-related data. It allows you to retrieve information about players, games, tournaments, and more.
Here are some of its key features:

  • User Profiles: Fetch details about a specific Chess.com user, including ratings, titles, and membership status.
  • Game Archives: Access a player’s monthly game archives, which include all games played during a specific month.
  • Clubs and Tournaments: Retrieve details about clubs, tournaments, and events.

For this tutorial, we’ll use the Game Archives endpoint to:

  • Fetch a user’s game history for a specific month.
  • Include details like the result, time controls, and opening name.

Example Endpoint: Game Archives

To fetch the game archive for a Chess.com user:

https://api.chess.com/pub/player/{username}/games/{year}/{month}

For example:

https://api.chess.com/pub/player/hollowleaf/games/2024/11

By understanding how endpoints work, you’ll be able to explore other parts of the Chess.com API and integrate additional features into your application in the future.

Setting Up a React Application

In this section, we’ll create a React application from scratch using Create React App. This will serve as the foundation for our Chess.com API integration.

Step 1: Create a New React Application

React is a popular JavaScript library for building user interfaces. To get started, we’ll use a tool called Create React App, which sets up a new project with all the necessary configurations and dependencies pre-installed.

  1. Open Your Terminal
    You’ll need a terminal or command-line tool to run commands. On Windows, you can use Command Prompt or PowerShell. On macOS or Linux, use the built-in terminal.
  2. Run the Create React App Command
    In your terminal, type npx create-react-app chessdotcom-api-app. The npx command ensures that you’re using the latest version of Create React App, while chessdotcom-api-app is the name of your project folder. This command downloads and installs all the dependencies required for a React project. It might take a few minutes depending on your internet connection.
  3. Navigate Into Your Project Folder
    Once the installation is complete, use the cd chessdotcom-api-app command to navigate into the folder where your new React project is located.
  4. What’s Inside the Folder?
    Your project folder contains several files and directories:
    • node_modules folder with all the dependencies installed for your project.
    • public folder that includes static assets like the index.html file.
    • src folder, which is the main location for your React components and logic.
    • package.json file, which lists project metadata and dependencies.
    • README.md file, which contains instructions and information about your project.

At this stage, your new React application is ready, and you’re set to begin building your project.

Open the Project in VS Code

Once your React application is created, the next step is to open it in a code editor. We’ll use Visual Studio Code (VS Code), a popular editor for web development.

  1. Launch VS Code
    Open Visual Studio Code from your operating system’s application menu.
  2. Add the Project Folder to Workspace
    In VS Code:
    • Click on File in the top menu bar.
    • Select Add Folder to Workspace.
    • In the file explorer that appears, navigate to the folder where you created your React project (chessdotcom-api-app).
    • Select the folder and click Add.
    This step ensures that all files and folders in your project are accessible from within VS Code.
  3. Explore the Workspace
    Once the folder is added, you’ll see the project structure in the VS Code Explorer panel on the left. Key elements include:
    • src folder: This is where you’ll write your React code.
    • public folder: Contains the index.html file, where your React application is injected.
    • package.json: Lists project dependencies and configuration.
  4. Open the Terminal in VS Code
    To run commands directly from VS Code:
    • Go to the top menu and select View Terminal.
    • A terminal window will open at the bottom of the editor, starting in your project folder.
  5. Verify Everything is Set Up
    In the terminal, start the development server by typing npm start. This will run your React application and open it in your default web browser at http://localhost:3000.
    If you see the default React welcome page in your browser, everything is set up correctly.

Note: If you have another server running on your machine (for example, another React project), you may encounter a message like:
“Something is already running on port 3000.”
In this case, you will be given two options:

  1. Use a Different Port: React will prompt you to run the application on a different port, such as http://localhost:3001. You can press Y to confirm.
  2. Shut Down the Other Server: If you prefer to run your application on port 3000, you’ll need to stop the other server. To do this, go to the terminal where the other server is running and press Ctrl + C (or Cmd + C on macOS) to terminate it.

Introducing Material UI

In this section, we’ll enhance the user interface of our React application using Material UI, a popular library of pre-styled and customizable components. Material UI allows us to quickly build professional, modern-looking interfaces without having to write extensive CSS. By the end of this section, we’ll have a clean and responsive form with the following features:

  • An input field for the Chess.com username.
  • Input fields for the Year and Month.
  • A button to trigger the API call and fetch the games.

This form will lay the groundwork for integrating the Chess.com API in the next section.

Why Use Material UI?

Material UI (MUI) is based on Google’s Material Design principles, focusing on clean, consistent, and user-friendly interfaces. Here are some reasons to use it:

  1. Pre-Styled Components: Includes a library of pre-designed components like buttons, tables, and input fields.
  2. Customizability: You can easily tweak the styles using inline styling, theming, or CSS overrides.
  3. Accessibility: Material UI components are built with accessibility in mind, making your application more user-friendly.
  4. Ease of Use: You can achieve professional designs with minimal effort, reducing the need to write custom CSS.

By using Material UI, we can focus more on building the application logic rather than spending time on UI design from scratch.

Step 1: Install Material UI

To begin using Material UI, we need to install the required packages. These include:

  • [@mui](/@/mui)/material: The core Material UI component library.
  • [@emotion](/@/emotion)/react and [@emotion](/@/emotion)/styled: Required for styling the Material UI components.

Install these packages by running the following command in your terminal:

npm install [@mui](/@/mui)/material [@emotion](/@/emotion)/react [@emotion](/@/emotion)/styled

The [@emotion](/@/emotion)/react and [@emotion](/@/emotion)/styled packages are necessary for Material UI’s styling to work. Once installed, you can start using Material UI components in your project.

Step 2: Create the Basic Form

Now, we’ll set up a basic form in App.js with:

  • An input field for the Chess.com username.
  • An input field for the year and month.
  • A button to fetch games.

Replace the content of src/App.js with the following:

import React, { useState } from 'react';
import { TextField, Button, Container, Typography, Box } from '[@mui](/@/mui)/material';

function App() {
  const [username, setUsername] = useState(''); // State to store the Chess.com username
  const [year, setYear] = useState(new Date().getFullYear()); // State to store the year, defaults to the current year
  const [month, setMonth] = useState(new Date().getMonth() + 1); // State to store the month, defaults to the current month

  // Function to handle the fetching of games
  const fetchGames = () => {
    console.log(`Fetching games for ${username} from ${year}-${month}`); // Logs the form values to the console
  };

  return (
    <Container>
      {/* Title for the application */}
      <Typography variant="h4" gutterBottom>
        Chess.com Game Fetcher
      </Typography>

      {/* Container for inputs and button aligned in a row */}
      <Box display="flex" justifyContent="flex-start" alignItems="center" gap={2} style={{ marginTop: '20px' }}>
        {/* Input for Chess.com username */}
        <TextField
          label="Chess.com Username"
          variant="outlined"
          value={username}
          onChange={(e) => setUsername(e.target.value)} // Updates the username state
        />

        {/* Input for year */}
        <TextField
          label="Year"
          variant="outlined"
          type="number"
          value={year}
          onChange={(e) => setYear(e.target.value)} // Updates the year state
        />

        {/* Input for month */}
        <TextField
          label="Month"
          variant="outlined"
          type="number"
          value={month}
          onChange={(e) => setMonth(e.target.value)} // Updates the month state
        />

        {/* Button to fetch games */}
        <Button
          variant="contained"
          color="primary"
          onClick={fetchGames} // Calls fetchGames when clicked
        >
          Fetch Games
        </Button>
      </Box>
    </Container>
  );
}

export default App;

Step 3: Understanding the Code

  • Container: A Material UI layout component that centers content and adds padding.
  • Typography: Styles text elements like headings. Used for the title “Chess.com Game Fetcher.”
  • TextField: Input components for:
    • Chess.com username.
    • Year (defaults to the current year).
    • Month (defaults to the current month).
  • Button: Triggers the fetchGames function when clicked.
  • useState Hooks:
    • username: Stores the entered Chess.com username.
    • year: Stores the year for the game archive.
    • month: Stores the month for the game archive.
  • fetchGames Function: Logs the entered usernameyear, and month values to the console. Will later fetch data from the Chess.com API.

Step 4: Preview the Form

Save your changes and check the browser. You should see:

  • A title labeled “Chess.com Game Fetcher.”
  • Three input fields for the username, year and month.
  • A button labeled “Fetch Games.”

image.png
At this stage, you’ve built a functional form using Material UI. In the next section, we’ll connect this form to the Chess.com API to fetch and display game data.

Fetching and Displaying Games

In this section, we’ll integrate the Chess.com API into our React application. The goal is to fetch a list of chess games for a specific player, process the data to extract essential details, and display the information in a structured format using a Material UI Table.
We’ll also learn how to clean PGN (Portable Game Notation) data to extract bare moves without metadata, comments, or move numbers for a clean display.

Step 1: Understand the Chess.com API Endpoint

The Chess.com API provides access to historical game data for users. The endpoint we’re using retrieves monthly games for a specified player.
Endpoint:

https://api.chess.com/pub/player/{username}/games/{year}/{month}

Key Parameters:

  • {username}: The Chess.com username.
  • {year} and {month}: The year and month for the games to fetch.

The response includes metadata (like players’ ratings and results) and the full PGN for each game.

Step 2: Fetch Games from the Chess.com API

To fetch the data, we’ll:

  1. Dynamically construct the API URL based on user input (username, year, and month).
  2. Use the Fetch API to make a GET request.
  3. Parse the JSON response and store the games in a state variable.

Here’s the function:

const fetchGames = async () => {
  if (!username || !year || !month) {
    alert("Please provide a username, year, and month."); // Validate input.
    return;
  }

  // Construct the API URL dynamically based on input
  const url = `https://api.chess.com/pub/player/${username}/games/${year}/${month}`;

  try {
    const response = await fetch(url); // Fetch data from Chess.com API
    const data = await response.json(); // Parse the JSON response

    if (data.games) {
      setGames(data.games); // Store the games in the state if available
    } else {
      alert("No games found for the specified period.");
    }
  } catch (error) {
    console.error("Error fetching games:", error); // Log any errors
    alert("Failed to fetch games. Please try again.");
  }
};

Step 3: Extract Bare Moves from PGN

The PGN data often includes metadata, move numbers, and comments, which can clutter the display. To clean this data, we’ll use the following function to extract only the moves:

const extractMoves = (pgn) => {
  // Remove metadata lines (lines that start with [ and end with ])
  const noMetadata = pgn.replace(/^\[.*\]$/gm, '');

  // Remove comments inside {}
  const noComments = noMetadata.replace(/\{.*?\}/g, '');

  // Remove move numbers (e.g., "1.", "1...")
  const noMoveNumbers = noComments.replace(/\d+\.+/g, '');

  // Clean up extra spaces and return
  return noMoveNumbers.trim().replace(/\s+/g, ' '); // Replace multiple spaces with a single space
};

This function ensures that only the bare moves are displayed in a clean, human-readable format.

Step 4: Display Games in a Table

We’ll use Material UI’s Table component to display the fetched games. Each row will represent a single game, showing key details like the players, results, and bare moves.
Here’s the code:

<Table>
  <TableHead>
    <TableRow>
      <TableCell>White</TableCell> {/* Column for the White player's name and rating */}
      <TableCell>Black</TableCell> {/* Column for the Black player's name and rating */}
      <TableCell>Result</TableCell> {/* Column for the game result */}
      <TableCell>Moves</TableCell> {/* Column for the extracted moves */}
    </TableRow>
  </TableHead>
  <TableBody>
    {games.map((game, index) => (
      <TableRow key={index}> {/* Unique key for each row */}
        {/* White player details */}
        <TableCell>
          {game.white.username} ({game.white.rating})
        </TableCell>
        {/* Black player details */}
        <TableCell>
          {game.black.username} ({game.black.rating})
        </TableCell>
        {/* Game result */}
        <TableCell>
          {game.white.result === "win"
            ? "White Wins"
            : game.black.result === "win"
            ? "Black Wins"
            : "Draw"}
        </TableCell>
        {/* Extracted moves */}
        <TableCell>
          <pre style={{ whiteSpace: "pre-wrap", wordWrap: "break-word" }}>
            {extractMoves(game.pgn)} {/* Display the clean moves */}
          </pre>
        </TableCell>
      </TableRow>
    ))}
  </TableBody>
</Table>

Final Code

Here’s the complete implementation:

import React, { useState } from "react";
import {
  TextField,
  Button,
  Container,
  Typography,
  Box,
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableRow,
} from "[@mui](/@/mui)/material";

function App() {
  const [username, setUsername] = useState(""); // State for the username input
  const [year, setYear] = useState(""); // State for the year input
  const [month, setMonth] = useState(""); // State for the month input
  const [games, setGames] = useState([]); // State to store fetched games

  // Fetch games from the Chess.com API
  const fetchGames = async () => {
    if (!username || !year || !month) {
      alert("Please provide a username, year, and month."); // Validate input
      return;
    }

    const url = `https://api.chess.com/pub/player/${username}/games/${year}/${month}`;

    try {
      const response = await fetch(url); // Fetch data from Chess.com API
      const data = await response.json(); // Parse the JSON response

      if (data.games) {
        setGames(data.games); // Store the games in the state if available
      } else {
        alert("No games found for the specified period.");
      }
    } catch (error) {
      console.error("Error fetching games:", error); // Log errors
      alert("Failed to fetch games. Please try again.");
    }
  };

  // Function to clean and extract bare moves from PGN
  const extractMoves = (pgn) => {
    const noMetadata = pgn.replace(/^\[.*\]$/gm, ''); // Remove metadata
    const noComments = noMetadata.replace(/\{.*?\}/g, ''); // Remove comments
    const noMoveNumbers = noComments.replace(/\d+\.+/g, ''); // Remove move numbers
    return noMoveNumbers.trim().replace(/\s+/g, ' '); // Clean up extra spaces
  };

  return (
    <Container>
      <Typography variant="h4" gutterBottom>
        Chess.com Game Viewer
      </Typography>

      {/* Input Form */}
      <Box display="flex" gap="16px" marginBottom="20px">
        <TextField
          label="Username"
          value={username}
          onChange={(e) => setUsername(e.target.value)}
        />
        <TextField
          label="Year"
          value={year}
          onChange={(e) => setYear(e.target.value)}
        />
        <TextField
          label="Month"
          value={month}
          onChange={(e) => setMonth(e.target.value)}
        />
        <Button variant="contained" onClick={fetchGames}>
          Fetch Games
        </Button>
      </Box>

      {/* Games Table */}
      {games.length > 0 && (
        <Table>
          <TableHead>
            <TableRow>
              <TableCell>White</TableCell>
              <TableCell>Black</TableCell>
              <TableCell>Result</TableCell>
              <TableCell>Moves</TableCell>
            </TableRow>
          </TableHead>
          <TableBody>
            {games.map((game, index) => (
              <TableRow key={index}>
                <TableCell>
                  {game.white.username} ({game.white.rating})
                </TableCell>
                <TableCell>
                  {game.black.username} ({game.black.rating})
                </TableCell>
                <TableCell>
                  {game.white.result === "win"
                    ? "White Wins"
                    : game.black.result === "win"
                    ? "Black Wins"
                    : "Draw"}
                </TableCell>
                <TableCell>
                  <pre style={{ whiteSpace: "pre-wrap", wordWrap: "break-word" }}>
                    {extractMoves(game.pgn)}
                  </pre>
                </TableCell>
              </TableRow>
            ))}
          </TableBody>
        </Table>
      )}
    </Container>
  );
}

export default App;

Explanation:

  • The app allows users to fetch chess games from Chess.com by entering a username, year, and month.
  • It uses React’s useState to manage user inputs and the fetched game data dynamically.
  • fetchGames function retrieves games from Chess.com’s API, constructs the API URL based on user inputs, and handles errors gracefully.
  • The extractMoves function processes the game’s PGN (Portable Game Notation) to extract and clean just the chess moves, removing metadata, comments, and move numbers.
  • The interface is built with Material UI, featuring input fields for user data, a fetch button, and a table for displaying the results.
  • The games are displayed in a structured table, showing details like players, their ratings, the game result, and the extracted chess moves.
  • The app dynamically updates and displays game data based on user input, providing a clean, responsive user experience.

You should now see the following:image.png

Formatting Our Output

In this section, we’ll enhance the game viewer by integrating key features into our app. These include rendering chessboards with the final game positions using react-chessboard, extracting moves, and adding detailed game data.

Key Goals:

  • Use react-chessboard to display the final position of games.
  • Extract clean move sequences from PGN files.
  • Enhance the game table with FEN-calculated chessboards and game details.

Step 1: Install Required Libraries

To achieve our goals, we need two additional libraries:

  • react-chessboard for rendering the chessboard.
  • chess.js for calculating the FEN (Forsyth-Edwards Notation) of a game from its moves.

Install both libraries with the following command:

npm install react-chessboard chess.js

After installation, import the required components into your file:

import { Chessboard } from "react-chessboard"; // For chessboard rendering
import { Chess } from "chess.js"; // For FEN calculation

Step 2: Calculate FEN from PGN

The calculateFenFromMoves function takes a PGN (Portable Game Notation) string, cleans it to extract moves, and uses chess.js to compute the FEN of the final position. This FEN is used to render the chessboard.
Here’s the function:

const calculateFenFromMoves = (pgn) => {
  const chess = new Chess(); // Initialize a new Chess.js instance
  const cleanMoves = extractMoves(pgn); // Extract clean moves from PGN (previously defined)
  chess.loadPgn(cleanMoves); // Load moves into Chess.js
  return chess.fen(); // Return the FEN of the final position
};

Step 3: Fetch Games and Limit Results

We’ll update the fetchGames function to:

  1. Fetch games from the Chess.com API.
  2. Calculate the FEN for each game’s final position.
  3. Limit the number of games displayed to the first 50 for performance.
  4. Handle invalid games gracefully by skipping them. (This will handle the cases where you start the game from a specific position)

Here’s the updated function:

const fetchGames = async () => {
  if (!username || !month || !year) {
    alert("Please provide username, year, and month."); // Validate inputs
    return;
  }

  const url = `https://api.chess.com/pub/player/${username}/games/${year}/${month}`;

  try {
    const response = await fetch(url); // Fetch games from Chess.com API
    const data = await response.json(); // Parse JSON response

    const gamesWithFen = (data.games || [])
      .slice(0, 50) // Limit to the first 50 games
      .reduce((acc, game) => {
        try {
          const finalFen = calculateFenFromMoves(game.pgn); // Calculate FEN
          acc.push({
            ...game,
            finalFen, // Attach FEN to the game object
          });
        } catch (error) {
          console.warn(`Skipping invalid game with PGN: ${game.pgn}`, error); // Skip invalid games
        }
        return acc;
      }, []);

    setGames(gamesWithFen); // Update state with valid games
  } catch (error) {
    console.error("Error fetching games:", error); // Log fetch errors
    alert("Failed to fetch games. Please try again.");
  }
};

Step 4: Update the Game Table

We’ll now display the fetched games in a table. Each row will show:

  • A chessboard displaying the game’s final position.
  • Game details, including players, ratings, results, date, and move list.

Here’s the code to render the table:

{games.length > 0 && (
  <Table sx={{ marginTop: 3 }}>
    <TableBody>
      {games.map((game, index) => (
        <TableRow key={index}>
          {/* Chessboard Column */}
          <TableCell>
            <Chessboard
              position={game.finalFen || "start"} // Display final position or default to start
              arePiecesDraggable={false} // Disable piece dragging
              boardWidth={200} // Set board size
            />
          </TableCell>

          {/* Game Details Column */}
          <TableCell>
            <Box sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
              <Typography variant="h6">
                {game.white.username || "Anonymous"} ({game.white.rating}) vs{" "}
                {game.black.username || "Anonymous"} ({game.black.rating})
              </Typography>
              <Typography variant="body1">
                Result:{" "}
                {game.white.result === "win"
                  ? "White Wins"
                  : game.black.result === "win"
                  ? "Black Wins"
                  : "Draw"}
              </Typography>
              <Typography variant="body2">
                Date: {new Date(game.end_time * 1000).toLocaleString()}
              </Typography>
              <Typography variant="body2" sx={{ whiteSpace: "pre-wrap" }}>
                Moves: {extractMoves(game.pgn)}
              </Typography>
            </Box>
          </TableCell>
        </TableRow>
      ))}
    </TableBody>
  </Table>
)}

Final Code

Here’s the complete app code after implementing all steps:

import React, { useState } from "react";
import {
  TextField,
  Button,
  Container,
  Typography,
  Box,
  Table,
  TableBody,
  TableCell,
  TableRow,
} from "[@mui](/@/mui)/material";
import { Chess } from "chess.js";
import { Chessboard } from "react-chessboard";

// Function to clean and extract bare moves from PGN
const extractMoves = (pgn) => {
  const noMetadata = pgn.replace(/^\[.*\]$/gm, ""); // Remove metadata
  const noComments = noMetadata.replace(/\{.*?\}/g, ""); // Remove comments
  const noMoveNumbers = noComments.replace(/\d+\.+/g, ""); // Remove move numbers
  return noMoveNumbers.trim().replace(/\s+/g, " "); // Clean up extra spaces
};

// Function to calculate FEN from moves
const calculateFenFromMoves = (pgn) => {
  const chess = new Chess(); // Initialize Chess.js instance
  const cleanMoves = extractMoves(pgn); // Extract clean moves (defined above)
  chess.loadPgn(cleanMoves); // Load moves into Chess.js
  return chess.fen(); // Return the FEN of the final position
};

function App() {
  const [username, setUsername] = useState("");
  const [month, setMonth] = useState("");
  const [year, setYear] = useState("");
  const [games, setGames] = useState([]);

  const fetchGames = async () => {
    if (!username || !month || !year) {
      alert("Please provide username, year, and month."); // Validate inputs
      return;
    }

    const url = `https://api.chess.com/pub/player/${username}/games/${year}/${month}`;

    try {
      const response = await fetch(url); // Fetch games from Chess.com API
      const data = await response.json(); // Parse JSON response

      const gamesWithFen = (data.games || [])
        .slice(0, 50) // Limit to the first 50 games
        .reduce((acc, game) => {
          try {
            const finalFen = calculateFenFromMoves(game.pgn); // Calculate FEN
            acc.push({
              ...game,
              finalFen,
            });
          } catch (error) {
            console.warn(`Skipping invalid game with PGN: ${game.pgn}`, error);
          }
          return acc;
        }, []);

      setGames(gamesWithFen); // Update state with valid games
    } catch (error) {
      console.error("Error fetching games:", error); // Log fetch errors
      alert("Failed to fetch games. Please try again.");
    }
  };

  return (
    <Container>
      <Typography variant="h4" gutterBottom>
        Chess.com Game Viewer
      </Typography>
      <Box sx={{ display: "flex", gap: 2, marginTop: 3 }}>
        <TextField
          label="Username"
          value={username}
          onChange={(e) => setUsername(e.target.value)}
        />
        <TextField
          label="Year"
          value={year}
          onChange={(e) => setYear(e.target.value)}
        />
        <TextField
          label="Month"
          value={month}
          onChange={(e) => setMonth(e.target.value)}
        />
        <Button variant="contained" onClick={fetchGames}>
          Fetch Games
        </Button>
      </Box>
      {games.length > 0 && (
        <Table sx={{ marginTop: 3 }}>
          <TableBody>
            {games.map((game, index) => (
              <TableRow key={index}>
                <TableCell>
                  <Chessboard
                    position={game.finalFen || "start"}
                    arePiecesDraggable={false}
                    boardWidth={200}
                  />
                </TableCell>
                <TableCell>
                  <Box sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
                    <Typography variant="h6">
                      {game.white.username || "Anonymous"} ({game.white.rating})
                      vs {game.black.username || "Anonymous"} (
                      {game.black.rating})
                    </Typography>
                    <Typography variant="body1">
                      Result:{" "}
                      {game.white.result === "win"
                        ? "White Wins"
                        : game.black.result === "win"
                        ? "Black Wins"
                        : "Draw"}
                    </Typography>
                    <Typography variant="body2">
                      Date: {new Date(game.end_time * 1000).toLocaleString()}
                    </Typography>
                    <Typography
                      variant="body2"
                      sx={{ whiteSpace: "pre-wrap" }}
                    >
                      Moves: {extractMoves(game.pgn)}
                    </Typography>
                  </Box>
                </TableCell>
              </TableRow>
            ))}
          </TableBody>
        </Table>
      )}
    </Container>
  );
}

export default App;

Explanation:

  • Chessboard Rendering: Uses react-chessboard to display the final position of each game.
  • Move Extraction: Extracts and cleans up moves from the PGN format.
  • FEN Calculation: Utilizes chess.js to calculate the FEN for the final game position.
  • Error Handling: Skips invalid games and logs a warning without stopping the app.
  • Game Table: Displays a user-friendly table with chessboard visuals, player details, results, and moves.

You should now see the following:
image.png

Summary

In this blog, we dove into the fascinating intersection of chess and web development by building a React application powered by the Chess.com API. We began by understanding the API’s capabilities and how it allows us to fetch detailed data about users’ games. Leveraging this data, we developed an interactive application that displays user-specific chess games in a visually engaging and organized format.
Using react-chessboard, we rendered the final positions of games directly on the interface, while Material UI helped us structure and style the data for clarity and responsiveness. Our application features a two-column layout: one side displays the final position of each game on a chessboard, while the other provides detailed game information, including player names and ratings, game results, dates, and a list of moves extracted from the PGN. Along the way, we emphasized best practices, such as efficient data processing, robust error handling, and clean presentation.
This project demonstrates how to combine chess knowledge with modern web technologies to create a practical and aesthetically pleasing application. Whether you’re interested in building tools for game analysis, exploring educational chess applications, or creating personalized chess visualizations, the skills and techniques covered here serve as a strong foundation. The chessboard is just the start—your creativity can take this application in countless exciting directions. Let your passion for chess and coding inspire your next project!

Learn More

Here are the tools and resources we used in this article to build our Lichess game viewer application. Each of these is essential for recreating and understanding the project:

Visual Studio Code: We used VS Code as our code editor throughout this project. It’s a powerful tool for coding, debugging, and managing projects. Download it to streamline your development workflow.

Lichess API: The Lichess API is the backbone of this project, providing access to chess game data. Explore its extensive capabilities and learn how to fetch games, puzzles, and more.

React: React is the framework we used to build this dynamic and interactive application. Learn how to create components, manage state, and build engaging user interfaces.

Material UI: Material UI is the library we used to style the application and create components like tables, text fields, and buttons. Explore its components and theming options to enhance your React apps.

react-chessboard: This library allowed us to display the final position of games as a static chessboard. Learn how to use it to create visually appealing chessboard components in your applications.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *