Skip to content

Commit

Permalink
Open Sourced WasmBoy Amp-script Demo / Experiment (#202)
Browse files Browse the repository at this point in the history
* Moved over the amp demo from secret repo

* Got the amp demo workflow working

* Fixed the amp rollup config for prod

* Fixed travis npm i errors maybe added building demos to travis steps

* Run the demo build before running tests
  • Loading branch information
torch2424 committed Dec 2, 2018
1 parent 3a83f9a commit 2a5506f
Show file tree
Hide file tree
Showing 11 changed files with 1,099 additions and 601 deletions.
3 changes: 2 additions & 1 deletion .travis.yml
Expand Up @@ -3,9 +3,10 @@ notifications:
language: node_js
sudo: false
node_js:
- "node"
- 'node'
install:
- npm install
script:
- npm run prettier:lint
- npm run demo:build
- npm test
37 changes: 37 additions & 0 deletions demo/amp/README.md
@@ -0,0 +1,37 @@
# wasmBoy-amp

An Experiment of using the WasmBoy TS Core inside of AMP

# Amp Script Notes

- Empty amp script wont build. Should default to a 1x1 to force build
- Need better docs on using the core
- Need to export helper functions used on the core into a seperate npm module
- Images are sanitized :p

This hack didnt work

```
// Create an svg with an image tag (hack around img bad list)
const svg = document.createElement('svg');
svg.setAttribute('width', 160);
svg.setAttribute('height', 144);
const image = document.createElement('image');
image.setAttribute('xlink:href', imageDataUrl);
image.setAttribute('x', 0);
image.setAttribute('y', 0);
image.setAttribute('width', 160);
image.setAttribute('height', 144);
svg.appendChild(image);
document.body.appendChild(svg);
```

- No classList on nodes
- Cant create amp-image with data uri
- c.replace is not a function :p
- Can't set attribute to a number
- Query selector on element not working?
- Couldn't add event listener to button unless I created it
- Can't append child to element gotten by id
- key down event only works on divs sometimes?
18 changes: 18 additions & 0 deletions demo/amp/dataUriToArray.js
@@ -0,0 +1,18 @@
// https://stackoverflow.com/questions/6850276/how-to-convert-dataurl-to-file-object-in-javascript
export default function(dataURI) {
const byteString = atob(dataURI.split(',')[1]);

const mimeString = dataURI
.split(',')[0]
.split(':')[1]
.split(';')[0];

// write the bytes of the string to an ArrayBuffer
const ab = new ArrayBuffer(byteString.length);
const ia = new Uint8Array(ab);
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}

return ia;
}
93 changes: 93 additions & 0 deletions demo/amp/index.html
@@ -0,0 +1,93 @@
<!doctype html>
<html >
<head>
<meta charset="utf-8">
<link rel="canonical" href="self.html" />
<meta name="viewport" content="width=device-width,minimum-scale=1">
<style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>

<style amp-custom>
html,
body {
margin: 0;
padding: 0;
font-family: Helvetica, Arial, sans-serif;
background: #edf0f0;

}

#root {
display: flex;
position: relative;
justify-content: center;
align-items: center;
flex-wrap: wrap;

width: 100vw;
height: 100vh;
padding: 10px;
max-width: 800px;
}

#description {
z-index: 2;
width: 100%;
white-space: pre-line;
}

#controls {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 1;
opacity: 0;
}

#wasmboy-svg-output {
display: block;
width: 80%;
border: solid 1px #000;
}

</style>

<script async src="https://cdn.ampproject.org/v0.js"></script>
<script async custom-element="amp-script" src="https://cdn.ampproject.org/v0/amp-script-0.1.js"></script>
</head>

<body>

<amp-script layout="container" src="wasmboy-amp.js">
<div id="root">
<h1>⚡wAsMP Boy⚡</h1>
<p id="description">
<a href="https://github.com/torch2424/wasmBoy" target="_blank">WasmBoy</a> TS Core Running within <a href="https://github.com/ampproject/amphtml/tree/master/extensions/amp-script" target="_blank">amp-script</a> and <a href="https://github.com/ampproject/worker-dom" target="_blank">Worker DOM</a>.
Runs slow due to experimenal amp-script / Worker DOM limitations.

<b>NOTE:</b>
Play on desktop.
You must click the game to control it.

<b>You must toggle the amp-script experiment:</b>
`AMP.toggleExperiment('amp-script')` in Dev tools console.

