Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .github/release-notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<p align="center">
<img src="https://raw.githubusercontent.com/Mason363/NumChess/{{TAG}}/docs/hero.gif" width="640" alt="Playing against a bot">
</p>

Chess for the NumWorks N0120, in a native app of about 20 KB.

### Install

1. Download **`chess.nwa`** below.
2. Plug in the calculator and open [my.numworks.com/apps](https://my.numworks.com/apps) in Chrome or Edge.
3. Upload the file. **Chess** appears at the end of the home screen.

### What's inside

* **Play the bots:** 12 opponents from Pip (150) to Atlas (2100). Play White, Black or a random color, with undo, flip and resign.
* **Puzzles:** generated on the calculator, so they never run out. Modes are Mix, Mate in 1, 2 or 3, and Best move, with a rating, a streak, hints and solutions.
* **2 Players:** a chess clock (1+0 up to 30+0, or none) and a board that turns to face whoever is to move.
* **Full rules:** castling, en passant, underpromotion, and draws by stalemate, repetition, the 50-move rule or insufficient material.

<p align="center">
<img src="https://raw.githubusercontent.com/Mason363/NumChess/{{TAG}}/docs/puzzle.gif" width="400" alt="Solving a puzzle">
<img src="https://raw.githubusercontent.com/Mason363/NumChess/{{TAG}}/docs/clock.gif" width="400" alt="Two-player clock">
</p>
18 changes: 18 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Build
on: [push, pull_request]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install ARM toolchain
run: sudo apt-get update && sudo apt-get install -y gcc-arm-none-eabi libnewlib-arm-none-eabi
- name: Engine tests
run: make test
- name: Build
run: make build check
- uses: actions/upload-artifact@v4
with:
name: chess.nwa
path: output/chess.nwa
36 changes: 36 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Release
on:
push:
tags: ['v*']
workflow_dispatch:
inputs:
tag:
description: Tag to release, for example v1.0.0
required: true

permissions:
contents: write

jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install ARM toolchain
run: sudo apt-get update && sudo apt-get install -y gcc-arm-none-eabi libnewlib-arm-none-eabi
- name: Test
run: make test
- name: Build
run: make build check
- name: Publish release
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ github.event.inputs.tag || github.ref_name }}
run: |
if gh release view "$TAG" > /dev/null 2>&1; then
gh release upload "$TAG" output/chess.nwa --clobber
else
sed "s/{{TAG}}/$TAG/g" .github/release-notes.md > notes.md
gh release create "$TAG" output/chess.nwa --target "$GITHUB_SHA" \
--title "NumChess $TAG" --notes-file notes.md
fi
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
output/
__pycache__/
48 changes: 48 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# NumChess — chess for the NumWorks calculator.
# make build output/chess.nwa
# make run install on a connected calculator
# make check link the app like the calculator does (output/chess.bin)
# make test run engine tests on the host
Q ?= @
CC = arm-none-eabi-gcc
NWLINK = npx --yes -- nwlink@1.0.0
BUILD_DIR = output
SRC = src/main.c src/chess.c src/libc.c

CFLAGS = -std=c99 $(shell $(NWLINK) eadk-cflags-device)
CFLAGS += -Os -Wall -fno-math-errno -fno-tree-loop-distribute-patterns -fno-reorder-functions -flto-partition=one
CFLAGS += -flto -fno-fat-lto-objects -fwhole-program -fvisibility=internal
LDFLAGS = -Wl,--relocatable -nostartfiles --specs=nano.specs
LDFLAGS += -Wl,-e,main -Wl,-u,eadk_app_name -Wl,-u,eadk_app_icon -Wl,-u,eadk_api_level
LDFLAGS += -Wl,--gc-sections -flinker-output=nolto-rel

.PHONY: build run check test clean
build: $(BUILD_DIR)/chess.nwa

run: $(BUILD_DIR)/chess.nwa
@echo "INSTALL $<"
$(Q) $(NWLINK) install-nwa $<

check: $(BUILD_DIR)/chess.nwa
$(Q) $(NWLINK) nwa-bin $< $(BUILD_DIR)/chess.bin
@echo "BIN $(BUILD_DIR)/chess.bin: $$(wc -c < $(BUILD_DIR)/chess.bin) bytes"

$(BUILD_DIR)/chess.nwa: $(SRC) src/chess.h src/sprites.h $(BUILD_DIR)/icon.o
@echo "LD $@"
$(Q) $(CC) $(CFLAGS) $(LDFLAGS) $(SRC) $(BUILD_DIR)/icon.o -o $@
$(Q) arm-none-eabi-strip --strip-unneeded $@
$(Q) arm-none-eabi-size $@

$(BUILD_DIR)/icon.o: src/icon.png | $(BUILD_DIR)
@echo "ICON $<"
$(Q) $(NWLINK) png-icon-o $< $@

$(BUILD_DIR):
$(Q) mkdir -p $@

