├── 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 | 
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 |
--------------------------------------------------------------------------------