<b>ROM played is: <a href="http://tangramgames.dk/tobutobugirl/" target="_blank">Tobu Tobu Girl</a></b>

<b>Controls:</b>
Up,Down,Left,Right = Arrow Keys, WASD
A = Z
B = X
Start = Enter
Select = Shift
Play/Pause = Space
</p>

</div>
</amp-script>

</body>

</html>

148 changes: 148 additions & 0 deletions demo/amp/index.js
@@ -0,0 +1,148 @@
import getWasmBoyTsCore from '../../dist/core/getWasmBoyTsCore.esm.js';

import stringToArrayBuffer from 'string-to-arraybuffer';

import dataUriToArray from './dataUriToArray.js';
import rgbaArrayBufferToSvg from './pixelToSvg.js';
import romUrl from '../../test/performance/testroms/tobutobugirl/tobutobugirl.gb';

let imageDataArray;

const runTask = async () => {
const WasmBoy = await getWasmBoyTsCore();
console.log('WasmBoy', WasmBoy);

// Convert the rom Url to an array buffer
const ROM = dataUriToArray(romUrl);
console.log('Rom', ROM);

// Clear Memory
for (let i = 0; i <= WasmBoy.byteMemory.length; i++) {
WasmBoy.byteMemory[i] = 0;
}

// Load the ROM into memory
WasmBoy.byteMemory.set(ROM, WasmBoy.instance.exports.CARTRIDGE_ROM_LOCATION);

// Config the core
const configParams = [0, 1, 0, 0, 0, 0, 0, 0, 0];
WasmBoy.instance.exports.config.apply(WasmBoy.instance, configParams);

const keyMap = {
A: {
active: false,
keyCodes: [90]
},
B: {
active: false,
keyCodes: [88]
},
UP: {
active: false,
keyCodes: [38, 87]
},
DOWN: {
active: false,
keyCodes: [40, 83]
},
LEFT: {
active: false,
keyCodes: [37, 65]
},
RIGHT: {
active: false,
keyCodes: [39, 68]
},
START: {
active: false,
keyCodes: [13]
},
SELECT: {
active: false,
keyCodes: [16]
}
};

let isPlaying = true;
const keyMapEventHandler = (event, shouldActivate) => {
event.preventDefault();

// First check for play pause
if (event.keyCode === 32 && !shouldActivate) {
console.log('Togling Play/Pause...');
isPlaying = !isPlaying;
if (isPlaying) {
play();
}
return;
}

Object.keys(keyMap).some(key => {
if (keyMap[key].keyCodes.includes(event.keyCode)) {
if (shouldActivate) {
keyMap[key].active = true;
} else {
keyMap[key].active = false;
}
return true;
}
return false;
});
};

// Create an input handler
const controlsOverlay = document.createElement('input');
controlsOverlay.setAttribute('id', 'controls');

controlsOverlay.addEventListener('keydown', event => keyMapEventHandler(event, true));
controlsOverlay.addEventListener('keyup', event => keyMapEventHandler(event, false));
document.body.appendChild(controlsOverlay);

let frameSkip = 0;
let maxFrameSkip = 2;

// Start playing the rom
const play = () => {
// Run a frame
WasmBoy.instance.exports.executeFrame();

// Render graphics
if (frameSkip >= maxFrameSkip) {
// Reset the frameskip
frameSkip = 0;

// Remove the old svg element
const oldSvg = document.getElementById('wasmboy-svg-output');
if (oldSvg) {
oldSvg.remove();
}

const imageSvg = rgbaArrayBufferToSvg(160, 144, WasmBoy.byteMemory, WasmBoy.instance.exports.FRAME_LOCATION);
imageSvg.setAttribute('id', 'wasmboy-svg-output');
document.body.appendChild(imageSvg);
} else {
frameSkip++;
}

// Handle Input
WasmBoy.instance.exports.setJoypadState(
keyMap.UP.active ? 1 : 0,
keyMap.RIGHT.active ? 1 : 0,
keyMap.DOWN.active ? 1 : 0,
keyMap.LEFT.active ? 1 : 0,
keyMap.A.active ? 1 : 0,
keyMap.B.active ? 1 : 0,
keyMap.SELECT.active ? 1 : 0,
keyMap.START.active ? 1 : 0
);

// Wait 16ms for 60fps
if (isPlaying) {
setTimeout(() => play(), 0);
}
};
play();

console.log('Playing ROM...');
};
runTask();

0 comments on commit 2a5506f

Please sign in to comment.