test: | $(BUILD_DIR)
$(Q) cc -O2 -DPLATFORM_DEVICE=1 $(shell $(NWLINK) eadk-cflags-simulator) test/perft.c -o $(BUILD_DIR)/perft
$(Q) $(BUILD_DIR)/perft

clean:
$(Q) rm -rf $(BUILD_DIR)
178 changes: 178 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
<p align="center">
<img src="docs/icon.png" width="84" alt="NumChess icon">
</p>

<h1 align="center">NumChess</h1>

<p align="center">
<b>Chess for the NumWorks N0120 calculator.</b><br>
12 bots, endless puzzles and a two-player clock, in a native app of about 20 KB.
</p>

<p align="center">
<a href="https://github.com/Mason363/NumChess/releases/latest"><img src="https://img.shields.io/github/v/release/Mason363/NumChess?label=release&color=81b64c" alt="Latest release"></a>
<a href="https://github.com/Mason363/NumChess/actions/workflows/build.yml"><img src="https://github.com/Mason363/NumChess/actions/workflows/build.yml/badge.svg" alt="Build status"></a>
<img src="https://img.shields.io/badge/installed%20size-20%20KB-3a3835" alt="Installed size: 20 KB">
</p>

<p align="center">
<a href="https://github.com/Mason363/NumChess/releases/latest/download/chess.nwa"><b>Download chess.nwa</b></a>
&nbsp;·&nbsp; <a href="#install">Install</a>
&nbsp;·&nbsp; <a href="#controls">Controls</a>
&nbsp;·&nbsp; <a href="#build">Build</a>
</p>

<p align="center">
<img src="docs/hero.gif" width="640" alt="Choosing a bot and playing the Italian Game against Nova">
</p>

## Highlights

* **12 bots**, from Pip (150) to Atlas (2100), most of them at the beginner end.
* **Endless puzzles** created on the calculator: mate in 1, 2 or 3, or find the one winning move. A rating and a streak track your progress.
* **Two-player mode** with a chess clock. The board turns to face whoever is to move.
* **Fast and small:** about 20 KB installed. The piece set takes 658 bytes, and there's no puzzle database.

<table>
<tr>
<td align="center"><img src="docs/menu.png" width="390" alt="Main menu"><br><sub>Main menu</sub></td>
<td align="center"><img src="docs/italian.png" width="390" alt="A game in progress"><br><sub>A game against Nova</sub></td>
</tr>
</table>

## Play the bots

Pick an opponent and a color (White, random or Black), then play. Each bot beats the one below it in bot-vs-bot matches. The Elo numbers are rough estimates.

<p align="center">
<img src="docs/roster.png" width="640" alt="All 12 bots with their Elo ratings">
</p>

<table>
<tr>
<td align="center"><img src="docs/bots.gif" width="390" alt="Scrolling through the bots"><br><sub>Choose a bot and your color</sub></td>
<td align="center"><img src="docs/bots.png" width="390" alt="Atlas selected"><br><sub>Atlas, the strongest bot, thinks up to 2.5 s per move</sub></td>
</tr>
<tr>
<td align="center"><img src="docs/select.png" width="390" alt="Legal moves shown as dots and capture rings"><br><sub>Legal moves as dots, captures as rings</sub></td>
<td align="center"><img src="docs/check.png" width="390" alt="King in check glowing red"><br><sub>Check glow, last-move highlight, captured pieces with material count</sub></td>
</tr>
<tr>
<td align="center"><img src="docs/promotion.png" width="390" alt="Promotion picker"><br><sub>Promotion picker</sub></td>
<td align="center"><img src="docs/gameover.png" width="390" alt="Game over card"><br><sub>Result card with rematch</sub></td>
</tr>
<tr>
<td align="center"><img src="docs/pause.png" width="390" alt="Pause menu"><br><sub>Back opens the pause menu: undo, flip, resign</sub></td>
<td></td>
</tr>
</table>

## Puzzles

Every puzzle is generated on the calculator. It plays a quick game between a careful side and a blunder-prone side, then stops at a position with a forced mate or a single winning move. Puzzles never run out, and the app stores none of them.

<p align="center">
<img src="docs/puzzle.gif" width="640" alt="Solving a mate in 2 puzzle">
</p>

* **Modes:** Mix, Mate in 1, Mate in 2, Mate in 3 and Best move.
* **Opening move:** each puzzle starts by playing the opponent's last move, which is the mistake you punish.
* **Accepted solutions:** any move that still forces the mate counts. The opponent defends as stubbornly as possible.
* **Rating:** it goes up or down after each puzzle, and Mix and Best move pick puzzles near it. A streak counts your solves in a row.
* **Pause menu:** Hint highlights the piece to move, Solution plays the answer, and Next skips.
* **Speed:** the next puzzle is prepared while you look at the solved one.

