Building a Mobile Chess App


My interest in chess was recently reignited by an Atlantic article covering the ways in which extraordinarily powerful engines have upended the game. In particular, I found myself fascinated by chess master Levy Rozman’s analyses of Stockfish vs. AlphaZero games, but needed a strategy refresher to follow Rozman’s explanations. The one kilobyte JavaScript chess engine created by Oscar Toledo provided a good starting point, but I wanted a slightly stronger opponent and a few more features (like the ability to play offline on my phone, and the ability to play as black).

While there’s no shortage of fantastic mobile chess apps, I desired something a bit more minimalist. Plus, after a few humbling defeats, I developed a slight vendetta against the one kilobyte bot, and set a goal of becoming a strong enough player to easily destroy it. I decided on creating my own mobile app as a side project, using Toledo’s Nanochess engine as a starting point.

Play an in-browser version below, or read on to learn how to convert browser-based JavaScript games into offline-capable mobile apps.


Converting a browser-based JavaScript game into a mobile app

To produce a working prototype while avoiding the chore of spinning up a mobile development environment, I gravitated towards a front-end HTML/JS/CSS chess app that I could embed in a React Native Webview (sort of like an iframe for mobile applications). After selecting the following open source projects, I was most mostly done without writing a single line of code:

I made several modifications to Óscar Toledo G.’s chess engine. As the stock version used a difficulty setting of two ply, I increased that to four, offering a stronger challenge. (To increase the variety of the engine’s openings, I randomized ply between two and four for the first three turns.) I also wrapped the AI in an object constructor function to better encapsulate and define its interface, and randomly selected player color.

To make FontAwesome’s chess piece icons somewhat more programmer-friendly, I downloaded FontAweseome as a font file, remapped the icons to unicode chess piece symbols (e.g. ♜) using Birdfont and deleted all but the six chess piece icons I needed, allowing CSS rules such as the following:

#board .white-rook::after, #board .black-rook::after {
  font-family: chess;
  content: '♜';
}

#board .white-rook::after { color: #FFFFF0; }
#board .black-rook::after { color: #222; }

All that was left for me to do was create an HTML chess board (a styled table) and wire together the various components. While there’s nothing particularly special about my chess app, the ease in which I built it is a testament to the work of other open source developers.


Instructions:

Required Tools


Build Process

Step 1: Start by constructing your browser application inside a web directory - mine looked like this:

web
├── index.html
|
├── js
|   ├── ai.js
|   ├── chess.js
|   └── main.js
|
└── css
    ├── basic.css
    ├── board.css
    ├── chessawesome.css
    ├── indicators.css
    ├── rotation.css
    └── typography.css

Step 2: As React Native Webviews can’t easily serve CSS & JS asset files from a local directory structure, compact the app into a single index.html file using the SingleFile browser extension – this encodes any assets (images, fonts, etc.) as inline base64 strings. Install SingleFile and customize its options as follows:

Then, use SingleFile to convert the app to a single file.


Step 3: Create an Expo application - refer to the Expo tutorial for instructions.

Add react-native-webview package to the project’s dependencies in its package.json file, and run yarn install.

Add the compacted index.html file to the Expo project’s root directory, and replace the content of the project’s App.js with the following code:

import { SafeAreaView } from 'react-native';
import { WebView } from 'react-native-webview';

import { useAssets } from 'expo-asset';
import { useState } from 'react';
import { readAsStringAsync } from 'expo-file-system';

export default function App() {
  const [index, indexLoadingError] = useAssets(
    require('./index.html')
  );

  const [html, setHtml] = useState('');

  if (index) {
    readAsStringAsync(index[0].localUri).then((data) => {
      setHtml(data);
    });
  }

  return (
    <SafeAreaView style={{ flex: 1, backgroundColor: '#333' }}>
      <WebView
        source={{ html }}
      />
    </SafeAreaView>
  );
}

The app may now be launched locally via Expo (e.g yarn expo run:android / yarn expo run:ios – refer to https://reactnative.dev/docs/environment-setup for instruction on setting up a development environment for Android or iOS apps).


Step 4: Build your application through Expo Application Services to obtain an installable mobile application (e.g. eas build --profile preview --platform android)

Refer to https://docs.expo.dev/build/internal-distribution for instruction on building an app for personal usage without going through the Google or Apple app stores.