Skip to content

Commit

Permalink
feat: add a copy of chip8 web
Browse files Browse the repository at this point in the history
  • Loading branch information
joao-conde committed Sep 26, 2023
1 parent f66bc94 commit 4f747f8
Show file tree
Hide file tree
Showing 20 changed files with 711 additions and 0 deletions.
24 changes: 24 additions & 0 deletions jc-nes-web/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[package]
name = "jc-nes-web"
version = "1.0.0"
authors = ["João Conde <joaodiasconde@gmail.com>"]
edition = "2018"
description = "CHIP-8 Web Emulator in Rust"
license = "MIT"
keywords = ["CHIP-8", "emulator", "rust", "wasm"]

[dependencies.jc-nes]
path = "../jc-nes"

[dependencies.salvo]
version = "0.45"
features = ["serve-static"]

[dependencies.tokio]
version = "1.29"
features = ["macros"]

[profile.release]
lto = true
debug = false
opt-level = 3
29 changes: 29 additions & 0 deletions jc-nes-web/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# CHIP-8 Web Emulator

Uses the [`jc-nes`](../jc-nes/) crate compiled to [WebAssembly](https://webassembly.org/) and the [Salvo crate](https://crates.io/crates/salvo/0.46.0) to serve the web emulator.

## Building

```
$ cargo build --release
```

## Running

```
$ cargo run --release
```

## Controls

Select a ROM to play.

Keys available:

`1 2 3 4`

`Q W E R`

`A S D F`

`Z X C V`
22 changes: 22 additions & 0 deletions jc-nes-web/site/canvas.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const PIXEL_SET_COLOR = 0x50CB93FF;
const PIXEL_UNSET_COLOR = 0x1B1A17FF;

const canvas = document.createElement("canvas");
canvas.width = 256;
canvas.height = 240;
const ctx = canvas.getContext("2d");

const scaledCanvas = document.querySelector("canvas#scaled");
const scaledCtx = scaledCanvas.getContext("2d");
scaledCtx.scale(scaledCanvas.width / canvas.width, scaledCanvas.height / canvas.height);
scaledCtx.imageSmoothingEnabled = false;

const image = ctx.createImageData(canvas.width, canvas.height);
const videoBuff = new DataView(image.data.buffer);

export function updateCanvas(pixels) {
for (let i = 0; i < pixels.length; i++)
videoBuff.setUint32(i * 4, pixels[i] ? PIXEL_SET_COLOR : PIXEL_UNSET_COLOR);
ctx.putImageData(image, 0, 0);
scaledCtx.drawImage(canvas, 0, 0);
}
35 changes: 35 additions & 0 deletions jc-nes-web/site/handlers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Nes } from "./jc_nes.js";

import { getROM } from "./roms.js";
import { KEY_MAPPER } from "./keys.js";
import { updateCanvas } from "./canvas.js";

let nes = null;

export const play = async () => {
const rom = await getROM();
nes = new Nes();
nes.load_rom(rom);
};

export const clock = () => {
if (nes) nes.clock();
};

export const render = () => {
if (nes) updateCanvas(nes.get_frame());
};

export const onKeyDown = (event) => {
const key = KEY_MAPPER[event.key];
if (key === undefined) return;
if (nes) nes.btn_down(key);
document.querySelector(`#key-${event.key}`).style.opacity = "1";
};

export const onKeyUp = (event) => {
const key = KEY_MAPPER[event.key];
if (key === undefined) return;
if (nes) nes.btn_up(key);
document.querySelector(`#key-${event.key}`).style.opacity = "0.3";
};
111 changes: 111 additions & 0 deletions jc-nes-web/site/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
@font-face {
font-family: 'M20';
src: url(./public/fonts/m20.ttf);
}

* {
font-family: 'M20';
color: #50CB93;
}

html, body {
margin: 0;
height: 100%;
background: #1B1A17;
}

body {
display: flex;
align-items: center;
justify-content: center;
}

main {
display: flex;
padding: 5em 2em;
justify-content: center;
}

main > div {
margin: 0 1em;
}

h1 {
margin: 0 0 0.25em 0;
font-size: 3rem;
font-weight: normal;
}

canvas {
margin-bottom: 0.5em;
border: 3px dashed #50CB93;
}

select {
display: flex;
background: transparent;
margin-left: -0.3em;
color: #50CB93;
border: unset;
font-size: 20px;
width: 100%;
margin-top: 0.5em;
}

select option {
font-size: 18px;
background: #1B1A17;
}

img {
width: 28px;
opacity: 0.3;
}

img:hover {
transition: all 0.1s ease;
opacity: 1;
}

a {
text-decoration: none;
}

#keyboard {
display: grid;
text-align: center;
grid-template-columns: repeat(4, 3em);
gap: 1em;
margin-bottom: 1em;
}

select:focus {
outline: none !important;
}

.left-sidebar {
width: 240px;
}

.right-sidebar > div:first-child {
height: 48px;
}

.key {
padding: 1em;
width: 1em;
height: 1em;
color: #50CB93;
outline: 2px solid #50CB93;
opacity: 0.3;
}

.key-overlay > div:first-child {
margin-bottom: 1em;
}

.separator {
width: 100%;
margin: 0.5em 0 1.5em -0.1em;
border-top: 3px dashed #50CB93;
}
48 changes: 48 additions & 0 deletions jc-nes-web/site/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<!DOCTYPE html>
<html>
<head>
<title>NES Emulator</title>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="./index.css">
<link rel="icon" type="image/svg+xml" href="./public/icons/favicon.svg">
<link rel="alternate icon" type="image/png" href="./public/icons/favicon-16x16.png">
<link rel="alternate icon" type="image/png" href="./public/icons/favicon-32x32.png">
</head>
<body>
<main>
<div class="left-sidebar"/></div>

<div class="main-wrapper">
<h1>NES</h1>

<canvas id="scaled" width="960" height="480"></canvas>

<div class="actions">
<a href="https://github.com/joao-conde/jc-nes">
<img src="./public/icons/github.png">
</a>
<a href="https://ko-fi.com/joaoconde">
<img src="./public/icons/kofi.png">
</a>
</div>
</div>

<div class="right-sidebar">
<div></div>

<select id="roms">
<option value="" disabled selected>Choose a ROM...</option>
</select>

<div class="separator"></div>

<div class="key-overlay">
<div>Keyboard</div>
<div id="keyboard"></div>
</div>
</div>
</main>

<script type="module" src="./index.js"></script>
</body>
</html>
25 changes: 25 additions & 0 deletions jc-nes-web/site/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { default as wasm } from "./jc_nes.js";

import { listROMs } from "./roms.js";
import { listKeys } from "./keys.js";
import { play, clock, render, onKeyDown, onKeyUp } from "./handlers.js";

const FPS = 1000;
const CLOCK_HZ = 1000;

(async () => {
// init wasm module
await wasm();

// set clock, video, audio and keyboard handlers
window.setInterval(clock, 1000 / CLOCK_HZ);
window.setInterval(render, 1000 / FPS);
// window.onkeydown = onKeyDown;
// window.onkeyup = onKeyUp;

// play ROM on change
document.querySelector('#roms').onchange = e => play(e.target.value);

listROMs();
listKeys();
})();
Loading

0 comments on commit 4f747f8

Please sign in to comment.