Skip to content
This repository has been archived by the owner on Dec 24, 2020. It is now read-only.

Commit

Permalink
Final push for the new improved Space Invaders
Browse files Browse the repository at this point in the history
  • Loading branch information
jeffpar committed Sep 28, 2019
1 parent fda3c20 commit aeb7769
Show file tree
Hide file tree
Showing 19 changed files with 880 additions and 824 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
layout: post
title: Space Invaders Revisited
date: 2019-09-27 10:00:00
permalink: /blog/2019/09/27/
date: 2019-09-28 10:00:00
permalink: /blog/2019/09/28/
preview: /blog/images/space-invaders-1978.png
machines:
- id: invaders
Expand Down Expand Up @@ -54,12 +54,12 @@ A few months ago, I decided to continue the evolution of those new classes, star
that I had previously emulated: the 8080-based arcade machine Space Invaders.

First, since I always like to start with an operational debugger, I took the most useful features common to
all the PCjs debuggers and packed them into a new [Debugger](/modules/devices/dbgio.js) base class, which provided most
all the PCjs debuggers and packed them into a new [Debugger](/modules/devices/dbgio.js) base class, which provides most
of the commands that the new [8080 Debugger](/modules/devices/dbg8080.js) needs.

Similarly, I separated management of the browser display elements into a new [Monitor](/modules/devices/monitor.js)
base class, so that the Space Invaders [Video](/modules/devices/invaders/video.js) device can focus on the graphics
hardware. And the handful of the machine's I/O ports are implemented by a [Chip](/modules/devices/invaders/chip.js)
Then I separated management of the browser display elements into a new [Monitor](/modules/devices/monitor.js)
base class, so that the Space Invaders [Video](/modules/devices/invaders/video.js) device could focus on the graphics
hardware. And the handful of machine I/O ports are implemented by a [Chip](/modules/devices/invaders/chip.js)
device that extends a standard [Port](/modules/devices/ports.js) class, which plugs into the new [Bus](/modules/devices/bus.js)
class, which implements as many buses as a machine needs (eg, memory and I/O).

Expand All @@ -74,7 +74,7 @@ to first "include" (ie, *import* or *require*) the class that contains *printf()

I've also done away with specialized PCjs printing functions like *printMessage()* and *printMessageIO()*. Instead,
if a device wants to assign certain print operations to certain message groups (ie, sets of messages that can be turned
on or off through the debugger), it simply includes the **MESSAGE** id as the first parameter to *printf()*.
on or off through the debugger), it simply includes the MESSAGE id as the first parameter to *printf()*.

Debugger input and output controls have been unified into a single textarea "window", there's improved breakpoint
management for setting read and write breakpoints on any valid memory or I/O address, an execution history buffer can
Expand All @@ -83,9 +83,13 @@ browser debug console window via a global *window.command()* function.

## Time to Kill

The new emulation should be running below. Keys are defined by the [Input](/modules/devices/input.js) device's
keyboard mappings in the machine's [configuration file](/devices/pc8080/machine/invaders/new/invaders.json). Here's
a summary:
Now, as much as I love Space Invaders -- it was the first arcade game I became addicted to back in 1979 -- the goal here
wasn't really to make yet another clone of Space Invaders. I just wanted to make it easier to build more web-based emulators,
fix some things that have long bugged me, make the animation smoother, improve debugging and machine configuration, and so on.

The new Space Invaders emulation should be running below. Keys are mapped by the [Input](/modules/devices/input.js) device
to the machine's buttons using "map" data provided in the machine [configuration file](/devices/pc8080/machine/invaders/new/invaders.json).
Here's a summary:

- **1**: One Player
- **2**: Two Players
Expand All @@ -94,8 +98,23 @@ a summary:
- **D** or **Right**: Move Right
- **L** or **Space**: Fire

If it's not running, or it's not running well, then there's obviously more work to do. An emulator is never really done,
because the emulation can always be made just a little bit better. But I feel like this is a nice fresh start.
For touch-screen devices like the iPhone and iPad, I've implemented a quick-and-dirty mapping, where regions across
the top of the monitor correspond to first three buttons:

- Top Left: One Player
- Top Right: Two Players
- Top Center: Insert Coin

and regions across the bottom of the monitor correspond to the last three buttons:

- Left Side: Move Left and Move Right
- Right Side: Fire

This is purely experimental and may only work in portrait mode; landscape and full-screen modes will probably need
more work to make them usable.

An emulator is never really done, because an emulation can always be made just a little bit better. But I feel like
this is a nice fresh start.

{% include machine.html id="invaders" config="json" %}

Expand All @@ -118,4 +137,4 @@ because the emulation can always be made just a little bit better. But I feel l
</div>

