├── .gitignore ├── src ├── test │ ├── resources │ │ ├── E03TestRom.ch8 │ │ ├── E05SoundLoop.ch8 │ │ ├── E05TimerLoop.ch8 │ │ ├── E06KeypadLoop.ch8 │ │ └── E07GraphicsRom.ch8 │ └── java │ │ └── net │ │ └── saga │ │ └── console │ │ └── chip8 │ │ └── test │ │ ├── E03ClockExecutionAndMemoryTest.java │ │ ├── E05TimersTest.java │ │ ├── E06KeypadInputTest.java │ │ ├── E08UtilitiesTest.java │ │ ├── E04FlowControlTest.java │ │ ├── E02BitwiseOpCodeTest.java │ │ ├── E01ArithmeticOpCodeTest.java │ │ └── E07GraphicsTest.java └── main │ ├── resources │ ├── about.png │ ├── input.png │ ├── open.png │ ├── pause.png │ ├── play.png │ └── skip.png │ └── java │ └── net │ └── saga │ └── console │ └── chip8 │ ├── util │ ├── Audio.java │ ├── Input.java │ ├── Chip8DisplayPanel.java │ └── Chip8Utils.java │ ├── Chip8Model.java │ ├── Chip8.java │ ├── SwingMain.form │ ├── SwingMain.java │ ├── InputMapping.form │ └── InputMapping.java ├── pom.xml ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | target/** 2 | nb-** 3 | **/keycloak.json 4 | /nbproject/ -------------------------------------------------------------------------------- /src/test/resources/E03TestRom.ch8: -------------------------------------------------------------------------------- 1 | `a b%c0 -------------------------------------------------------------------------------- /src/main/resources/about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secondsun/chip8/HEAD/src/main/resources/about.png -------------------------------------------------------------------------------- /src/main/resources/input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secondsun/chip8/HEAD/src/main/resources/input.png -------------------------------------------------------------------------------- /src/main/resources/open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secondsun/chip8/HEAD/src/main/resources/open.png -------------------------------------------------------------------------------- /src/main/resources/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secondsun/chip8/HEAD/src/main/resources/pause.png -------------------------------------------------------------------------------- /src/main/resources/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secondsun/chip8/HEAD/src/main/resources/play.png -------------------------------------------------------------------------------- /src/main/resources/skip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secondsun/chip8/HEAD/src/main/resources/skip.png -------------------------------------------------------------------------------- /src/test/resources/E05SoundLoop.ch8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secondsun/chip8/HEAD/src/test/resources/E05SoundLoop.ch8 -------------------------------------------------------------------------------- /src/test/resources/E05TimerLoop.ch8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secondsun/chip8/HEAD/src/test/resources/E05TimerLoop.ch8 -------------------------------------------------------------------------------- /src/test/resources/E06KeypadLoop.ch8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secondsun/chip8/HEAD/src/test/resources/E06KeypadLoop.ch8 -------------------------------------------------------------------------------- /src/test/resources/E07GraphicsRom.ch8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secondsun/chip8/HEAD/src/test/resources/E07GraphicsRom.ch8 -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | net.saga.console 5 | chip8 6 | 0.1.0-SNAPSHOT 7 | jar 8 | 9 | 10 | junit 11 | junit 12 | 4.12 13 | test 14 | 15 | 16 | 17 | UTF-8 18 | 1.8 19 | 1.8 20 | 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 summers pittma 2 | 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /src/main/java/net/saga/console/chip8/util/Audio.java: -------------------------------------------------------------------------------- 1 | package net.saga.console.chip8.util; 2 | 3 | import java.util.function.IntSupplier; 4 | import javax.sound.sampled.AudioFormat; 5 | import javax.sound.sampled.AudioSystem; 6 | import javax.sound.sampled.LineUnavailableException; 7 | import javax.sound.sampled.SourceDataLine; 8 | 9 | /** 10 | * This class is a helper class for emitting, playing, and viewing audio data. 11 | * 12 | * @author summers 13 | */ 14 | public final class Audio { 15 | 16 | private static final AudioFormat AF = new AudioFormat((float) 44100, 8, 1, true, false); 17 | private static SourceDataLine SDL; 18 | private static byte[] BUF = new byte[1]; 19 | 20 | static { 21 | try { 22 | SDL = AudioSystem.getSourceDataLine(AF); 23 | SDL.open(); 24 | 25 | } catch (LineUnavailableException ex) { 26 | SDL = null; 27 | } 28 | } 29 | 30 | private static final IntSupplier STREAM = (new IntSupplier() { 31 | 32 | int i = 0; 33 | 34 | @Override 35 | public int getAsInt() { 36 | double angle = i / ((float) 44100 / 880) * 2.0 * Math.PI; 37 | i++; 38 | return (byte) (Math.sin(angle) * 20); 39 | 40 | } 41 | }); 42 | 43 | private static boolean PLAYING = false; 44 | 45 | public static void play() { 46 | if (!PLAYING) { 47 | SDL.start(); 48 | } 49 | 50 | for (int i = 0; i < (44100 / 60); i++) { 51 | int number = STREAM.getAsInt(); 52 | BUF[0] = (byte) number; 53 | SDL.write(BUF, 0, 1); 54 | } 55 | 56 | } 57 | 58 | public static void stop() { 59 | if (PLAYING) { 60 | SDL.stop(); 61 | PLAYING = false; 62 | } 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/net/saga/console/chip8/util/Input.java: -------------------------------------------------------------------------------- 1 | package net.saga.console.chip8.util; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public final class Input { 7 | 8 | private final static Map keyMap = new HashMap<>(); 9 | 10 | static { 11 | keyMap.put("0", 0x0); 12 | keyMap.put("1", 0x1); 13 | keyMap.put("2", 0x2); 14 | keyMap.put("3", 0x3); 15 | keyMap.put("4", 0x4); 16 | keyMap.put("5", 0x5); 17 | keyMap.put("6", 0x6); 18 | keyMap.put("7", 0x7); 19 | keyMap.put("8", 0x8); 20 | keyMap.put("9", 0x9); 21 | keyMap.put("a", 0xa); 22 | keyMap.put("b", 0xb); 23 | keyMap.put("c", 0xc); 24 | keyMap.put("d", 0xd); 25 | keyMap.put("e", 0xe); 26 | keyMap.put("f", 0xf); 27 | 28 | } 29 | 30 | public static void map(int i, String text) { 31 | if (text != null && !text.isEmpty()) { 32 | keyMap.put(text.toLowerCase(), i); 33 | } 34 | } 35 | 36 | private Input() { 37 | } 38 | 39 | private static int KEYS = -1; 40 | 41 | public static void press(int i) { 42 | KEYS = 0x0000000F & i; 43 | } 44 | 45 | public static void press(String input) { 46 | input = input.toLowerCase(); 47 | if (keyMap.containsKey(input)) { 48 | KEYS = 0x0000000F & keyMap.get(input); 49 | } 50 | 51 | } 52 | 53 | public static void unpress(String input) { 54 | input = input.toLowerCase(); 55 | 56 | if (keyMap.containsKey(input)) { 57 | KEYS = -1; 58 | } 59 | } 60 | 61 | public static void unpress() { 62 | KEYS = -1; 63 | } 64 | 65 | /** 66 | * returns the currently pressed keys 67 | * 68 | * @return the int value of the key pressed 69 | */ 70 | public static int read() { 71 | return KEYS; 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/test/java/net/saga/console/chip8/test/E03ClockExecutionAndMemoryTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | package net.saga.console.chip8.test; 7 | 8 | import java.io.IOException; 9 | import net.saga.console.chip8.Chip8; 10 | import net.saga.console.chip8.util.Chip8Utils; 11 | import org.junit.Assert; 12 | import org.junit.Before; 13 | import org.junit.Test; 14 | 15 | /** 16 | * 17 | * We will take our chip 8 processor and have it begin executing instructions 18 | * from memory. 19 | * 20 | * @author summers 21 | */ 22 | public class E03ClockExecutionAndMemoryTest { 23 | 24 | private Chip8 chip8; 25 | 26 | /** 27 | * Until now we have only executed instructions manually. Eventually for a 28 | * working virtual machine we will need to load a program from an external 29 | * source. 30 | * 31 | * This provided Chip8Utils class has utility methods for creating a Chip8 32 | * system with memory loaded from a file. 33 | * 34 | * 35 | * @throws java.io.IOException 36 | */ 37 | @Before 38 | public void loadMemory() throws IOException { 39 | this.chip8 = Chip8Utils.createFromRom(E03ClockExecutionAndMemoryTest.class.getResource("/E03TestRom.ch8")); 40 | } 41 | 42 | /** 43 | * A cycle loop of the chip8 processor should 44 | * 45 | * fetch the instruction pointed at by the PC 46 | * increment the PC 47 | * decode the instruction 48 | * execute the instruction 49 | * repeat 50 | * 51 | * This will be modified slightly in E05Timers to update timers as well. 52 | * 53 | * Some notes, an instruction is two bytes, however the memory is addressed 54 | * as bytes. This means you will need to load two bytes and combine them 55 | * into a single instruction. 56 | * 57 | */ 58 | @Test 59 | public void testCycle() { 60 | chip8.cycle(); 61 | chip8.cycle(); 62 | chip8.cycle(); 63 | chip8.cycle(); 64 | Assert.assertEquals(0x15, chip8.getV0()); 65 | Assert.assertEquals(0x20, chip8.getV1()); 66 | Assert.assertEquals(0x25, chip8.getV2()); 67 | Assert.assertEquals(0x30, chip8.getV3()); 68 | Assert.assertEquals(0x208, chip8.getPC()); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/net/saga/console/chip8/util/Chip8DisplayPanel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 summers. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package net.saga.console.chip8.util; 25 | 26 | import java.awt.Color; 27 | import java.awt.Graphics; 28 | import javax.swing.JPanel; 29 | import net.saga.console.chip8.Chip8; 30 | 31 | /** 32 | * 33 | * @author summers 34 | */ 35 | public class Chip8DisplayPanel extends JPanel { 36 | 37 | private static final long serialVersionUID = 0x203920L; 38 | 39 | private Chip8 chip8 = new Chip8(); 40 | 41 | @Override 42 | public void paint(Graphics g) { 43 | int width = super.getWidth(); 44 | int height = super.getHeight(); 45 | int pixelWidth = width / 64; 46 | int pixelHeight = height / 32; 47 | byte[] video = chip8.getScreen(); 48 | for (int x = 0; x < 64; x++) { 49 | for (int y = 0; y < 32; y++) { 50 | if (video[y * 64 + x] == 0) { 51 | g.setColor(Color.BLACK); 52 | } else { 53 | g.setColor(Color.WHITE); 54 | } 55 | g.fillRect(x * pixelWidth, y*pixelHeight , pixelWidth, pixelHeight); 56 | } 57 | } 58 | } 59 | 60 | public void setChip8(Chip8 chip8) { 61 | this.chip8 = chip8; 62 | } 63 | 64 | 65 | 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/net/saga/console/chip8/util/Chip8Utils.java: -------------------------------------------------------------------------------- 1 | package net.saga.console.chip8.util; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.net.URISyntaxException; 6 | import java.net.URL; 7 | import java.nio.file.Files; 8 | import java.nio.file.Path; 9 | import java.nio.file.Paths; 10 | import java.util.logging.Level; 11 | import java.util.logging.Logger; 12 | import net.saga.console.chip8.Chip8; 13 | 14 | public final class Chip8Utils { 15 | 16 | private Chip8Utils() { 17 | } 18 | 19 | public static Chip8 createFromRom(URL rom) throws IOException { 20 | try { 21 | return createFromRom(Paths.get(rom.toURI())); 22 | } catch (URISyntaxException ex) { 23 | Logger.getLogger(Chip8Utils.class.getName()).log(Level.SEVERE, null, ex); 24 | throw new IOException(ex); 25 | } 26 | } 27 | 28 | public static Chip8 createFromRom(String rom) throws IOException { 29 | return createFromRom(Paths.get(rom)); 30 | } 31 | 32 | public static Chip8 createFromRom(File rom) throws IOException { 33 | return createFromRom(rom.toPath()); 34 | } 35 | 36 | public static Chip8 createFromRom(Path rom) throws IOException { 37 | 38 | byte[] reader = Files.readAllBytes(rom); 39 | int index = 0x200; 40 | byte[] memory = new byte[4096];//4k memory 41 | try { 42 | for (byte read : reader) { 43 | memory[index] = (byte) (read); 44 | index += 1; 45 | } 46 | } catch (ArrayIndexOutOfBoundsException ignore) { 47 | throw new IOException("File is too big to fit in memory", ignore); 48 | } 49 | 50 | Chip8 chip8 = new Chip8(memory); 51 | return chip8; 52 | 53 | } 54 | 55 | /** 56 | * 57 | * Packs a graphics row (8 pixels of the sprite) into a btye 58 | * 59 | * @param x 60 | * @param y 61 | * @param video 62 | * @return 63 | */ 64 | public static byte getSpriteRow(int x, int y, byte[] video) { 65 | x = x % 64; 66 | y = y % 32; 67 | byte byte1 = video[x + y * 64]; 68 | byte byte2 = video[x + 1 + y * 64]; 69 | byte byte3 = video[x + 2 + y * 64]; 70 | byte byte4 = video[x + 3 + y * 64]; 71 | byte byte5 = video[x + 4 + y * 64]; 72 | byte byte6 = video[x + 5 + y * 64]; 73 | byte byte7 = video[x + 6 + y * 64]; 74 | byte byte8 = video[x + 7 + y * 64]; 75 | 76 | return (byte) ((byte1 << 7) 77 | | (byte2 << 6) 78 | | (byte3 << 5) 79 | | (byte4 << 4) 80 | | (byte5 << 3) 81 | | (byte6 << 2) 82 | | (byte7 << 1) 83 | | (byte8)); 84 | 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /src/test/java/net/saga/console/chip8/test/E05TimersTest.java: -------------------------------------------------------------------------------- 1 | package net.saga.console.chip8.test; 2 | 3 | import java.io.IOException; 4 | import net.saga.console.chip8.Chip8; 5 | import net.saga.console.chip8.util.Chip8Utils; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import static org.junit.Assert.*; 9 | import org.junit.Ignore; 10 | 11 | /** 12 | * 13 | * @author summers 14 | */ 15 | public class E05TimersTest { 16 | 17 | private Chip8 chip8; 18 | 19 | @Before 20 | public void setUp() throws IOException { 21 | this.chip8 = Chip8Utils.createFromRom(getClass().getResource("/E05TimerLoop.ch8")); 22 | this.chip8.execute(0x6064); 23 | this.chip8.execute(0x6127); 24 | this.chip8.execute(0x6212); 25 | this.chip8.execute(0x63AE); 26 | this.chip8.execute(0x64FF); 27 | this.chip8.execute(0x65B4); 28 | this.chip8.execute(0x6642); 29 | this.chip8.execute(0x6F25); 30 | } 31 | 32 | /** 33 | * As documented in E03, we will modify the cycle method to count down the 34 | * delay timer. 35 | * 36 | * There are also timer instructions to set the timer to begin a countdown. 37 | * 38 | * FX15 Set the timer to VX FX07 Store the value of the timer to VX 39 | * 40 | * The timer works on a 60 hertz cycle. IE if you set the timer to 60 it 41 | * will countdown to 0 over the course of a second. 42 | * 43 | */ 44 | @Test 45 | public void testDelayTimerOpcodes() { 46 | chip8.execute(0xF015); //Set timer to 0x64 47 | chip8.execute(0xF107); //Read timer into V1 48 | assertEquals(0x64, chip8.getV1()); 49 | 50 | } 51 | 52 | @Test(timeout = 500l) 53 | public void testDelayTimerCountdown() { 54 | while (chip8.getV5() != 255) { 55 | chip8.cycle(); 56 | } 57 | } 58 | 59 | /** 60 | * There is a second timer, the sound timer. The sound timer will emit a 61 | * tone until it reaches 0. It operates on the same cycle as the delay 62 | * timer. 63 | * 64 | * Opcodes : 0xFX18 Set the sound timer to the value in VX 65 | * 66 | */ 67 | @Test 68 | public void testSoundTimer() { 69 | chip8.execute(0xF018); //Set timer to 0x64 70 | chip8.cycle(); 71 | } 72 | 73 | /** 74 | * This test will test that sound is actually emitted. 75 | * 76 | * It is disabled by default because it could be annoying. 77 | * 78 | * @throws java.io.IOException loading the program may throw an ioexception. 79 | */ 80 | @Ignore 81 | @Test(timeout = 110000l) 82 | public void testEmitSoundTimer() throws IOException { 83 | 84 | Chip8 soundChip = Chip8Utils.createFromRom(getClass().getResource("/E05SoundLoop.ch8")); 85 | while (soundChip.getV5() != 255) { 86 | soundChip.cycle(); 87 | } 88 | 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CHIP 8 by secondsun 2 | =================== 3 | 4 | A TDD Chip-8 implementation designed to teach how to write an "emulator" from scratch. 5 | 6 | [![You Tube Demo](https://img.youtube.com/vi/X8fVRDHrw18/0.jpg)](https://www.youtube.com/watch?v=X8fVRDHrw18) 7 | 8 | ## Welcome 9 | 10 | Chip-8 is a was created in the 1970s by Joseph Weisbecker to run games on the 8-bit computers of the day. Chip-8 includes an assembly language, an interpreter, and the runtime. There is a community around the platform which has created several games, and it has also expanded the platform to include newer features. However, this project focuses on just the "vanilla" Chip-8 from the 1970s. 11 | 12 | This project focuses on implementing the Chip8 interpreter in a test driven manner. You don't need any experience in writing emulators, but understanding Hexedecimal notation and binary operators are important. 13 | 14 | The master branch of this project is a stubbed out TDD project. The [working](https://github.com/secondsun/chip8/tree/working) branch has my finished project. 15 | 16 | ## Prerequisites 17 | 18 | You need to have [Java 8](http://java.oracle.com) and [Maven](http://maven.apache.org) installed. You can confirm it is working by running : 19 | 20 | ```bash 21 | mvn clean install 22 | ``` 23 | 24 | You should maven attempt to run the project and fail because the tests do not pass. Implementing these tests and thus Chip8 is part of th exercise. 25 | 26 | ## Code overview 27 | 28 | Our tests are in the [test](src/test/java/net/saga/console/chip8/test) directory. They are grouped into logical categories to to work through. Feel free to add more tests if you don't feel comfortable with your code or want to see more how it works. 29 | 30 | There are a few helper classes for outputting audio, loading roms, displaying graphics, and handling input. 31 | 32 | Finally, the [Chip8](src/main/java/net/saga/console/chip8/Chip8.java) class has a few parameters initialized but is otherwise empty. This is the class file you will implement. 33 | 34 | ## Let's Code 35 | 36 | The project is broken up into eight sections : 37 | - 01 [Arithmetic](src/test/java/net/saga/console/chip8/test/E01ArithmeticOpCodeTest.java) 38 | - 02 [Bitwise operators](src/test/java/net/saga/console/chip8/test/E02BitwiseOpCodeTest.java) 39 | - 03 [Execution](src/test/java/net/saga/console/chip8/test/E03ClockExecutionAndMemoryTest.java) 40 | - 04 [Flow Control](src/test/java/net/saga/console/chip8/test/E04FlowControlTest.java) 41 | - 05 [Timers](src/test/java/net/saga/console/chip8/test/E05TimersTest.java) 42 | - 06 [Keypad Input](src/test/java/net/saga/console/chip8/test/E06KeypadInputTest.java) 43 | - 07 [Graphics](src/test/java/net/saga/console/chip8/test/E07GraphicsTest.java) 44 | - 08 [Utilities](src/test/java/net/saga/console/chip8/test/E08UtilitiesTest.java) 45 | 46 | Additionally you may want to review other documents on CHIP8 from the web. My reference for writing this project was [Mastering CHIP-8 by Matthew Mikolay](http://mattmik.com/files/chip8/mastering/chip8.html) and the [CHIP-8 Wikipedia article](https://en.wikipedia.org/wiki/CHIP-8). 47 | 48 | # Exec 49 | 50 | When you are finished you can run Chip8 in a Swing window 51 | 52 | ```bash 53 | mvn exec:java -Dexec.mainClass="net.saga.console.chip8.SwingMain" 54 | ``` 55 | 56 | Find some roms online and run them in your emulator. -------------------------------------------------------------------------------- /src/main/java/net/saga/console/chip8/Chip8Model.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 summers. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software")), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package net.saga.console.chip8; 25 | 26 | import static java.lang.Integer.toHexString; 27 | import javax.swing.table.DefaultTableModel; 28 | 29 | /** 30 | * 31 | * @author summers 32 | */ 33 | public class Chip8Model extends DefaultTableModel { 34 | 35 | private static final long serialVersionUID = 1L; 36 | 37 | public Chip8Model() { 38 | super(20, 2); 39 | super.setColumnIdentifiers(new String[]{"", "V"}); 40 | super.setValueAt("V0", 0, 0); 41 | super.setValueAt("V1", 1, 0); 42 | super.setValueAt("V2", 2, 0); 43 | super.setValueAt("V3", 3, 0); 44 | super.setValueAt("V4", 4, 0); 45 | super.setValueAt("V5", 5, 0); 46 | super.setValueAt("V6", 6, 0); 47 | super.setValueAt("V7", 7, 0); 48 | super.setValueAt("V8", 8, 0); 49 | super.setValueAt("V9", 9, 0); 50 | super.setValueAt("VA", 10, 0); 51 | super.setValueAt("VB", 11, 0); 52 | super.setValueAt("VC", 12, 0); 53 | super.setValueAt("VD", 13, 0); 54 | super.setValueAt("VE", 14, 0); 55 | super.setValueAt("VF", 15, 0); 56 | super.setValueAt("", 16, 0); 57 | super.setValueAt("I", 17, 0); 58 | super.setValueAt("PC", 18, 0); 59 | super.setValueAt("SP", 19, 0); 60 | } 61 | 62 | public void update(Chip8 chip8) { 63 | setValueAt(toHexString(chip8.getV0()), 0, 1); 64 | setValueAt(toHexString(chip8.getV1()), 1, 1); 65 | setValueAt(toHexString(chip8.getV2()), 2, 1); 66 | setValueAt(toHexString(chip8.getV3()), 3, 1); 67 | setValueAt(toHexString(chip8.getV4()), 4, 1); 68 | setValueAt(toHexString(chip8.getV5()), 5, 1); 69 | setValueAt(toHexString(chip8.getV6()), 6, 1); 70 | setValueAt(toHexString(chip8.getV7()), 7, 1); 71 | setValueAt(toHexString(chip8.getV8()), 8, 1); 72 | setValueAt(toHexString(chip8.getV9()), 9, 1); 73 | setValueAt(toHexString(chip8.getVA()), 10, 1); 74 | setValueAt(toHexString(chip8.getVB()), 11, 1); 75 | setValueAt(toHexString(chip8.getVC()), 12, 1); 76 | setValueAt(toHexString(chip8.getVD()), 13, 1); 77 | setValueAt(toHexString(chip8.getVE()), 14, 1); 78 | setValueAt(toHexString(chip8.getVF()), 15, 1); 79 | setValueAt("", 16, 1); 80 | setValueAt(toHexString(chip8.getiRegister()), 17, 1); 81 | setValueAt(toHexString(chip8.getPC()), 18, 1); 82 | setValueAt(toHexString(chip8.getSP()), 19, 1); 83 | } 84 | 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/test/java/net/saga/console/chip8/test/E06KeypadInputTest.java: -------------------------------------------------------------------------------- 1 | package net.saga.console.chip8.test; 2 | 3 | import java.io.IOException; 4 | import net.saga.console.chip8.Chip8; 5 | import net.saga.console.chip8.util.Chip8Utils; 6 | import net.saga.console.chip8.util.Input; 7 | import org.junit.After; 8 | import static org.junit.Assert.assertEquals; 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | 12 | /** 13 | * 14 | * Chip-8 has support for a 16 button keypad input. The keys are assigned values 15 | * from 0x0 - 0xF. The mapping from your keyboard/input device to chip8 16 | * shouldn't matter for these tests. 17 | */ 18 | public class E06KeypadInputTest { 19 | 20 | private Chip8 chip8; 21 | 22 | @Before 23 | public void setUp() throws IOException { 24 | this.chip8 = Chip8Utils.createFromRom(getClass().getResource("/E06KeypadLoop.ch8")); 25 | this.chip8.execute(0x6064); 26 | this.chip8.execute(0x6127); 27 | this.chip8.execute(0x6212); 28 | this.chip8.execute(0x63AE); 29 | this.chip8.execute(0x64FF); 30 | this.chip8.execute(0x65B4); 31 | this.chip8.execute(0x6642); 32 | this.chip8.execute(0x6F25); 33 | } 34 | 35 | @After 36 | public void unpress() { 37 | Input.unpress(); 38 | } 39 | 40 | /** 41 | * The opcode FX0A will wait for a key press and store its value into 42 | * register VX. 43 | * 44 | * This test will execute the opcode and then call cycle several times. As 45 | * long as the program counter does not increment we will assume that chip8 46 | * is not executing. 47 | * 48 | */ 49 | @Test 50 | public void testChip8WaitsForKeyboardInput() { 51 | int pc = chip8.getPC(); 52 | chip8.cycle(); 53 | chip8.cycle(); 54 | chip8.cycle(); 55 | chip8.cycle(); 56 | assertEquals(pc, chip8.getPC()); 57 | } 58 | 59 | @Test 60 | public void testChip8ContinuesAfterKeyboardInput() { 61 | int pc = chip8.getPC(); 62 | chip8.cycle(); 63 | chip8.cycle(); 64 | assertEquals(pc, chip8.getPC()); 65 | 66 | Input.press(0xA); 67 | chip8.cycle(); 68 | chip8.cycle(); 69 | assertEquals(0xA, chip8.getV6()); 70 | 71 | } 72 | 73 | /** 74 | * The next opcode will skip the next instruction until the keypress matches 75 | * a value in a certain register. 76 | * 77 | * EX9E : Skip the next instruction of the key corresponding to the value in 78 | * VX is pressed. 79 | * 80 | */ 81 | @Test 82 | public void skipIfPressed() { 83 | Input.press(0x1); 84 | chip8.execute(0x6002);//Store 0x02 into V0 85 | chip8.execute(0xE09E);//Skip if 0x02 is pressed (it isn't) 86 | assertEquals(0x200, chip8.getPC()); 87 | 88 | Input.press(0x2); 89 | chip8.execute(0x6002);//Store 0x02 into V0 90 | chip8.execute(0xE09E);//Skip if 0x02 is pressed (it is) 91 | assertEquals(0x202, chip8.getPC()); 92 | 93 | } 94 | 95 | /** 96 | * The next opcode will not skip the next instruction if the keypress 97 | * matches a value in a certain register. 98 | * 99 | * EXA1 : Skip the next instruction of the key corresponding to the value in 100 | * VX is not pressed. 101 | * 102 | */ 103 | @Test 104 | public void skipIfNotPressed() { 105 | Input.press(0x1); 106 | chip8.execute(0x6002);//Store 0x02 into V0 107 | chip8.execute(0xE0A1);//Skip if 0x02 is not pressed (it isn't) 108 | assertEquals(0x202, chip8.getPC()); 109 | 110 | Input.press(0x2); 111 | chip8.execute(0x6002);//Store 0x02 into V0 112 | chip8.execute(0xE0A1);//Skip if 0x02 is pressed (it is) 113 | assertEquals(0x202, chip8.getPC()); 114 | } 115 | 116 | 117 | } 118 | -------------------------------------------------------------------------------- /src/main/java/net/saga/console/chip8/Chip8.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 summers. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package net.saga.console.chip8; 25 | 26 | import net.saga.console.chip8.util.Input; 27 | import net.saga.console.chip8.util.Audio; 28 | import java.util.Arrays; 29 | import java.util.Random; 30 | 31 | /** 32 | * This class is the main chip8 system 33 | */ 34 | public class Chip8 { 35 | 36 | private int pc = 0x200; 37 | private int iRegister = 0; 38 | private final int[] registers = new int[0x10]; 39 | private final Random random = new Random(); 40 | private int sp = 0; 41 | private int delayTimer = 0; 42 | private int soundTimer = 0; 43 | private final int[] stack = new int[16]; 44 | private final byte[] memory; 45 | private byte[] video = new byte[64 * 32]; 46 | private long nextTimer = 0; 47 | 48 | public Chip8() { 49 | this.memory = new byte[4096]; 50 | } 51 | 52 | public Chip8(byte[] memory) { 53 | if (memory.length > 4096) { 54 | throw new IllegalArgumentException("Memory may not be greater than 4096 bytes"); 55 | } 56 | this.memory = memory; 57 | } 58 | 59 | public int getPC() { 60 | return pc; 61 | } 62 | 63 | private void setVX(int value, int register) { 64 | registers[register] = (0x000000FF & value); 65 | } 66 | 67 | private int getVX(int x) { 68 | return 0x000000FF & registers[x]; 69 | } 70 | 71 | public int getV0() { 72 | return registers[0] & 0xFF; 73 | } 74 | 75 | public int getV1() { 76 | return registers[1] & 0xFF; 77 | } 78 | 79 | public int getV2() { 80 | return registers[0x2] & 0xFF; 81 | } 82 | 83 | public int getV3() { 84 | return registers[0x3] & 0xFF; 85 | } 86 | 87 | public int getV4() { 88 | return registers[0x4] & 0xFF; 89 | } 90 | 91 | public int getV5() { 92 | return registers[0x5] & 0xFF; 93 | } 94 | 95 | public int getV6() { 96 | return registers[0x6] & 0xFF; 97 | } 98 | 99 | public int getV7() { 100 | return registers[0x7] & 0xFF; 101 | } 102 | 103 | public int getV8() { 104 | return registers[0x8] & 0xFF; 105 | } 106 | 107 | public int getV9() { 108 | return registers[0x9] & 0xFF; 109 | } 110 | 111 | public int getVA() { 112 | return registers[0xa] & 0xFF; 113 | } 114 | 115 | public int getVB() { 116 | return registers[0xb] & 0xFF; 117 | } 118 | 119 | public int getVC() { 120 | return registers[0xc] & 0xFF; 121 | } 122 | 123 | public int getVD() { 124 | return registers[0xd] & 0xFF; 125 | } 126 | 127 | public int getVE() { 128 | return registers[0xe] & 0xFF; 129 | } 130 | 131 | public int getVF() { 132 | return registers[0xf] & 0xFF; 133 | } 134 | 135 | public void execute(int instruction) { 136 | throw new UnsupportedOperationException("Unsupported opcode:" + Integer.toHexString(instruction)); 137 | } 138 | 139 | public void cycle() { 140 | 141 | } 142 | 143 | public int getiRegister() { 144 | return iRegister; 145 | } 146 | 147 | public byte[] getScreen() { 148 | return Arrays.copyOf(video, video.length); 149 | } 150 | 151 | public byte[] getMemory() { 152 | return Arrays.copyOf(memory, memory.length); 153 | } 154 | 155 | public int getSP() { 156 | return sp; 157 | } 158 | 159 | } 160 | -------------------------------------------------------------------------------- /src/test/java/net/saga/console/chip8/test/E08UtilitiesTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 summers. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package net.saga.console.chip8.test; 25 | 26 | import net.saga.console.chip8.Chip8; 27 | import static org.junit.Assert.assertEquals; 28 | import org.junit.Before; 29 | import org.junit.Test; 30 | 31 | /** 32 | * 33 | * Chip 8 has a few opcodes which are convenient utility methods. 34 | */ 35 | public class E08UtilitiesTest { 36 | 37 | private Chip8 chip8; 38 | 39 | @Before 40 | public void setup() { 41 | this.chip8 = new Chip8(); 42 | this.chip8.execute(0x6064); 43 | this.chip8.execute(0x6127); 44 | this.chip8.execute(0x6212); 45 | this.chip8.execute(0x63AE); 46 | this.chip8.execute(0x64FF); 47 | this.chip8.execute(0x65B4); 48 | this.chip8.execute(0x6642); 49 | this.chip8.execute(0x6F25); 50 | } 51 | 52 | 53 | /** 54 | * FX33 : Convert the value in VX to decimal and store each digit in I, I+1, 55 | * and I+2 along with any leading 0s. 56 | */ 57 | @Test 58 | public void testBCD() { 59 | chip8.execute(0xA200); 60 | 61 | chip8.execute(0xF033); 62 | byte[] memory = chip8.getMemory(); 63 | assertEquals(1, memory[0x200]); 64 | assertEquals(0, memory[0x201]); 65 | assertEquals(0, memory[0x202]); 66 | 67 | 68 | chip8.execute(0xF133); 69 | memory = chip8.getMemory(); 70 | assertEquals(0, memory[0x200]); 71 | assertEquals(3, memory[0x201]); 72 | assertEquals(9, memory[0x202]); 73 | 74 | chip8.execute(0xF433); 75 | memory = chip8.getMemory(); 76 | assertEquals(2, memory[0x200]); 77 | assertEquals(5, memory[0x201]); 78 | assertEquals(5, memory[0x202]); 79 | 80 | } 81 | 82 | /** 83 | * Chip8 has the ability to copy a range of registers to memory. 84 | * FX55 : Store the values of registers V0 -> VX to memory addresses I -> I + X. 85 | * Set I to I + X + 1 afterward. 86 | */ 87 | @Test 88 | public void copyToMemory() { 89 | chip8.execute(0xA200); 90 | chip8.execute(0xF355); 91 | 92 | byte[] memory = chip8.getMemory(); 93 | 94 | assertEquals((byte)0x64, memory[0x200]); 95 | assertEquals((byte)0x27, memory[0x201]); 96 | assertEquals((byte)0x12, memory[0x202]); 97 | assertEquals((byte)0xAE, memory[0x203]); 98 | assertEquals(0x204, chip8.getiRegister()); 99 | 100 | } 101 | 102 | /** 103 | * Chip8 has the ability to copy a range of memory to registers. 104 | * FX65 : Store the values of memory addresses I -> I + X to registers V0 -> VX. 105 | * Set I to I + X + 1 afterward. 106 | */ 107 | @Test 108 | public void copyFromMemory() { 109 | chip8.execute(0xA200); 110 | chip8.execute(0xF355); 111 | 112 | byte[] memory = chip8.getMemory(); 113 | 114 | assertEquals((byte)0x64, memory[0x200]); 115 | assertEquals((byte)0x27, memory[0x201]); 116 | assertEquals((byte)0x12, memory[0x202]); 117 | assertEquals((byte)0xAE, memory[0x203]); 118 | assertEquals(0x204, chip8.getiRegister()); 119 | 120 | chip8.execute(0xA200); 121 | this.chip8.execute(0x6000); 122 | this.chip8.execute(0x6100); 123 | this.chip8.execute(0x6200); 124 | this.chip8.execute(0x6300); 125 | chip8.execute(0xF365); 126 | 127 | assertEquals((byte)0x64, chip8.getV0()); 128 | assertEquals((byte)0x27, chip8.getV1()); 129 | assertEquals((byte)0x12, chip8.getV2()); 130 | assertEquals((byte)0xAE, (byte)chip8.getV3()); 131 | assertEquals(0x204, chip8.getiRegister()); 132 | 133 | 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /src/test/java/net/saga/console/chip8/test/E04FlowControlTest.java: -------------------------------------------------------------------------------- 1 | package net.saga.console.chip8.test; 2 | 3 | import net.saga.console.chip8.Chip8; 4 | import org.junit.After; 5 | import org.junit.AfterClass; 6 | import org.junit.Before; 7 | import org.junit.BeforeClass; 8 | import org.junit.Test; 9 | import static org.junit.Assert.assertEquals; 10 | 11 | /** 12 | * This test suite will work with flow control op codes. They will adjust the 13 | * program counter to change the next command which will be executed. 14 | * 15 | * @author summers 16 | */ 17 | public class E04FlowControlTest { 18 | 19 | private Chip8 chip8; 20 | 21 | public E04FlowControlTest() { 22 | } 23 | 24 | @BeforeClass 25 | public static void setUpClass() { 26 | } 27 | 28 | @AfterClass 29 | public static void tearDownClass() { 30 | } 31 | 32 | @Before 33 | public void setUp() { 34 | this.chip8 = new Chip8(); 35 | this.chip8.execute(0x6064); 36 | this.chip8.execute(0x6127); 37 | this.chip8.execute(0x6212); 38 | this.chip8.execute(0x63AE); 39 | this.chip8.execute(0x64FF); 40 | this.chip8.execute(0x65B4); 41 | this.chip8.execute(0x6642); 42 | this.chip8.execute(0x6F25); 43 | } 44 | 45 | @After 46 | public void tearDown() { 47 | } 48 | 49 | /** 50 | * Jump instructions are the most basic flow control. They simply tell the 51 | * processor to start executing programs from a different area of memory. 52 | * 53 | * Chip-8 has two jump instructions : 1NNN This instruction sets the program 54 | * counter to execute from memory address NNN BNNN This instruction sets the 55 | * program counter to execute from memory address (NNN + V0) 56 | */ 57 | @Test 58 | public void testJump() { 59 | chip8.execute(0x1DAE); 60 | assertEquals(0xDAE, chip8.getPC()); 61 | 62 | chip8.execute(0xB432); 63 | assertEquals(1174, chip8.getPC()); 64 | 65 | } 66 | 67 | /** 68 | * Chip8 has build in support for subroutines. 69 | * 70 | * There are two subroutine opcodes : 2NNN start executing subroutine at NNN 71 | * 00EE return from the subroutine 72 | * 73 | * For this we will have to do manipulations to the stack. 2NNN should write 74 | * the current instruction address to the stack and increment the stack 75 | * pointer. Most chip8 systems support 16 entries in the stack pointer. 76 | * 77 | */ 78 | @Test 79 | public void testSubroutines() { 80 | chip8.execute(0x2DAE); 81 | assertEquals(0xDAE, chip8.getPC()); 82 | 83 | chip8.execute(0x00EE); 84 | assertEquals(0x200, chip8.getPC()); 85 | 86 | } 87 | 88 | /** 89 | * We will now implement conditional skipping. Conditional skips are 90 | * basically jumps if some condition is met like a register having a value. 91 | * 92 | * Opcodes : 93 | * 3XNN : Skip the next instruction if the value in Vx equals NN 94 | * 5XY0 : Skip the next instruction if the value in Vx equals Vy 95 | * 4XNN : Skip the next instruction if the value in Vx does not equal NN 96 | * 9XY0 : Skip the next instruction if the value in Vx does not equal Vy 97 | * 98 | * This test will be running the program counter forward so it will also test that 99 | * we aren't doing anything too wrong with it. 100 | * 101 | * Also, don't forget we have the follow constants loaded into registers already : 102 | * V0 = 0x64 103 | * V1 = 0x27 104 | * V2 = 0x12 105 | * V3 = 0xAE 106 | * V4 = 0xFF 107 | * V5 = 0xB4 108 | * V6 = 0x42 109 | * VF = 0x25 110 | * 111 | */ 112 | @Test 113 | public void testEqualJumps() { 114 | chip8.execute(0x3064); // Skip if V0 == 0x64 115 | assertEquals(0x202, chip8.getPC());//Increment the PC by 2 116 | 117 | chip8.execute(0x3164); // Skip if V1 == 0x64, doesn't skip because V1 == 0x27 118 | assertEquals(0x202, chip8.getPC());//Do not increment the PC 119 | 120 | chip8.execute(0x6764); // Set V7 to 64 121 | chip8.execute(0x5070); // Skip if V0 == V7 122 | assertEquals(0x204, chip8.getPC());//Increment the PC by 2 123 | 124 | chip8.execute(0x5170); // Skip if V1 == V7 (It doesn't) 125 | assertEquals(0x204, chip8.getPC());//Increment the PC by 2 126 | } 127 | 128 | @Test 129 | public void testNonEqualJumps() { 130 | chip8.execute(0x4064); // Skip if V0 != 0x64 (it won't skip) 131 | assertEquals(0x200, chip8.getPC());//Increment the PC by 2 132 | 133 | chip8.execute(0x4164); // Skip if V1 == 0x64, skips because V1 == 0x27 134 | assertEquals(0x202, chip8.getPC());//Do not increment the PC 135 | 136 | chip8.execute(0x6764); // Set V7 to 64 137 | chip8.execute(0x9070); // Skip if V0 != V7(It won't skip) 138 | assertEquals(0x202, chip8.getPC());//Increment the PC by 2 139 | 140 | chip8.execute(0x9170); // Skip if V1 != V7 141 | assertEquals(0x204, chip8.getPC());//Increment the PC by 2 142 | 143 | 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/test/java/net/saga/console/chip8/test/E02BitwiseOpCodeTest.java: -------------------------------------------------------------------------------- 1 | package net.saga.console.chip8.test; 2 | 3 | import java.lang.reflect.Field; 4 | import java.util.Random; 5 | import java.util.logging.Level; 6 | import java.util.logging.Logger; 7 | import net.saga.console.chip8.Chip8; 8 | import org.junit.After; 9 | import org.junit.AfterClass; 10 | import org.junit.Before; 11 | import org.junit.BeforeClass; 12 | import org.junit.Test; 13 | import static org.junit.Assert.assertEquals; 14 | import static org.junit.Assert.assertEquals; 15 | 16 | /** 17 | * 18 | * In E01 We set up the Chip-8 processor and implemented the basic arithmetic 19 | * opcodes. In this section we will implement bitwise opcodes. 20 | * 21 | * These include AND, OR, XOR, and shifts. 22 | * 23 | * In this tests we will initialize the registers to the following values : V0 = 24 | * 0x64 V1 = 0x27 V2 = 0x12 V3 = 0xAE V4 = 0xFF V5 = 0xB4 V6 = 0x42 VF = 0x25 25 | * 26 | * All other registers will be 0. 27 | * 28 | * Additionally we will be using reflection to control the random values 29 | * generated by java. 30 | * 31 | */ 32 | public class E02BitwiseOpCodeTest { 33 | 34 | private Chip8 chip8; 35 | 36 | public E02BitwiseOpCodeTest() { 37 | } 38 | 39 | @BeforeClass 40 | public static void setUpClass() { 41 | } 42 | 43 | @AfterClass 44 | public static void tearDownClass() { 45 | } 46 | 47 | @Before 48 | public void setUp() { 49 | this.chip8 = new Chip8(); 50 | controlRandom(); 51 | this.chip8.execute(0x6064); 52 | this.chip8.execute(0x6127); 53 | this.chip8.execute(0x6212); 54 | this.chip8.execute(0x63AE); 55 | this.chip8.execute(0x64FF); 56 | this.chip8.execute(0x65B4); 57 | this.chip8.execute(0x6642); 58 | this.chip8.execute(0x6F25); 59 | } 60 | 61 | @After 62 | public void tearDown() { 63 | } 64 | 65 | /** 66 | * 8XY2 Set VX to VX & VY 67 | */ 68 | @Test 69 | public void testAndOpCodes() { 70 | chip8.execute(0x8012); // v0 = 0x64 & 0x27 71 | assertEquals(36, chip8.getV0()); 72 | assertEquals(0x27, chip8.getV1()); 73 | 74 | chip8.execute(0x8232); // v2 = 0x12 & 0xAE 75 | assertEquals(2, chip8.getV2()); 76 | assertEquals(0xAE, chip8.getV3()); 77 | 78 | chip8.execute(0x8FE2); // 0x25 & 0x0 79 | assertEquals(0, chip8.getVF()); 80 | 81 | } 82 | 83 | /** 84 | * 8XY1 Set VX to VX | VY 85 | */ 86 | @Test 87 | public void testOROpCodes() { 88 | chip8.execute(0x8011); // v0 = 0x64 | 0x27 89 | assertEquals(103, chip8.getV0()); 90 | assertEquals(0x27, chip8.getV1()); 91 | 92 | chip8.execute(0x8231); // v2 = 0x12 | 0xAE 93 | assertEquals(190, chip8.getV2()); 94 | assertEquals(0xAE, chip8.getV3()); 95 | 96 | chip8.execute(0x8FE1); // 0x25 | 0x0 97 | assertEquals(0x25, chip8.getVF()); 98 | 99 | } 100 | 101 | /** 102 | * 8XY3 Set VX to VX ^ VY 103 | */ 104 | @Test 105 | public void testXOROpCodes() { 106 | chip8.execute(0x8013); // v0 = 0x64 ^ 0x27 107 | assertEquals(67, chip8.getV0()); 108 | assertEquals(0x27, chip8.getV1()); 109 | 110 | chip8.execute(0x8233); // v2 = 0x12 ^ 0xAE 111 | assertEquals(188, chip8.getV2()); 112 | assertEquals(0xAE, chip8.getV3()); 113 | 114 | chip8.execute(0x8FE3); // 0x25 ^ 0x0 115 | assertEquals(0x25, chip8.getVF()); 116 | 117 | } 118 | 119 | /** 120 | * 8XY6 Shift VX Right one place and store the result in VX. Also store the 121 | * lest significant bit in VF 122 | */ 123 | @Test 124 | public void testShiftRight() { 125 | chip8.execute(0x8016); // v0 = 0x27 >> 1; xF = 0x1 126 | assertEquals(0x32, chip8.getV0()); 127 | assertEquals(0x0, chip8.getVF()); 128 | 129 | chip8.execute(0x8236); // v2 = 0xAE >> 1; VF = 0x0 130 | assertEquals(0x09, chip8.getV2()); 131 | assertEquals(0x0, chip8.getVF()); 132 | 133 | chip8.execute(0x8446); // V4 = 0xFF >> 1; VF = 0x1; 134 | assertEquals(127, chip8.getV4()); 135 | assertEquals(0x1, chip8.getVF()); 136 | 137 | } 138 | 139 | /** 140 | * 8XYE Shift VX left one place and store the result in VX. Also store the 141 | * most significant bit in VF 142 | */ 143 | @Test 144 | public void testShiftLeft() { 145 | chip8.execute(0x801E); // v0 = 0x27 << 1; xF = 0x1 146 | assertEquals(200, chip8.getV0()); 147 | assertEquals(0x0, chip8.getVF()); 148 | 149 | chip8.execute(0x823E); // v2 = 0xAE << 1; VF = 0x0 150 | assertEquals(36, chip8.getV2()); 151 | assertEquals(0x0, chip8.getVF()); 152 | 153 | chip8.execute(0x844E); // V4 = 0xFF << 1; VF = 0x1; 154 | assertEquals(254, chip8.getV4()); 155 | assertEquals(0x1, chip8.getVF()); 156 | 157 | } 158 | 159 | /** 160 | * CXNN Generate a Random number, mask it with NN and store the results in 161 | * Vx. 162 | */ 163 | @Test 164 | public void testRandom() { 165 | //230, 198,153, 29 166 | chip8.execute(0xC1FF); // V1 = 230 & 0xFF 167 | assertEquals(230, chip8.getV1()); 168 | 169 | chip8.execute(0xC23E); // v2 = 198 & 0x3E 170 | assertEquals(6, chip8.getV2()); 171 | 172 | chip8.execute(0xC44E); // V4 = 153 & 0x4E 173 | assertEquals(8, chip8.getV4()); 174 | 175 | 176 | } 177 | 178 | private void controlRandom() { 179 | try { 180 | Field field = chip8.getClass().getDeclaredField("random"); 181 | field.setAccessible(true); 182 | field.set(chip8, new Random(42)); 183 | } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException ex) { 184 | throw new RuntimeException(ex); 185 | } 186 | } 187 | 188 | } 189 | -------------------------------------------------------------------------------- /src/test/java/net/saga/console/chip8/test/E01ArithmeticOpCodeTest.java: -------------------------------------------------------------------------------- 1 | package net.saga.console.chip8.test; 2 | 3 | import net.saga.console.chip8.Chip8; 4 | import org.junit.After; 5 | import org.junit.AfterClass; 6 | import org.junit.Assert; 7 | import org.junit.Before; 8 | import org.junit.BeforeClass; 9 | import org.junit.Test; 10 | 11 | /** 12 | * 13 | * The first part of writing a Chip-8 emulator is setting up the main memory 14 | * correctly. 15 | * 16 | * Specifically there are several constants which are found below 0x0200 and 17 | * programs typically start at 0x0200 18 | */ 19 | public class E01ArithmeticOpCodeTest { 20 | 21 | private Chip8 chip8; 22 | 23 | public E01ArithmeticOpCodeTest() { 24 | } 25 | 26 | @BeforeClass 27 | public static void setUpClass() { 28 | } 29 | 30 | @AfterClass 31 | public static void tearDownClass() { 32 | } 33 | 34 | @Before 35 | public void setUp() { 36 | this.chip8 = new Chip8(); 37 | } 38 | 39 | @After 40 | public void tearDown() { 41 | } 42 | 43 | /** 44 | * This chip 8 program counter starts at 0x200. This seems like an obvious 45 | * first test. 46 | */ 47 | @Test 48 | public void testPCInitializedto0x200() { 49 | Assert.assertEquals(0x0200, chip8.getPC()); 50 | } 51 | 52 | /** 53 | * CHIP-8 has 16 8 bit data registers numbered V0 - VF. 54 | * 55 | * The next few tests test the initial values of the registers and several 56 | * simple opcodes. 57 | * 58 | */ 59 | @Test 60 | public void testDataRegistersInitializedTo0() { 61 | Assert.assertEquals(0x0, chip8.getV0());//Test initialized to 0 62 | Assert.assertEquals(0x0, chip8.getV1());//Test initialized to 0 63 | Assert.assertEquals(0x0, chip8.getV2());//Test initialized to 0 64 | Assert.assertEquals(0x0, chip8.getV3());//Test initialized to 0 65 | Assert.assertEquals(0x0, chip8.getV4());//Test initialized to 0 66 | Assert.assertEquals(0x0, chip8.getV5());//Test initialized to 0 67 | Assert.assertEquals(0x0, chip8.getV6());//Test initialized to 0 68 | Assert.assertEquals(0x0, chip8.getV7());//Test initialized to 0 69 | Assert.assertEquals(0x0, chip8.getV8());//Test initialized to 0 70 | Assert.assertEquals(0x0, chip8.getV9());//Test initialized to 0 71 | Assert.assertEquals(0x0, chip8.getVA());//Test initialized to 0 72 | Assert.assertEquals(0x0, chip8.getVB());//Test initialized to 0 73 | Assert.assertEquals(0x0, chip8.getVC());//Test initialized to 0 74 | Assert.assertEquals(0x0, chip8.getVD());//Test initialized to 0 75 | Assert.assertEquals(0x0, chip8.getVE());//Test initialized to 0 76 | Assert.assertEquals(0x0, chip8.getVF());//Test initialized to 0 77 | } 78 | 79 | /** 80 | * The opcode 0x6XNN stores the constant NN into register VX. 81 | * 82 | * IE 0x6A42 would store 42 into register VA. It should be noted at this 83 | * point that CHIP-8 is big-endian. 84 | * 85 | * 86 | * 87 | */ 88 | @Test 89 | public void testLoadConstant() { 90 | chip8.execute(0x6015); 91 | Assert.assertEquals(0x15, chip8.getV0());//Test loads 15 to V0 92 | 93 | chip8.execute(0x6120); 94 | Assert.assertEquals(0x20, chip8.getV1());//etc 95 | 96 | chip8.execute(0x6225); 97 | Assert.assertEquals(0x25, chip8.getV2()); 98 | 99 | chip8.execute(0x6330); 100 | Assert.assertEquals(0x30, chip8.getV3()); 101 | 102 | chip8.execute(0x6435); 103 | Assert.assertEquals(0x35, chip8.getV4()); 104 | 105 | chip8.execute(0x6540); 106 | Assert.assertEquals(0x40, chip8.getV5()); 107 | 108 | chip8.execute(0x6645); 109 | Assert.assertEquals(0x45, chip8.getV6()); 110 | 111 | chip8.execute(0x6750); 112 | Assert.assertEquals(0x50, chip8.getV7()); 113 | 114 | chip8.execute(0x6855); 115 | Assert.assertEquals(0x55, chip8.getV8()); 116 | 117 | chip8.execute(0x6960); 118 | Assert.assertEquals(0x60, chip8.getV9()); 119 | 120 | chip8.execute(0x6A65); 121 | Assert.assertEquals(0x65, chip8.getVA()); 122 | 123 | chip8.execute(0x6B70); 124 | Assert.assertEquals(0x70, chip8.getVB()); 125 | 126 | chip8.execute(0x6C75); 127 | Assert.assertEquals(0x75, chip8.getVC()); 128 | 129 | chip8.execute(0x6D80); 130 | Assert.assertEquals(0x80, chip8.getVD()); 131 | 132 | chip8.execute(0x6E85); 133 | Assert.assertEquals(0x85, chip8.getVE()); 134 | 135 | chip8.execute(0x6F90); 136 | Assert.assertEquals(0x90, chip8.getVF()); 137 | } 138 | 139 | /** 140 | * The opcode 0x7XNN adds the constant NN into the value of register VX. 141 | * 142 | * IE 0x6A42 0x7A42 143 | * 144 | * would store 42 into register VA and then add 42 to get 84. 145 | * 146 | * A special note, registers are 8 bit and should overflow appropriately. 147 | * 148 | * We will use fewer tests now, but feel free to add more to test edge cases 149 | * and other questions you might have about your code. 150 | * 151 | */ 152 | @Test 153 | public void testAddConstant() { 154 | chip8.execute(0x6015); 155 | chip8.execute(0x7015); 156 | Assert.assertEquals(0x2A, chip8.getV0()); 157 | 158 | chip8.execute(0x6A42); 159 | chip8.execute(0x7A42); 160 | Assert.assertEquals(0x84, chip8.getVA());//Test loads 15 to V0 161 | 162 | chip8.execute(0x6EFF); 163 | chip8.execute(0x7E01); 164 | Assert.assertEquals(0x0, chip8.getVE());//Test Overflow 165 | 166 | } 167 | 168 | 169 | 170 | /** 171 | * The opcode 8XY0 stores the value of register VY into VX 172 | * 173 | * IE 0x6A42 0x8EA0 174 | * 175 | * would store 42 into register VA and then copy it to register VE. 176 | * 177 | * 178 | */ 179 | @Test 180 | public void testCopyRegister() { 181 | chip8.execute(0x6A42); 182 | chip8.execute(0x8EA0); 183 | Assert.assertEquals(0x42, chip8.getVA()); 184 | Assert.assertEquals(0x42, chip8.getVE()); 185 | 186 | chip8.execute(0x6ADE); 187 | chip8.execute(0x8FA0); 188 | Assert.assertEquals(0x42, chip8.getVE()); 189 | Assert.assertEquals(0xDE, chip8.getVF()); 190 | 191 | } 192 | 193 | /** 194 | * The opcode 8XY4 adds the value of register VY to VX 195 | * 196 | * IE 0x6A42 0x6E42 0x8EA4 197 | * 198 | * would store 42 into register VA and VE and then add VA into register VE. 199 | * This instruction will modify register VF. If there is an overflow then VF 200 | * will be set to 01. If there is not an overflow it will be set to 00. 201 | * 202 | * This means that VF is always modified. 203 | * 204 | * 205 | */ 206 | @Test 207 | public void testAddRegister() { 208 | chip8.execute(0x6A42); 209 | chip8.execute(0x6E42); 210 | chip8.execute(0x8FA0); 211 | chip8.execute(0x8EA4); 212 | Assert.assertEquals(0x42, chip8.getVA()); 213 | Assert.assertEquals(0x84, chip8.getVE()); 214 | Assert.assertEquals(0x00, chip8.getVF()); 215 | 216 | chip8.execute(0x6AF0); 217 | chip8.execute(0x6E42); 218 | chip8.execute(0x8FA0); 219 | chip8.execute(0x8EA4); 220 | Assert.assertEquals(0xF0, chip8.getVA()); 221 | Assert.assertEquals(0x32, chip8.getVE()); 222 | Assert.assertEquals(0x01, chip8.getVF()); 223 | 224 | } 225 | 226 | 227 | /** 228 | * The opcodes 8XY5 and 8XY7 perform subtraction operations and set VF to 229 | * indicate a borrow. 230 | * 231 | * 8XY5 sets VX to VX - VY 8XY7 sets VX to VY - VX 232 | * 233 | * IE 0x6B84 0x6D25 0x8DB5 234 | * 235 | * would store 84 into register VB and 25 into VD. Then VD would be set to 236 | * (25 - 84) which would underflow to xxx. Additionally VF would be set to 237 | * 0x00 to indicate the borrow. 238 | * 239 | * IE 0x6B84 0x6D25 0x8DB7 240 | * 241 | * would store 84 into register VB and 25 into VD. Then VD would be set to 242 | * (84 - 25) which would be 59. Additionally VF would be set to 0x01 to 243 | * indicate there was no borrow. 244 | * 245 | */ 246 | @Test 247 | public void testSubtractRegister() { 248 | chip8.execute(0x6B84); // Set Register B to 0x84 249 | chip8.execute(0x6F84); // Set Register F to 0x84 (this tests carry) 250 | chip8.execute(0x6D25); // Set Register D to 0x25 251 | chip8.execute(0x8DB5); // Set Register D to 0x25 - 0x84. This underflows and forces a carry 252 | 253 | Assert.assertEquals(0x84, chip8.getVB()); 254 | Assert.assertEquals(161, chip8.getVD()); 255 | Assert.assertEquals(0x00, chip8.getVF()); 256 | 257 | chip8.execute(0x6B84); 258 | chip8.execute(0x6F84); 259 | chip8.execute(0x6D25); 260 | chip8.execute(0x8DB7); 261 | 262 | Assert.assertEquals(0x84, chip8.getVB()); 263 | Assert.assertEquals(95, chip8.getVD()); 264 | Assert.assertEquals(0x01, chip8.getVF()); 265 | 266 | } 267 | 268 | } 269 | -------------------------------------------------------------------------------- /src/main/java/net/saga/console/chip8/SwingMain.form: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 |
246 | -------------------------------------------------------------------------------- /src/test/java/net/saga/console/chip8/test/E07GraphicsTest.java: -------------------------------------------------------------------------------- 1 | package net.saga.console.chip8.test; 2 | 3 | import java.io.IOException; 4 | import net.saga.console.chip8.Chip8; 5 | import net.saga.console.chip8.util.Chip8Utils; 6 | import static org.junit.Assert.assertEquals; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | 10 | /** 11 | * This last test class covers graphics. There are three main parts of graphics 12 | * : The I Register, Fonts, and Sprites. 13 | */ 14 | public class E07GraphicsTest { 15 | 16 | private Chip8 chip8; 17 | 18 | @Before 19 | public void setUp() throws IOException { 20 | //Graphics ROM has the sprite FF3C stored starting at 202 21 | // This is the following two row sprite 22 | // 23 | // ******** 24 | // **** 25 | // 26 | //Executed the program just infinitely loops 27 | this.chip8 = Chip8Utils.createFromRom(getClass().getResource("/E07GraphicsRom.ch8")); 28 | this.chip8.execute(0x6064); 29 | this.chip8.execute(0x6127); 30 | this.chip8.execute(0x6212); 31 | this.chip8.execute(0x63AE); 32 | this.chip8.execute(0x64FF); 33 | this.chip8.execute(0x65B4); 34 | this.chip8.execute(0x6642); 35 | this.chip8.execute(0x673F); 36 | this.chip8.execute(0x681F); 37 | this.chip8.execute(0x6F25); 38 | } 39 | 40 | /** 41 | * The I Register holds memory addresses. A program can not read the value 42 | * currently set in the I register but may only set it. 43 | * 44 | * There are two opcodes for the IRegister : ANNN : Stores in the I register 45 | * the address 0xNNN FX1E : Adds to the value in the I-Register the value of 46 | * VX. 47 | */ 48 | @Test 49 | public void testIRegister() { 50 | this.chip8.execute(0xA123); 51 | assertEquals(0x123, chip8.getiRegister()); 52 | 53 | this.chip8.execute(0xF11E); 54 | assertEquals(0x123 + 0x27, chip8.getiRegister()); 55 | 56 | } 57 | 58 | /** 59 | * 60 | * Sprints in chip8 are represented in memory as columns of 8 bits with a 61 | * variable number of rows. The 8 bits each correspond to a pixel with 1 62 | * being toggled and 0 being transparent. The sprites are accessed starting 63 | * at the memory address pointed to by the I register. 64 | * 65 | * Sprites are XOR drawn. This means that a 1 will flip that particular 66 | * pixel. If a pixel is unset by this operation then the register VF is set 67 | * to 1. Otherwise it is 0. 68 | * 69 | * DXYN : Draw a Sprite of N rows at position VX,VY with the data pointed to 70 | * by the I register. 71 | * 72 | * 73 | */ 74 | @Test 75 | public void drawSprite() { 76 | chip8.execute(0xA202); 77 | chip8.execute(0xD122); // Draw Sprite at 39, 18 78 | assertEquals(0, chip8.getVF()); 79 | 80 | byte[] video = chip8.getScreen(); 81 | assertEquals(1, video[39 + 64 * 18]); 82 | assertEquals(1, video[40 + 64 * 18]); 83 | assertEquals(1, video[41 + 64 * 18]); 84 | assertEquals(1, video[42 + 64 * 18]); 85 | assertEquals(1, video[43 + 64 * 18]); 86 | assertEquals(1, video[44 + 64 * 18]); 87 | assertEquals(1, video[45 + 64 * 18]); 88 | assertEquals(1, video[46 + 64 * 18]); 89 | 90 | assertEquals(0, video[39 + 64 * 19]); 91 | assertEquals(0, video[40 + 64 * 19]); 92 | assertEquals(1, video[41 + 64 * 19]); 93 | assertEquals(1, video[42 + 64 * 19]); 94 | assertEquals(1, video[43 + 64 * 19]); 95 | assertEquals(1, video[44 + 64 * 19]); 96 | assertEquals(0, video[45 + 64 * 19]); 97 | assertEquals(0, video[46 + 64 * 19]); 98 | 99 | chip8.execute(0xD122); // Draw Sprite at 39,18 100 | assertEquals(1, chip8.getVF()); 101 | 102 | video = chip8.getScreen(); 103 | assertEquals(0, video[39 + 64 * 18]); 104 | assertEquals(0, video[40 + 64 * 18]); 105 | assertEquals(0, video[41 + 64 * 18]); 106 | assertEquals(0, video[42 + 64 * 18]); 107 | assertEquals(0, video[43 + 64 * 18]); 108 | assertEquals(0, video[44 + 64 * 18]); 109 | assertEquals(0, video[45 + 64 * 18]); 110 | assertEquals(0, video[46 + 64 * 18]); 111 | 112 | assertEquals(0, video[39 + 64 * 19]); 113 | assertEquals(0, video[40 + 64 * 19]); 114 | assertEquals(0, video[41 + 64 * 19]); 115 | assertEquals(0, video[42 + 64 * 19]); 116 | assertEquals(0, video[43 + 64 * 19]); 117 | assertEquals(0, video[44 + 64 * 19]); 118 | assertEquals(0, video[45 + 64 * 19]); 119 | assertEquals(0, video[46 + 64 * 19]); 120 | 121 | } 122 | 123 | /** 124 | * Sprites wrap around on their axis. IE If you draw to X 65 it will wrap to 125 | * position 1. 126 | */ 127 | @Test 128 | public void drawSpriteWrap() { 129 | chip8.execute(0xA202); 130 | chip8.execute(0xD212); // Draw Sprite at 18, 7 (39 wraps to 7) 131 | assertEquals(0, chip8.getVF()); 132 | 133 | byte[] video = chip8.getScreen(); 134 | assertEquals(1, video[18 + 64 * 7]); 135 | assertEquals(1, video[19 + 64 * 7]); 136 | assertEquals(1, video[20 + 64 * 7]); 137 | assertEquals(1, video[21 + 64 * 7]); 138 | assertEquals(1, video[22 + 64 * 7]); 139 | assertEquals(1, video[23 + 64 * 7]); 140 | assertEquals(1, video[24 + 64 * 7]); 141 | assertEquals(1, video[25 + 64 * 7]); 142 | 143 | assertEquals(0, video[18 + 64 * 8]); 144 | assertEquals(0, video[19 + 64 * 8]); 145 | assertEquals(1, video[20 + 64 * 8]); 146 | assertEquals(1, video[21 + 64 * 8]); 147 | assertEquals(1, video[22 + 64 * 8]); 148 | assertEquals(1, video[23 + 64 * 8]); 149 | assertEquals(0, video[24 + 64 * 8]); 150 | assertEquals(0, video[25 + 64 * 8]); 151 | 152 | } 153 | 154 | /** 155 | * Sprites wrap around on their axis. IE If you draw to X 65 it will wrap to 156 | * position 1. 157 | */ 158 | @Test 159 | public void drawSpriteBottomRightEdgeTests() { 160 | chip8.execute(0xA202); 161 | chip8.execute(0xD781); 162 | chip8.execute(0xD871); 163 | chip8.execute(0xD781); 164 | chip8.execute(0xD871); 165 | } 166 | 167 | /** 168 | * The opcode 00E0 clears the screen. 169 | * 170 | */ 171 | @Test 172 | public void testClearVideo() { 173 | chip8.execute(0xA202); 174 | chip8.execute(0xD212); // Draw Sprite at 18, 7 (39 wraps to 7) 175 | chip8.execute(0x00E0); 176 | 177 | byte[] video = chip8.getScreen(); 178 | 179 | assertEquals(0, video[18 + 64 * 7]); 180 | assertEquals(0, video[19 + 64 * 7]); 181 | assertEquals(0, video[20 + 64 * 7]); 182 | assertEquals(0, video[21 + 64 * 7]); 183 | assertEquals(0, video[22 + 64 * 7]); 184 | assertEquals(0, video[23 + 64 * 7]); 185 | assertEquals(0, video[24 + 64 * 7]); 186 | assertEquals(0, video[25 + 64 * 7]); 187 | 188 | } 189 | 190 | /** 191 | * Chip 8 has build in hexedecimal fonts. These fonts are stored in the 200 192 | * bytes of reserved data and accessed using a special opcode. 193 | * 194 | * FX29 : Set the I register to the address of the sprite corresponding to 195 | * the hex digit stored in VX. 196 | */ 197 | @Test 198 | public void testHexFonts() { 199 | 200 | chip8.execute(0x6100); //Store 00 in V1 201 | chip8.execute(0x6200); //Store 00 in V2 202 | 203 | for (int i = 0; i <= 0xf; i++) { 204 | chip8.execute(0x6000 + i); //Store 00 in V0 205 | chip8.execute(0xF029); // Set I to the memory address of the font sprite in V0 206 | chip8.execute(0xD125); // Draw all 5 lines of the sprite at 0, 0. 207 | 208 | checkGraphics(i);//Test for being drawn. 209 | chip8.execute(0x00E0); // Clear Screen 210 | 211 | } 212 | 213 | } 214 | 215 | private void checkGraphics(int i) { 216 | byte[] video = chip8.getScreen(); 217 | switch (i) { 218 | case 0: 219 | assertEquals((byte)0xF0, (byte)Chip8Utils.getSpriteRow(0, 0, video)); 220 | assertEquals((byte)0x90, Chip8Utils.getSpriteRow(0, 1, video)); 221 | assertEquals((byte)0x90, Chip8Utils.getSpriteRow(0, 2, video)); 222 | assertEquals((byte)0x90, Chip8Utils.getSpriteRow(0, 3, video)); 223 | assertEquals((byte)0xF0, Chip8Utils.getSpriteRow(0, 4, video)); 224 | break; 225 | case 1: 226 | assertEquals((byte)0x20, Chip8Utils.getSpriteRow(0, 0, video)); 227 | assertEquals((byte)0x60, Chip8Utils.getSpriteRow(0, 1, video)); 228 | assertEquals((byte)0x20, Chip8Utils.getSpriteRow(0, 2, video)); 229 | assertEquals((byte)0x20, Chip8Utils.getSpriteRow(0, 3, video)); 230 | assertEquals((byte)0x70, Chip8Utils.getSpriteRow(0, 4, video)); 231 | break; 232 | case 2: 233 | assertEquals((byte)0xF0, Chip8Utils.getSpriteRow(0, 0, video)); 234 | assertEquals((byte)0x10, Chip8Utils.getSpriteRow(0, 1, video)); 235 | assertEquals((byte)0xF0, Chip8Utils.getSpriteRow(0, 2, video)); 236 | assertEquals((byte)0x80, Chip8Utils.getSpriteRow(0, 3, video)); 237 | assertEquals((byte)0xF0, Chip8Utils.getSpriteRow(0, 4, video)); 238 | break; 239 | case 3: 240 | assertEquals((byte)0xF0, Chip8Utils.getSpriteRow(0, 0, video)); 241 | assertEquals((byte)0x10, Chip8Utils.getSpriteRow(0, 1, video)); 242 | assertEquals((byte)0xF0, Chip8Utils.getSpriteRow(0, 2, video)); 243 | assertEquals((byte)0x10, Chip8Utils.getSpriteRow(0, 3, video)); 244 | assertEquals((byte)0xF0, Chip8Utils.getSpriteRow(0, 4, video)); 245 | break; 246 | case 4: 247 | assertEquals((byte)0x90, Chip8Utils.getSpriteRow(0, 0, video)); 248 | assertEquals((byte)0x90, Chip8Utils.getSpriteRow(0, 1, video)); 249 | assertEquals((byte)0xF0, Chip8Utils.getSpriteRow(0, 2, video)); 250 | assertEquals((byte)0x10, Chip8Utils.getSpriteRow(0, 3, video)); 251 | assertEquals((byte)0x10, Chip8Utils.getSpriteRow(0, 4, video)); 252 | break; 253 | case 5: 254 | assertEquals((byte)0xF0, Chip8Utils.getSpriteRow(0, 0, video)); 255 | assertEquals((byte)0x80, Chip8Utils.getSpriteRow(0, 1, video)); 256 | assertEquals((byte)0xF0, Chip8Utils.getSpriteRow(0, 2, video)); 257 | assertEquals((byte)0x10, Chip8Utils.getSpriteRow(0, 3, video)); 258 | assertEquals((byte)0xF0, Chip8Utils.getSpriteRow(0, 4, video)); 259 | break; 260 | case 6: 261 | assertEquals((byte)0xF0, Chip8Utils.getSpriteRow(0, 0, video)); 262 | assertEquals((byte)0x80, Chip8Utils.getSpriteRow(0, 1, video)); 263 | assertEquals((byte)0xF0, Chip8Utils.getSpriteRow(0, 2, video)); 264 | assertEquals((byte)0x90, Chip8Utils.getSpriteRow(0, 3, video)); 265 | assertEquals((byte)0xF0, Chip8Utils.getSpriteRow(0, 4, video)); 266 | break; 267 | case 7: 268 | assertEquals((byte)0xF0, Chip8Utils.getSpriteRow(0, 0, video)); 269 | assertEquals((byte)0x10, Chip8Utils.getSpriteRow(0, 1, video)); 270 | assertEquals((byte)0x20, Chip8Utils.getSpriteRow(0, 2, video)); 271 | assertEquals((byte)0x40, Chip8Utils.getSpriteRow(0, 3, video)); 272 | assertEquals((byte)0x40, Chip8Utils.getSpriteRow(0, 4, video)); 273 | break; 274 | case 8: 275 | assertEquals((byte)0xF0, Chip8Utils.getSpriteRow(0, 0, video)); 276 | assertEquals((byte)0x90, Chip8Utils.getSpriteRow(0, 1, video)); 277 | assertEquals((byte)0xF0, Chip8Utils.getSpriteRow(0, 2, video)); 278 | assertEquals((byte)0x90, Chip8Utils.getSpriteRow(0, 3, video)); 279 | assertEquals((byte)0xF0, Chip8Utils.getSpriteRow(0, 4, video)); 280 | break; 281 | case 9: 282 | assertEquals((byte)0xF0, Chip8Utils.getSpriteRow(0, 0, video)); 283 | assertEquals((byte)0x90, Chip8Utils.getSpriteRow(0, 1, video)); 284 | assertEquals((byte)0xF0, Chip8Utils.getSpriteRow(0, 2, video)); 285 | assertEquals((byte)0x10, Chip8Utils.getSpriteRow(0, 3, video)); 286 | assertEquals((byte)0xF0, Chip8Utils.getSpriteRow(0, 4, video)); 287 | break; 288 | case 0xA: 289 | assertEquals((byte)0xF0, Chip8Utils.getSpriteRow(0, 0, video)); 290 | assertEquals((byte)0x90, Chip8Utils.getSpriteRow(0, 1, video)); 291 | assertEquals((byte)0xF0, Chip8Utils.getSpriteRow(0, 2, video)); 292 | assertEquals((byte)0x90, Chip8Utils.getSpriteRow(0, 3, video)); 293 | assertEquals((byte)0x90, Chip8Utils.getSpriteRow(0, 4, video)); 294 | break; 295 | case 0xB: 296 | assertEquals((byte)0xE0, Chip8Utils.getSpriteRow(0, 0, video)); 297 | assertEquals((byte)0x90, Chip8Utils.getSpriteRow(0, 1, video)); 298 | assertEquals((byte)0xE0, Chip8Utils.getSpriteRow(0, 2, video)); 299 | assertEquals((byte)0x90, Chip8Utils.getSpriteRow(0, 3, video)); 300 | assertEquals((byte)0xE0, Chip8Utils.getSpriteRow(0, 4, video)); 301 | break; 302 | case 0xC: 303 | assertEquals((byte)0xF0, Chip8Utils.getSpriteRow(0, 0, video)); 304 | assertEquals((byte)0x80, Chip8Utils.getSpriteRow(0, 1, video)); 305 | assertEquals((byte)0x80, Chip8Utils.getSpriteRow(0, 2, video)); 306 | assertEquals((byte)0x80, Chip8Utils.getSpriteRow(0, 3, video)); 307 | assertEquals((byte)0xF0, Chip8Utils.getSpriteRow(0, 4, video)); 308 | break; 309 | case 0xD: 310 | assertEquals((byte)0xE0, Chip8Utils.getSpriteRow(0, 0, video)); 311 | assertEquals((byte)0x90, Chip8Utils.getSpriteRow(0, 1, video)); 312 | assertEquals((byte)0x90, Chip8Utils.getSpriteRow(0, 2, video)); 313 | assertEquals((byte)0x90, Chip8Utils.getSpriteRow(0, 3, video)); 314 | assertEquals((byte)0xE0, Chip8Utils.getSpriteRow(0, 4, video)); 315 | break; 316 | case 0xE: 317 | assertEquals((byte)0xF0, Chip8Utils.getSpriteRow(0, 0, video)); 318 | assertEquals((byte)0x80, Chip8Utils.getSpriteRow(0, 1, video)); 319 | assertEquals((byte)0xF0, Chip8Utils.getSpriteRow(0, 2, video)); 320 | assertEquals((byte)0x80, Chip8Utils.getSpriteRow(0, 3, video)); 321 | assertEquals((byte)0xF0, Chip8Utils.getSpriteRow(0, 4, video)); 322 | break; 323 | case 0xF: 324 | assertEquals((byte)0xF0, Chip8Utils.getSpriteRow(0, 0, video)); 325 | assertEquals((byte)0x80, Chip8Utils.getSpriteRow(0, 1, video)); 326 | assertEquals((byte)0xF0, Chip8Utils.getSpriteRow(0, 2, video)); 327 | assertEquals((byte)0x80, Chip8Utils.getSpriteRow(0, 3, video)); 328 | assertEquals((byte)0x80, Chip8Utils.getSpriteRow(0, 4, video)); 329 | 330 | break; 331 | } 332 | } 333 | 334 | } 335 | -------------------------------------------------------------------------------- /src/main/java/net/saga/console/chip8/SwingMain.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 summers. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package net.saga.console.chip8; 25 | 26 | import net.saga.console.chip8.util.Chip8DisplayPanel; 27 | import net.saga.console.chip8.util.Chip8Utils; 28 | import java.awt.KeyEventDispatcher; 29 | import java.awt.KeyboardFocusManager; 30 | import java.awt.event.KeyEvent; 31 | import java.awt.event.KeyListener; 32 | import java.io.File; 33 | import java.io.IOException; 34 | import static java.lang.Thread.sleep; 35 | import java.util.logging.Level; 36 | import java.util.logging.Logger; 37 | import javax.swing.JFileChooser; 38 | import javax.swing.JOptionPane; 39 | import net.saga.console.chip8.util.Input; 40 | 41 | /** 42 | * 43 | * @author summers 44 | */ 45 | public class SwingMain extends javax.swing.JFrame implements KeyListener { 46 | 47 | private static final long serialVersionUID = 1L; 48 | 49 | final JFileChooser fc = new JFileChooser(); 50 | private Chip8 chip8; 51 | private boolean running = false; 52 | private boolean pause = false; 53 | private boolean step = false; 54 | 55 | /** 56 | * Creates new form SwingMain 57 | */ 58 | public SwingMain() { 59 | this.chip8Runner = new Thread(() -> { 60 | while (true) { 61 | try { 62 | if (running) { 63 | if (pause == false || step) { 64 | step = false; 65 | chip8.cycle(); 66 | ((Chip8Model) jTable1.getModel()).update(chip8); 67 | } 68 | outputPanel.repaint(); 69 | } 70 | sleep(1); 71 | } catch (InterruptedException ex) { 72 | Logger.getLogger(SwingMain.class.getName()).log(Level.SEVERE, null, ex); 73 | } 74 | } 75 | }); 76 | initComponents(); 77 | KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); 78 | manager.addKeyEventDispatcher(new KeyEventDispatcher() { 79 | @Override 80 | public boolean dispatchKeyEvent(KeyEvent e) { 81 | switch (e.getID()) { 82 | case KeyEvent.KEY_PRESSED: 83 | Input.press(e.getKeyChar() + ""); 84 | break; 85 | case KeyEvent.KEY_RELEASED: 86 | Input.unpress(); 87 | break; 88 | default: 89 | break; 90 | } 91 | return false; 92 | } 93 | }); 94 | } 95 | 96 | /** 97 | * This method is called from within the constructor to initialize the form. 98 | * WARNING: Do NOT modify this code. The content of this method is always 99 | * regenerated by the Form Editor. 100 | */ 101 | @SuppressWarnings("unchecked") 102 | // //GEN-BEGIN:initComponents 103 | private void initComponents() { 104 | 105 | jPanel1 = new javax.swing.JPanel(); 106 | openButton = new javax.swing.JButton(); 107 | aboutButton = new javax.swing.JButton(); 108 | inputMapButton = new javax.swing.JButton(); 109 | outputPanel = new Chip8DisplayPanel(); 110 | jScrollPane1 = new javax.swing.JScrollPane(); 111 | jTable1 = new javax.swing.JTable(); 112 | jButton1 = new javax.swing.JButton(); 113 | jButton2 = new javax.swing.JButton(); 114 | jButton3 = new javax.swing.JButton(); 115 | 116 | setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); 117 | setBackground(java.awt.Color.white); 118 | setMinimumSize(new java.awt.Dimension(286, 175)); 119 | addKeyListener(new java.awt.event.KeyAdapter() { 120 | public void keyPressed(java.awt.event.KeyEvent evt) { 121 | formKeyPressed(evt); 122 | } 123 | public void keyReleased(java.awt.event.KeyEvent evt) { 124 | formKeyReleased(evt); 125 | } 126 | }); 127 | 128 | jPanel1.setBackground(new java.awt.Color(51, 51, 255)); 129 | outputPanel.addKeyListener(SwingMain.this); 130 | 131 | openButton.setBackground(new java.awt.Color(102, 102, 255)); 132 | openButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/open.png"))); // NOI18N 133 | openButton.setToolTipText("Open Binary"); 134 | openButton.addActionListener(new java.awt.event.ActionListener() { 135 | public void actionPerformed(java.awt.event.ActionEvent evt) { 136 | openButtonActionPerformed(evt); 137 | } 138 | }); 139 | 140 | aboutButton.setBackground(new java.awt.Color(102, 102, 255)); 141 | aboutButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/about.png"))); // NOI18N 142 | aboutButton.setToolTipText("About"); 143 | aboutButton.addActionListener(new java.awt.event.ActionListener() { 144 | public void actionPerformed(java.awt.event.ActionEvent evt) { 145 | aboutButtonActionPerformed(evt); 146 | } 147 | }); 148 | 149 | inputMapButton.setBackground(new java.awt.Color(102, 102, 255)); 150 | inputMapButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/input.png"))); // NOI18N 151 | inputMapButton.addActionListener(new java.awt.event.ActionListener() { 152 | public void actionPerformed(java.awt.event.ActionEvent evt) { 153 | inputMapButtonActionPerformed(evt); 154 | } 155 | }); 156 | 157 | javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); 158 | jPanel1.setLayout(jPanel1Layout); 159 | jPanel1Layout.setHorizontalGroup( 160 | jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) 161 | .addGroup(jPanel1Layout.createSequentialGroup() 162 | .addContainerGap() 163 | .addComponent(openButton, javax.swing.GroupLayout.PREFERRED_SIZE, 48, javax.swing.GroupLayout.PREFERRED_SIZE) 164 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 165 | .addComponent(aboutButton, javax.swing.GroupLayout.PREFERRED_SIZE, 48, javax.swing.GroupLayout.PREFERRED_SIZE) 166 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 167 | .addComponent(inputMapButton, javax.swing.GroupLayout.PREFERRED_SIZE, 48, javax.swing.GroupLayout.PREFERRED_SIZE) 168 | .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) 169 | ); 170 | jPanel1Layout.setVerticalGroup( 171 | jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) 172 | .addGroup(jPanel1Layout.createSequentialGroup() 173 | .addContainerGap() 174 | .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) 175 | .addComponent(inputMapButton, javax.swing.GroupLayout.PREFERRED_SIZE, 48, javax.swing.GroupLayout.PREFERRED_SIZE) 176 | .addComponent(aboutButton, javax.swing.GroupLayout.PREFERRED_SIZE, 48, javax.swing.GroupLayout.PREFERRED_SIZE) 177 | .addComponent(openButton, javax.swing.GroupLayout.PREFERRED_SIZE, 48, javax.swing.GroupLayout.PREFERRED_SIZE)) 178 | .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) 179 | ); 180 | 181 | outputPanel.setMinimumSize(new java.awt.Dimension(64, 32)); 182 | outputPanel.addKeyListener(SwingMain.this); 183 | 184 | javax.swing.GroupLayout outputPanelLayout = new javax.swing.GroupLayout(outputPanel); 185 | outputPanel.setLayout(outputPanelLayout); 186 | outputPanelLayout.setHorizontalGroup( 187 | outputPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) 188 | .addGap(0, 697, Short.MAX_VALUE) 189 | ); 190 | outputPanelLayout.setVerticalGroup( 191 | outputPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) 192 | .addGap(0, 0, Short.MAX_VALUE) 193 | ); 194 | 195 | jTable1.setModel(new Chip8Model()); 196 | jScrollPane1.setViewportView(jTable1); 197 | 198 | jButton1.setBackground(new java.awt.Color(102, 102, 255)); 199 | jButton1.setIcon(new javax.swing.ImageIcon(getClass().getResource("/play.png"))); // NOI18N 200 | jButton1.addActionListener(new java.awt.event.ActionListener() { 201 | public void actionPerformed(java.awt.event.ActionEvent evt) { 202 | jButton1ActionPerformed(evt); 203 | } 204 | }); 205 | 206 | jButton2.setBackground(new java.awt.Color(102, 102, 255)); 207 | jButton2.setIcon(new javax.swing.ImageIcon(getClass().getResource("/pause.png"))); // NOI18N 208 | jButton2.addActionListener(new java.awt.event.ActionListener() { 209 | public void actionPerformed(java.awt.event.ActionEvent evt) { 210 | jButton2ActionPerformed(evt); 211 | } 212 | }); 213 | 214 | jButton3.setBackground(new java.awt.Color(102, 102, 255)); 215 | jButton3.setIcon(new javax.swing.ImageIcon(getClass().getResource("/skip.png"))); // NOI18N 216 | jButton3.addActionListener(new java.awt.event.ActionListener() { 217 | public void actionPerformed(java.awt.event.ActionEvent evt) { 218 | jButton3ActionPerformed(evt); 219 | } 220 | }); 221 | 222 | javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); 223 | getContentPane().setLayout(layout); 224 | layout.setHorizontalGroup( 225 | layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) 226 | .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() 227 | .addContainerGap() 228 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) 229 | .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) 230 | .addGroup(layout.createSequentialGroup() 231 | .addComponent(outputPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) 232 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 233 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) 234 | .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 186, javax.swing.GroupLayout.PREFERRED_SIZE) 235 | .addGroup(layout.createSequentialGroup() 236 | .addGap(2, 2, 2) 237 | .addComponent(jButton2, javax.swing.GroupLayout.PREFERRED_SIZE, 48, javax.swing.GroupLayout.PREFERRED_SIZE) 238 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 239 | .addComponent(jButton1, javax.swing.GroupLayout.PREFERRED_SIZE, 48, javax.swing.GroupLayout.PREFERRED_SIZE) 240 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 241 | .addComponent(jButton3, javax.swing.GroupLayout.PREFERRED_SIZE, 48, javax.swing.GroupLayout.PREFERRED_SIZE))))) 242 | .addContainerGap()) 243 | ); 244 | layout.setVerticalGroup( 245 | layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) 246 | .addGroup(layout.createSequentialGroup() 247 | .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) 248 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 249 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) 250 | .addComponent(outputPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) 251 | .addGroup(layout.createSequentialGroup() 252 | .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 456, Short.MAX_VALUE) 253 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 254 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) 255 | .addComponent(jButton1, javax.swing.GroupLayout.PREFERRED_SIZE, 48, javax.swing.GroupLayout.PREFERRED_SIZE) 256 | .addComponent(jButton2, javax.swing.GroupLayout.PREFERRED_SIZE, 48, javax.swing.GroupLayout.PREFERRED_SIZE) 257 | .addComponent(jButton3, javax.swing.GroupLayout.PREFERRED_SIZE, 48, javax.swing.GroupLayout.PREFERRED_SIZE)))) 258 | .addContainerGap()) 259 | ); 260 | 261 | pack(); 262 | }// //GEN-END:initComponents 263 | 264 | private void openButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_openButtonActionPerformed 265 | if (evt.getSource() == openButton) { 266 | int returnVal = fc.showOpenDialog(SwingMain.this); 267 | 268 | if (returnVal == JFileChooser.APPROVE_OPTION) { 269 | try { 270 | running = false; 271 | File file = fc.getSelectedFile(); 272 | SwingMain.this.chip8 = Chip8Utils.createFromRom(file); 273 | restartEmulator(); 274 | } catch (IOException ex) { 275 | Logger.getLogger(SwingMain.class.getName()).log(Level.SEVERE, null, ex); 276 | JOptionPane.showMessageDialog(null, ex, "Error", JOptionPane.ERROR_MESSAGE); 277 | } 278 | } 279 | } 280 | }//GEN-LAST:event_openButtonActionPerformed 281 | 282 | private void aboutButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_aboutButtonActionPerformed 283 | JOptionPane.showMessageDialog(null, "Chip-8 in Java by secondsun. \n github.com/secondsun/chip8"); 284 | }//GEN-LAST:event_aboutButtonActionPerformed 285 | 286 | private void inputMapButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_inputMapButtonActionPerformed 287 | new InputMapping().setVisible(true); 288 | }//GEN-LAST:event_inputMapButtonActionPerformed 289 | 290 | private void formKeyPressed(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_formKeyPressed 291 | Input.press(evt.getKeyChar()); 292 | }//GEN-LAST:event_formKeyPressed 293 | 294 | private void formKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_formKeyReleased 295 | Input.unpress(); 296 | }//GEN-LAST:event_formKeyReleased 297 | 298 | private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton2ActionPerformed 299 | this.pause = true; 300 | }//GEN-LAST:event_jButton2ActionPerformed 301 | 302 | private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton1ActionPerformed 303 | this.pause = false; 304 | }//GEN-LAST:event_jButton1ActionPerformed 305 | 306 | private void jButton3ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton3ActionPerformed 307 | this.step = true; 308 | }//GEN-LAST:event_jButton3ActionPerformed 309 | 310 | /** 311 | * @param args the command line arguments 312 | */ 313 | public static void main(String args[]) { 314 | /* Set the Nimbus look and feel */ 315 | // 316 | /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel. 317 | * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html 318 | */ 319 | try { 320 | for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) { 321 | if ("Nimbus".equals(info.getName())) { 322 | javax.swing.UIManager.setLookAndFeel(info.getClassName()); 323 | break; 324 | } 325 | } 326 | } catch (ClassNotFoundException ex) { 327 | java.util.logging.Logger.getLogger(SwingMain.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); 328 | } catch (InstantiationException ex) { 329 | java.util.logging.Logger.getLogger(SwingMain.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); 330 | } catch (IllegalAccessException ex) { 331 | java.util.logging.Logger.getLogger(SwingMain.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); 332 | } catch (javax.swing.UnsupportedLookAndFeelException ex) { 333 | java.util.logging.Logger.getLogger(SwingMain.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); 334 | } 335 | // 336 | 337 | /* Create and display the form */ 338 | java.awt.EventQueue.invokeLater(new Runnable() { 339 | public void run() { 340 | new SwingMain().setVisible(true); 341 | } 342 | }); 343 | } 344 | 345 | // Variables declaration - do not modify//GEN-BEGIN:variables 346 | private javax.swing.JButton aboutButton; 347 | private javax.swing.JButton inputMapButton; 348 | private javax.swing.JButton jButton1; 349 | private javax.swing.JButton jButton2; 350 | private javax.swing.JButton jButton3; 351 | private javax.swing.JPanel jPanel1; 352 | private javax.swing.JScrollPane jScrollPane1; 353 | private javax.swing.JTable jTable1; 354 | private javax.swing.JButton openButton; 355 | private javax.swing.JPanel outputPanel; 356 | // End of variables declaration//GEN-END:variables 357 | 358 | private void restartEmulator() { 359 | Chip8DisplayPanel panel = (Chip8DisplayPanel) outputPanel; 360 | panel.setChip8(chip8); 361 | if (!chip8Runner.isAlive()) { 362 | this.chip8Runner = new Thread(() -> { 363 | while (true) { 364 | try { 365 | if (running) { 366 | if (pause == false || step) { 367 | step = false; 368 | chip8.cycle(); 369 | ((Chip8Model) jTable1.getModel()).update(chip8); 370 | } 371 | outputPanel.repaint(); 372 | } 373 | sleep(1); 374 | } catch (InterruptedException ex) { 375 | Logger.getLogger(SwingMain.class.getName()).log(Level.SEVERE, null, ex); 376 | } 377 | } 378 | }); 379 | chip8Runner.start(); 380 | } 381 | running = true; 382 | 383 | } 384 | private Thread chip8Runner; 385 | 386 | @Override 387 | public void keyTyped(KeyEvent e) { 388 | } 389 | 390 | @Override 391 | public void keyPressed(KeyEvent e) { 392 | Input.press(e.getKeyChar()); 393 | } 394 | 395 | @Override 396 | public void keyReleased(KeyEvent e) { 397 | Input.unpress(); 398 | } 399 | 400 | } 401 | -------------------------------------------------------------------------------- /src/main/java/net/saga/console/chip8/InputMapping.form: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 |
443 | -------------------------------------------------------------------------------- /src/main/java/net/saga/console/chip8/InputMapping.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 summers. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package net.saga.console.chip8; 25 | 26 | import javax.swing.text.AttributeSet; 27 | import javax.swing.text.BadLocationException; 28 | import javax.swing.text.PlainDocument; 29 | import net.saga.console.chip8.util.Input; 30 | 31 | /** 32 | * 33 | * @author summers 34 | */ 35 | public class InputMapping extends javax.swing.JFrame { 36 | 37 | /** 38 | * Creates new form InputMapping 39 | */ 40 | public InputMapping() { 41 | initComponents(); 42 | } 43 | 44 | /** 45 | * This method is called from within the constructor to initialize the form. 46 | * WARNING: Do NOT modify this code. The content of this method is always 47 | * regenerated by the Form Editor. 48 | */ 49 | @SuppressWarnings("unchecked") 50 | // //GEN-BEGIN:initComponents 51 | private void initComponents() { 52 | 53 | jLabel1 = new javax.swing.JLabel(); 54 | jLabel2 = new javax.swing.JLabel(); 55 | jLabel3 = new javax.swing.JLabel(); 56 | jLabel4 = new javax.swing.JLabel(); 57 | jLabel5 = new javax.swing.JLabel(); 58 | jLabel6 = new javax.swing.JLabel(); 59 | jLabel7 = new javax.swing.JLabel(); 60 | jLabel8 = new javax.swing.JLabel(); 61 | jLabel9 = new javax.swing.JLabel(); 62 | jLabel10 = new javax.swing.JLabel(); 63 | jLabel11 = new javax.swing.JLabel(); 64 | jLabel12 = new javax.swing.JLabel(); 65 | jLabel13 = new javax.swing.JLabel(); 66 | jLabel14 = new javax.swing.JLabel(); 67 | jLabel15 = new javax.swing.JLabel(); 68 | jLabel16 = new javax.swing.JLabel(); 69 | zeroMapping = new javax.swing.JTextField(); 70 | oneMapping = new javax.swing.JTextField(); 71 | twoMapping = new javax.swing.JTextField(); 72 | threeMapping = new javax.swing.JTextField(); 73 | fourMapping = new javax.swing.JTextField(); 74 | fiveMapping = new javax.swing.JTextField(); 75 | sixMapping = new javax.swing.JTextField(); 76 | sevenMapping = new javax.swing.JTextField(); 77 | eightMapping = new javax.swing.JTextField(); 78 | nineMapping = new javax.swing.JTextField(); 79 | aMapping = new javax.swing.JTextField(); 80 | bMapping = new javax.swing.JTextField(); 81 | cMapping = new javax.swing.JTextField(); 82 | dMapping = new javax.swing.JTextField(); 83 | eMapping = new javax.swing.JTextField(); 84 | fMapping = new javax.swing.JTextField(); 85 | jButton1 = new javax.swing.JButton(); 86 | jButton2 = new javax.swing.JButton(); 87 | 88 | setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); 89 | 90 | jLabel1.setText("0x0"); 91 | 92 | jLabel2.setText("0x1"); 93 | 94 | jLabel3.setText("0x2"); 95 | 96 | jLabel4.setText("0x3"); 97 | 98 | jLabel5.setText("0x4"); 99 | 100 | jLabel6.setText("0x5"); 101 | 102 | jLabel7.setText("0x6"); 103 | 104 | jLabel8.setText("0x7"); 105 | 106 | jLabel9.setText("0x8"); 107 | 108 | jLabel10.setText("0x9"); 109 | 110 | jLabel11.setText("0xA"); 111 | 112 | jLabel12.setText("0xB"); 113 | 114 | jLabel13.setText("0xC"); 115 | 116 | jLabel14.setText("0xD"); 117 | 118 | jLabel15.setText("0xE"); 119 | 120 | jLabel16.setText("0xF"); 121 | 122 | zeroMapping.setDocument(new SingleCharacterDocument()); 123 | zeroMapping.addActionListener(new java.awt.event.ActionListener() { 124 | public void actionPerformed(java.awt.event.ActionEvent evt) { 125 | zeroMappingActionPerformed(evt); 126 | } 127 | }); 128 | 129 | oneMapping.setDocument(new SingleCharacterDocument()); 130 | 131 | twoMapping.setDocument(new SingleCharacterDocument()); 132 | 133 | threeMapping.setDocument(new SingleCharacterDocument()); 134 | 135 | fourMapping.setDocument(new SingleCharacterDocument()); 136 | fourMapping.addActionListener(new java.awt.event.ActionListener() { 137 | public void actionPerformed(java.awt.event.ActionEvent evt) { 138 | fourMappingActionPerformed(evt); 139 | } 140 | }); 141 | 142 | fiveMapping.setDocument(new SingleCharacterDocument()); 143 | 144 | sixMapping.setDocument(new SingleCharacterDocument()); 145 | 146 | sevenMapping.setDocument(new SingleCharacterDocument()); 147 | 148 | eightMapping.setDocument(new SingleCharacterDocument()); 149 | 150 | nineMapping.setDocument(new SingleCharacterDocument()); 151 | 152 | aMapping.setDocument(new SingleCharacterDocument()); 153 | 154 | bMapping.setDocument(new SingleCharacterDocument()); 155 | 156 | cMapping.setDocument(new SingleCharacterDocument()); 157 | 158 | dMapping.setDocument(new SingleCharacterDocument()); 159 | 160 | eMapping.setDocument(new SingleCharacterDocument()); 161 | 162 | fMapping.setDocument(new SingleCharacterDocument()); 163 | 164 | jButton1.setText("Save"); 165 | jButton1.addActionListener(new java.awt.event.ActionListener() { 166 | public void actionPerformed(java.awt.event.ActionEvent evt) { 167 | jButton1ActionPerformed(evt); 168 | } 169 | }); 170 | 171 | jButton2.setText("Cancel"); 172 | jButton2.addActionListener(new java.awt.event.ActionListener() { 173 | public void actionPerformed(java.awt.event.ActionEvent evt) { 174 | jButton2ActionPerformed(evt); 175 | } 176 | }); 177 | 178 | javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); 179 | getContentPane().setLayout(layout); 180 | layout.setHorizontalGroup( 181 | layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) 182 | .addGroup(layout.createSequentialGroup() 183 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) 184 | .addGroup(layout.createSequentialGroup() 185 | .addContainerGap() 186 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) 187 | .addGroup(layout.createSequentialGroup() 188 | .addComponent(jLabel1) 189 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 190 | .addComponent(zeroMapping)) 191 | .addGroup(layout.createSequentialGroup() 192 | .addComponent(jLabel2) 193 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 194 | .addComponent(oneMapping)) 195 | .addGroup(layout.createSequentialGroup() 196 | .addComponent(jLabel3) 197 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 198 | .addComponent(twoMapping)) 199 | .addGroup(layout.createSequentialGroup() 200 | .addComponent(jLabel4) 201 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 202 | .addComponent(threeMapping)) 203 | .addGroup(layout.createSequentialGroup() 204 | .addComponent(jLabel5) 205 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 206 | .addComponent(fourMapping)) 207 | .addGroup(layout.createSequentialGroup() 208 | .addComponent(jLabel6) 209 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 210 | .addComponent(fiveMapping)) 211 | .addGroup(layout.createSequentialGroup() 212 | .addComponent(jLabel8) 213 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 214 | .addComponent(sevenMapping)) 215 | .addGroup(layout.createSequentialGroup() 216 | .addComponent(jLabel7) 217 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 218 | .addComponent(sixMapping))) 219 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) 220 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) 221 | .addGroup(layout.createSequentialGroup() 222 | .addComponent(jLabel9) 223 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 224 | .addComponent(eightMapping)) 225 | .addGroup(layout.createSequentialGroup() 226 | .addComponent(jLabel10) 227 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 228 | .addComponent(nineMapping)) 229 | .addGroup(layout.createSequentialGroup() 230 | .addComponent(jLabel11) 231 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 232 | .addComponent(aMapping)) 233 | .addGroup(layout.createSequentialGroup() 234 | .addComponent(jLabel12) 235 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 236 | .addComponent(bMapping)) 237 | .addGroup(layout.createSequentialGroup() 238 | .addComponent(jLabel13) 239 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 240 | .addComponent(cMapping)) 241 | .addGroup(layout.createSequentialGroup() 242 | .addComponent(jLabel14) 243 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 244 | .addComponent(dMapping)) 245 | .addGroup(layout.createSequentialGroup() 246 | .addComponent(jLabel15) 247 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 248 | .addComponent(eMapping)) 249 | .addGroup(layout.createSequentialGroup() 250 | .addComponent(jLabel16) 251 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 252 | .addComponent(fMapping)))) 253 | .addGroup(layout.createSequentialGroup() 254 | .addGap(60, 60, 60) 255 | .addComponent(jButton2, javax.swing.GroupLayout.PREFERRED_SIZE, 80, javax.swing.GroupLayout.PREFERRED_SIZE) 256 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 257 | .addComponent(jButton1, javax.swing.GroupLayout.PREFERRED_SIZE, 80, javax.swing.GroupLayout.PREFERRED_SIZE) 258 | .addGap(0, 0, Short.MAX_VALUE))) 259 | .addContainerGap()) 260 | ); 261 | layout.setVerticalGroup( 262 | layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) 263 | .addGroup(layout.createSequentialGroup() 264 | .addContainerGap() 265 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) 266 | .addGroup(layout.createSequentialGroup() 267 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) 268 | .addComponent(jLabel1) 269 | .addComponent(zeroMapping, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) 270 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 271 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) 272 | .addComponent(jLabel2) 273 | .addComponent(oneMapping, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) 274 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 275 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) 276 | .addComponent(jLabel3) 277 | .addComponent(twoMapping, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) 278 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 279 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) 280 | .addComponent(jLabel4) 281 | .addComponent(threeMapping, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) 282 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 283 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) 284 | .addComponent(jLabel5) 285 | .addComponent(fourMapping, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) 286 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) 287 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) 288 | .addComponent(jLabel6) 289 | .addComponent(fiveMapping, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) 290 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 291 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) 292 | .addComponent(jLabel7) 293 | .addComponent(sixMapping, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) 294 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 295 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) 296 | .addComponent(jLabel8) 297 | .addComponent(sevenMapping, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) 298 | .addGroup(layout.createSequentialGroup() 299 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) 300 | .addComponent(jLabel9) 301 | .addComponent(eightMapping, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) 302 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 303 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) 304 | .addComponent(jLabel10) 305 | .addComponent(nineMapping, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) 306 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 307 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) 308 | .addComponent(jLabel11) 309 | .addComponent(aMapping, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) 310 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 311 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) 312 | .addComponent(jLabel12) 313 | .addComponent(bMapping, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) 314 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 315 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) 316 | .addComponent(jLabel13) 317 | .addComponent(cMapping, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) 318 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 319 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) 320 | .addComponent(jLabel14) 321 | .addComponent(dMapping, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) 322 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 323 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) 324 | .addComponent(jLabel15) 325 | .addComponent(eMapping, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) 326 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 327 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) 328 | .addComponent(jLabel16) 329 | .addComponent(fMapping, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))) 330 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) 331 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) 332 | .addComponent(jButton2) 333 | .addComponent(jButton1)) 334 | .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) 335 | ); 336 | 337 | pack(); 338 | }// //GEN-END:initComponents 339 | 340 | private void zeroMappingActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_zeroMappingActionPerformed 341 | // TODO add your handling code here: 342 | }//GEN-LAST:event_zeroMappingActionPerformed 343 | 344 | private void fourMappingActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_fourMappingActionPerformed 345 | // TODO add your handling code here: 346 | }//GEN-LAST:event_fourMappingActionPerformed 347 | 348 | private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton2ActionPerformed 349 | InputMapping.this.setVisible(false); 350 | this.dispose(); 351 | }//GEN-LAST:event_jButton2ActionPerformed 352 | 353 | private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton1ActionPerformed 354 | Input.map(0x0, zeroMapping.getText()); 355 | Input.map(0x1, oneMapping.getText()); 356 | Input.map(0x2, twoMapping.getText()); 357 | Input.map(0x3, threeMapping.getText()); 358 | Input.map(0x4, fourMapping.getText()); 359 | Input.map(0x5, fiveMapping.getText()); 360 | Input.map(0x6, sixMapping.getText()); 361 | Input.map(0x7, sevenMapping.getText()); 362 | Input.map(0x8, eightMapping.getText()); 363 | Input.map(0x9, nineMapping.getText()); 364 | Input.map(0xa, aMapping.getText()); 365 | Input.map(0xb, bMapping.getText()); 366 | Input.map(0xc, cMapping.getText()); 367 | Input.map(0xd, dMapping.getText()); 368 | Input.map(0xe, eMapping.getText()); 369 | Input.map(0xf, fMapping.getText()); 370 | this.setVisible(false); 371 | this.dispose(); 372 | }//GEN-LAST:event_jButton1ActionPerformed 373 | 374 | /** 375 | * @param args the command line arguments 376 | */ 377 | public static void main(String args[]) { 378 | /* Set the Nimbus look and feel */ 379 | // 380 | /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel. 381 | * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html 382 | */ 383 | try { 384 | for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) { 385 | if ("Nimbus".equals(info.getName())) { 386 | javax.swing.UIManager.setLookAndFeel(info.getClassName()); 387 | break; 388 | } 389 | } 390 | } catch (ClassNotFoundException ex) { 391 | java.util.logging.Logger.getLogger(InputMapping.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); 392 | } catch (InstantiationException ex) { 393 | java.util.logging.Logger.getLogger(InputMapping.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); 394 | } catch (IllegalAccessException ex) { 395 | java.util.logging.Logger.getLogger(InputMapping.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); 396 | } catch (javax.swing.UnsupportedLookAndFeelException ex) { 397 | java.util.logging.Logger.getLogger(InputMapping.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); 398 | } 399 | // 400 | 401 | /* Create and display the form */ 402 | java.awt.EventQueue.invokeLater(new Runnable() { 403 | public void run() { 404 | new InputMapping().setVisible(true); 405 | } 406 | }); 407 | } 408 | 409 | private static class SingleCharacterDocument extends PlainDocument { 410 | 411 | private static final long serialVersionUID = 1L; 412 | 413 | @Override 414 | public void insertString(int offs, String str, AttributeSet a) throws BadLocationException { 415 | if ((getLength() + str.length()) <= 1) { 416 | super.insertString(offs, str, a); 417 | } 418 | } 419 | 420 | } 421 | 422 | // Variables declaration - do not modify//GEN-BEGIN:variables 423 | private javax.swing.JTextField aMapping; 424 | private javax.swing.JTextField bMapping; 425 | private javax.swing.JTextField cMapping; 426 | private javax.swing.JTextField dMapping; 427 | private javax.swing.JTextField eMapping; 428 | private javax.swing.JTextField eightMapping; 429 | private javax.swing.JTextField fMapping; 430 | private javax.swing.JTextField fiveMapping; 431 | private javax.swing.JTextField fourMapping; 432 | private javax.swing.JButton jButton1; 433 | private javax.swing.JButton jButton2; 434 | private javax.swing.JLabel jLabel1; 435 | private javax.swing.JLabel jLabel10; 436 | private javax.swing.JLabel jLabel11; 437 | private javax.swing.JLabel jLabel12; 438 | private javax.swing.JLabel jLabel13; 439 | private javax.swing.JLabel jLabel14; 440 | private javax.swing.JLabel jLabel15; 441 | private javax.swing.JLabel jLabel16; 442 | private javax.swing.JLabel jLabel2; 443 | private javax.swing.JLabel jLabel3; 444 | private javax.swing.JLabel jLabel4; 445 | private javax.swing.JLabel jLabel5; 446 | private javax.swing.JLabel jLabel6; 447 | private javax.swing.JLabel jLabel7; 448 | private javax.swing.JLabel jLabel8; 449 | private javax.swing.JLabel jLabel9; 450 | private javax.swing.JTextField nineMapping; 451 | private javax.swing.JTextField oneMapping; 452 | private javax.swing.JTextField sevenMapping; 453 | private javax.swing.JTextField sixMapping; 454 | private javax.swing.JTextField threeMapping; 455 | private javax.swing.JTextField twoMapping; 456 | private javax.swing.JTextField zeroMapping; 457 | // End of variables declaration//GEN-END:variables 458 | } 459 | --------------------------------------------------------------------------------