<table>
<tr>
<td align="center"><img src="docs/puzzle-menu.png" width="390" alt="Puzzle modes"><br><sub>Puzzle modes</sub></td>
<td align="center"><img src="docs/puzzle-mate1.png" width="390" alt="Mate in 1"><br><sub>Mate in 1: the pill shows who is to move</sub></td>
</tr>
<tr>
<td align="center"><img src="docs/puzzle-mate3.png" width="390" alt="Mate in 3 as Black"><br><sub>Mate in 3 as Black: the board flips for you</sub></td>
<td align="center"><img src="docs/puzzle-best.png" width="390" alt="Best move puzzle"><br><sub>Best move: find the knight fork</sub></td>
</tr>
<tr>
<td align="center"><img src="docs/puzzle-hint.png" width="390" alt="Hint highlight"><br><sub>Hint</sub></td>
<td align="center"><img src="docs/puzzle-wrong.png" width="390" alt="Wrong move"><br><sub>Wrong move: it's taken back and your rating drops</sub></td>
</tr>
<tr>
<td align="center"><img src="docs/puzzle-solved.png" width="390" alt="Solved"><br><sub>Solved</sub></td>
<td align="center"><img src="docs/puzzle-pause.png" width="390" alt="Puzzle pause menu"><br><sub>Hint, solution, next</sub></td>
</tr>
</table>

## Two players

Play over the board with a chess clock. After every move the board turns to face the next player. If you prefer a fixed board, use Flip in the pause menu.

<p align="center">
<img src="docs/clock.gif" width="640" alt="Two players with a 3+2 clock">
</p>

<table>
<tr>
<td align="center"><img src="docs/time.png" width="260" alt="Time controls"><br><sub>1+0 to 30+0, or no clock</sub></td>
<td align="center"><img src="docs/clock.png" width="260" alt="Clocks"><br><sub>The active clock is lit</sub></td>
<td align="center"><img src="docs/clock-low.png" width="260" alt="Low time"><br><sub>Under 10 s it turns red and shows tenths</sub></td>
</tr>
</table>

All the rules are included: castling, en passant, underpromotion, and draws by stalemate, threefold repetition, the 50-move rule or insufficient material. You can also agree to a draw or resign.

## Controls

| Key | Action |
|---|---|
| Arrows | Move the cursor (it wraps around the edges) |
| OK or EXE | Select a piece, then play a move |
| Back | Deselect, open the pause menu, or go back a screen |
| ⌫ | Undo |
| Home | Quit |

## Install

1. Download **[chess.nwa](https://github.com/Mason363/NumChess/releases/latest/download/chess.nwa)** from the [latest release](https://github.com/Mason363/NumChess/releases/latest).
2. Plug the calculator into your computer and open **[my.numworks.com/apps](https://my.numworks.com/apps)** in Chrome or Edge.
3. Upload `chess.nwa`. **Chess** appears at the end of the calculator's home screen.

If you're building from source, `make run` builds and installs in one step.

## Small by design

<p align="center">
<img src="docs/pieces.png" width="640" alt="The piece set, zoomed 4x">
</p>

| | |
|---|---|
| Installed size | about 20 KB (20,074 bytes of code and data) |
| Piece set | 658 bytes: 3-bit anti-aliased sprites, and symmetric pieces store only half |
| Puzzle data | none: every puzzle is generated live |
| RAM | about 52 KB, all static, no heap |

* **Engine** (`src/chess.c`):
* 0x88 board with incremental hashing and evaluation.
* PeSTO piece-square tables, mirrored and packed into 384 bytes.
* Alpha-beta search with PVS, a transposition table, null-move pruning, late-move reductions, killer moves and history heuristics.
* A mate solver.
* Bots get weaker through search depth, evaluation noise and occasional random moves.
* **Graphics** (`src/main.c`):
* Each board square is drawn in one pass: highlights, check glow, move hints and the piece, all blended in RGB565.
* Rounded cards and circles are anti-aliased.
* Moves animate with an ease-out curve.
* Text uses the calculator's own fonts.
* **Pieces** are drawn as signed-distance shapes by `tools/pieces.py` and rendered to sprites. `tools/icon.py` makes the app icon the same way.
* **Build:** `-Os`, LTO, and small replacements for the libc routines.

## Build

Requirements: the ARM embedded toolchain (`arm-none-eabi-gcc`) and Node.js, which `make` uses to fetch NumWorks' `nwlink` SDK.

```sh
make # output/chess.nwa
make check # link it the way the calculator does and print the installed size
make test # engine tests on the host: perft on six positions, hashing, search benchmark
make run # install on a connected calculator
```

Every push is built by GitHub Actions. Pushing a `v*` tag publishes a release with `chess.nwa` attached.
Binary file added docs/bots.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/bots.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/check.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/clock-low.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/clock.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/clock.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/gameover.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/hero.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/italian.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/menu.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/pause.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/pieces.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/promotion.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/puzzle-best.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/puzzle-hint.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/puzzle-mate1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/puzzle-mate3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/puzzle-menu.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/puzzle-pause.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/puzzle-solved.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/puzzle-wrong.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/puzzle.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/roster.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/select.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/time.png
Loading
Loading