*[@jeffpar](https://jeffpar.com)*
*September 27, 2019*
*September 28, 2019*
30 changes: 24 additions & 6 deletions devices/pc8080/machine/invaders/new/invaders.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,30 @@
"input": {
"class": "Input",
"map": {
"1p": "1",
"2p": "2",
"coin": "3",
"left": ["A","Left"],
"right": ["D","Right"],
"fire": ["L"," "]
"1p": {
"keys": "1",
"grid": [4, 4, 0, 0]
},
"2p": {
"keys": "2",
"grid": [4, 4, 3, 0]
},
"coin": {
"keys": "3",
"grid": [4, 4, 2, 0]
},
"left": {
"keys": ["A","Left"],
"grid": [4, 4, 0, 3]
},
"right": {
"keys": ["D","Right"],
"grid": [4, 4, 1, 3]
},
"fire": {
"keys": ["L"," "],
"grid": [4, 4, 3, 3]
}
}
},
"chip": {
Expand Down
56 changes: 30 additions & 26 deletions modules/devices/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@

/**
* @typedef {Object} SurfaceListener
* @property {string} id
* @property {number} cxGrid
* @property {number} cyGrid
* @property {number} xGrid
Expand Down Expand Up @@ -155,18 +156,26 @@ class Input extends Device {

/*
* There are two map forms: a two-dimensional grid, and a list of logical key names; for the latter,
* we convert each logical key name to an object with "keynames" and "state" properties, and as the keys
* go down and up, the corresponding "state" is updated (0 or 1).
* we convert each logical key name to an object with "keys", "grid", and "state" properties, and
* as the keys go down and up (or mouse/touch events occur within the grid), the corresponding "state"
* is updated (0 or 1).
*/
this.map = this.config['map'];
if (this.map && !this.map.length) {
let ids = Object.keys(this.map);
for (let i = 0; i < ids.length; i++) {
let grid = [];
let id = ids[i];
let keynames = this.map[id];
if (typeof keynames == "string") keynames = [keynames];
let keys = this.map[id];
if (typeof keys == "string") {
keys = [keys];
} else if (keys.length == undefined) {
grid = keys['grid'];
keys = keys['keys'];
if (typeof keys == "string") keys = [keys];
}
let state = 0;
this.map[id] = {keynames, state};
this.map[id] = {keys, grid, state};
}
}

Expand Down Expand Up @@ -211,15 +220,25 @@ class Input extends Device {
}

/**
* addKeyListener(id, func)
* addListener(id, func)
*
* @this {Input}
* @param {string} id
* @param {function(string,boolean)} func
*/
addKeyListener(id, func)
addListener(id, func)
{
this.aKeyListeners.push({id, func});
let map = this.map[id];
if (map) {
let keys = map.keys;
if (keys && keys.length) {
this.aKeyListeners.push({id, func});
}
let grid = map.grid;
if (grid && grid.length) {
this.aSurfaceListeners.push({id, cxGrid: grid[0], cyGrid: grid[1], xGrid: grid[2], yGrid: grid[3], func});
}
}
}

/**
Expand Down Expand Up @@ -370,21 +389,6 @@ class Input extends Device {
}
}

/**
* addSurfaceListener(cxGrid, cyGrid, xGrid, yGrid, func)
*
* @this {Input}
* @param {number} cxGrid
* @param {number} cyGrid
* @param {number} xGrid
* @param {number} yGrid
* @param {function(boolean)} func
*/
addSurfaceListener(cxGrid, cyGrid, xGrid, yGrid, func)
{
this.aSurfaceListeners.push({cxGrid, cyGrid, xGrid, yGrid, func});
}

/**
* checkSurfaceListeners(action, x, y, cx, cy)
*
Expand All @@ -401,13 +405,13 @@ class Input extends Device {
for (let i = 0; i < this.aSurfaceListeners.length; i++) {
let listener = this.aSurfaceListeners[i];
if (action == Input.ACTION.RELEASE) {
listener.func(false);
listener.func(listener.id, false);
continue;
}
let cxSpan = (cx / listener.cxGrid)|0, xActive = (x / cxSpan)|0;
let cySpan = (cy / listener.cyGrid)|0, yActive = (y / cySpan)|0;
if (xActive == listener.xGrid && yActive == listener.yGrid) {
listener.func(true);
listener.func(listener.id, true);
}
}
}
Expand Down Expand Up @@ -645,7 +649,7 @@ class Input extends Device {
let ids = Object.keys(this.map);
for (let i = 0; i < ids.length; i++) {
let id = ids[i];
if (this.map[id].keynames.indexOf(keyName) >= 0) {
if (this.map[id].keys.indexOf(keyName) >= 0) {
this.checkKeyListeners(id, down);
this.map[id].state = down? 1 : 0;
return true;
Expand Down
17 changes: 5 additions & 12 deletions modules/devices/invaders/chip.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,18 +63,11 @@ class Chip extends Port {
this.bus.addBlocks(config['addr'], config['size'], Port.TYPE.READWRITE, this);
}
this.input = /** @type {Input} */ (this.findDeviceByClass(Machine.CLASS.INPUT));
this.input.addKeyListener("1p", this.onButton.bind(this));
this.input.addKeyListener("2p", this.onButton.bind(this));
this.input.addKeyListener("coin", this.onButton.bind(this));
this.input.addKeyListener("left", this.onButton.bind(this));
this.input.addKeyListener("right", this.onButton.bind(this));
this.input.addKeyListener("fire", this.onButton.bind(this));
this.input.addSurfaceListener(4, 4, 0, 0, this.onButton.bind(this, "1p"));
this.input.addSurfaceListener(4, 4, 3, 0, this.onButton.bind(this, "2p"));
this.input.addSurfaceListener(4, 4, 2, 0, this.onButton.bind(this, "coin"));
this.input.addSurfaceListener(4, 4, 0, 3, this.onButton.bind(this, "left"));
this.input.addSurfaceListener(4, 4, 1, 3, this.onButton.bind(this, "right"));
this.input.addSurfaceListener(4, 4, 3, 3, this.onButton.bind(this, "fire"));
let onButton = this.onButton.bind(this);
let buttonIDs = Object.keys(Chip.STATUS1.KEYMAP);
for (let i = 0; i < buttonIDs.length; i++) {
this.input.addListener(buttonIDs[i], onButton);
}
this.onReset();
}

Expand Down
Loading

0 comments on commit aeb7769

Please sign in to comment.