diff --git a/.gitignore b/.gitignore index 307f9e2c9add070a2c9bfbcc5d442544aa286fa5..32b911074ab04ddd1fb116a8b1dda0bb608e354a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ /dist node_modules .parcel-cache -main.rom \ No newline at end of file +*.nes \ No newline at end of file diff --git a/app.js b/app.js index a97304a26bb507644dc16a26a663be3c9ac03e0c..0cb277c989fc66e2f16edb2e4095bc87fae702ee 100644 --- a/app.js +++ b/app.js @@ -4,7 +4,7 @@ var SCREEN_WIDTH = 256; var SCREEN_HEIGHT = 240; const nesCanvas = document.getElementById("nes-canvas"); -canvas_ctx = nesCanvas.getContext("2d"); +let canvas_ctx = nesCanvas.getContext("2d"); let image = canvas_ctx.getImageData(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); canvas_ctx.fillStyle = "black"; @@ -35,6 +35,10 @@ var outputData = document.getElementById("outputData"); var debugCheckbox = document.getElementById("debug"); const latencyDiv = document.getElementById("latency"); const latencyScan = document.getElementById("scan"); +const selectGame = document.getElementById("selectGame"); +const mainGame = document.getElementById("mainGame"); +const gameSelectionButton = document.getElementById("gameSelectionButton"); +const gameDropdown = document.getElementById("gameDropdown"); // buttons const neutral = document.getElementById("neutral"); @@ -79,7 +83,7 @@ const recordingButtons = [ recordB, // recordSelect, // recordStart, -] +]; const clearButtons = [ clearNeutral, @@ -91,7 +95,7 @@ const clearButtons = [ clearB, // clearSelect, // clearStart, -] +]; // TODO fix weird indexes const codeDataButtonIndexes = [ @@ -100,11 +104,11 @@ const codeDataButtonIndexes = [ [0, 0], // Down [14, 19], // Left [14, 19], // Right - [0, 5], // A - [0, 5], // B + [0, 5], // A + [0, 5], // B // [0, 5], // Select // [0, 5], // Start -] +]; let codeDataButtonSubstrings = [ // Indexes 15-19 inclusive for neutral input @@ -166,7 +170,7 @@ let codeDataButtonSubstrings = [ [], // B // [], // Select // [], // Start -] +]; let recordingButtonIsHeldArr = [ false, // recordNeutral @@ -178,12 +182,12 @@ let recordingButtonIsHeldArr = [ false, // recordB // false, // recordSelect // false, // recordStart -] +]; function analyze(arr) { // Find min length in arr let minLength = Infinity; - arr.forEach(codeData => { + arr.forEach((codeData) => { if (codeData.length < minLength) { minLength = codeData.length; } @@ -192,7 +196,7 @@ function analyze(arr) { for (let i = 0; i < minLength; i++) { let allSame = true; let thisLetter = arr[0][i]; - arr.forEach(codeData => { + arr.forEach((codeData) => { if (codeData[i] !== thisLetter) { allSame = false; } @@ -211,41 +215,52 @@ function analyze(arr) { clearButtons.forEach((button, index) => { button.onclick = () => { codeDataButtonSubstrings[index] = []; - } + }; }); // Add a listener for each recording button to set the corresponding index in the array to true when pressed recordingButtons.forEach((button, index) => { button.onpointerdown = () => { recordingButtonIsHeldArr[index] = true; - } + }; button.onpointerup = () => { recordingButtonIsHeldArr[index] = false; let relevantRecordedData = recordedData[index]; // Get the top 20% of the recordedData for this button // Sum the counts - let counts = Object.values(relevantRecordedData).map(data => data.count); + let counts = Object.values(relevantRecordedData).map((data) => data.count); const sum = counts.reduce((partialSum, a) => partialSum + a, 0); let threshold = 0; if (index === 0) { - threshold = sum * 1.00; + threshold = sum * 1.0; } else { - threshold = sum * 1.00; + threshold = sum * 1.0; } // Get sorted list of recordedData for this button - let sortedRecordedData = Object.entries(relevantRecordedData).sort((a, b) => b[1].count - a[1].count); + let sortedRecordedData = Object.entries(relevantRecordedData).sort( + (a, b) => b[1].count - a[1].count, + ); // Convert sortedRecordedData to an array of arrays // where each element is [code.data, { count: count }] - sortedRecordedData = sortedRecordedData.map(data => [data[0], data[1].count]); - - let annotatedVals = sortedRecordedData.map(data => { - return [data[0].substring( - codeDataButtonIndexes[index][0], - codeDataButtonIndexes[index][1] - ), data[0], data[1], getButtonReadouts(data[0])]; + sortedRecordedData = sortedRecordedData.map((data) => [ + data[0], + data[1].count, + ]); + + let annotatedVals = sortedRecordedData.map((data) => { + return [ + data[0].substring( + codeDataButtonIndexes[index][0], + codeDataButtonIndexes[index][1], + ), + data[0], + data[1], + getButtonReadouts(data[0]), + ]; }); - let filteredAnnotatedVals = annotatedVals.filter(([relevantSlice, codeData, count, readout]) => { - /* + let filteredAnnotatedVals = annotatedVals.filter( + ([relevantSlice, codeData, count, readout]) => { + /* index=4 means that the user clicked the "Right+" button meaning they're calibrating the right input. relevantSlice="qwer", the substring that we just recorded codeDataButtonSubstrings is the array of arrays of button substrings @@ -256,23 +271,29 @@ recordingButtons.forEach((button, index) => { then look through codeDataButtonSubstrings[0, 3, 4] and return false if relevantSlice is found in any of them otherwise return true */ - let ourStartStop = codeDataButtonIndexes[index]; // [14, 19] - let matchingStartStopIndexes = []; - for (let i = 0; i < codeDataButtonIndexes.length; i++) { - if (JSON.stringify(codeDataButtonIndexes[i]) === JSON.stringify(ourStartStop)) { - // if (codeDataButtonIndexes[i] === ourStartStop) { - matchingStartStopIndexes.push(i); + let ourStartStop = codeDataButtonIndexes[index]; // [14, 19] + let matchingStartStopIndexes = []; + for (let i = 0; i < codeDataButtonIndexes.length; i++) { + if ( + JSON.stringify(codeDataButtonIndexes[i]) === + JSON.stringify(ourStartStop) + ) { + // if (codeDataButtonIndexes[i] === ourStartStop) { + matchingStartStopIndexes.push(i); + } } - } - for (buttonIndex of matchingStartStopIndexes) { - if (codeDataButtonSubstrings[buttonIndex].includes(relevantSlice)) { - console.log(`Found ${relevantSlice} in codeDataButtonSubstrings[${buttonIndex}], skipping`); - return false; + for (buttonIndex of matchingStartStopIndexes) { + if (codeDataButtonSubstrings[buttonIndex].includes(relevantSlice)) { + console.log( + `Found ${relevantSlice} in codeDataButtonSubstrings[${buttonIndex}], skipping`, + ); + return false; + } } - } - return true; - }); + return true; + }, + ); // Create accumulator and iterate over sortedRecordedData and filter out values // below the threshold let bestValues = []; @@ -288,9 +309,13 @@ recordingButtons.forEach((button, index) => { // let stringBestValues = bestValues.map(a => a[0]); // TODO bestValues can contain duplicates, removing here - bestValues = bestValues.filter((value, index, self) => self.indexOf(value) === index); - bestValues.forEach(relevantSlice => { - console.log(`Adding ${relevantSlice} to codeDataButtonSubstrings[${index}]`); + bestValues = bestValues.filter( + (value, index, self) => self.indexOf(value) === index, + ); + bestValues.forEach((relevantSlice) => { + console.log( + `Adding ${relevantSlice} to codeDataButtonSubstrings[${index}]`, + ); codeDataButtonSubstrings[index].push(relevantSlice); }); console.log(codeDataButtonSubstrings[index]); @@ -382,7 +407,7 @@ select.onpointerup = () => { }; function checkButtonStatus() { - const pressed = { + let pressed = { neutral: qr.neutral, left: pressedButtons.left || qr.left, right: pressedButtons.right || qr.right, @@ -393,6 +418,11 @@ function checkButtonStatus() { start: pressedButtons.start || qr.start, select: pressedButtons.select || qr.select, }; + + if (pressed.start) { + pressed = { start: true }; + } + neutral.style = pressed.neutral ? "background-color: red;" : ""; up.style = pressed.up ? "background-color: red;" : ""; down.style = pressed.down ? "background-color: red;" : ""; @@ -455,26 +485,34 @@ function getButtonReadouts(codeData) { let directionComponent = codeData.substring( codeDataButtonIndexes[0][0], - codeDataButtonIndexes[0][1] + codeDataButtonIndexes[0][1], ); let aComponent = codeData.substring( codeDataButtonIndexes[5][0], - codeDataButtonIndexes[5][1] + codeDataButtonIndexes[5][1], ); - if (codeDataButtonSubstrings[0].some((substring) => directionComponent === substring)) { + if ( + codeDataButtonSubstrings[0].some( + (substring) => directionComponent === substring, + ) + ) { // console.log(directionComponent); localQr.neutral = true; } else { // Left if ( - codeDataButtonSubstrings[3].some((substring) => directionComponent === substring) + codeDataButtonSubstrings[3].some( + (substring) => directionComponent === substring, + ) ) { localQr.left = true; } // Right if ( - codeDataButtonSubstrings[4].some((substring) => directionComponent === substring) + codeDataButtonSubstrings[4].some( + (substring) => directionComponent === substring, + ) ) { localQr.right = true; } @@ -584,3 +622,10 @@ function tick() { requestAnimationFrame(tick); } + +gameSelectionButton.onclick = () => { + selectGame.hidden = true; + mainGame.hidden = false; + + nesWorker.postMessage({ rom: gameDropdown.value }); +}; diff --git a/index.html b/index.html index 6b05681deb7153abb16f32ab6dcc9359eeea04c0..f79a2babe2b512e1eeaae2aae6c3cb7bb0f15073 100644 --- a/index.html +++ b/index.html @@ -3,60 +3,79 @@ <body style="font-family: 'Courier New', Courier, monospace"> <div> - <div style="float: left; width: 49%"> - <h1>QRcade</h1> - <h3>Spencer Kee & Stephen Downward</h3> - <p>Play Mario by manipulating a QR code! Move the stick left and right for movement, and press the button on the - right side to jump. Be careful not to cover the QR code with your hand. Click start (with the mouse) twice to - start the game.</p> - - <div id="loadingMessage">🎥 Unable to access video stream (please make sure you have a webcam enabled)</div> - <canvas id="canvas" hidden></canvas> + <div id="selectGame"> + <select id="gameDropdown"> + <Option value="baseball.nes">Baseball</Option> + <Option value="donkeykong.nes">Donkey Kong</Option> + <Option value="f1.nes">F1</Option> + <Option value="mario.nes">Mario</Option> + <Option value="pinball.nes">Pinball</Option> + <Option value="punchout.nes">Punchout</Option> + <Option value="tetris.nes">Tetris</Option> + <Option value="topgun.nes">Topgun</Option> + </select> + <button id="gameSelectionButton">Gimme gimme gimme</button> </div> - <div style="float: right; width: 50%;"> - <canvas id="nes-canvas" width="256" height="240" style="width: 100%"></canvas> - <button id="neutral">Neutral</button> - <button id="up">Up</button> - <button id="down">Down</button> - <button id="left">Left</button> - <button id="right">Right</button> - <button id="a">A</button> - <button id="b">B</button> - <button id="start">Start</button> - <button id="select">Select</button> - <div> - <button id="neutralClear">NoInput-</button> - <button id="upClear">Up-</button> - <button id="downClear">Down-</button> - <button id="leftClear">Left-</button> - <button id="rightClear">Right-</button> - <button id="aClear">A-</button> - <button id="bClear">B-</button> - <button id="startClear">Start-</button> - <button id="selectClear">Select-</button> + <div id="mainGame" hidden> + <div style="float: left; width: 49%"> + <h1>QRcade</h1> + <h3>Spencer Kee & Stephen Downward</h3> + <p>Play Mario by manipulating a QR code! Move the stick left and right for movement, and press the button on the + right side to jump. Be careful not to cover the QR code with your hand. Click start (with the mouse) twice to + start the game.</p> + + <div id="loadingMessage">🎥 Unable to access video stream (please make sure you have a webcam enabled)</div> + <canvas id="canvas" hidden></canvas> </div> - <div> - <button id="neutralRec">NoInput+</button> - <button id="upRec">Up+</button> - <button id="downRec">Down+</button> - <button id="leftRec">Left+</button> - <button id="rightRec">Right+</button> - <button id="aRec">A+</button> - <button id="bRec">B+</button> - <button id="startRec">Start+</button> - <button id="selectRec">Select+</button> + <div style="float: right; width: 50%;"> + <canvas id="nes-canvas" width="256" height="240" style="width: 100%"></canvas> + <table> + <tr> + <td><button id="neutral">Neutral</button></td> + <td><button id="up">Up</button></td> + <td><button id="down">Down</button></td> + <td><button id="left">Left</button></td> + <td><button id="right">Right</button></td> + <td><button id="a">A</button></td> + <td><button id="b">B</button></td> + <td><button id="start">Start</button></td> + <td><button id="select">Select</button></td> + </tr> + <tr> + <td><button id="neutralClear">NoInput-</button></td> + <td><button id="upClear">Up-</button></td> + <td><button id="downClear">Down-</button></td> + <td><button id="leftClear">Left-</button></td> + <td><button id="rightClear">Right-</button></td> + <td><button id="aClear">A-</button></td> + <td><button id="bClear">B-</button></td> + <td><button id="startClear">Start-</button></td> + <td><button id="selectClear">Select-</button></td> + </tr> + <tr> + <td><button id="neutralRec">NoInput+</button></td> + <td><button id="upRec">Up+</button></td> + <td><button id="downRec">Down+</button></td> + <td><button id="leftRec">Left+</button></td> + <td><button id="rightRec">Right+</button></td> + <td><button id="aRec">A+</button></td> + <td><button id="bRec">B+</button></td> + <td><button id="startRec">Start+</button></td> + <td><button id="selectRec">Select+</button></td> + </tr> + </table> </div> </div> - </div> - <!-- Add debug checkbox --> - <div> - <label for="debug">Debug</label> - <input type="checkbox" id="debug" /> - </div> - <div id="output" hidden> - <div id="latency"></div> - <div id="scan"></div> - <div><span id="outputData"></span></div> + <!-- Add debug checkbox --> + <div> + <label for="debug">Debug</label> + <input type="checkbox" id="debug" /> + </div> + <div id="output" hidden> + <div id="latency"></div> + <div id="scan"></div> + <div><span id="outputData"></span></div> + </div> </div> </body> @@ -64,4 +83,4 @@ <script type="module" src="app.js"></script> </head> -</html> \ No newline at end of file +</html> diff --git a/nes.js b/nes.js index 17c81272e11574094de5a1e5a61b38e01dc2b5df..a6422be0f587e2249203a81dd1eddfa68e1824aa 100644 --- a/nes.js +++ b/nes.js @@ -18,6 +18,11 @@ var nes = new jsnes.NES({ var pressedKeys = {}; onmessage = (msg) => { + if (msg.data.rom) { + nes_load_url(msg.data.rom); + return; + } + const keys = { up: jsnes.Controller.BUTTON_UP, down: jsnes.Controller.BUTTON_DOWN, @@ -84,5 +89,3 @@ function nes_load_url(path) { req.send(); } - -nes_load_url("main.rom"); diff --git a/package.json b/package.json index 54e7360139477ef3ac03b3f35fae9517cba2351a..ac8b45980c7ac813200cbaf3c002e90402828c86 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,7 @@ "license": "MIT", "scripts": { "start": "parcel index.html", - "build": "parcel build index.html", - "postbuild": "cp -r main.rom dist/" + "build": "parcel build index.html" }, "dependencies": { "jsnes": "^1.2.1",