├── bin └── .gitignore ├── roms ├── BLITZ ├── BRIX ├── GUESS ├── MAZE ├── PONG ├── PONG2 ├── TANK ├── UFO ├── VBRIX ├── VERS ├── BLINKY ├── HIDDEN ├── KALEID ├── MERLIN ├── MISSILE ├── PUZZLE ├── SYZYGY ├── TETRIS ├── TICTAC ├── WIPEOFF ├── 15PUZZLE ├── CONNECT4 └── INVADERS ├── doc └── images │ └── chip8.PNG ├── .classpath ├── .gitignore ├── .project ├── .settings └── org.eclipse.jdt.core.prefs ├── LICENSE ├── README.md └── src └── me └── oskarmendel └── chip8 ├── Screen.java ├── Keyboard.java ├── Chip8.java └── Memory.java /bin/.gitignore: -------------------------------------------------------------------------------- 1 | /me/ 2 | -------------------------------------------------------------------------------- /roms/BLITZ: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenprogrammer/CHIP-8-Emulator/HEAD/roms/BLITZ -------------------------------------------------------------------------------- /roms/BRIX: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenprogrammer/CHIP-8-Emulator/HEAD/roms/BRIX -------------------------------------------------------------------------------- /roms/GUESS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenprogrammer/CHIP-8-Emulator/HEAD/roms/GUESS -------------------------------------------------------------------------------- /roms/MAZE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenprogrammer/CHIP-8-Emulator/HEAD/roms/MAZE -------------------------------------------------------------------------------- /roms/PONG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenprogrammer/CHIP-8-Emulator/HEAD/roms/PONG -------------------------------------------------------------------------------- /roms/PONG2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenprogrammer/CHIP-8-Emulator/HEAD/roms/PONG2 -------------------------------------------------------------------------------- /roms/TANK: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenprogrammer/CHIP-8-Emulator/HEAD/roms/TANK -------------------------------------------------------------------------------- /roms/UFO: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenprogrammer/CHIP-8-Emulator/HEAD/roms/UFO -------------------------------------------------------------------------------- /roms/VBRIX: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenprogrammer/CHIP-8-Emulator/HEAD/roms/VBRIX -------------------------------------------------------------------------------- /roms/VERS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenprogrammer/CHIP-8-Emulator/HEAD/roms/VERS -------------------------------------------------------------------------------- /roms/BLINKY: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenprogrammer/CHIP-8-Emulator/HEAD/roms/BLINKY -------------------------------------------------------------------------------- /roms/HIDDEN: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenprogrammer/CHIP-8-Emulator/HEAD/roms/HIDDEN -------------------------------------------------------------------------------- /roms/KALEID: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenprogrammer/CHIP-8-Emulator/HEAD/roms/KALEID -------------------------------------------------------------------------------- /roms/MERLIN: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenprogrammer/CHIP-8-Emulator/HEAD/roms/MERLIN -------------------------------------------------------------------------------- /roms/MISSILE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenprogrammer/CHIP-8-Emulator/HEAD/roms/MISSILE -------------------------------------------------------------------------------- /roms/PUZZLE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenprogrammer/CHIP-8-Emulator/HEAD/roms/PUZZLE -------------------------------------------------------------------------------- /roms/SYZYGY: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenprogrammer/CHIP-8-Emulator/HEAD/roms/SYZYGY -------------------------------------------------------------------------------- /roms/TETRIS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenprogrammer/CHIP-8-Emulator/HEAD/roms/TETRIS -------------------------------------------------------------------------------- /roms/TICTAC: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenprogrammer/CHIP-8-Emulator/HEAD/roms/TICTAC -------------------------------------------------------------------------------- /roms/WIPEOFF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenprogrammer/CHIP-8-Emulator/HEAD/roms/WIPEOFF -------------------------------------------------------------------------------- /roms/15PUZZLE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenprogrammer/CHIP-8-Emulator/HEAD/roms/15PUZZLE -------------------------------------------------------------------------------- /roms/CONNECT4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenprogrammer/CHIP-8-Emulator/HEAD/roms/CONNECT4 -------------------------------------------------------------------------------- /roms/INVADERS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenprogrammer/CHIP-8-Emulator/HEAD/roms/INVADERS -------------------------------------------------------------------------------- /doc/images/chip8.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenprogrammer/CHIP-8-Emulator/HEAD/doc/images/chip8.PNG -------------------------------------------------------------------------------- /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | *.smod 19 | 20 | # Compiled Static libraries 21 | *.lai 22 | *.la 23 | *.a 24 | *.lib 25 | 26 | # Executables 27 | *.exe 28 | *.out 29 | *.app 30 | /target/ 31 | 32 | .DS_Store -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | CHIP-8-Emulator 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.jdt.core.javanature 16 | 17 | 18 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 4 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 5 | org.eclipse.jdt.core.compiler.compliance=1.8 6 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 7 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 8 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 9 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 10 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 11 | org.eclipse.jdt.core.compiler.source=1.8 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Brokenprogrammer 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included 12 | in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CHIP-8-Emulator 2 | CHIP-8 is an interpreted programming language used for the COSMAC VIP and Telmac 1800 8-bit microcomputers in the mid-1970s. 3 | CHIP-8 is a great starting project if you want to look into creating emulators and that is the reason for me making this project. 4 | I decided to use a programming language I feel comfortable in so I've implemetned this CHIP-8 emulator / interpreter in Java. 5 | 6 | ![CHIP-8](doc/images/chip8.PNG "CHIP-8") 7 | 8 | ## Lessons learned 9 | While developing this application I learned once again more about bitwise operations and how to use them in real applications, the most 10 | interesting part was to learn about how to emulate or interpret in this case another program written by someone else in another language. Wikipedia 11 | was to great help and a great resource for the operations connected to each opcode. I learned more about JavaFX and it's canvas and AnimationTimer which 12 | was very refreshing compared to building applications mostly relying on control objects. 13 | 14 | ## References 15 | Here are some useful resources used while building this application. 16 | 17 | * [http://en.wikipedia.org/wiki/CHIP-8](http://en.wikipedia.org/wiki/CHIP-8) 18 | * [http://www.multigesture.net/articles/how-to-write-an-emulator-chip-8-interpreter/](http://www.multigesture.net/articles/how-to-write-an-emulator-chip-8-interpreter/) 19 | * [http://devernay.free.fr/hacks/chip8/C8TECH10.HTM](http://devernay.free.fr/hacks/chip8/C8TECH10.HTM) 20 | -------------------------------------------------------------------------------- /src/me/oskarmendel/chip8/Screen.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2017 Brokenprogrammer 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 all 14 | * 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 THE 22 | * SOFTWARE. 23 | */ 24 | package me.oskarmendel.chip8; 25 | 26 | import javafx.scene.canvas.Canvas; 27 | import javafx.scene.canvas.GraphicsContext; 28 | import javafx.scene.paint.Color; 29 | 30 | /** 31 | * 32 | * @author Oskar Mendel 33 | * @version 0.00.00 34 | * @name Screen.java 35 | */ 36 | public class Screen extends Canvas{ 37 | 38 | private static final int WIDTH = 64; 39 | private static final int HEIGHT = 32; 40 | 41 | private int scale = 12; 42 | 43 | private GraphicsContext gc; 44 | 45 | public int[][] graphic = new int[WIDTH][HEIGHT]; // Graphics in Chip 8 is a black 46 | // and white screen of 2048 47 | // pixels (62*32). 48 | 49 | public Screen() { 50 | super(800, 400); 51 | setFocusTraversable(true); 52 | 53 | gc = this.getGraphicsContext2D(); 54 | gc.setFill(Color.BLACK); 55 | gc.fillRect(0, 0, 800, 400); 56 | clear(); 57 | } 58 | 59 | /** 60 | * Clears the display setting all the pixels to zero. 61 | */ 62 | public void clear() { 63 | for(int y = 0; y < HEIGHT; y++) { 64 | for(int x = 0; x < WIDTH; x++) { 65 | graphic[x][y] = 0; 66 | } 67 | } 68 | } 69 | 70 | /** 71 | * Renders the display. 72 | */ 73 | public void render() { 74 | for(int x = 0; x < graphic.length; x++) { 75 | for(int y = 0; y < graphic[y].length; y++) { 76 | if (graphic[x][y] == 1) { 77 | gc.setFill(Color.WHITE); 78 | } else { 79 | gc.setFill(Color.BLACK); 80 | } 81 | 82 | gc.fillRect(x*scale, (y*scale), scale, scale); 83 | } 84 | } 85 | } 86 | 87 | /** 88 | * Gets the content of target pixel. 89 | * 90 | * @param x - X coordinate. 91 | * @param y - Y coordinate. 92 | * @return The pixel at target x and y coordinate, 1 for white 0 for black. 93 | */ 94 | public int getPixel(int x, int y) { 95 | return graphic[x][y]; 96 | } 97 | 98 | /** 99 | * Sets the pixel at target location. 100 | * 101 | * @param x - X coordinate. 102 | * @param y - Y coordinate. 103 | */ 104 | public void setPixel(int x, int y) { 105 | graphic[x][y] ^= 1; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/me/oskarmendel/chip8/Keyboard.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2017 Brokenprogrammer 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 all 14 | * 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 THE 22 | * SOFTWARE. 23 | */ 24 | package me.oskarmendel.chip8; 25 | 26 | import javafx.scene.input.KeyCode; 27 | 28 | /** 29 | * 30 | * @author Oskar Mendel 31 | * @version 0.00.00 32 | * @name Keyboard.java 33 | */ 34 | public class Keyboard { 35 | 36 | private boolean[] keys = new boolean[16]; // Chip 8 uses a HEX based keypad (0x0 - 37 | // 0xF), This array stores the state of 38 | // each key. 39 | 40 | public static final int[] FONT = { 41 | 0xF0, 0x90, 0x90, 0x90, 0xF0, // 0 42 | 0x20, 0x60, 0x20, 0x20, 0x70, // 1 43 | 0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2 44 | 0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3 45 | 0x90, 0x90, 0xF0, 0x10, 0x10, // 4 46 | 0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5 47 | 0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6 48 | 0xF0, 0x10, 0x20, 0x40, 0x50, // 7 49 | 0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8 50 | 0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9 51 | 0xF0, 0x90, 0xF0, 0x90, 0x90, // A 52 | 0xE0, 0x90, 0xE0, 0x90, 0xE0, // B 53 | 0xF0, 0x80, 0x80, 0x80, 0xF0, // C 54 | 0xE0, 0x90, 0x90, 0x90, 0xE0, // D 55 | 0xF0, 0x80, 0xF0, 0x80, 0xF0, // E 56 | 0xF0, 0x80, 0xF0, 0x80, 0x80 // F 57 | }; 58 | 59 | public Keyboard() { 60 | for (int i = 0; i < 15; i++) { 61 | keys[i] = false; 62 | } 63 | } 64 | 65 | 66 | public boolean setKeyDown(KeyCode k) { 67 | switch (k) { 68 | case DIGIT1: 69 | keys[0] = true; 70 | break; 71 | case DIGIT2: 72 | keys[1] = true; 73 | break; 74 | case DIGIT3: 75 | keys[2] = true; 76 | break; 77 | case DIGIT4: 78 | keys[3] = true; 79 | break; 80 | case Q: 81 | keys[4] = true; 82 | break; 83 | case W: 84 | keys[5] = true; 85 | break; 86 | case E: 87 | keys[6] = true; 88 | break; 89 | case R: 90 | keys[7] = true; 91 | break; 92 | case A: 93 | keys[8] = true; 94 | break; 95 | case S: 96 | keys[9] = true; 97 | break; 98 | case D: 99 | keys[10] = true; 100 | break; 101 | case F: 102 | keys[11] = true; 103 | break; 104 | case Z: 105 | keys[12] = true; 106 | break; 107 | case X: 108 | keys[13] = true; 109 | break; 110 | case C: 111 | keys[14] = true; 112 | break; 113 | case V: 114 | keys[15] = true; 115 | break; 116 | default: 117 | break; 118 | } 119 | 120 | return true; 121 | } 122 | 123 | public boolean setKeyUp(KeyCode k) { 124 | switch (k) { 125 | case DIGIT1: 126 | keys[0] = false; 127 | break; 128 | case DIGIT2: 129 | keys[1] = false; 130 | break; 131 | case DIGIT3: 132 | keys[2] = false; 133 | break; 134 | case DIGIT4: 135 | keys[3] = false; 136 | break; 137 | case Q: 138 | keys[4] = false; 139 | break; 140 | case W: 141 | keys[5] = false; 142 | break; 143 | case E: 144 | keys[6] = false; 145 | break; 146 | case R: 147 | keys[7] = false; 148 | break; 149 | case A: 150 | keys[8] = false; 151 | break; 152 | case S: 153 | keys[9] = false; 154 | break; 155 | case D: 156 | keys[10] = false; 157 | break; 158 | case F: 159 | keys[11] = false; 160 | break; 161 | case Z: 162 | keys[12] = false; 163 | break; 164 | case X: 165 | keys[13] = false; 166 | break; 167 | case C: 168 | keys[14] = false; 169 | break; 170 | case V: 171 | keys[15] = false; 172 | break; 173 | default: 174 | break; 175 | } 176 | 177 | return true; 178 | } 179 | 180 | public boolean isPressed(int j) { 181 | return keys[j]; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/me/oskarmendel/chip8/Chip8.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2017 Brokenprogrammer 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 all 14 | * 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 THE 22 | * SOFTWARE. 23 | */ 24 | package me.oskarmendel.chip8; 25 | 26 | import java.io.BufferedInputStream; 27 | import java.io.DataInputStream; 28 | import java.io.File; 29 | import java.io.FileInputStream; 30 | import java.io.FileNotFoundException; 31 | import java.io.IOException; 32 | 33 | import javafx.animation.KeyFrame; 34 | import javafx.animation.Timeline; 35 | import javafx.application.Application; 36 | import javafx.event.EventHandler; 37 | import javafx.scene.Scene; 38 | import javafx.scene.control.Menu; 39 | import javafx.scene.control.MenuBar; 40 | import javafx.scene.control.MenuItem; 41 | import javafx.scene.input.KeyEvent; 42 | import javafx.scene.layout.VBox; 43 | import javafx.stage.FileChooser; 44 | import javafx.stage.Stage; 45 | import javafx.util.Duration; 46 | 47 | /** 48 | * Main entry point of the application. 49 | * 50 | * @author Oskar Mendel 51 | * @version 0.00.00 52 | * @name Chip8.java 53 | */ 54 | public class Chip8 extends Application { 55 | 56 | private static final int SCREEN_WIDTH = 800; 57 | private static final int SCREEN_HEIGHT = 450; 58 | 59 | private Stage mainStage; 60 | 61 | Timeline gameLoop; 62 | 63 | private Memory memory; 64 | private Screen screen; 65 | private Keyboard keyboard; 66 | 67 | /** 68 | * Setup the graphics and input systen and clear the memory and screen. 69 | */ 70 | private void initialize() { 71 | mainStage.setTitle("CHIP-8-Emulator"); 72 | 73 | screen = new Screen(); 74 | keyboard = new Keyboard(); 75 | 76 | // Initialize menu that contains buttons for exiting and switching applications to run. 77 | MenuBar menuBar = new MenuBar(); 78 | Menu menuFile = new Menu("File"); 79 | 80 | MenuItem loadRomItem = new MenuItem("Load ROM"); 81 | loadRomItem.setOnAction(e -> { 82 | // Open file choose to let the user select a ROM. 83 | FileChooser f = new FileChooser(); 84 | f.setTitle("Open ROM File"); 85 | File file = f.showOpenDialog(mainStage); 86 | 87 | if (file != null) { 88 | loadProgram(file.getPath()); 89 | } 90 | }); 91 | MenuItem exitItem = new MenuItem("Exit"); 92 | exitItem.setOnAction(e -> { 93 | System.exit(0); 94 | }); 95 | 96 | menuFile.getItems().add(loadRomItem); 97 | menuFile.getItems().add(exitItem); 98 | 99 | menuBar.getMenus().add(menuFile); 100 | 101 | // Initial render of the screen. 102 | screen.render(); 103 | 104 | // Place all elements into the main window. 105 | VBox root = new VBox(); 106 | root.getChildren().add(menuBar); 107 | root.getChildren().add(screen); 108 | 109 | 110 | Scene mainScene = new Scene(root); 111 | 112 | // Handle key presses. 113 | mainScene.setOnKeyPressed(new EventHandler() { 114 | @Override 115 | public void handle(KeyEvent e) { 116 | keyboard.setKeyDown(e.getCode()); 117 | } 118 | }); 119 | 120 | // Handle key releases. 121 | mainScene.setOnKeyReleased(new EventHandler() { 122 | @Override 123 | public void handle(KeyEvent e) { 124 | keyboard.setKeyUp(e.getCode()); 125 | } 126 | }); 127 | 128 | 129 | // Set up the main window for show. 130 | mainStage.setScene(mainScene); 131 | mainStage.setMaxWidth(SCREEN_WIDTH); 132 | mainStage.setMaxHeight(SCREEN_HEIGHT); 133 | mainStage.setMinWidth(SCREEN_WIDTH); 134 | mainStage.setMinHeight(SCREEN_HEIGHT); 135 | mainStage.setResizable(false); 136 | 137 | gameLoop = new Timeline(); 138 | gameLoop.setCycleCount(Timeline.INDEFINITE); 139 | 140 | // Construct the keyframe telling the application what to happen inside the game loop. 141 | KeyFrame kf = new KeyFrame( 142 | Duration.seconds(0.003), 143 | actionEvent -> { 144 | try { 145 | // Fetch opcode 146 | memory.fetchOpcode(); 147 | // Decode & Execute opcode 148 | memory.decodeOpcode(); 149 | } catch (RuntimeException e) { 150 | gameLoop.stop(); 151 | } 152 | 153 | // Render 154 | if (memory.isDrawFlag()) { 155 | screen.render(); 156 | memory.setDrawFlag(false); 157 | } 158 | 159 | // Update Timers 160 | if (memory.getDelayTimer() > 0) { 161 | memory.setDelayTimer(memory.getDelayTimer() - 1); 162 | } 163 | 164 | if (memory.getSoundTimer() > 0) { 165 | if (memory.getSoundTimer() == 1) { 166 | System.out.println("Make Sound!"); 167 | } 168 | memory.setSoundTimer(memory.getSoundTimer() - 1); 169 | } 170 | }); 171 | 172 | gameLoop.getKeyFrames().add(kf); 173 | loadProgram("roms/INVADERS"); 174 | 175 | mainStage.show(); 176 | } 177 | 178 | /** 179 | * Copy the program to run into the memory 180 | * 181 | * @param program 182 | * - The program to copy into memory. 183 | */ 184 | private void loadProgram(String program) { 185 | gameLoop.stop(); 186 | 187 | screen.clear(); 188 | memory = new Memory(screen, keyboard); 189 | 190 | // Load binary and pass it to memory 191 | try { 192 | File f = new File(program); 193 | 194 | DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(f))); 195 | 196 | byte[] b = new byte[(int) f.length()]; 197 | in.read(b); 198 | 199 | memory.loadProgram(b); 200 | 201 | in.close(); 202 | } catch (FileNotFoundException e) { 203 | e.printStackTrace(); 204 | } catch (IOException e) { 205 | e.printStackTrace(); 206 | } 207 | 208 | gameLoop.play(); 209 | } 210 | 211 | public static void main(String[] args) { 212 | launch(args); 213 | } 214 | 215 | @Override 216 | public void start(Stage primaryStage) throws Exception { 217 | mainStage = primaryStage; 218 | initialize(); 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/me/oskarmendel/chip8/Memory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2017 Brokenprogrammer 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 all 14 | * 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 THE 22 | * SOFTWARE. 23 | */ 24 | package me.oskarmendel.chip8; 25 | 26 | import java.util.Random; 27 | 28 | /** 29 | * 30 | * @author Oskar Mendel 31 | * @version 0.00.00 32 | * @name Memory.java 33 | */ 34 | public class Memory { 35 | 36 | private int opcode; // Used to store the current opcode. 37 | private int[] memory = new int[4096]; // Entire memory for the Chip 8. 38 | private int[] V = new int[16]; // The 15 CPU registers for the Chip 8. 39 | private int I; // Index register. 40 | private int pc; // Program counter which can contain a value from 0x000 to 41 | // 0xFFF. 42 | 43 | private int[] stack = new int[16]; // Remembers location between jumps. 44 | private int sp; // In order to remember which level of the stack was used we 45 | // use the stack pointer. 46 | 47 | private int delayTimer; // Timer registers that counts at 60Hz. When set 48 | // above zero they will count down to zero. 49 | private int soundTimer; // Make a sound whenever the sound timer reaches 50 | // zero. 51 | 52 | private boolean drawFlag = false; 53 | private static final Random RANDOM = new Random(); 54 | 55 | private Screen screen; 56 | private Keyboard keyboard; 57 | 58 | /** 59 | * Initializes the memory. 60 | * 61 | * @param s - Screen to communicate with. 62 | * @param k - Keyboard to read keys from. 63 | */ 64 | public Memory(Screen s, Keyboard k) { 65 | this.screen = s; 66 | this.keyboard = k; 67 | 68 | pc = 0x200; // Program counter always starts at 0x200. 69 | opcode = 0; // Reset current opcode. 70 | I = 0; // Reset the index register. 71 | sp = 0; // Reset the stack pointer. 72 | 73 | //Reset memory 74 | for(int i = 0; i < memory.length; i++) { 75 | memory[i] = 0; 76 | } 77 | 78 | // Reset stack and the V registers. 79 | for (int i = 0; i < 16; i++) { 80 | stack[i] = 0; 81 | V[i] = 0; 82 | } 83 | 84 | //Load font into memory 85 | for (int i = 0; i < 80; i++) { 86 | memory[i] = Keyboard.FONT[i]; 87 | } 88 | 89 | this.drawFlag = true; 90 | this.delayTimer = 0; 91 | this.soundTimer = 0; 92 | } 93 | 94 | /** 95 | * Loads the program into the memory which is placing the program into the memory 96 | * starting from the memory location 0x200 (512). 97 | * 98 | * @param b - Byte array containing the bytes read from the program file. 99 | */ 100 | public void loadProgram(byte[] b) { 101 | for (int i = 0; i < b.length; i++) { 102 | memory[i + 512] = (b[i] & 0xFF); 103 | } 104 | } 105 | 106 | /** 107 | * Fetches a single opcode. 108 | */ 109 | public void fetchOpcode() { 110 | opcode = ((memory[pc] << 8) | (memory[pc + 1])); 111 | } 112 | 113 | /** 114 | * Decodes an opcode and performs target action. 115 | */ 116 | public void decodeOpcode() { 117 | int x = 0; 118 | 119 | switch (opcode) { 120 | case 0x00E0: 121 | // Clear display 122 | screen.clear(); 123 | drawFlag = true; 124 | pc += 2; 125 | return; 126 | case 0x00EE: 127 | // Returns from a subroutine 128 | pc = stack[sp--]; 129 | drawFlag = true; 130 | pc += 2; 131 | return; 132 | } 133 | 134 | switch (opcode & 0xF000) { 135 | case 0x1000: 136 | // 1NNN - Jump to address NNN 137 | pc = opcode & 0x0FFF; 138 | 139 | return; 140 | case 0x2000: 141 | // 2NNN - Call subroutine at nnn. 142 | stack[++sp] = pc; 143 | 144 | pc = opcode & 0x0FFF; 145 | return; 146 | case 0x3000: 147 | // 3XNN - Skip next instruction if Vx = kk. 148 | if (V[(opcode & 0x0F00) >>> 8] == (opcode & 0x00FF)) { 149 | pc += 4; 150 | } else { 151 | pc += 2; 152 | } 153 | 154 | return; 155 | case 0x4000: 156 | // 4XNN - Skip next instruction if Vx != kk. 157 | if (V[(opcode & 0x0F00) >>> 8] != (opcode & 0x00FF)) { 158 | pc += 4; 159 | } else { 160 | pc += 2; 161 | } 162 | return; 163 | case 0x5000: 164 | // 5XY0 - Skip next instruction if Vx = Vy. 165 | if (V[(opcode & 0x0F00) >>> 8] == V[(opcode & 0x00F0) >>> 4]) { 166 | pc += 4; 167 | } else { 168 | pc += 2; 169 | } 170 | return; 171 | case 0x6000: 172 | // 6XNN - Set Vx = kk. 173 | V[(opcode & 0x0F00) >>> 8] = (opcode & 0x00FF); 174 | 175 | pc += 2; 176 | return; 177 | case 0x7000: 178 | // 7XNN - Adds NN to VX. 179 | x = (opcode & 0x0F00) >>> 8; 180 | //V[x] = ((V[x] + (opcode & 0x00FF)) & 0xFF); 181 | int NN = (opcode & 0x00FF); 182 | int result = V[x] + NN; 183 | // resolve overflow 184 | if (result >= 256) { 185 | V[x] = result - 256; 186 | } else { 187 | V[x] = result; 188 | } 189 | 190 | pc += 2; 191 | return; 192 | } 193 | 194 | switch (opcode & 0xF00F) { 195 | case 0x8000: 196 | // 8XY0 - Set Vx = Vy. 197 | V[(opcode & 0x0F00) >>> 8] = V[(opcode & 0x00F0) >>> 4]; 198 | pc += 2; 199 | return; 200 | case 0x8001: 201 | // 8XY1 - Set Vx = (Vx OR Vy). 202 | x = (opcode & 0x0F00) >>> 8; 203 | V[x] = (V[x] | V[(opcode & 0x00F0) >>> 4]); 204 | pc += 2; 205 | return; 206 | case 0x8002: 207 | // 8XY2 - Set Vx = (Vx AND Vy). 208 | x = (opcode & 0x0F00) >>> 8; 209 | V[x] = (V[x] & V[(opcode & 0x00F0) >>> 4]); 210 | pc += 2; 211 | return; 212 | case 0x8003: 213 | // 8XY3 - Set Vx = Vx XOR Vy. 214 | x = (opcode & 0x0F00) >>> 8; 215 | V[x] = (V[x] ^ V[(opcode & 0x00F0) >>> 4]); 216 | 217 | pc += 2; 218 | return; 219 | case 0x8004: 220 | // 8XY4 - Set Vx = Vx + Vy, set VF = carry. 221 | x = (opcode & 0x0F00) >>> 8; 222 | int sum = V[x] + V[(opcode & 0x00F0) >>> 4]; 223 | 224 | V[0xF] = sum > 0xFF ? 1 : 0; 225 | V[x] = (sum & 0xFF); 226 | 227 | pc += 2; 228 | return; 229 | case 0x8005: 230 | // 8XY5 - Set Vx = Vx - Vy, set VF = NOT borrow. 231 | x = (opcode & 0x0F00) >>> 8; 232 | 233 | if(V[(opcode & 0x00F0) >>> 4] > V[x]) { 234 | V[0xF] = 0; //There is a borrow. 235 | } else { 236 | V[0xF] = 1; 237 | } 238 | 239 | V[x] = (V[x] - V[(opcode & 0x00F0) >>> 4]) & 0xFF; 240 | 241 | pc += 2; 242 | return; 243 | case 0x8006: 244 | // 8XY6 - Set Vx = Vx SHR 1. 245 | // Shift Vx right by 1. Sets VF to the least significant bit of Vx before shift. 246 | x = (opcode & 0x0F00) >>> 8; 247 | 248 | V[0xF] = (V[x] & 0x1) == 1 ? 1 : 0; 249 | 250 | V[x] = (V[x] >>> 1); 251 | 252 | pc += 2; 253 | return; 254 | case 0x8007: 255 | // 8XY7 - Set Vx = Vy - Vx, set VF = NOT borrow. 256 | // VF is set to 0 when there is a borrow and 1 otherwise. 257 | x = (opcode & 0x0F00) >>> 8; 258 | 259 | if (V[(opcode & 0x00F0) >>> 4] > V[x]) { 260 | V[0xF] = 1; 261 | } else { 262 | V[0xF] = 0; 263 | } 264 | 265 | V[x] = ((V[(opcode & 0x00F0) >>> 4] - V[x]) & 0xFF); 266 | 267 | pc += 2; 268 | return; 269 | case 0x800E: 270 | // 8XYE - Set Vx = Vx SHL 1. 271 | // Shift Vx left by 1. Sets VF to the value of the most significant bit of Vx before the shift. 272 | x = (opcode & 0x0F00) >>> 8; 273 | 274 | V[0xF] = (V[x] >>> 7) == 0x1 ? 1 : 0; 275 | 276 | V[x] = ((V[x] << 1) & 0xFF); 277 | 278 | pc += 2; 279 | return; 280 | case 0x9000: 281 | // 9XY0 - Skip next instruction if Vx != Vy. 282 | x = (opcode & 0x0F00) >>> 8; 283 | 284 | if (V[x] != V[(opcode & 0x00F0) >>> 4]) { 285 | pc += 4; 286 | } else { 287 | pc += 2; 288 | } 289 | 290 | return; 291 | } 292 | 293 | switch (opcode & 0xF000) { 294 | case 0xA000: 295 | // ANNN - Set I = nnn. 296 | I = (opcode & 0x0FFF); 297 | 298 | pc += 2; 299 | return; 300 | case 0xB000: 301 | // BNNN - Jump to location nnn + V0. 302 | pc = (opcode & 0x0FFF) + V[0]; 303 | 304 | return; 305 | case 0xC000: 306 | // CXNN - Set Vx = random byte AND NN. 307 | x = (opcode & 0x0F00) >>> 8; 308 | 309 | V[x] = ((RANDOM.nextInt(256)) & (opcode & 0x00FF)); 310 | 311 | pc += 2; 312 | return; 313 | case 0xD000: 314 | // DXYN - Display n-byte sprite starting at memory location I at (Vx, Vy), set VF = collision. 315 | x = V[(opcode & 0x0F00) >> 8]; 316 | int y = V[(opcode & 0x00F0) >> 4]; 317 | int height = opcode & 0x000F; 318 | V[0xF] = 0; 319 | for (int yLine = 0; yLine < height; yLine++) { 320 | int pixel = memory[I + yLine]; 321 | 322 | for (int xLine = 0; xLine < 8; xLine++) { 323 | // check each bit (pixel) in the 8 bit row 324 | if ((pixel & (0x80 >> xLine)) != 0) { 325 | 326 | // wrap pixels if they're drawn off screen 327 | int xCoord = x+xLine; 328 | int yCoord = y+yLine; 329 | 330 | if (xCoord < 64 && yCoord < 32) { 331 | // if pixel already exists, set carry (collision) 332 | if (screen.getPixel(xCoord, yCoord) == 1) { 333 | V[0xF] = 1; 334 | } 335 | // draw via xor 336 | screen.setPixel(xCoord,yCoord); 337 | } 338 | } 339 | } 340 | } 341 | drawFlag = true; 342 | pc += 2; 343 | return; 344 | } 345 | 346 | switch (opcode & 0xF0FF) { 347 | case 0xE09E: 348 | // EX9E - Skip next instruction if key with the value of Vx is pressed. 349 | if(keyboard.isPressed(V[(opcode & 0x0F00) >>> 8])) { 350 | pc += 4; 351 | } else { 352 | pc += 2; 353 | } 354 | 355 | return; 356 | case 0xE0A1: 357 | // EXA1 - Skip next instruction if key with the value of Vx is not pressed. 358 | if(!keyboard.isPressed(V[(opcode & 0x0F00) >>> 8])) { 359 | pc += 4; 360 | } else { 361 | pc += 2; 362 | } 363 | 364 | return; 365 | case 0xF007: 366 | // FX07 - Set Vx = delay timer value. 367 | x = (opcode & 0x0F00) >>> 8; 368 | V[x] = (delayTimer & 0xFF); 369 | 370 | pc += 2; 371 | return; 372 | case 0xF00A: 373 | // FX0A - Wait for a key press, store the value of the key in Vx. 374 | x = (opcode & 0x0F00) >>> 8; 375 | 376 | for (int j = 0; j <= 0xF; j++) { 377 | if (keyboard.isPressed(j)) { 378 | V[x] = j; 379 | pc += 2; 380 | return; 381 | } 382 | } 383 | 384 | //If no key was pressed return, try again. 385 | return; 386 | case 0xF015: 387 | // FX15 - Set delay timer = Vx. 388 | x = (opcode & 0x0F00) >>> 8; 389 | 390 | this.delayTimer = V[x]; 391 | 392 | pc += 2; 393 | return; 394 | case 0xF018: 395 | // FX18 - Set sound timer = Vx. 396 | x = (opcode & 0x0F00) >>> 8; 397 | 398 | this.soundTimer = V[x]; 399 | 400 | pc += 2; 401 | return; 402 | case 0xF01E: 403 | // FX1E - Set I = I + Vx. 404 | x = (opcode & 0x0F00) >>> 8; 405 | 406 | //Setting VF to 1 when range overflow. 407 | if(I + V[x] > 0xFFF) { 408 | V[0xF] = 1; 409 | } else { 410 | V[0xF] = 0; 411 | } 412 | 413 | I = ((I + V[x]) & 0xFFF); 414 | 415 | pc += 2; 416 | return; 417 | case 0xF029: 418 | // FX29 - Set I = location of sprite for digit Vx. 419 | x = (opcode & 0x0F00) >>> 8; 420 | 421 | I = V[x] * 5; 422 | drawFlag = true; 423 | pc += 2; 424 | return; 425 | case 0xF033: 426 | // FX33 - Store binary coded decimal representation of Vx 427 | // in memory locations I, I+1, and I+2. 428 | x = (opcode & 0x0F00) >>> 8; 429 | 430 | memory[I] = (V[x] / 100); 431 | memory[I + 1] = ((V[x] % 100) / 10); 432 | memory[I + 2] = ((V[x] % 100) % 10); 433 | 434 | pc += 2; 435 | return; 436 | case 0xF055: 437 | // FX55 - Store registers V0 through Vx in memory starting at location I. 438 | x = (opcode & 0x0F00) >>> 8; 439 | 440 | for (int j = 0; j <= x; j++) { 441 | memory[I + j] = V[j]; 442 | } 443 | 444 | pc += 2; 445 | return; 446 | case 0xF065: 447 | // FX65 - Read registers V0 through Vx from memory starting at location I. 448 | x = (opcode & 0x0F00) >>> 8; 449 | 450 | for (int j = 0; j <= x; j++) { 451 | V[j] = memory[I + j] & 0xFF; 452 | } 453 | 454 | pc += 2; 455 | return; 456 | } 457 | } 458 | 459 | /** 460 | * Getter for the delay timer. 461 | * 462 | * @return The delay timer. 463 | */ 464 | public int getDelayTimer() { 465 | return this.delayTimer; 466 | } 467 | 468 | /** 469 | * Setter for the delay timer. 470 | * 471 | * @param d - Delay timer value. 472 | */ 473 | public void setDelayTimer(int d) { 474 | this.delayTimer = d; 475 | } 476 | 477 | /** 478 | * Getter for the sound timer. 479 | * 480 | * @return The delay timer. 481 | */ 482 | public int getSoundTimer() { 483 | return this.soundTimer; 484 | } 485 | 486 | /** 487 | * Setter for the sound timer. 488 | * 489 | * @param s - Sound timer value. 490 | */ 491 | public void setSoundTimer(int s) { 492 | this.soundTimer = s; 493 | } 494 | 495 | /** 496 | * Getter for the draw flag. 497 | * 498 | * @return The draw flag. 499 | */ 500 | public boolean isDrawFlag() { 501 | return this.drawFlag; 502 | } 503 | 504 | /** 505 | * Setter for the draw flag. 506 | * 507 | * @param b - draw flag value. 508 | */ 509 | public void setDrawFlag(boolean b) { 510 | this.drawFlag = b; 511 | } 512 | } 513 | --------------------------------------------------------------------------------