├── JavaNESBrain
├── .classpath
├── .gitignore
├── .project
├── .settings
│ ├── org.eclipse.core.resources.prefs
│ └── org.eclipse.jdt.core.prefs
├── lib
│ └── json-simple-1.1.1.jar
└── src
│ ├── com
│ └── starflask
│ │ └── JavaNESBrain
│ │ ├── SuperBrain.java
│ │ ├── VirtualGamePad.java
│ │ ├── data
│ │ ├── BrainInfoPane.java
│ │ ├── BrainInfoWindow.java
│ │ ├── DebugCell.java
│ │ ├── GalagaGameDataManager.java
│ │ ├── GameDataManager.java
│ │ ├── MarioGameDataManager.java
│ │ ├── PixelDataListener.java
│ │ └── Sprite.java
│ │ ├── evolution
│ │ ├── Gene.java
│ │ ├── GenePool.java
│ │ ├── Genome.java
│ │ ├── NeuralNetwork.java
│ │ ├── Neuron.java
│ │ └── Species.java
│ │ └── utils
│ │ ├── FastMath.java
│ │ ├── Vector2Int.java
│ │ ├── Vector2f.java
│ │ └── Vector3f.java
│ ├── icon.png
│ └── jp
│ └── tanakh
│ └── bjne
│ ├── nes
│ ├── Apu.java
│ ├── Cpu.java
│ ├── Mapper.java
│ ├── MapperAdapter.java
│ ├── MapperMaker.java
│ ├── Mbc.java
│ ├── Nes.java
│ ├── Ppu.java
│ ├── ROMEventListener.java
│ ├── Regs.java
│ ├── Renderer.java
│ ├── Rom.java
│ ├── SaveState.java
│ └── mapper
│ │ ├── CNROM.java
│ │ ├── MMC1.java
│ │ ├── MMC3.java
│ │ ├── NullMapper.java
│ │ ├── Rambo1.java
│ │ ├── UNROM.java
│ │ ├── VRC6.java
│ │ └── tengenrambo1.txt
│ └── ui
│ ├── AWTRenderer.java
│ ├── BJNEmulator.java
│ └── FPSTimer.java
└── README.md
/JavaNESBrain/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/JavaNESBrain/.gitignore:
--------------------------------------------------------------------------------
1 | /bin/
2 |
--------------------------------------------------------------------------------
/JavaNESBrain/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | JavaNESBrain
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 |
--------------------------------------------------------------------------------
/JavaNESBrain/.settings/org.eclipse.core.resources.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | encoding//src/jp/tanakh/bjne/nes/Regs.java=UTF-8
3 | encoding//src/jp/tanakh/bjne/nes/mapper/tengenrambo1.txt=UTF-8
4 |
--------------------------------------------------------------------------------
/JavaNESBrain/.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.7
4 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
5 | org.eclipse.jdt.core.compiler.compliance=1.7
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.7
12 |
--------------------------------------------------------------------------------
/JavaNESBrain/lib/json-simple-1.1.1.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ethereumdegen/JavaNESBrain/88a34d2573c2e3de3980b28729e7b340efbd1023/JavaNESBrain/lib/json-simple-1.1.1.jar
--------------------------------------------------------------------------------
/JavaNESBrain/src/com/starflask/JavaNESBrain/SuperBrain.java:
--------------------------------------------------------------------------------
1 | package com.starflask.JavaNESBrain;
2 |
3 | import java.io.FileWriter;
4 | import java.io.IOException;
5 | import java.util.ArrayList;
6 | import java.util.Arrays;
7 | import java.util.Collections;
8 | import java.util.Comparator;
9 | import java.util.HashMap;
10 | import java.util.List;
11 |
12 | import javax.swing.UIManager;
13 |
14 | import jp.tanakh.bjne.nes.Cpu;
15 | import jp.tanakh.bjne.nes.Nes;
16 | import jp.tanakh.bjne.nes.ROMEventListener;
17 | import jp.tanakh.bjne.ui.BJNEmulator;
18 |
19 | import com.starflask.JavaNESBrain.data.BrainInfoWindow;
20 | import com.starflask.JavaNESBrain.data.GalagaGameDataManager;
21 | import com.starflask.JavaNESBrain.data.GameDataManager;
22 | import com.starflask.JavaNESBrain.data.MarioGameDataManager;
23 | import com.starflask.JavaNESBrain.evolution.Gene;
24 | import com.starflask.JavaNESBrain.evolution.GenePool;
25 | import com.starflask.JavaNESBrain.evolution.Genome;
26 | import com.starflask.JavaNESBrain.evolution.NeuralNetwork;
27 | import com.starflask.JavaNESBrain.evolution.Neuron;
28 | import com.starflask.JavaNESBrain.evolution.Species;
29 | import com.starflask.JavaNESBrain.utils.FastMath;
30 |
31 | /**
32 | * //use this as vm arg: -Dsun.java2d.opengl=True
33 | *
34 | * This basically..
35 | *
36 | * reads bytes from memory to build array of the 'world blocks' build and
37 | * mutates genes and neurons sends commands out through a virtual gamepad when
38 | * the tile bytes and neurons match
39 | *
40 | *
41 | */
42 |
43 |
44 | /**
45 | * BUG: loading the state sometimes messes with the tile pattern ?
46 | *
47 | * why doesnt the environment draw nicely in the debug window?
48 | *
49 |
50 | */
51 |
52 | public class SuperBrain implements Runnable, ROMEventListener {
53 |
54 | VirtualGamePad gamepad = new VirtualGamePad();
55 |
56 | BJNEmulator emulator;
57 |
58 | GameDataManager gameData;
59 |
60 | public SuperBrain(BJNEmulator emulator) {
61 |
62 | this.emulator=emulator;
63 |
64 | emulator.addROMEventListener(this);
65 |
66 |
67 | }
68 |
69 | boolean built = false;
70 | boolean firstUpdateOccured = false;
71 | public void build() {
72 |
73 |
74 | loadGameDataManager();
75 | System.out.println(" GD " + gameData);
76 |
77 | built = true;
78 |
79 |
80 | }
81 |
82 | @Override
83 | public void run() {
84 |
85 | while(true)
86 | {
87 | //System.out.println("looping");
88 | if (built) {
89 |
90 |
91 | if(getCPU()!=null )
92 | {
93 | update();
94 | }
95 |
96 |
97 |
98 | }else{
99 | try {
100 | //give a chance for the 'built' boolean to be written TRUE to on faster PCs (i hate you sometimes java)
101 | Thread.sleep(100);
102 | } catch (InterruptedException e) {
103 | e.printStackTrace();
104 | }
105 | }
106 |
107 |
108 |
109 |
110 | }
111 |
112 | }
113 |
114 |
115 |
116 | public static final int MaxNodes = 1000000;
117 |
118 | GenePool pool;
119 |
120 | BrainInfoWindow infoWindow;
121 |
122 |
123 | boolean alreadyGivenUp = false;
124 |
125 | private void update() {
126 |
127 |
128 | if(!firstUpdateOccured)
129 | {
130 | firstUpdateOccured = true;
131 |
132 | initializePool();
133 |
134 |
135 | emulator.setGamepadInput( gamepad.getIntegerBuffer() );
136 |
137 | infoWindow = new BrainInfoWindow( gamepad , gameData , pool );
138 |
139 | }
140 |
141 | Species species = pool.getCurrentSpecies();
142 | Genome genome = pool.getCurrentGenome();
143 |
144 | /*
145 | * if forms.ischecked(showNetwork) then displayGenome(genome) end
146 | */
147 | if (pool.getCurrentFrame() % 5 == 0) {
148 | evaluateCurrent();
149 |
150 |
151 | // emulator.setControllers(getController(), getController());
152 | getGameDataManager().siphonData();
153 |
154 |
155 | boolean giveUp = getGameDataManager().updateGiveUpTimer( );
156 |
157 | int fitness = getGameDataManager().getCurrentFitness() ;
158 |
159 | if (fitness == 0) {
160 | fitness = -1;
161 | }
162 | genome.setFitness(fitness);
163 |
164 | if ( giveUp && !alreadyGivenUp ) {
165 |
166 | alreadyGivenUp = true;
167 |
168 | if (fitness > pool.getMaxFitness()) {
169 | pool.setMaxFitness(fitness);
170 | // forms.settext(maxFitnessLabel, "Max Fitness: " ..
171 | // math.floor(pool.maxFitness))
172 | // writeFile("backup." .. pool.generation .. "." ..
173 | // forms.gettext(saveLoadFile))
174 | }
175 |
176 |
177 |
178 |
179 |
180 | pool.setCurrentSpecies(0);
181 | pool.setCurrentGenome(0);
182 |
183 | while ( pool.getCurrentSpecies().getGenomes().isEmpty() || fitnessAlreadyMeasured() ) {
184 | nextGenome();
185 | }
186 |
187 | initializeRun();
188 | }
189 |
190 | infoWindow.update();
191 |
192 |
193 | }
194 |
195 | /* int measured = 0;
196 | int total = 0;
197 |
198 | // for every genome in every species increment total and if fitness is
199 | // not zero then increment measured
200 |
201 | for (Species s : pool.getSpecies()) {
202 | for (Genome g : s.getGenomes()) {
203 | total++;
204 | if (g.getFitness() != 0) {
205 | measured++;
206 | }
207 |
208 | }
209 | }*/
210 |
211 |
212 | pool.setCurrentFrame( getRunFrameCount() );
213 |
214 |
215 |
216 |
217 | }
218 |
219 | private int getRunFrameCount() {
220 | return (int) (getNES().getFrameCount() - runInitFrameCount);
221 | }
222 |
223 | private void nextGenome() {
224 | pool.setCurrentGenome(pool.getCurrentGenomeIndex() + 1);
225 | if (pool.getCurrentGenomeIndex() >= pool.getCurrentSpecies().getGenomes().size()) {
226 | pool.setCurrentGenome(0);
227 | pool.setCurrentSpecies(pool.getCurrentSpeciesIndex() + 1);
228 |
229 | if (pool.getCurrentSpeciesIndex() >= pool.getSpecies().size()) {
230 | pool.newGeneration();
231 | pool.setCurrentSpecies(0);
232 | }
233 | }
234 |
235 | }
236 |
237 | private boolean fitnessAlreadyMeasured() {
238 |
239 | return pool.getCurrentGenome().getFitness() != 0;
240 | }
241 |
242 | private GameDataManager getGameDataManager() {
243 |
244 | return gameData;
245 | }
246 |
247 | public void initializePool() {
248 |
249 | getNES().saveState( 0 );
250 | getNES().registerROMEventListener(this);
251 |
252 | getNES().audioEnabled = false;
253 |
254 | pool = new GenePool(gameData);
255 |
256 | initializeRun();
257 |
258 | }
259 |
260 | long runInitFrameCount = 0;
261 |
262 | public void initializeRun() {
263 |
264 | getNES().requestLoadState( 0 );
265 |
266 |
267 |
268 | // resets the game
269 |
270 | runInitFrameCount = getNES().getFrameCount();
271 |
272 | pool.setCurrentFrame(0);
273 |
274 |
275 |
276 |
277 | gamepad.clear();
278 |
279 | Species species = pool.getCurrentSpecies();
280 | Genome genome = pool.getCurrentGenome();
281 | generateNetwork(genome);
282 | evaluateCurrent();
283 |
284 | }
285 |
286 |
287 |
288 | public void onStateLoaded() {
289 | //loadGameDataManager();
290 | alreadyGivenUp = false;
291 | getGameDataManager().initializeRun();
292 | }
293 |
294 | public void evaluateCurrent() {
295 | Species species = pool.getCurrentSpecies();
296 | Genome genome = pool.getCurrentGenome();
297 |
298 | HashMap gamePadOutputs = evaluateNetwork(genome.getNetwork(), getGameDataManager()
299 | .getBrainSystemInputs());
300 |
301 | if(gamePadOutputs!=null)
302 | {
303 |
304 | // if left and right are pressed at once, dont press either.. same with
305 | // up and down
306 | if (gamePadOutputs.containsKey("P1 Left") && gamePadOutputs.get("P1 Left")
307 | && gamePadOutputs.containsKey("P1 Right") && gamePadOutputs.get("P1 Right")) {
308 | gamePadOutputs.put("P1 Left", false);
309 | gamePadOutputs.put("P1 Right", false);
310 | }
311 |
312 | if (gamePadOutputs.containsKey("P1 Up") && gamePadOutputs.get("P1 Up")
313 | && gamePadOutputs.containsKey("P1 Down") && gamePadOutputs.get("P1 Down")) {
314 | gamePadOutputs.put("P1 Up", false);
315 | gamePadOutputs.put("P1 Down", false);
316 | }
317 |
318 |
319 |
320 |
321 | gamepad.setOutputs(gamePadOutputs);
322 |
323 | }
324 |
325 | }
326 |
327 | /*
328 | * Thsi explains how neurons fit into genes and etc..
329 | */
330 | private void generateNetwork(Genome genome) {
331 | NeuralNetwork network = new NeuralNetwork();
332 |
333 | for (int i = 0; i < getGameDataManager().getNumInputs(); i++) {
334 | network.getNeurons().put(i, new Neuron());
335 | }
336 |
337 | for (int o = 0; o < getGameDataManager().getNumOutputs(); o++) {
338 | network.getNeurons().put(MaxNodes + o, new Neuron());
339 |
340 | }
341 |
342 | Collections.sort(genome.getGenes(), new Comparator() {
343 |
344 | @Override
345 | public int compare(Gene g1, Gene g2) {
346 |
347 | return g1.getNeuralOutIndex() < g2.getNeuralOutIndex() ? -1 : (g1.getNeuralOutIndex() == g2
348 | .getNeuralOutIndex() ? 0 : 1);
349 |
350 | }
351 |
352 | // sort by the out number
353 | // table.sort(genome.genes, function (a,b)
354 | // return (a.out < b.out)
355 | // end
356 | });
357 |
358 | for (int i = 0; i < genome.getGenes().size(); i++) {
359 | Gene gene = genome.getGenes().get(i);
360 | if (gene.isEnabled()) {
361 | if (network.getNeurons().get(gene.getNeuralOutIndex()) == null) {
362 | network.getNeurons().put(gene.getNeuralOutIndex(), new Neuron());
363 | }
364 |
365 | Neuron neuron = network.getNeurons().get(gene.getNeuralOutIndex());
366 | neuron.getIncomingGeneList().add(gene);
367 |
368 | if (network.getNeurons().get(gene.getNeuralInIndex()) == null)
369 | network.getNeurons().put(gene.getNeuralInIndex(), new Neuron());
370 | }
371 | }
372 |
373 | genome.setNetwork(network);
374 | }
375 |
376 | /**
377 | * Input is the neural network and number of inputs, output is the current
378 | * gamepad button-press states
379 | *
380 | *
381 | *
382 | *
383 | */
384 | private HashMap evaluateNetwork(NeuralNetwork network, List inputList) {
385 |
386 |
387 |
388 | inputList.add(1);
389 |
390 | if (inputList.size() != this.getGameDataManager().getNumInputs()) {
391 | System.err.println("Incorrect number of neural network inputs. " + inputList.size() + " vs " + this.getGameDataManager().getNumInputs() );
392 | return null;
393 | }
394 |
395 | for (int i = 0; i < this.getGameDataManager().getNumInputs(); i++) {
396 | if(network.getNeurons().containsKey( i ))
397 | {
398 | network.getNeurons().get(i).setValue(inputList.get(i));
399 | }else{
400 | System.err.println( "no neuron at " + i );
401 | }
402 | }
403 |
404 | //for every neuron, sum up the values of its incoming genes and set its own value to that
405 | for (Neuron neuron : network.getNeurons().values()) {
406 | float sum = 0;
407 |
408 | for (int j = 0; j < neuron.getIncomingGeneList().size(); j++) {
409 | Gene incoming = neuron.getIncomingGeneList().get(j);
410 | Neuron other = network.getNeurons().get(incoming.getNeuralInIndex());
411 | sum = sum + incoming.getWeight() * other.getValue();
412 |
413 | }
414 |
415 | if (neuron.getIncomingGeneList().size() > 0) {
416 | neuron.setValue(sigmoid(sum));
417 | }
418 | }
419 |
420 | HashMap gamepadOutputs = new HashMap();
421 |
422 |
423 | for (int o = 0; o < this.getGameDataManager().getNumOutputs(); o++) {
424 |
425 | String button = "P1 " + this.getGameDataManager().getButtonNames()[o];
426 |
427 | if (network.getNeurons().get(MaxNodes + o).getValue() > 0) {
428 |
429 | gamepadOutputs.put(button, true);
430 |
431 | //if(o == 0){System.out.println(" button press " + button);}
432 |
433 | } else {
434 | gamepadOutputs.put(button, false);
435 | }
436 |
437 | }
438 |
439 | return gamepadOutputs;
440 | }
441 |
442 | public String getRomName() {
443 | if (emulator.getCurrentRomName() == null) {
444 | return "none";
445 | }
446 |
447 | return emulator.getCurrentRomName();
448 | }
449 |
450 |
451 | public void saveGenePoolToFile() throws Exception
452 | {
453 | //FileWriter file = new FileWriter("c:\\test.json");
454 | //file.write(pool.getAsJson().toJSONString());
455 | //file.flush();
456 | //file.close();
457 |
458 | }
459 |
460 | public static float sigmoid(float sum) {
461 | return 2 / (1 + FastMath.exp(-4.9f * sum)) - 1;
462 | }
463 |
464 |
465 |
466 | public Cpu getCPU()
467 | {
468 | if(getNES()!=null)
469 | {
470 | return getNES().getCpu();
471 | }
472 | return null;
473 | }
474 |
475 | private Nes getNES() {
476 |
477 | return emulator.getNES();
478 | }
479 |
480 | public GenePool getPool() {
481 | return pool;
482 | }
483 |
484 |
485 |
486 | private void loadGameDataManager() {
487 | if (getRomName().startsWith("Super Mario Bros."))
488 | {
489 | gameData = new MarioGameDataManager(this);
490 | }else{
491 | gameData = new GalagaGameDataManager(this);
492 | }
493 |
494 | getNES().registerPixelDataListener(gameData);
495 |
496 | }
497 |
498 |
499 | }
500 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/com/starflask/JavaNESBrain/VirtualGamePad.java:
--------------------------------------------------------------------------------
1 | package com.starflask.JavaNESBrain;
2 |
3 | import java.awt.event.KeyEvent;
4 | import java.util.HashMap;
5 |
6 |
7 | public class VirtualGamePad {
8 |
9 | int[] integerBuffer = new int[16]; // 0 to 7 are player 1
10 |
11 |
12 | HashMap KEYNAMEMAP = new HashMap();
13 |
14 | VirtualGamePad()
15 | {
16 | KEYNAMEMAP.put("P1 A", 0);
17 | KEYNAMEMAP.put("P1 B", 1);
18 | KEYNAMEMAP.put("P1 Select", 2);
19 | KEYNAMEMAP.put("P1 Enter", 3);
20 | KEYNAMEMAP.put("P1 Up", 4);
21 | KEYNAMEMAP.put("P1 Down", 5);
22 | KEYNAMEMAP.put("P1 Left", 6);
23 | KEYNAMEMAP.put("P1 Right", 7);
24 |
25 |
26 | }
27 |
28 |
29 |
30 |
31 | public void setOutputs(HashMap gamePadOutputs) {
32 |
33 | for(String key : gamePadOutputs.keySet())
34 | {
35 | setKeyState(key , gamePadOutputs.get(key) ) ;
36 | }
37 | }
38 |
39 |
40 |
41 | private void setKeyState(String keyname, Boolean pressed) {
42 |
43 |
44 | int keyIndex = KEYNAMEMAP.get(keyname);
45 |
46 | int value = pressed ? 1 : 0 ;
47 |
48 |
49 |
50 | integerBuffer[keyIndex] = value;
51 |
52 | integerBuffer[2] = 0; //no select
53 | integerBuffer[3] = 0; //no enter
54 |
55 | }
56 |
57 | public void clear() {
58 | for (int i = 0; i < 16; i++){
59 | // integerBuffer[i] = 0;
60 | }
61 |
62 | }
63 |
64 | public int[] getIntegerBuffer() {
65 |
66 | return integerBuffer;
67 | }
68 |
69 |
70 | static final int[][] keyDef = {
71 | { KeyEvent.VK_Z, KeyEvent.VK_X, KeyEvent.VK_SHIFT,
72 | KeyEvent.VK_ENTER, KeyEvent.VK_UP, KeyEvent.VK_DOWN,
73 | KeyEvent.VK_LEFT, KeyEvent.VK_RIGHT, },
74 | { KeyEvent.VK_V, KeyEvent.VK_B, KeyEvent.VK_N, KeyEvent.VK_M,
75 | KeyEvent.VK_O, KeyEvent.VK_COMMA, KeyEvent.VK_K,
76 | KeyEvent.VK_L, } };
77 |
78 |
79 | public void onHardwareKey(int keyCode, boolean pressed) {
80 | for (int i = 0; i < 16; i++){
81 | if (keyCode == keyDef[i/8][i%8]){
82 | integerBuffer[i ] = (pressed ? 1 : 0);
83 |
84 | System.out.println("forcing key " + i);
85 | }
86 | }
87 | }
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/com/starflask/JavaNESBrain/data/BrainInfoPane.java:
--------------------------------------------------------------------------------
1 | package com.starflask.JavaNESBrain.data;
2 |
3 | import java.awt.Color;
4 | import java.awt.Frame;
5 | import java.awt.Graphics;
6 | import java.awt.Image;
7 | import java.awt.Panel;
8 | import java.awt.event.KeyAdapter;
9 | import java.awt.event.KeyEvent;
10 | import java.awt.event.WindowAdapter;
11 | import java.awt.event.WindowEvent;
12 | import java.awt.image.BufferedImage;
13 | import java.awt.image.DataBufferByte;
14 | import java.io.File;
15 | import java.io.IOException;
16 | import java.util.ArrayList;
17 | import java.util.HashMap;
18 | import java.util.Iterator;
19 | import java.util.List;
20 |
21 | import javax.imageio.ImageIO;
22 | import javax.swing.JPanel;
23 |
24 | import jp.tanakh.bjne.ui.AWTRenderer;
25 |
26 | import com.starflask.JavaNESBrain.SuperBrain;
27 | import com.starflask.JavaNESBrain.VirtualGamePad;
28 | import com.starflask.JavaNESBrain.evolution.Gene;
29 | import com.starflask.JavaNESBrain.evolution.GenePool;
30 | import com.starflask.JavaNESBrain.evolution.NeuralNetwork;
31 | import com.starflask.JavaNESBrain.evolution.Neuron;
32 | import com.starflask.JavaNESBrain.utils.FastMath;
33 | import com.starflask.JavaNESBrain.utils.Vector2Int;
34 | import com.starflask.JavaNESBrain.utils.Vector2f;
35 |
36 | public class BrainInfoPane extends JPanel {
37 |
38 | GameDataManager gameDataManager;
39 | GenePool pool;
40 |
41 | public BrainInfoPane(GameDataManager gameDataManager, GenePool pool)
42 | {
43 | this.gameDataManager=gameDataManager;
44 | this.pool=pool;
45 |
46 | }
47 |
48 |
49 | public void update()
50 | {
51 | repaint();
52 | }
53 |
54 | @Override
55 | public void paintComponent(Graphics g)
56 | {
57 | super.paintComponent(g);
58 | renderScreen(g);
59 |
60 | }
61 |
62 | void renderScreen(Graphics g) {
63 |
64 |
65 |
66 | /*byte[] bgr = ((DataBufferByte) image.getRaster().getDataBuffer())
67 | .getData();
68 |
69 | for (int i = 0; i < SCREEN_WIDTH * SCREEN_HEIGHT; i++) {
70 | //bgr[i * 3] = info.buf[i * 3 + 2];
71 | //bgr[i * 3 + 1] = info.buf[i * 3 + 1];
72 | //bgr[i * 3 + 2] = info.buf[i * 3 + 0];
73 |
74 | bgr[i * 3] = 0;
75 | bgr[i * 3 + 1] = 0;
76 | bgr[i * 3 + 2] = 0;
77 |
78 | }
79 | */
80 |
81 |
82 | int left = getInsets().left;
83 | int top = getInsets().top;
84 |
85 |
86 |
87 | //g.drawImage(image, left, top, left + SCREEN_WIDTH*SCREEN_SIZE_MULTIPLIER, top + SCREEN_HEIGHT*SCREEN_SIZE_MULTIPLIER, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, this);
88 | g.clearRect(left, top, left + BrainInfoWindow.SCREEN_WIDTH*BrainInfoWindow.SCREEN_SIZE_MULTIPLIER, top + BrainInfoWindow.SCREEN_HEIGHT*BrainInfoWindow.SCREEN_SIZE_MULTIPLIER);
89 |
90 | drawHeaderInfo(g);
91 | HashMap cells = getGameDataManager().drawNeurons(g);
92 | //drawNeurons(g);
93 | drawOutputs(g, cells);
94 |
95 | }
96 |
97 |
98 |
99 |
100 | private void drawHeaderInfo(Graphics g) {
101 |
102 |
103 | g.setColor(Color.BLACK);
104 |
105 |
106 | g.drawString("Generation #" + getPool().getGeneration(), 10, 50);
107 | g.drawString("Species:" + getPool().getCurrentSpecies().toString(), 200, 50);
108 | g.drawString("Genome:" + getPool().getCurrentGenome().toString(), 10, 80);
109 | g.drawString("Fitness:" + getPool().getCurrentGenome().getFitness(), 200, 80);
110 | g.drawString("Max Fitness:" + getPool().getMaxFitness(), 290, 80);
111 |
112 |
113 |
114 | }
115 |
116 |
117 | private GenePool getPool() {
118 |
119 | return pool;
120 | }
121 |
122 | private GameDataManager getGameDataManager() {
123 |
124 | return gameDataManager;
125 | }
126 |
127 | private void drawOutputs(Graphics g, HashMap cells) {
128 |
129 |
130 | NeuralNetwork network = getPool().getCurrentGenome().getNetwork();
131 |
132 | if(cells == null || cells.isEmpty() || network == null || network.getNeurons().isEmpty())
133 | {
134 | return;
135 | }
136 |
137 |
138 | //draw outputs
139 | //each output only has one assigned neuron, but that neuron can have multiple incoming genes
140 | for (int o = 0; o < getGameDataManager().getNumOutputs(); o++) {
141 |
142 | String button = getGameDataManager().getButtonNames()[o];
143 |
144 | g.setColor(Color.GRAY);
145 |
146 | DebugCell outputCell = new DebugCell();
147 | outputCell.x = 350;
148 | outputCell.y = 120 + 16 * o;
149 |
150 | try
151 | {
152 | outputCell.value = network.getNeurons().get( SuperBrain.MaxNodes + o) .getValue();
153 | }catch(Exception e)
154 | {
155 | e.printStackTrace();
156 | System.out.println( network );
157 | System.out.println( network.getNeurons() );
158 | System.out.println( SuperBrain.MaxNodes + o );
159 | System.out.println( network.getNeurons().get( SuperBrain.MaxNodes + o) );
160 | }
161 |
162 |
163 | if (network.getNeurons().get( SuperBrain.MaxNodes + o).getValue() > 0) {
164 | g.setColor(Color.RED);
165 | }
166 |
167 |
168 |
169 | g.drawString( button , 365, 120 + 12 + 16 * o);
170 | g.fillRect(350 ,120 + 16 * o, 12, 12);
171 |
172 |
173 | cells.put(SuperBrain.MaxNodes + o, outputCell );
174 |
175 | }
176 |
177 | //the key corresponds to which input affects this neuron
178 |
179 | for(int key : network.getNeurons().keySet())
180 | {
181 | Neuron neuron = network.getNeurons().get(key);
182 |
183 | if(key > getGameDataManager().getNumInputs() && key <= SuperBrain.MaxNodes)
184 | {
185 | DebugCell cell = new DebugCell();
186 | cell.x = 260;
187 | cell.y = 120;
188 | cell.value = neuron.getValue();
189 | cells.put(key, cell);
190 | }
191 |
192 |
193 | //draw lines for incoming gene list
194 | //neuron.getIncomingGeneList()
195 | }
196 |
197 | //neurons with a key in between 10 and 1000000 are 'middle men' and only connect to other...neurons? genes?
198 |
199 | for(int n =0; n < 4; n++)
200 | {
201 | for(Gene gene : pool.getCurrentGenome().getGenes() )
202 | {
203 | DebugCell cellIn = cells.get(gene.getNeuralInIndex());
204 | DebugCell cellOut = cells.get(gene.getNeuralOutIndex());
205 |
206 | if(gene.isEnabled() && cellIn!=null && cellOut != null )
207 | {
208 |
209 |
210 | if(gene.getNeuralInIndex() > getGameDataManager().getNumInputs() && gene.getNeuralInIndex() <= getMaxNodes() )
211 | {
212 |
213 | cellIn.x = 0.75f*cellIn.x + 0.25f*cellOut.x;
214 |
215 | if(cellIn.x >= cellOut.x)
216 | {
217 | cellIn.x = cellIn.x - 40;
218 | }
219 |
220 | if(cellIn.x < 190)
221 | {
222 | cellIn.x = 190;
223 | }
224 | if(cellIn.x > 320)
225 | {
226 | cellIn.x = 320;
227 | }
228 |
229 | cellIn.y = 0.75f*cellIn.y + 0.25f*cellOut.y;
230 |
231 | }
232 |
233 | if(gene.getNeuralOutIndex() > getGameDataManager().getNumInputs() && gene.getNeuralOutIndex() <= getMaxNodes() )
234 | {
235 | cellOut.x = 0.25f*cellIn.x + 0.75f*cellOut.x;
236 |
237 | if (cellIn.x >= cellOut.x )
238 | {
239 | cellOut.x = cellOut.x + 40;
240 | }
241 |
242 |
243 | if(cellOut.x < 190)
244 | {
245 | cellOut.x = 190;
246 | }
247 | if(cellOut.x > 320)
248 | {
249 | cellOut.x = 320;
250 | }
251 |
252 | cellOut.y = 0.25f*cellIn.y + 0.75f*cellOut.y;
253 |
254 | }
255 |
256 |
257 | }
258 | }
259 |
260 | }
261 |
262 | for(int cellIndex : cells.keySet())
263 | {
264 | DebugCell cell = cells.get(cellIndex);
265 |
266 | if(cellIndex > getGameDataManager().getNumInputs() || cell.value!=0 )
267 | {
268 | int colordarkness = (int) FastMath.floor((cell.value+1)/ 2*256 );
269 | if( colordarkness > 255){ colordarkness = 255 ; }
270 | if( colordarkness < 0){ colordarkness = 0 ; }
271 |
272 | float opacity = cell.value == 0 ? 0.5f : 1f;
273 |
274 | Color color = new Color(colordarkness / 255f, 0.2f, 0.2f, opacity);
275 |
276 | g.setColor(color);
277 | g.drawRect((int) cell.x , (int) cell.y , (int) (4 * opacity),(int) (4 * opacity));
278 |
279 | }
280 |
281 | }
282 |
283 | for(Gene gene : pool.getCurrentGenome().getGenes() )
284 | {
285 |
286 | DebugCell cellIn = cells.get(gene.getNeuralInIndex());
287 | DebugCell cellOut = cells.get(gene.getNeuralOutIndex());
288 | if(gene.isEnabled() && cellIn!=null && cellOut!=null )
289 | {
290 |
291 | Color lowColor = Color.darkGray;
292 | Color highColor = Color.green;
293 |
294 | boolean triggered = cellIn.value >= 0f;
295 | float colorDarkness = 0.5f - FastMath.floor(FastMath.abs(SuperBrain.sigmoid(gene.getWeight())*0.5f ));
296 |
297 | Color color = lowColor;
298 |
299 | if(triggered){
300 | color = highColor ;
301 | }
302 |
303 | g.setColor(color);
304 |
305 | g.drawLine((int) cellIn.x,(int)cellIn.y,(int) cellOut.x,(int) cellOut.y);
306 |
307 | }
308 |
309 | }
310 |
311 |
312 | }
313 |
314 | private int getMaxNodes() {
315 | return SuperBrain.MaxNodes;
316 | }
317 |
318 | }
--------------------------------------------------------------------------------
/JavaNESBrain/src/com/starflask/JavaNESBrain/data/BrainInfoWindow.java:
--------------------------------------------------------------------------------
1 | package com.starflask.JavaNESBrain.data;
2 |
3 | import java.awt.Color;
4 | import java.awt.Frame;
5 | import java.awt.Graphics;
6 | import java.awt.Image;
7 | import java.awt.event.KeyAdapter;
8 | import java.awt.event.KeyEvent;
9 | import java.awt.event.WindowAdapter;
10 | import java.awt.event.WindowEvent;
11 | import java.awt.image.BufferedImage;
12 | import java.awt.image.DataBufferByte;
13 | import java.io.File;
14 | import java.io.IOException;
15 | import java.util.ArrayList;
16 | import java.util.HashMap;
17 | import java.util.Iterator;
18 | import java.util.List;
19 |
20 | import javax.imageio.ImageIO;
21 | import javax.swing.JPanel;
22 |
23 | import jp.tanakh.bjne.ui.AWTRenderer;
24 |
25 | import com.starflask.JavaNESBrain.SuperBrain;
26 | import com.starflask.JavaNESBrain.VirtualGamePad;
27 | import com.starflask.JavaNESBrain.evolution.Gene;
28 | import com.starflask.JavaNESBrain.evolution.GenePool;
29 | import com.starflask.JavaNESBrain.evolution.NeuralNetwork;
30 | import com.starflask.JavaNESBrain.evolution.Neuron;
31 | import com.starflask.JavaNESBrain.utils.FastMath;
32 | import com.starflask.JavaNESBrain.utils.Vector2Int;
33 | import com.starflask.JavaNESBrain.utils.Vector2f;
34 |
35 | public class BrainInfoWindow extends Frame{
36 |
37 | static final int SCREEN_SIZE_MULTIPLIER = 2;
38 |
39 | static final int SCREEN_WIDTH = 256;
40 |
41 | static final int SCREEN_HEIGHT = 240;
42 |
43 |
44 |
45 | VirtualGamePad gamepad;
46 |
47 | BrainInfoPane pane;
48 |
49 |
50 | public BrainInfoWindow(VirtualGamePad gamepad, GameDataManager gameDataManager, GenePool pool) {
51 | super("NESBrain Info");
52 |
53 |
54 |
55 | System.out.println(" GD " + gameDataManager);
56 |
57 | pane = new BrainInfoPane(gameDataManager , pool);
58 | this.add(pane);
59 |
60 | //this.gameDataManager=gameDataManager;
61 | //this.pool=pool;
62 | this.gamepad=gamepad;
63 |
64 | BufferedImage img = null;
65 | try {
66 | img = ImageIO.read(new File("src/icon.png"));
67 | this.setIconImage( img );
68 | } catch (IOException e) {
69 | e.printStackTrace();
70 | }
71 |
72 |
73 | addWindowListener(new WindowAdapter() {
74 | @Override
75 | public void windowClosing(WindowEvent e) {
76 | onExit();
77 | }
78 | });
79 |
80 |
81 | setVisible(true);
82 | setVisible(false);
83 | setSize(SCREEN_WIDTH*SCREEN_SIZE_MULTIPLIER + getInsets().left + getInsets().right, SCREEN_HEIGHT*SCREEN_SIZE_MULTIPLIER + getInsets().top + getInsets().bottom);
84 | setVisible(true);
85 |
86 |
87 | pane.setSize(SCREEN_WIDTH*SCREEN_SIZE_MULTIPLIER + getInsets().left + getInsets().right, SCREEN_HEIGHT*SCREEN_SIZE_MULTIPLIER + getInsets().top + getInsets().bottom);
88 | pane.setVisible(true);
89 |
90 |
91 | this.addKeyListener(new KeyAdapter() {
92 | @Override
93 | public void keyPressed(KeyEvent e) {
94 | onHardwareKey(e.getKeyCode(), true);
95 | }
96 |
97 | @Override
98 | public void keyReleased(KeyEvent e) {
99 | onHardwareKey(e.getKeyCode(), false);
100 | }
101 | });
102 |
103 |
104 | }
105 |
106 |
107 | public void update()
108 | {
109 | pane.update();
110 |
111 |
112 |
113 |
114 | }
115 |
116 | private void onExit() {
117 | System.exit(0);
118 | }
119 |
120 | protected void onHardwareKey(int keyCode, boolean pressed) {
121 | gamepad.onHardwareKey(keyCode,pressed);
122 | }
123 |
124 | /*private BufferedImage image = new BufferedImage(SCREEN_WIDTH,
125 | SCREEN_HEIGHT, BufferedImage.TYPE_3BYTE_BGR);
126 | */
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 | }
--------------------------------------------------------------------------------
/JavaNESBrain/src/com/starflask/JavaNESBrain/data/DebugCell.java:
--------------------------------------------------------------------------------
1 | package com.starflask.JavaNESBrain.data;
2 |
3 | public class DebugCell {
4 |
5 | public float x;
6 | public float y;
7 | public float value;
8 | }
9 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/com/starflask/JavaNESBrain/data/GalagaGameDataManager.java:
--------------------------------------------------------------------------------
1 | package com.starflask.JavaNESBrain.data;
2 |
3 | import java.awt.Color;
4 | import java.awt.Graphics;
5 | import java.util.ArrayList;
6 | import java.util.HashMap;
7 | import java.util.List;
8 |
9 | import com.starflask.JavaNESBrain.SuperBrain;
10 |
11 | import jp.tanakh.bjne.nes.Renderer.ScreenInfo;
12 |
13 |
14 | /*
15 | * Need to feed inputs for the divebombing enemies and their projectiles
16 | * need to feed the enemy row offsets in as inputs
17 | * need to make the neurons operate like floating points, not just binary 0s and 1s (continuous nn)
18 | *
19 | * http://www.thealmightyguru.com/Games/Hacking/Wiki/index.php?title=Galaga
20 | */
21 |
22 | public class GalagaGameDataManager extends GameDataManager {
23 |
24 | //figure out where in the memory the sprites and the score is ..
25 |
26 | public GalagaGameDataManager(SuperBrain superBrain) {
27 | super(superBrain);
28 |
29 | }
30 |
31 |
32 |
33 | int InputSize = 240 + 1;
34 |
35 | int numInputs = InputSize+1;
36 |
37 | int gamescore = 0;
38 | int[] gameScoreDigits = new int[7];
39 |
40 | int playerXpos =0;
41 | int playerLives =0;
42 |
43 | int initLives = 0;
44 |
45 | @Override
46 | public void initializeRun() {
47 | super.initializeRun();
48 |
49 | initLives = readbyte(0x487);
50 | playerLives = readbyte(0x487);
51 |
52 | System.out.println("initializing run");
53 | }
54 |
55 | @Override
56 | public void siphonData()
57 | {
58 | gamescore = 0;
59 | for(int i=0;i<7;i++)
60 | {
61 | gameScoreDigits[i] = readbyte(0xE0 + 6-i);
62 | gamescore += gameScoreDigits[i] * (Math.pow(10, i));
63 | }
64 |
65 | //System.out.println(gamescore);
66 |
67 | playerXpos = readbyte(0x203);
68 |
69 |
70 | playerLives = readbyte(0x487);
71 |
72 | }
73 |
74 | @Override
75 | public int getCurrentScore() {
76 | return gamescore;
77 | }
78 |
79 |
80 | @Override
81 | public List getBrainSystemInputs()
82 | {
83 | siphonData();
84 |
85 |
86 | List inputs = new ArrayList();
87 |
88 | inputs.add( playerXpos );
89 |
90 | for(int row =0;row<5;row++)
91 | {
92 | for(int column =0; column < 10; column++)
93 | {
94 | int enemyInPosition = readbyte(0x0400 + column + row*0x10) ;
95 |
96 | // inputs.add(enemyInPosition);
97 | }
98 | }
99 |
100 | //screen is 256 by 256.. lets make it into 16-pixel large squares
101 | if(screenPixelData!=null)
102 | {
103 | ///System.out.println("can process pixels");
104 | //should average / blur colors together
105 |
106 | //height is 15 x 16
107 | //width is 16 x 16
108 |
109 | for(int row = 0; row < 15 ; row++)
110 | {
111 | for(int column =0; column < 16; column++)
112 | {
113 | PixelTile tile = new PixelTile(screenPixelData,row,column);
114 | inputs.add(tile.grayscaleValue);
115 | }
116 | }
117 |
118 |
119 | }
120 |
121 |
122 | // velocity of mario ??? this was commented out anyways
123 | // --mariovx = memory.read_s8(0x7B) ;
124 | // --mariovy = memory.read_s8(0x7D) ;
125 |
126 | return inputs ;
127 |
128 | }
129 |
130 | class PixelTile
131 | {
132 | /*
133 | *
134 | * for (int i = 0; i < SCREEN_WIDTH * SCREEN_HEIGHT; i++) {
135 | bgr[i * 3] = info.buf[i * 3 + 2];
136 | bgr[i * 3 + 1] = info.buf[i * 3 + 1];
137 | bgr[i * 3 + 2] = info.buf[i * 3 + 0];
138 | }
139 |
140 | */
141 | int grayscaleValue;
142 |
143 | public PixelTile(ScreenInfo screenPixelData, int row, int column) {
144 | grayscaleValue = 0;
145 |
146 | //the 3 is for the bgr
147 | int index = (column*240 + row )* 16 * 3 ;
148 |
149 | int sum = 0;
150 | for(int x = 0; x < 16; x++)
151 | {
152 | for(int y = 0; y < 16; y++)
153 | {
154 | sum += (int) screenPixelData.buf[index + x*16 + y + 0];
155 | sum += (int) screenPixelData.buf[index + x*16 + y + 1];
156 | sum += (int) screenPixelData.buf[index + x*16 + y + 2];
157 | }
158 | }
159 |
160 | byte val = screenPixelData.buf[index+2];
161 | //System.out.println("meep" + sum);
162 | grayscaleValue = (int) val;
163 | }
164 |
165 |
166 | }
167 |
168 |
169 |
170 | /**
171 | *
172 | */
173 | @Override
174 | public int getCurrentFitness() {
175 |
176 | if (getCurrentScore() > 3186) { // mario only
177 | return getCurrentScore() - (getCurrentFrame() / 200) + 1000;
178 | }
179 |
180 | return getCurrentScore() - (getCurrentFrame() / 200);
181 |
182 | }
183 |
184 |
185 |
186 | public HashMap drawNeurons(Graphics g ) {
187 |
188 |
189 | HashMap cells = new HashMap();
190 |
191 |
192 |
193 |
194 | //draw inputs
195 |
196 |
197 | g.setColor(Color.GRAY);
198 |
199 | g.drawString("Grid Map (AI Inputs)", 80, 110);
200 |
201 | List cellValues = getBrainSystemInputs();
202 |
203 |
204 |
205 | //Iterator cellValueInterator = cellValues.iterator();
206 |
207 | int inputCount = 0;
208 |
209 | for(int row = 0; row < 15 ; row++)
210 | {
211 | for(int column =0; column < 16; column++)
212 | {
213 | PixelTile tile = new PixelTile(screenPixelData,row,column);
214 |
215 | int enemyInPosition = tile.grayscaleValue ;
216 | //int enemyInPosition = readbyte(0x0400 + column + row*0x10) ;
217 |
218 |
219 | g.setColor(Color.GRAY);
220 |
221 | if(enemyInPosition < 5) //enemy
222 | {
223 | g.setColor(Color.RED);
224 | }
225 |
226 | if(enemyInPosition > 5 ) //tile
227 | {
228 | g.setColor(Color.BLACK);
229 | }
230 |
231 |
232 | DebugCell inputCell = new DebugCell();
233 | inputCell.x = -40 + (10)*16/2 + column*16;
234 | inputCell.y = 120 + (5)*16/2 + row*16;
235 | inputCell.value = enemyInPosition;
236 |
237 | cells.put(inputCount, inputCell );
238 |
239 |
240 | inputCount++;
241 |
242 | g.fillRect((int) inputCell.x,(int) inputCell.y, 8, 8);
243 |
244 |
245 | }
246 |
247 | }
248 |
249 |
250 | return cells;
251 |
252 |
253 |
254 | }
255 |
256 | int bonusTime = 0;
257 |
258 | /**
259 | * A genome will give up if it has not scored for 15 seconds or if it loses a life
260 | */
261 | @Override
262 | public boolean updateGiveUpTimer() {
263 | //int timeoutBonus = getCurrentFrame() / 2;
264 |
265 | // if mario gets farther than he has ever been this run...
266 | if (getCurrentScore() > bestScoreThisRun) {
267 | bestScoreThisRun = getCurrentScore();
268 |
269 | bonusTime+=1;
270 | }
271 |
272 | int timeElapsed = (int) (System.currentTimeMillis() - startTime);
273 |
274 | return timeElapsed >= getTimeoutConstant()+ bonusTime || playerLives < initLives; // should give up
275 |
276 | }
277 |
278 | @Override
279 | protected int getTimeoutConstant()
280 | {
281 |
282 | return 15000;
283 | }
284 |
285 | public int getNumInputs() {
286 |
287 | return numInputs;
288 | }
289 |
290 | ScreenInfo screenPixelData;
291 |
292 | @Override
293 | public void outputScreen(ScreenInfo scri) {
294 | this.screenPixelData=scri;
295 |
296 | }
297 |
298 |
299 |
300 | }
301 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/com/starflask/JavaNESBrain/data/GameDataManager.java:
--------------------------------------------------------------------------------
1 | package com.starflask.JavaNESBrain.data;
2 |
3 | import java.awt.Graphics;
4 | import java.util.ArrayList;
5 | import java.util.HashMap;
6 | import java.util.List;
7 |
8 | import com.starflask.JavaNESBrain.SuperBrain;
9 | import com.starflask.JavaNESBrain.evolution.GenePool;
10 | import com.starflask.JavaNESBrain.utils.FastMath;
11 | import com.starflask.JavaNESBrain.utils.Vector2Int;
12 |
13 | import jp.tanakh.bjne.nes.Renderer.ScreenInfo;
14 |
15 | public class GameDataManager implements PixelDataListener{
16 |
17 | //final static int TIMEOUT_CONSTANT = 1500;
18 | //int timeout;
19 | long startTime;
20 | int bestScoreThisRun = 0; // the most right that we ever got so far
21 |
22 | SuperBrain superBrain;
23 |
24 | public GameDataManager(SuperBrain superBrain) {
25 | this.superBrain = superBrain;
26 |
27 | init();
28 |
29 | }
30 |
31 | int numOutputs = 8;
32 |
33 | private String[] buttonNames;
34 |
35 | private void init() {
36 |
37 | // if (superBrain.getRomName().equals("Super Mario World (USA)"))
38 | // {
39 | String savefilename = "DP1.state";
40 |
41 | setButtonNames(new String[] { "A", "B", "Select", "Enter", "Up", "Down", "Left", "Right", });
42 |
43 | // }
44 |
45 | numOutputs = getButtonNames().length;
46 |
47 | }
48 |
49 | public void siphonData() {
50 |
51 | }
52 |
53 | protected String getRomName() {
54 | return superBrain.getRomName();
55 | }
56 |
57 | int getTile(Vector2Int delta) {
58 |
59 | return 0;
60 |
61 | }
62 |
63 | protected int readbyte(int addr) {
64 |
65 | return superBrain.getCPU().read8((short) addr);
66 |
67 | }
68 |
69 | Sprite[] sprites;
70 | Sprite[] extendedSprites;
71 |
72 | Integer[] inputs;
73 |
74 | public List getBrainSystemInputs() {
75 | siphonData();
76 |
77 | List inputs = new ArrayList();
78 |
79 | return inputs;
80 |
81 | }
82 |
83 | public int getNumInputs() {
84 |
85 | return 1;
86 | }
87 |
88 | public int getNumOutputs() {
89 |
90 | return numOutputs;
91 | }
92 |
93 | public String[] getButtonNames() {
94 | return buttonNames;
95 | }
96 |
97 | public void setButtonNames(String[] buttonNames) {
98 | this.buttonNames = buttonNames;
99 | }
100 |
101 | public int getCurrentScore() {
102 | return 0; // for mario, how 'right' he is. for galaga, the score
103 | }
104 |
105 | public int getCurrentFitness() {
106 |
107 | if (getCurrentScore() > 3186) { // mario only
108 | return getCurrentScore() - (getCurrentFrame() / 2) + 1000;
109 | }
110 |
111 | return getCurrentScore() - (getCurrentFrame() / 2);
112 |
113 | }
114 |
115 |
116 |
117 |
118 | public void initializeRun() {
119 |
120 |
121 |
122 | bestScoreThisRun = 0;
123 |
124 | startTime = System.currentTimeMillis();
125 | }
126 |
127 | /**
128 | * Milliseconds
129 | *
130 | */
131 | protected int getTimeoutConstant()
132 | {
133 | return 1000;
134 | }
135 |
136 |
137 | public boolean updateGiveUpTimer() { //totally broken, use galagas algo
138 | int timeoutBonus = getCurrentFrame() / 2;
139 |
140 | // if mario gets farther than he has ever been this run...
141 | if (getCurrentScore() > bestScoreThisRun) {
142 | bestScoreThisRun = getCurrentScore();
143 |
144 | startTime = System.currentTimeMillis(); // also reset the timeout
145 | }
146 |
147 | int timeElapsed = (int) (System.currentTimeMillis() - startTime);
148 |
149 | return timeElapsed + timeoutBonus <= 0; // should give up
150 |
151 | }
152 |
153 | protected int getCurrentFrame() {
154 |
155 | return superBrain.getPool().getCurrentFrame();
156 | }
157 |
158 | public HashMap drawNeurons(Graphics g) {
159 |
160 | return null;
161 | }
162 |
163 | @Override
164 | public void outputScreen(ScreenInfo scri) {
165 | // TODO Auto-generated method stub
166 |
167 | }
168 |
169 | }
170 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/com/starflask/JavaNESBrain/data/MarioGameDataManager.java:
--------------------------------------------------------------------------------
1 | package com.starflask.JavaNESBrain.data;
2 |
3 | import java.awt.Color;
4 | import java.awt.Graphics;
5 | import java.util.ArrayList;
6 | import java.util.HashMap;
7 | import java.util.List;
8 |
9 | import com.starflask.JavaNESBrain.SuperBrain;
10 | import com.starflask.JavaNESBrain.evolution.GenePool;
11 | import com.starflask.JavaNESBrain.utils.FastMath;
12 | import com.starflask.JavaNESBrain.utils.Vector2Int;
13 |
14 | public class MarioGameDataManager extends GameDataManager{
15 |
16 |
17 | int BoxRadius = 6;
18 |
19 | int InputSize = (BoxRadius*2+1)*(BoxRadius*2+1);
20 |
21 | int numInputs = InputSize+1;
22 |
23 | Vector2Int marioPos = new Vector2Int(0,0);
24 | Vector2Int screenPos = new Vector2Int(0,0);
25 |
26 |
27 | public MarioGameDataManager(SuperBrain superBrain) {
28 | super(superBrain);
29 |
30 | }
31 |
32 | @Override
33 | public void siphonData()
34 | {
35 |
36 | marioPos.setX( readbyte(0x6D)*0x100 + readbyte(0x86)) ;
37 | marioPos.setY( readbyte(0x03B8) + 16 );
38 |
39 | screenPos.setX( readbyte(0x03AD) ); //Mbc.ram[941] decimal
40 | screenPos.setY( readbyte(0x03B8) ); //Mbc.ram[952] decimal
41 |
42 | }
43 | int getTile(Vector2Int delta)
44 | {
45 |
46 |
47 |
48 | int x = (marioPos.getX() + delta.getX() + 8);
49 | int y = (marioPos.getY() + delta.getY() - 16);
50 |
51 |
52 | //is this math right?
53 | int page = (int) (FastMath.floor(x/256f)%2);
54 |
55 | int subx = (int) FastMath.floor((x%256)/16f);
56 | int suby = (int) FastMath.floor((y - 32)/16f);
57 | int addr = 0x500 + page*13*16+suby*16+subx ;
58 |
59 |
60 |
61 | if (suby >= 13 || suby < 0 )
62 | {
63 | return 0;
64 | }
65 |
66 | if (readbyte(addr) != 0 )
67 | {
68 | return 1;
69 | }else{
70 | return 0;
71 | }
72 |
73 |
74 |
75 |
76 | }
77 |
78 | @Override
79 | public List getBrainSystemInputs()
80 | {
81 | siphonData();
82 |
83 | sprites = getSprites();
84 | extendedSprites = getExtendedSprites();
85 |
86 | List inputs = new ArrayList();
87 |
88 |
89 |
90 | for(int dy = -BoxRadius*16 ; dy <= BoxRadius*16 ; dy+= 16)
91 | {
92 | for(int dx = -BoxRadius*16 ; dx <= BoxRadius*16 ; dx+= 16)
93 | {
94 | Vector2Int deltaPos = new Vector2Int(dx, dy);
95 |
96 | int cellValue = 0;
97 |
98 |
99 | int tile = getTile( deltaPos );
100 |
101 | if (tile == 1 && marioPos.getY() + dy < 0x1B0 )
102 | {
103 | cellValue = 1;
104 |
105 | }
106 |
107 |
108 | for(int i=1; i < sprites.length; i++)
109 | {
110 | if(sprites[i] != null)
111 | {
112 | float distx = FastMath.abs(sprites[i].getPos().getX() - (marioPos.getX()+dx) ) ;
113 | float disty = FastMath.abs(sprites[i].getPos().getY() - (marioPos.getY()+dy) ) ;
114 |
115 | if (distx <= 8 && disty <= 8 )
116 | {
117 | cellValue = -1;
118 | }
119 | }
120 | }
121 |
122 |
123 |
124 | inputs.add(cellValue);
125 | // 0 means nothing
126 | // 1 means a tile , white in color on the grid
127 | // -1 mean a baddie, black in color on the grid
128 |
129 | }
130 | }
131 |
132 |
133 | // velocity of mario ??? this was commented out anyways
134 | // --mariovx = memory.read_s8(0x7B) ;
135 | // --mariovy = memory.read_s8(0x7D) ;
136 |
137 | return inputs ;
138 |
139 | }
140 |
141 |
142 | public Vector2Int getMarioPos() {
143 |
144 | return marioPos;
145 | }
146 |
147 |
148 | @Override
149 | public int getCurrentScore() {
150 | return (int) getMarioPos().getX(); //for mario, how 'right' he is. for galaga, the score
151 | }
152 |
153 | public int getNumInputs() {
154 |
155 | return numInputs;
156 | }
157 |
158 | public int getBoxRadius() {
159 | return BoxRadius;
160 | }
161 |
162 | @Override
163 | protected int getTimeoutConstant()
164 | {
165 | return 500;
166 | }
167 |
168 |
169 | protected Sprite[] getSprites()
170 | {
171 | Sprite[] sprites = new Sprite[5];
172 |
173 |
174 |
175 | for(int slot=0;slot<=4;slot++)
176 | {
177 |
178 | int enemy = readbyte(0xF+slot) ;
179 | if (enemy != 0)
180 | {
181 | int ex = readbyte(0x6E + slot)*0x100 + readbyte(0x87+slot);
182 | int ey = readbyte(0xCF + slot)+24;
183 | sprites[ slot ] = new Sprite(ex,ey);
184 | }
185 |
186 |
187 |
188 | }
189 |
190 |
191 |
192 | return sprites;
193 | }
194 |
195 | protected Sprite[] getExtendedSprites()
196 | {
197 |
198 |
199 | return null;
200 | }
201 |
202 |
203 | public HashMap drawNeurons(Graphics g ) {
204 |
205 |
206 | HashMap cells = new HashMap();
207 |
208 |
209 |
210 |
211 | //draw inputs
212 |
213 |
214 | g.setColor(Color.GRAY);
215 |
216 | g.drawString("Grid Map (AI Inputs)", 80, 110);
217 |
218 | List cellValues = getBrainSystemInputs();
219 |
220 |
221 |
222 | //Iterator cellValueInterator = cellValues.iterator();
223 |
224 | int inputCount = 0;
225 |
226 | for(int dy = -getBoxRadius()*16 ; dy <= getBoxRadius()*16 ; dy+= 16)
227 | {
228 | for(int dx = -getBoxRadius()*16 ; dx <= getBoxRadius()*16 ; dx+= 16)
229 | {
230 | //Vector2Int deltaPos = new Vector2Int(dx, dy);
231 |
232 | int tile = cellValues.get(inputCount);
233 |
234 | g.setColor(Color.GRAY);
235 |
236 | if(tile < 0) //enemy
237 | {
238 | g.setColor(Color.RED);
239 | }
240 |
241 | if(tile > 0) //tile
242 | {
243 | g.setColor(Color.BLACK);
244 | }
245 |
246 |
247 | DebugCell inputCell = new DebugCell();
248 | inputCell.x = 30 + (getBoxRadius())*16/2 + dx/2;
249 | inputCell.y = 120 + (getBoxRadius())*16/2 + dy/2;
250 | inputCell.value = tile;
251 |
252 | cells.put(inputCount, inputCell );
253 |
254 |
255 | inputCount++;
256 |
257 | g.fillRect((int) inputCell.x,(int) inputCell.y, 8, 8);
258 |
259 |
260 | }
261 |
262 | }
263 |
264 |
265 | return cells;
266 |
267 |
268 |
269 | }
270 |
271 |
272 | }
273 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/com/starflask/JavaNESBrain/data/PixelDataListener.java:
--------------------------------------------------------------------------------
1 | package com.starflask.JavaNESBrain.data;
2 |
3 | import jp.tanakh.bjne.nes.Renderer.ScreenInfo;
4 |
5 | public interface PixelDataListener {
6 |
7 | void outputScreen(ScreenInfo scri);
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/com/starflask/JavaNESBrain/data/Sprite.java:
--------------------------------------------------------------------------------
1 | package com.starflask.JavaNESBrain.data;
2 |
3 | import com.starflask.JavaNESBrain.utils.Vector2Int;
4 |
5 | public class Sprite {
6 |
7 | Vector2Int pos;
8 | public Sprite(int x, int y) {
9 | pos = new Vector2Int(x,y);
10 | }
11 | public Vector2Int getPos() {
12 |
13 | return pos;
14 | }
15 |
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/com/starflask/JavaNESBrain/evolution/Gene.java:
--------------------------------------------------------------------------------
1 | package com.starflask.JavaNESBrain.evolution;
2 |
3 | public class Gene {
4 |
5 | private int into; // an index for neurons
6 | private int out;
7 | private float weight;
8 | private boolean enabled = true;
9 | int innovation;
10 |
11 | public Gene() {
12 |
13 | }
14 |
15 | public static Gene copyGene(Gene othergene) {
16 | Gene gene2 = new Gene();
17 | gene2.setInto(othergene.getNeuralInIndex());
18 | gene2.setOut(othergene.getNeuralOutIndex());
19 | gene2.setWeight(othergene.getWeight());
20 | gene2.setEnabled(othergene.isEnabled());
21 | gene2.innovation = othergene.innovation;
22 |
23 | return gene2;
24 | }
25 |
26 | public Gene copy() {
27 |
28 | return copyGene(this);
29 | }
30 |
31 | public int getNeuralOutIndex() {
32 |
33 | return out;
34 | }
35 |
36 | public int getInnovation() {
37 | return innovation;
38 | }
39 |
40 | public boolean isEnabled() {
41 | return enabled;
42 | }
43 |
44 | public void setEnabled(boolean enabled) {
45 | this.enabled = enabled;
46 | }
47 |
48 | public int getNeuralInIndex() {
49 |
50 | return into;
51 | }
52 |
53 | public float getWeight() {
54 | return weight;
55 | }
56 |
57 | public void setWeight(float weight) {
58 | this.weight = weight;
59 | }
60 |
61 |
62 |
63 | public void setInto(int into) {
64 | this.into = into;
65 | }
66 |
67 |
68 |
69 | public void setOut(int out) {
70 | this.out = out;
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/com/starflask/JavaNESBrain/evolution/GenePool.java:
--------------------------------------------------------------------------------
1 | package com.starflask.JavaNESBrain.evolution;
2 |
3 | import java.util.ArrayList;
4 |
5 |
6 | import java.util.Collection;
7 | import java.util.Collections;
8 | import java.util.Comparator;
9 | import java.util.HashMap;
10 | import java.util.List;
11 | import java.util.Random;
12 |
13 | import com.starflask.JavaNESBrain.SuperBrain;
14 | import com.starflask.JavaNESBrain.data.GameDataManager;
15 | import com.starflask.JavaNESBrain.utils.FastMath;
16 |
17 |
18 |
19 | public class GenePool {
20 |
21 | int Population = 300;
22 |
23 | int StaleSpecies = 15;
24 |
25 | List species = new ArrayList();
26 | private int generation = 0;
27 | int innovation = 10; //becomes equal to numOutputs
28 | private int currentSpecies = 0;
29 | private int currentGenome = 0;
30 | int currentFrame = 0;
31 | private int maxFitness = 0;
32 |
33 | Random rand = new Random();
34 |
35 | GameDataManager gameDataManager;
36 |
37 |
38 | public GenePool(GameDataManager gameDataManager)
39 | {
40 | this.gameDataManager=gameDataManager;
41 |
42 | for (int i=1; i < getPopulation() ; i++ )
43 | {
44 | Genome basic = createBasicGenome();
45 | addToSpecies(basic) ;
46 | }
47 |
48 |
49 | }
50 |
51 | public GameDataManager getGameDataManager() {
52 | return gameDataManager;
53 | }
54 |
55 |
56 | public int newInnovation()
57 | {
58 | return ++innovation;
59 | }
60 |
61 |
62 | public Genome createBasicGenome()
63 | {
64 | Genome genome = new Genome();
65 | innovation = 1;
66 |
67 | genome.maxneuron = getGameDataManager().getNumInputs() ; //numInputs
68 | mutate(genome);
69 |
70 | return genome;
71 | }
72 |
73 |
74 | private void mutate(Genome genome)
75 | {
76 | for (String key : genome.mutationRates.keySet() ){
77 |
78 | float rate = genome.mutationRates.get(key);
79 |
80 | if ( rand.nextBoolean() ){ //50 50 chance
81 | genome.mutationRates.put(key,0.95f*rate ) ;
82 | }else{
83 | genome.mutationRates.put(key,1.05263f*rate ) ;
84 | }
85 | }
86 |
87 | if (rand.nextFloat() < genome.mutationRates.get("connections")) {
88 | pointMutate(genome) ;
89 | }
90 |
91 | float p = genome.mutationRates.get("link");
92 | while (p > 0){
93 | if (rand.nextFloat() < p) {
94 | linkMutate(genome, false);
95 | }
96 | p = p - 1;
97 | }
98 |
99 | p = genome.mutationRates.get("bias");
100 | while (p > 0) {
101 | if (rand.nextFloat() < p) {
102 | linkMutate(genome, true);
103 | }
104 | p = p - 1;
105 | }
106 |
107 | p = genome.mutationRates.get("node");
108 | while (p > 0) {
109 | if (rand.nextFloat() < p) {
110 | nodeMutate(genome);
111 | }
112 | p = p - 1;
113 | }
114 |
115 | p = genome.mutationRates.get("enable");
116 | while (p > 0) {
117 | if (rand.nextFloat() < p) {
118 | enableDisableMutate(genome, true);
119 | }
120 | p = p - 1;
121 | }
122 |
123 | p = genome.mutationRates.get("disable");
124 | while (p > 0) {
125 | if (rand.nextFloat() < p) {
126 | enableDisableMutate(genome, false);
127 | }
128 | p = p - 1;
129 | }
130 | }
131 |
132 |
133 | float PerturbChance = 0.90f;
134 |
135 | private void pointMutate(Genome genome)
136 | {
137 | float step = genome.mutationRates.get("step") ;
138 |
139 | for (int i=0; i genes, Gene link) {
204 |
205 | for (Gene gene : genes)
206 | {
207 | if (gene.getNeuralInIndex() == link.getNeuralInIndex() && gene.getNeuralOutIndex() == link.getNeuralOutIndex())
208 | return true;
209 |
210 | }
211 | return false;
212 | }
213 |
214 |
215 | //what does this do?
216 | private int randomNeuronIndex(List genes, boolean nonInput)
217 | {
218 |
219 | HashMap neuronMatchesInputState = new HashMap();
220 |
221 | //every neuron corresponds with a gamepad button
222 | if (! nonInput ){
223 | for (int i=0; i < getGameDataManager().getNumInputs() ; i++ ){
224 | neuronMatchesInputState.put(i, true);
225 | }
226 | }
227 |
228 | for (int o=0; o < getGameDataManager().getNumOutputs(); o++ ) {
229 |
230 | neuronMatchesInputState.put( SuperBrain.MaxNodes +o, true);
231 | }
232 |
233 | for (int i=0; i < genes.size(); i++){
234 | if ((! nonInput) || genes.get(i).getNeuralInIndex() > getGameDataManager().getNumInputs()) {
235 | neuronMatchesInputState.put(genes.get(i).getNeuralInIndex(), true);
236 | }
237 | if ((! nonInput) || genes.get(i).getNeuralOutIndex() > getGameDataManager().getNumInputs() ){
238 | neuronMatchesInputState.put(genes.get(i).getNeuralOutIndex(), true);
239 | }
240 | }
241 |
242 |
243 |
244 |
245 | int numNeurons = neuronMatchesInputState.size();
246 |
247 | int randomIndex = rand.nextInt(numNeurons+1);
248 |
249 | //stop in a random place in the index hashmap and then give out that index
250 | for (Integer key : neuronMatchesInputState.keySet() )
251 | {
252 | randomIndex--;
253 |
254 | if (randomIndex == 0 )
255 | {
256 |
257 | return key;
258 | }
259 |
260 | }
261 |
262 |
263 | return 0;
264 |
265 | }
266 |
267 |
268 |
269 | private void nodeMutate(Genome genome)
270 | {
271 | if (genome.genes.size() == 0 )
272 | return;
273 |
274 |
275 | genome.maxneuron = genome.maxneuron + 1;
276 |
277 |
278 | int randomIndex = rand.nextInt(genome.genes.size());
279 | Gene gene = genome.genes.get( randomIndex ) ;
280 | if (!gene.isEnabled())
281 | return;
282 |
283 |
284 |
285 | gene.setEnabled(false);
286 |
287 | Gene gene1 = gene.copy();
288 | gene1.setOut(genome.maxneuron);
289 | gene1.setWeight(1.0f);
290 | gene1.innovation = newInnovation();
291 | gene1.setEnabled(true);
292 | genome.genes.add(gene1);
293 |
294 | Gene gene2 = gene.copy();
295 | gene2.setInto(genome.maxneuron);
296 | gene2.innovation = newInnovation();
297 | gene2.setEnabled(true);
298 | genome.genes.add(gene2);
299 | }
300 |
301 |
302 | public void enableDisableMutate(Genome genome, boolean enable)
303 | {
304 | List candidates = new ArrayList();
305 |
306 | //find the genes that are not this enablestate
307 | for (Gene gene : genome.genes) {
308 | if (gene.isEnabled() != enable)
309 | {
310 | candidates.add(gene);
311 | }
312 | }
313 |
314 | if (candidates.isEmpty())
315 | {
316 | return;
317 | }
318 |
319 | //flip the enablestate of a random candidate
320 | int randomIndex = rand.nextInt(candidates.size() ) ;
321 | Gene gene = candidates.get(randomIndex) ;
322 | gene.setEnabled(! gene.isEnabled()) ;
323 |
324 | }
325 |
326 |
327 | public int getCurrentFrame() {
328 |
329 | return currentFrame;
330 | }
331 |
332 |
333 | public Species getCurrentSpecies() {
334 |
335 | return species.get(currentSpecies);
336 | }
337 |
338 |
339 | public Genome getCurrentGenome() {
340 |
341 |
342 | return getCurrentSpecies().getGenomes().get(currentGenome );
343 |
344 |
345 |
346 | }
347 |
348 |
349 | public int getMaxFitness() {
350 | return maxFitness;
351 | }
352 |
353 |
354 | public void setMaxFitness(int maxFitness) {
355 | this.maxFitness = maxFitness;
356 | }
357 |
358 |
359 | public void setCurrentSpecies(int currentSpecies) {
360 | this.currentSpecies = currentSpecies;
361 | }
362 |
363 |
364 | public void setCurrentGenome(int currentGenome) {
365 | this.currentGenome = currentGenome;
366 | }
367 |
368 |
369 | public int getGeneration() {
370 | return generation;
371 | }
372 |
373 |
374 | public void setGeneration(int generation) {
375 | this.generation = generation;
376 | }
377 |
378 |
379 | public int getCurrentGenomeIndex() {
380 |
381 | return currentGenome;
382 | }
383 |
384 |
385 | public int getCurrentSpeciesIndex() {
386 |
387 | return currentSpecies;
388 | }
389 |
390 |
391 | public List getSpecies() {
392 |
393 | return species;
394 | }
395 |
396 |
397 | public void setCurrentFrame(int i) {
398 | currentFrame = i;
399 |
400 | }
401 |
402 | public void cullSpecies(boolean cutToOne) {
403 |
404 | for (int s = 0; s < getSpecies().size(); s++)
405 | {
406 |
407 | Species specie = getSpecies().get(s);
408 |
409 | //this will make better fitness get closer to zero
410 | Collections.sort( specie.getGenomes(),new Comparator(){
411 |
412 | @Override
413 | public int compare(Genome g1, Genome g2)
414 | {
415 | return g1.getFitness() < g2.getFitness() ? 1 :
416 | (g1.getFitness() == g2.getFitness() ? 0 : -1);
417 | }
418 |
419 |
420 | //If your Comparator's compare(T o1, T o2) return a negative when o1 is greater than o2, you get descending order
421 |
422 | // table.sort(species.genomes, function (a,b)
423 | // return (a.fitness > b.fitness)
424 | // end)
425 | });
426 |
427 |
428 | int remaining = (int) FastMath.ceil(specie.getGenomes().size()/2) ;
429 |
430 | if (cutToOne )
431 | {
432 | remaining = 1;
433 | }
434 |
435 | while (specie.getGenomes().size() > remaining)
436 | {
437 |
438 | System.out.println("culling " + specie.getGenomes().get( specie.getGenomes().size()-1 ) + " with " + specie.getGenomes().get( specie.getGenomes().size()-1 ).getFitness() + " fitness " );
439 |
440 | specie.getGenomes().remove( specie.getGenomes().size()-1 ); //keep removing the least fit genome (hopefully this isnt the least fit!)
441 |
442 | }
443 |
444 |
445 | }
446 |
447 | }
448 |
449 |
450 |
451 | public void newGeneration() {
452 |
453 | cullSpecies(false); // Cull the bottom half of each species
454 | rankGlobally();
455 | removeStaleSpecies();
456 | rankGlobally();
457 |
458 | for (int s = 0 ; s < getSpecies().size(); s++)
459 | {
460 | Species specie = getSpecies().get(s);
461 | calculateAverageFitness(specie) ;
462 | }
463 |
464 | //remove empty species and those with poor average fitness
465 | removeWeakSpecies();
466 | int sum = totalAverageFitness();
467 |
468 | List children = new ArrayList();
469 |
470 |
471 |
472 | for (int s = 0 ; s < getSpecies().size(); s++)
473 | {
474 | Species specie = getSpecies().get(s);
475 |
476 | if( specie.getGenomes().size() > 0 )
477 | {
478 | int breed = (int) (FastMath.floor(specie.averageFitness / sum * Population) - 1) ; //more fitness = more breeding
479 | for (int i=0; i < breed; i++) {
480 | children.add( breedChild(specie) );
481 | }
482 | }
483 |
484 | }
485 |
486 | cullSpecies(true); //-- Cull all but the top member of each species
487 |
488 |
489 | //make new children from the previous best child
490 | while (children.size() + getSpecies().size() < Population)
491 | {
492 | //sometimes species size is zero !
493 |
494 | if( getSpecies().size() > 0 )
495 | {
496 |
497 | int randIndex = rand.nextInt( getSpecies().size() ) ;
498 | Species specie = getSpecies().get(randIndex);
499 |
500 | if( specie.getGenomes().size() > 0 )
501 | {
502 | children.add(breedChild(specie));
503 | }
504 |
505 | }
506 | }
507 |
508 | //place those children into species that are most similar to them.. or maybe they are new unique species
509 | for (int c=0; c < children.size() ; c++)
510 | {
511 | Genome child = children.get(c);
512 | addToSpecies(child);
513 | }
514 |
515 | generation++;
516 |
517 |
518 | // writeFile("backup." .. pool.generation .. "." .. forms.gettext(saveLoadFile))
519 |
520 | }
521 |
522 | private void rankGlobally() {
523 |
524 | List genomes = new ArrayList();
525 | // local global = {}
526 |
527 | for (int s = 0; s < getSpecies().size() ;s++ )
528 | {
529 | Species specie = getSpecies().get(s);
530 | for (int g = 0; g < specie.getGenomes().size();g++)
531 | {
532 | genomes.add( specie.getGenomes().get(g));
533 |
534 | }
535 |
536 | }
537 |
538 |
539 |
540 | Collections.sort(genomes,new Comparator(){
541 |
542 | @Override
543 | public int compare(Genome g1, Genome g2)
544 | {
545 | return g1.getFitness() < g2.getFitness() ? 1 :
546 | (g1.getFitness() == g2.getFitness() ? 0 : -1);
547 | }
548 |
549 | //ranks in descending order so most fit is at 0
550 |
551 | //table.sort(global, function (a,b)
552 | // return (a.fitness < b.fitness)
553 | // end)
554 | });
555 |
556 |
557 |
558 |
559 |
560 | for (int g=0;g< genomes.size(); g++)
561 | {
562 | genomes.get(g).setGlobalRank(g);
563 | }
564 |
565 | }
566 |
567 | public void addToSpecies(Genome child)
568 | {
569 | boolean foundSpecies = false;
570 |
571 | for (int s=0; s < getSpecies().size() ; s++ ){
572 |
573 | Species specie = getSpecies().get(s);
574 |
575 | if (!foundSpecies && !specie.getGenomes().isEmpty() && child.sameSpeciesAs(specie.getGenomes().get(0)) )
576 | {
577 | specie.getGenomes().add(child);
578 | foundSpecies = true;
579 | }
580 |
581 | }
582 |
583 | if (!foundSpecies )
584 | {
585 | Species childSpecies = new Species();
586 |
587 | childSpecies.getGenomes().add(child);
588 | getSpecies().add(childSpecies);
589 |
590 | }
591 | }
592 |
593 |
594 |
595 |
596 |
597 | private void calculateAverageFitness(Species specie)
598 | {
599 | int total = 0;
600 |
601 |
602 | for (int g=0; g < specie.getGenomes().size(); g++)
603 | {
604 | Genome genome = specie.getGenomes().get(g);
605 | total = total + genome.getGlobalRank();
606 | }
607 |
608 | if(specie.getGenomes().size() > 0 )
609 | {
610 | specie.setAverageFitness( total / specie.getGenomes().size() );
611 | }
612 | }
613 |
614 | private int totalAverageFitness()
615 | {
616 | int total = 0;
617 |
618 | for (int s = 0;s < getSpecies().size(); s++)
619 | {
620 | Species specie = getSpecies().get(s);
621 | total = total + specie.getAverageFitness();
622 | }
623 |
624 | return total ;
625 | }
626 |
627 |
628 |
629 |
630 | private Genome breedChild(Species specie){
631 | Genome child = new Genome();
632 |
633 |
634 | if ( rand.nextFloat() < child.CrossoverChance ) //should it be the childs crossoverchance or a static one?
635 | {
636 | int index1 = rand.nextInt(specie.getGenomes().size() ) ;
637 | Genome g1 = specie.getGenomes().get(index1);
638 | int index2 = rand.nextInt(specie.getGenomes().size() ) ;
639 | Genome g2 = specie.getGenomes().get(index2);
640 | child = crossover(g1, g2);
641 | }else{
642 | int index = rand.nextInt(specie.getGenomes().size() ) ;
643 | Genome g = specie.getGenomes().get(index);
644 | child = Genome.copy(g);
645 | }
646 |
647 | mutate(child);
648 |
649 | return child;
650 | }
651 |
652 |
653 |
654 |
655 |
656 |
657 | private Genome crossover(Genome g1, Genome g2) //splice two genomes together
658 | {
659 | // -- Make sure g1 is the higher fitness genome
660 | if (g2.fitness > g1.fitness )
661 | {
662 | Genome tempg = g1;
663 | g1 = g2;
664 | g2 = tempg;
665 | }
666 |
667 | Genome child = new Genome();
668 |
669 | HashMap innovations2 = new HashMap();
670 |
671 |
672 | for (int i=0; i < g2.getGenes().size(); i++ )
673 | {
674 | Gene gene = g2.getGenes().get(i);
675 |
676 | innovations2.put(gene.getInnovation(), gene);
677 |
678 | }
679 |
680 | for (int i=0; i < g1.getGenes().size(); i++)
681 | {
682 | Gene gene1 = g1.getGenes().get(i);
683 | Gene gene2 = innovations2.get(gene1.getInnovation());
684 |
685 | if (gene2 != null && rand.nextBoolean() && gene2.isEnabled())
686 | {
687 | child.getGenes().add(gene2.copy());
688 | }else{
689 | child.getGenes().add(gene1.copy());
690 | }
691 | }
692 |
693 | //set to the max
694 | if( g1.maxneuron > g2.maxneuron)
695 | {
696 | child.maxneuron = g1.maxneuron;
697 | }else{
698 | child.maxneuron = g2.maxneuron;
699 | }
700 |
701 |
702 | for(String key : g1.mutationRates.keySet()) //give the child the mutations of g1
703 | {
704 | child.mutationRates.put(key, g1.mutationRates.get(key));
705 |
706 | }
707 |
708 | return child;
709 | }
710 |
711 |
712 |
713 |
714 |
715 | private void removeStaleSpecies()
716 | {
717 | // local survived = {}
718 |
719 | for (int s = 0; s < getSpecies().size(); s++)
720 | {
721 | Species specie = getSpecies().get(s);
722 |
723 |
724 |
725 | Collections.sort(specie.getGenomes() ,new Comparator(){
726 |
727 | @Override
728 | public int compare(Genome g1, Genome g2)
729 | {
730 | return g1.getFitness() < g2.getFitness() ? 1 :
731 | (g1.getFitness() == g2.getFitness() ? 0 : -1);
732 | }
733 |
734 | });
735 |
736 |
737 |
738 |
739 | if (!specie.getGenomes().isEmpty() && specie.getGenomes().get(0).fitness > specie.topFitness )
740 | {
741 | specie.topFitness = specie.getGenomes().get(0).fitness ;
742 | specie.staleness = 0 ;
743 | }else{
744 | specie.staleness = specie.staleness + 1 ;
745 | }
746 |
747 | if (specie.staleness > StaleSpecies && specie.topFitness < maxFitness)
748 | {
749 | getSpecies().remove(specie);
750 |
751 | }
752 | }
753 |
754 |
755 | }
756 |
757 | private void removeWeakSpecies()
758 | {
759 | //List survivalists = new ArrayList();
760 |
761 | float sum = totalAverageFitness();
762 |
763 |
764 | if(sum > 0)
765 | {
766 | for (int s = 0; s < getSpecies().size();s++)
767 | {
768 | Species specie = getSpecies().get(s);
769 |
770 | // float breed = FastMath.floor( (specie.averageFitness / sum) * Population );
771 |
772 | float breed = (specie.averageFitness / sum) * Population ;
773 |
774 | if (breed < 1f )
775 | {
776 | System.out.println("removing weak breed " + breed + " " + specie);
777 | species.remove(specie);
778 | }else{
779 | System.out.println("keeping strong breed " + breed + " " + specie);
780 |
781 | }
782 | }
783 |
784 | }
785 | }
786 |
787 |
788 | public int getPopulation() {
789 | return Population;
790 | }
791 | /*
792 | public JSONObject getAsJson() {
793 |
794 | JSONObject obj=new JSONObject();
795 | obj.put("generation",generation);
796 | obj.put("maxFitness",maxFitness);
797 | obj.put("species", species );
798 |
799 |
800 | return obj;
801 | }
802 |
803 | public void loadFromJSON(JSONObject json)
804 | {
805 | this.generation = (int) json.get("generation");
806 | this.maxFitness = (int) json.get("maxFitness");
807 | this.species = (List) json.get("species");
808 | }
809 | */
810 |
811 |
812 | }
813 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/com/starflask/JavaNESBrain/evolution/Genome.java:
--------------------------------------------------------------------------------
1 | package com.starflask.JavaNESBrain.evolution;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Collection;
5 | import java.util.HashMap;
6 | import java.util.List;
7 |
8 | import com.starflask.JavaNESBrain.utils.FastMath;
9 |
10 | public class Genome {
11 |
12 | List genes = new ArrayList();
13 |
14 | float MutateConnectionsChance = 0.25f;
15 | float PerturbChance = 0.90f;
16 | float CrossoverChance = 0.75f;
17 | float LinkMutationChance = 2.0f;
18 | float NodeMutationChance = 0.50f;
19 | float BiasMutationChance = 0.40f;
20 | float StepSize = 0.1f;
21 | float DisableMutationChance = 0.4f;
22 | float EnableMutationChance = 0.2f;
23 |
24 | int fitness;
25 | int adjustedFitness;
26 | NeuralNetwork network = new NeuralNetwork();
27 |
28 |
29 | int maxneuron = 0;
30 | int globalRank = 0;
31 |
32 |
33 | HashMap mutationRates = new HashMap();
34 |
35 | public Genome()
36 | {
37 |
38 | mutationRates.put("connections", MutateConnectionsChance);
39 | mutationRates.put("link", LinkMutationChance);
40 | mutationRates.put("bias", BiasMutationChance);
41 | mutationRates.put("node", NodeMutationChance);
42 | mutationRates.put("enable", EnableMutationChance);
43 | mutationRates.put("disable", DisableMutationChance);
44 | mutationRates.put("step", StepSize);
45 |
46 |
47 | }
48 |
49 | public static Genome copy(Genome otherGenome)
50 | {
51 | Genome genome2 = new Genome();
52 |
53 | //deep copy all genes
54 | for(Gene otherGene : otherGenome.genes)
55 | {
56 | genome2.genes.add(otherGene.copy() );
57 | }
58 |
59 |
60 | genome2.maxneuron = otherGenome.maxneuron;
61 | genome2.mutationRates.put("connections", otherGenome.mutationRates.get("connections"));
62 | genome2.mutationRates.put("link", otherGenome.mutationRates.get("link"));
63 | genome2.mutationRates.put("bias", otherGenome.mutationRates.get("bias"));
64 | genome2.mutationRates.put("node", otherGenome.mutationRates.get("node"));
65 | genome2.mutationRates.put("enable", otherGenome.mutationRates.get("enable"));
66 | genome2.mutationRates.put("disable", otherGenome.mutationRates.get("disable"));
67 |
68 | return genome2 ;
69 | }
70 |
71 | public void setFitness(int newFitness) {
72 | this.fitness=newFitness;
73 |
74 | }
75 |
76 | public int getFitness() {
77 |
78 | return fitness;
79 | }
80 |
81 | public NeuralNetwork getNetwork() {
82 | return network;
83 | }
84 |
85 | public void setNetwork(NeuralNetwork network) {
86 | this.network = network;
87 | }
88 |
89 | public List getGenes() {
90 |
91 | return genes;
92 | }
93 |
94 | public void setGlobalRank(int rank) {
95 | globalRank=rank;
96 | }
97 |
98 | public int getGlobalRank() {
99 |
100 | return globalRank;
101 | }
102 |
103 |
104 | float DeltaDisjoint = 2.0f;
105 | float DeltaWeights = 0.4f;
106 | float DeltaThreshold = 1.0f;
107 |
108 |
109 | public boolean sameSpeciesAs(Genome otherGenome)
110 | {
111 | float dd = DeltaDisjoint*disjoint(this.genes, otherGenome.genes);
112 | float dw = DeltaWeights*weights(this.genes, otherGenome.genes);
113 |
114 | return dd + dw < DeltaThreshold ;
115 | }
116 |
117 |
118 | private float disjoint(List genes1, List genes2)
119 | {
120 |
121 | HashMap innovativeGene1 = new HashMap();
122 |
123 | for (int i = 1; i < genes1.size(); i++ )
124 | {
125 | Gene gene = genes1.get(i);
126 | innovativeGene1.put(gene.innovation, true) ;
127 | }
128 |
129 | HashMap innovativeGene2 = new HashMap();
130 | for (int i = 1; i < genes2.size(); i++ )
131 | {
132 | Gene gene = genes2.get(i);
133 | innovativeGene2.put(gene.innovation, true) ;
134 | }
135 |
136 | float disjointGenes = 0 ;
137 |
138 | for (int i = 1; i < genes1.size(); i++ )
139 | {
140 | Gene gene = genes1.get(i);
141 |
142 | if (!innovativeGene2.containsKey( gene.innovation ) || ! innovativeGene2.get(gene.innovation) )
143 | {
144 | disjointGenes++;
145 | }
146 | }
147 |
148 | for (int i = 1; i < genes2.size(); i++ )
149 | {
150 | Gene gene = genes2.get(i);
151 |
152 | if (!innovativeGene1.containsKey( gene.innovation ) || ! innovativeGene1.get(gene.innovation) )
153 | disjointGenes++;
154 |
155 |
156 | }
157 |
158 |
159 |
160 | int max = genes1.size();
161 | if(genes2.size() > genes1.size())
162 | {
163 | max = genes2.size();
164 | }
165 |
166 |
167 | return disjointGenes / max ;
168 | }
169 |
170 | private float weights( List genes1, List genes2 )
171 | {
172 |
173 |
174 | HashMap innovativeGene2 = new HashMap();
175 | for (int i = 1; i < genes2.size(); i++)
176 | {
177 | Gene gene = genes2.get(i);
178 |
179 | innovativeGene2.put(gene.innovation, gene);
180 |
181 | }
182 |
183 | float sum = 0;
184 | float coincident = 0;
185 |
186 | for (int i = 1; i < genes1.size(); i++)
187 | {
188 | Gene gene = genes1.get(i);
189 |
190 | if (innovativeGene2.get(gene.innovation) != null )
191 | {
192 | Gene gene2 = innovativeGene2.get(gene.innovation);
193 | sum = sum + FastMath.abs(gene.getWeight() - gene2.getWeight());
194 | coincident++;
195 | }
196 | }
197 |
198 | return sum / coincident ;
199 | }
200 |
201 | @Override
202 | public String toString()
203 | {
204 | return "GENOME"+this.hashCode();
205 | }
206 |
207 | }
208 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/com/starflask/JavaNESBrain/evolution/NeuralNetwork.java:
--------------------------------------------------------------------------------
1 | package com.starflask.JavaNESBrain.evolution;
2 |
3 | import java.util.ArrayList;
4 | import java.util.HashMap;
5 | import java.util.List;
6 |
7 | public class NeuralNetwork {
8 | /*List neurons = new ArrayList();
9 |
10 | public List getNeurons() {
11 | return neurons;
12 | }
13 | */
14 |
15 | HashMap neurons = new HashMap();
16 |
17 | public HashMap getNeurons() {
18 | return neurons;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/com/starflask/JavaNESBrain/evolution/Neuron.java:
--------------------------------------------------------------------------------
1 | package com.starflask.JavaNESBrain.evolution;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 |
7 | public class Neuron {
8 |
9 | List incoming = new ArrayList(); //incoming gene list
10 | float value;
11 |
12 |
13 |
14 | public Neuron()
15 | {
16 |
17 | }
18 |
19 | public List getIncomingGeneList() {
20 |
21 | return incoming;
22 | }
23 |
24 | //NOBODY DOES THIS !
25 | public void setValue(float val) {
26 | value = val;
27 |
28 | }
29 |
30 | public float getValue() {
31 | return value;
32 | }
33 |
34 |
35 |
36 |
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/com/starflask/JavaNESBrain/evolution/Species.java:
--------------------------------------------------------------------------------
1 | package com.starflask.JavaNESBrain.evolution;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Collection;
5 | import java.util.List;
6 |
7 | public class Species {
8 |
9 | int topFitness;
10 | int staleness;
11 |
12 | List genomes = new ArrayList();
13 |
14 |
15 | public Species()
16 | {
17 |
18 | }
19 |
20 |
21 | public List getGenomes() {
22 |
23 | return genomes;
24 | }
25 |
26 |
27 | int averageFitness; //does this have to be buffered and stored?
28 |
29 | public int getAverageFitness() {
30 |
31 | return averageFitness;
32 | }
33 |
34 |
35 | public void setAverageFitness(int f) {
36 | averageFitness = f;
37 | }
38 |
39 | @Override
40 | public String toString()
41 | {
42 | return "SPECIES"+this.hashCode() + "(g" + genomes.size() +")[f"+averageFitness+"]";
43 | }
44 |
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/com/starflask/JavaNESBrain/utils/Vector2Int.java:
--------------------------------------------------------------------------------
1 | package com.starflask.JavaNESBrain.utils;
2 |
3 | public class Vector2Int {
4 | private int x;
5 | private int y;
6 |
7 | public Vector2Int(int x, int y) {
8 | this.x=x;
9 | this.y=y;
10 | }
11 |
12 | public int getX() {
13 | return x;
14 | }
15 | public void setX(int x) {
16 | this.x = x;
17 | }
18 | public int getY() {
19 | return y;
20 | }
21 | public void setY(int y) {
22 | this.y = y;
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/com/starflask/JavaNESBrain/utils/Vector2f.java:
--------------------------------------------------------------------------------
1 | package com.starflask.JavaNESBrain.utils;
2 |
3 |
4 |
5 | import java.io.Externalizable;
6 | import java.io.IOException;
7 | import java.io.ObjectInput;
8 | import java.io.ObjectOutput;
9 | import java.util.logging.Logger;
10 |
11 | /**
12 | * Vector2f
defines a Vector for a two float value vector.
13 | *
14 | * @author Mark Powell
15 | * @author Joshua Slack
16 | */
17 | public final class Vector2f implements Cloneable {
18 | private static final Logger logger = Logger.getLogger(Vector2f.class.getName());
19 |
20 | private static final long serialVersionUID = 1L;
21 |
22 | public static final Vector2f ZERO = new Vector2f(0f, 0f);
23 | public static final Vector2f UNIT_XY = new Vector2f(1f, 1f);
24 |
25 | /**
26 | * the x value of the vector.
27 | */
28 | public float x;
29 | /**
30 | * the y value of the vector.
31 | */
32 | public float y;
33 |
34 | /**
35 | * Creates a Vector2f with the given initial x and y values.
36 | *
37 | * @param x
38 | * The x value of this Vector2f.
39 | * @param y
40 | * The y value of this Vector2f.
41 | */
42 | public Vector2f(float x, float y) {
43 | this.x = x;
44 | this.y = y;
45 | }
46 |
47 | /**
48 | * Creates a Vector2f with x and y set to 0. Equivalent to Vector2f(0,0).
49 | */
50 | public Vector2f() {
51 | x = y = 0;
52 | }
53 |
54 | /**
55 | * Creates a new Vector2f that contains the passed vector's information
56 | *
57 | * @param vector2f
58 | * The vector to copy
59 | */
60 | public Vector2f(Vector2f vector2f) {
61 | this.x = vector2f.x;
62 | this.y = vector2f.y;
63 | }
64 |
65 | /**
66 | * set the x and y values of the vector
67 | *
68 | * @param x
69 | * the x value of the vector.
70 | * @param y
71 | * the y value of the vector.
72 | * @return this vector
73 | */
74 | public Vector2f set(float x, float y) {
75 | this.x = x;
76 | this.y = y;
77 | return this;
78 | }
79 |
80 | /**
81 | * set the x and y values of the vector from another vector
82 | *
83 | * @param vec
84 | * the vector to copy from
85 | * @return this vector
86 | */
87 | public Vector2f set(Vector2f vec) {
88 | this.x = vec.x;
89 | this.y = vec.y;
90 | return this;
91 | }
92 |
93 | /**
94 | * add
adds a provided vector to this vector creating a
95 | * resultant vector which is returned. If the provided vector is null, null
96 | * is returned.
97 | *
98 | * @param vec
99 | * the vector to add to this.
100 | * @return the resultant vector.
101 | */
102 | public Vector2f add(Vector2f vec) {
103 | if (null == vec) {
104 | logger.warning("Provided vector is null, null returned.");
105 | return null;
106 | }
107 | return new Vector2f(x + vec.x, y + vec.y);
108 | }
109 |
110 | /**
111 | * addLocal
adds a provided vector to this vector internally,
112 | * and returns a handle to this vector for easy chaining of calls. If the
113 | * provided vector is null, null is returned.
114 | *
115 | * @param vec
116 | * the vector to add to this vector.
117 | * @return this
118 | */
119 | public Vector2f addLocal(Vector2f vec) {
120 | if (null == vec) {
121 | logger.warning("Provided vector is null, null returned.");
122 | return null;
123 | }
124 | x += vec.x;
125 | y += vec.y;
126 | return this;
127 | }
128 |
129 | /**
130 | * addLocal
adds the provided values to this vector
131 | * internally, and returns a handle to this vector for easy chaining of
132 | * calls.
133 | *
134 | * @param addX
135 | * value to add to x
136 | * @param addY
137 | * value to add to y
138 | * @return this
139 | */
140 | public Vector2f addLocal(float addX, float addY) {
141 | x += addX;
142 | y += addY;
143 | return this;
144 | }
145 |
146 | /**
147 | * add
adds this vector by vec
and stores the
148 | * result in result
.
149 | *
150 | * @param vec
151 | * The vector to add.
152 | * @param result
153 | * The vector to store the result in.
154 | * @return The result vector, after adding.
155 | */
156 | public Vector2f add(Vector2f vec, Vector2f result) {
157 | if (null == vec) {
158 | logger.warning("Provided vector is null, null returned.");
159 | return null;
160 | }
161 | if (result == null)
162 | result = new Vector2f();
163 | result.x = x + vec.x;
164 | result.y = y + vec.y;
165 | return result;
166 | }
167 |
168 | /**
169 | * dot
calculates the dot product of this vector with a
170 | * provided vector. If the provided vector is null, 0 is returned.
171 | *
172 | * @param vec
173 | * the vector to dot with this vector.
174 | * @return the resultant dot product of this vector and a given vector.
175 | */
176 | public float dot(Vector2f vec) {
177 | if (null == vec) {
178 | logger.warning("Provided vector is null, 0 returned.");
179 | return 0;
180 | }
181 | return x * vec.x + y * vec.y;
182 | }
183 |
184 | /**
185 | * cross
calculates the cross product of this vector with a
186 | * parameter vector v.
187 | *
188 | * @param v
189 | * the vector to take the cross product of with this.
190 | * @return the cross product vector.
191 | */
192 | public Vector3f cross(Vector2f v) {
193 | return new Vector3f(0, 0, determinant(v));
194 | }
195 |
196 | public float determinant(Vector2f v) {
197 | return (x * v.y) - (y * v.x);
198 | }
199 |
200 | /**
201 | * Sets this vector to the interpolation by changeAmnt from this to the
202 | * finalVec this=(1-changeAmnt)*this + changeAmnt * finalVec
203 | *
204 | * @param finalVec
205 | * The final vector to interpolate towards
206 | * @param changeAmnt
207 | * An amount between 0.0 - 1.0 representing a percentage change
208 | * from this towards finalVec
209 | */
210 | public Vector2f interpolate(Vector2f finalVec, float changeAmnt) {
211 | this.x = (1 - changeAmnt) * this.x + changeAmnt * finalVec.x;
212 | this.y = (1 - changeAmnt) * this.y + changeAmnt * finalVec.y;
213 | return this;
214 | }
215 |
216 | /**
217 | * Sets this vector to the interpolation by changeAmnt from beginVec to
218 | * finalVec this=(1-changeAmnt)*beginVec + changeAmnt * finalVec
219 | *
220 | * @param beginVec
221 | * The begining vector (delta=0)
222 | * @param finalVec
223 | * The final vector to interpolate towards (delta=1)
224 | * @param changeAmnt
225 | * An amount between 0.0 - 1.0 representing a precentage change
226 | * from beginVec towards finalVec
227 | */
228 | public Vector2f interpolate(Vector2f beginVec, Vector2f finalVec,
229 | float changeAmnt) {
230 | this.x = (1 - changeAmnt) * beginVec.x + changeAmnt * finalVec.x;
231 | this.y = (1 - changeAmnt) * beginVec.y + changeAmnt * finalVec.y;
232 | return this;
233 | }
234 |
235 | /**
236 | * Check a vector... if it is null or its floats are NaN or infinite, return
237 | * false. Else return true.
238 | *
239 | * @param vector
240 | * the vector to check
241 | * @return true or false as stated above.
242 | */
243 | public static boolean isValidVector(Vector2f vector) {
244 | if (vector == null) return false;
245 | if (Float.isNaN(vector.x) ||
246 | Float.isNaN(vector.y)) return false;
247 | if (Float.isInfinite(vector.x) ||
248 | Float.isInfinite(vector.y)) return false;
249 | return true;
250 | }
251 |
252 | /**
253 | * length
calculates the magnitude of this vector.
254 | *
255 | * @return the length or magnitude of the vector.
256 | */
257 | public float length() {
258 | return FastMath.sqrt(lengthSquared());
259 | }
260 |
261 | /**
262 | * lengthSquared
calculates the squared value of the
263 | * magnitude of the vector.
264 | *
265 | * @return the magnitude squared of the vector.
266 | */
267 | public float lengthSquared() {
268 | return x * x + y * y;
269 | }
270 |
271 | /**
272 | * distanceSquared
calculates the distance squared between
273 | * this vector and vector v.
274 | *
275 | * @param v the second vector to determine the distance squared.
276 | * @return the distance squared between the two vectors.
277 | */
278 | public float distanceSquared(Vector2f v) {
279 | double dx = x - v.x;
280 | double dy = y - v.y;
281 | return (float) (dx * dx + dy * dy);
282 | }
283 |
284 | /**
285 | * distanceSquared
calculates the distance squared between
286 | * this vector and vector v.
287 | *
288 | * @param v the second vector to determine the distance squared.
289 | * @return the distance squared between the two vectors.
290 | */
291 | public float distanceSquared(float otherX, float otherY) {
292 | double dx = x - otherX;
293 | double dy = y - otherY;
294 | return (float) (dx * dx + dy * dy);
295 | }
296 |
297 | /**
298 | * distance
calculates the distance between this vector and
299 | * vector v.
300 | *
301 | * @param v the second vector to determine the distance.
302 | * @return the distance between the two vectors.
303 | */
304 | public float distance(Vector2f v) {
305 | return FastMath.sqrt(distanceSquared(v));
306 | }
307 |
308 | /**
309 | * mult
multiplies this vector by a scalar. The resultant
310 | * vector is returned.
311 | *
312 | * @param scalar
313 | * the value to multiply this vector by.
314 | * @return the new vector.
315 | */
316 | public Vector2f mult(float scalar) {
317 | return new Vector2f(x * scalar, y * scalar);
318 | }
319 |
320 | /**
321 | * multLocal
multiplies this vector by a scalar internally,
322 | * and returns a handle to this vector for easy chaining of calls.
323 | *
324 | * @param scalar
325 | * the value to multiply this vector by.
326 | * @return this
327 | */
328 | public Vector2f multLocal(float scalar) {
329 | x *= scalar;
330 | y *= scalar;
331 | return this;
332 | }
333 |
334 | /**
335 | * multLocal
multiplies a provided vector to this vector
336 | * internally, and returns a handle to this vector for easy chaining of
337 | * calls. If the provided vector is null, null is returned.
338 | *
339 | * @param vec
340 | * the vector to mult to this vector.
341 | * @return this
342 | */
343 | public Vector2f multLocal(Vector2f vec) {
344 | if (null == vec) {
345 | logger.warning("Provided vector is null, null returned.");
346 | return null;
347 | }
348 | x *= vec.x;
349 | y *= vec.y;
350 | return this;
351 | }
352 |
353 | /**
354 | * Multiplies this Vector2f's x and y by the scalar and stores the result in
355 | * product. The result is returned for chaining. Similar to
356 | * product=this*scalar;
357 | *
358 | * @param scalar
359 | * The scalar to multiply by.
360 | * @param product
361 | * The vector2f to store the result in.
362 | * @return product, after multiplication.
363 | */
364 | public Vector2f mult(float scalar, Vector2f product) {
365 | if (null == product) {
366 | product = new Vector2f();
367 | }
368 |
369 | product.x = x * scalar;
370 | product.y = y * scalar;
371 | return product;
372 | }
373 |
374 | /**
375 | * divide
divides the values of this vector by a scalar and
376 | * returns the result. The values of this vector remain untouched.
377 | *
378 | * @param scalar
379 | * the value to divide this vectors attributes by.
380 | * @return the result Vector
.
381 | */
382 | public Vector2f divide(float scalar) {
383 | return new Vector2f(x / scalar, y / scalar);
384 | }
385 |
386 | /**
387 | * divideLocal
divides this vector by a scalar internally,
388 | * and returns a handle to this vector for easy chaining of calls. Dividing
389 | * by zero will result in an exception.
390 | *
391 | * @param scalar
392 | * the value to divides this vector by.
393 | * @return this
394 | */
395 | public Vector2f divideLocal(float scalar) {
396 | x /= scalar;
397 | y /= scalar;
398 | return this;
399 | }
400 |
401 | /**
402 | * negate
returns the negative of this vector. All values are
403 | * negated and set to a new vector.
404 | *
405 | * @return the negated vector.
406 | */
407 | public Vector2f negate() {
408 | return new Vector2f(-x, -y);
409 | }
410 |
411 | /**
412 | * negateLocal
negates the internal values of this vector.
413 | *
414 | * @return this.
415 | */
416 | public Vector2f negateLocal() {
417 | x = -x;
418 | y = -y;
419 | return this;
420 | }
421 |
422 | /**
423 | * subtract
subtracts the values of a given vector from those
424 | * of this vector creating a new vector object. If the provided vector is
425 | * null, an exception is thrown.
426 | *
427 | * @param vec
428 | * the vector to subtract from this vector.
429 | * @return the result vector.
430 | */
431 | public Vector2f subtract(Vector2f vec) {
432 | return subtract(vec, null);
433 | }
434 |
435 | /**
436 | * subtract
subtracts the values of a given vector from those
437 | * of this vector storing the result in the given vector object. If the
438 | * provided vector is null, an exception is thrown.
439 | *
440 | * @param vec
441 | * the vector to subtract from this vector.
442 | * @param store
443 | * the vector to store the result in. It is safe for this to be
444 | * the same as vec. If null, a new vector is created.
445 | * @return the result vector.
446 | */
447 | public Vector2f subtract(Vector2f vec, Vector2f store) {
448 | if (store == null)
449 | store = new Vector2f();
450 | store.x = x - vec.x;
451 | store.y = y - vec.y;
452 | return store;
453 | }
454 |
455 | /**
456 | * subtract
subtracts the given x,y values from those of this
457 | * vector creating a new vector object.
458 | *
459 | * @param valX
460 | * value to subtract from x
461 | * @param valY
462 | * value to subtract from y
463 | * @return this
464 | */
465 | public Vector2f subtract(float valX, float valY) {
466 | return new Vector2f(x - valX, y - valY);
467 | }
468 |
469 | /**
470 | * subtractLocal
subtracts a provided vector to this vector
471 | * internally, and returns a handle to this vector for easy chaining of
472 | * calls. If the provided vector is null, null is returned.
473 | *
474 | * @param vec
475 | * the vector to subtract
476 | * @return this
477 | */
478 | public Vector2f subtractLocal(Vector2f vec) {
479 | if (null == vec) {
480 | logger.warning("Provided vector is null, null returned.");
481 | return null;
482 | }
483 | x -= vec.x;
484 | y -= vec.y;
485 | return this;
486 | }
487 |
488 | /**
489 | * subtractLocal
subtracts the provided values from this
490 | * vector internally, and returns a handle to this vector for easy chaining
491 | * of calls.
492 | *
493 | * @param valX
494 | * value to subtract from x
495 | * @param valY
496 | * value to subtract from y
497 | * @return this
498 | */
499 | public Vector2f subtractLocal(float valX, float valY) {
500 | x -= valX;
501 | y -= valY;
502 | return this;
503 | }
504 |
505 | /**
506 | * normalize
returns the unit vector of this vector.
507 | *
508 | * @return unit vector of this vector.
509 | */
510 | public Vector2f normalize() {
511 | float length = length();
512 | if (length != 0) {
513 | return divide(length);
514 | }
515 |
516 | return divide(1);
517 | }
518 |
519 | /**
520 | * normalizeLocal
makes this vector into a unit vector of
521 | * itself.
522 | *
523 | * @return this.
524 | */
525 | public Vector2f normalizeLocal() {
526 | float length = length();
527 | if (length != 0) {
528 | return divideLocal(length);
529 | }
530 |
531 | return divideLocal(1);
532 | }
533 |
534 | /**
535 | * smallestAngleBetween
returns (in radians) the minimum
536 | * angle between two vectors. It is assumed that both this vector and the
537 | * given vector are unit vectors (iow, normalized).
538 | *
539 | * @param otherVector
540 | * a unit vector to find the angle against
541 | * @return the angle in radians.
542 | */
543 | public float smallestAngleBetween(Vector2f otherVector) {
544 | float dotProduct = dot(otherVector);
545 | float angle = FastMath.acos(dotProduct);
546 | return angle;
547 | }
548 |
549 | /**
550 | * angleBetween
returns (in radians) the angle required to
551 | * rotate a ray represented by this vector to lie colinear to a ray
552 | * described by the given vector. It is assumed that both this vector and
553 | * the given vector are unit vectors (iow, normalized).
554 | *
555 | * @param otherVector
556 | * the "destination" unit vector
557 | * @return the angle in radians.
558 | */
559 | public float angleBetween(Vector2f otherVector) {
560 | float angle = FastMath.atan2(otherVector.y, otherVector.x)
561 | - FastMath.atan2(y, x);
562 | return angle;
563 | }
564 |
565 | public float getX() {
566 | return x;
567 | }
568 |
569 | public Vector2f setX(float x) {
570 | this.x = x;
571 | return this;
572 | }
573 |
574 | public float getY() {
575 | return y;
576 | }
577 |
578 | public Vector2f setY(float y) {
579 | this.y = y;
580 | return this;
581 | }
582 | /**
583 | * getAngle
returns (in radians) the angle represented by
584 | * this Vector2f as expressed by a conversion from rectangular coordinates (x
, y
)
585 | * to polar coordinates (r, theta).
586 | *
587 | * @return the angle in radians. [-pi, pi)
588 | */
589 | public float getAngle() {
590 | return FastMath.atan2(y, x);
591 | }
592 |
593 | /**
594 | * zero
resets this vector's data to zero internally.
595 | */
596 | public Vector2f zero() {
597 | x = y = 0;
598 | return this;
599 | }
600 |
601 | /**
602 | * hashCode
returns a unique code for this vector object
603 | * based on it's values. If two vectors are logically equivalent, they will
604 | * return the same hash code value.
605 | *
606 | * @return the hash code value of this vector.
607 | */
608 | public int hashCode() {
609 | int hash = 37;
610 | hash += 37 * hash + Float.floatToIntBits(x);
611 | hash += 37 * hash + Float.floatToIntBits(y);
612 | return hash;
613 | }
614 |
615 | @Override
616 | public Vector2f clone() {
617 | try {
618 | return (Vector2f) super.clone();
619 | } catch (CloneNotSupportedException e) {
620 | throw new AssertionError(); // can not happen
621 | }
622 | }
623 |
624 | /**
625 | * Saves this Vector2f into the given float[] object.
626 | *
627 | * @param floats
628 | * The float[] to take this Vector2f. If null, a new float[2] is
629 | * created.
630 | * @return The array, with X, Y float values in that order
631 | */
632 | public float[] toArray(float[] floats) {
633 | if (floats == null) {
634 | floats = new float[2];
635 | }
636 | floats[0] = x;
637 | floats[1] = y;
638 | return floats;
639 | }
640 |
641 | /**
642 | * are these two vectors the same? they are is they both have the same x and
643 | * y values.
644 | *
645 | * @param o
646 | * the object to compare for equality
647 | * @return true if they are equal
648 | */
649 | public boolean equals(Object o) {
650 | if (!(o instanceof Vector2f)) {
651 | return false;
652 | }
653 |
654 | if (this == o) {
655 | return true;
656 | }
657 |
658 | Vector2f comp = (Vector2f) o;
659 | if (Float.compare(x, comp.x) != 0)
660 | return false;
661 | if (Float.compare(y, comp.y) != 0)
662 | return false;
663 | return true;
664 | }
665 |
666 | /**
667 | * toString
returns the string representation of this vector
668 | * object. The format of the string is such: com.jme.math.Vector2f
669 | * [X=XX.XXXX, Y=YY.YYYY]
670 | *
671 | * @return the string representation of this vector.
672 | */
673 | public String toString() {
674 | return "(" + x + ", " + y + ")";
675 | }
676 |
677 | /**
678 | * Used with serialization. Not to be called manually.
679 | *
680 | * @param in
681 | * ObjectInput
682 | * @throws IOException
683 | * @throws ClassNotFoundException
684 | * @see java.io.Externalizable
685 | */
686 | public void readExternal(ObjectInput in) throws IOException,
687 | ClassNotFoundException {
688 | x = in.readFloat();
689 | y = in.readFloat();
690 | }
691 |
692 | /**
693 | * Used with serialization. Not to be called manually.
694 | *
695 | * @param out
696 | * ObjectOutput
697 | * @throws IOException
698 | * @see java.io.Externalizable
699 | */
700 | public void writeExternal(ObjectOutput out) throws IOException {
701 | out.writeFloat(x);
702 | out.writeFloat(y);
703 | }
704 |
705 |
706 | public void rotateAroundOrigin(float angle, boolean cw) {
707 | if (cw)
708 | angle = -angle;
709 | float newX = FastMath.cos(angle) * x - FastMath.sin(angle) * y;
710 | float newY = FastMath.sin(angle) * x + FastMath.cos(angle) * y;
711 | x = newX;
712 | y = newY;
713 | }
714 | }
715 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ethereumdegen/JavaNESBrain/88a34d2573c2e3de3980b28729e7b340efbd1023/JavaNESBrain/src/icon.png
--------------------------------------------------------------------------------
/JavaNESBrain/src/jp/tanakh/bjne/nes/Apu.java:
--------------------------------------------------------------------------------
1 | package jp.tanakh.bjne.nes;
2 |
3 | import java.util.Arrays;
4 | import java.util.LinkedList;
5 | import java.util.Queue;
6 |
7 | import jp.tanakh.bjne.nes.Renderer.SoundInfo;
8 |
9 | public class Apu {
10 | public Apu(Nes n) {
11 | this.nes = n;
12 | reset();
13 | }
14 |
15 | public void reset() {
16 | ch = new ChState[4];
17 | ch[0] = new ChState();
18 | ch[1] = new ChState();
19 | ch[2] = new ChState();
20 | ch[3] = new ChState();
21 |
22 | sch = new ChState[4];
23 | sch[0] = new ChState();
24 | sch[1] = new ChState();
25 | sch[2] = new ChState();
26 | sch[3] = new ChState();
27 |
28 | dmc = new DmcState();
29 |
30 | ch[3].shiftRegister = 1;
31 | befClock = befSync = 0;
32 | }
33 |
34 | public byte read(short adr) {
35 | if (adr == 0x4015) {
36 | sync();
37 | return (byte) ((sch[0].length == 0 ? 0 : 1) | ((sch[1].length == 0 ? 0 : 1) << 1) | ((sch[2].length == 0 ? 0 : 1) << 2)
38 | | ((sch[3].length == 0 ? 0 : 1) << 3) | ((sdmc.enable ? 1 : 0) << 4) | ((sdmc.irq ? 1 : 0) << 7));
39 | }
40 | return (byte) 0xA0;
41 | }
42 |
43 | public void write(short adr, byte dat) {
44 | // delay writing data for generating sound
45 | writeQueue.add(new WriteDat(nes.getCpu().getMasterClock(), adr, dat));
46 | while (writeQueue.size() > 1000) {
47 | WriteDat wd = writeQueue.remove();
48 | doWrite(ch, dmc, wd.adr, wd.dat);
49 | }
50 | // process for status register
51 | sync();
52 | doWrite(sch, sdmc, adr, dat);
53 | }
54 |
55 | public void genAudio(SoundInfo info) {
56 | double cpuClock = nes.getCpu().getFrequency();
57 |
58 | long curClock = nes.getCpu().getMasterClock();
59 | int sample = info.sample;
60 |
61 | byte[] buf = info.buf;
62 | int span = info.ch * (info.bps / 8);
63 |
64 | double incClk = ((double) (curClock - befClock)) / sample; // executed
65 | double sampleClk = cpuClock / info.freq; // CPU clocks per sample
66 |
67 | Arrays.fill(buf, (byte) 0x00);
68 |
69 | if (nes.getMapper() != null) // external APU
70 | nes.getMapper().audio(info);
71 |
72 | for (int i = 0; i < sample; i++) {
73 | //long pos = (curClock - befClock) * i / sample + befClock;
74 | long pos = (long) (befClock + sampleClk * i);
75 | while (!writeQueue.isEmpty() && writeQueue.peek().clk <= pos) {
76 | WriteDat wd = writeQueue.remove();
77 | doWrite(ch, dmc, wd.adr, wd.dat);
78 | }
79 |
80 | double v = 0;
81 |
82 | for (int j = 0; j < 4; j++) {
83 | ChState cc = ch[j];
84 |
85 | boolean pause = false;
86 | if (!cc.enable)
87 | continue;
88 | if (cc.length == 0)
89 | pause = true;
90 |
91 | // length counter
92 | if (cc.lengthEnable) {
93 | double length_clk = cpuClock / 60.0;
94 | cc.lengthClk += incClk;
95 | while (cc.lengthClk > length_clk) {
96 | cc.lengthClk -= length_clk;
97 | if (cc.length > 0)
98 | cc.length--;
99 | }
100 | }
101 | // linear counter
102 | if (j == TRI) {
103 | if (cc.counterStart != 0)
104 | cc.linearCounter = cc.linearLatch;
105 | else {
106 | double linear_clk = cpuClock / 240.0;
107 | cc.linearClk += incClk;
108 | while (cc.linearClk > linear_clk) {
109 | cc.linearClk -= linear_clk;
110 | if (cc.linearCounter > 0)
111 | cc.linearCounter--;
112 | }
113 | }
114 | if (!cc.holdnote && cc.linearCounter != 0)
115 | cc.counterStart = 0;
116 |
117 | if (cc.linearCounter == 0)
118 | pause = true;
119 | }
120 |
121 | // envelope
122 | int vol = 16;
123 | if (j != TRI) {
124 | if (cc.envelopeEnable) {
125 | double decay_clk = cpuClock / (240.0 / (cc.envelopeRate + 1));
126 | cc.envelopeClk += incClk;
127 | while (cc.envelopeClk > decay_clk) {
128 | cc.envelopeClk -= decay_clk;
129 | if (cc.volume > 0)
130 | cc.volume--;
131 | else {
132 | if (!cc.lengthEnable) // loop
133 | cc.volume = 0xf;
134 | else
135 | cc.volume = 0;
136 | }
137 | }
138 | }
139 | vol = cc.volume;
140 | }
141 |
142 | // sweep
143 | if ((j == SQ1 || j == SQ2) && cc.sweepEnable && !cc.sweepPausing) {
144 | double sweep_clk = cpuClock / (120.0 / (cc.sweepRate + 1));
145 | cc.sweepClk += incClk;
146 | while (cc.sweepClk > sweep_clk) {
147 | cc.sweepClk -= sweep_clk;
148 | if (cc.sweepShift != 0 && cc.length != 0) {
149 | if (!cc.sweepMode) // increase
150 | cc.waveLength += cc.waveLength >> cc.sweepShift;
151 | else
152 | // decrease
153 | cc.waveLength += ~(cc.waveLength >> cc.sweepShift);
154 | if (cc.waveLength < 0x008)
155 | cc.sweepPausing = true;
156 | if ((cc.waveLength & ~0x7FF) != 0)
157 | cc.sweepPausing = true;
158 | cc.waveLength &= 0x7FF;
159 | }
160 | }
161 | }
162 |
163 | pause |= cc.sweepPausing;
164 | pause |= cc.waveLength == 0;
165 | if (pause)
166 | continue;
167 |
168 | // generate wave
169 | double t = ((j == SQ1 || j == SQ2) ? sqProduce(cc, sampleClk) : (j == TRI) ? triProduce(cc, sampleClk) : (j == NOI) ? noiProduce(cc, sampleClk)
170 | : 0);
171 |
172 | v += t * vol / 16;
173 | }
174 |
175 | v += dmcProduce(sampleClk);
176 |
177 | if (info.bps == 8) {
178 | buf[i * span + 0] += (byte) (v * 30);
179 | if (info.ch == 2)
180 | buf[i * span + 1] += (byte) (v * 30);
181 | } else {
182 | {
183 | short b = (short) ((buf[i * span + 0] & 0xff) | (buf[i * span + 1] << 8));
184 | short w = (short) Math.min(32767, Math.max(-32767, b + v * 8000));
185 | buf[i * span + 0] = (byte) (w & 0xff);
186 | buf[i * span + 1] = (byte) (w >> 8);
187 | }
188 | if (info.ch == 2) {
189 | short b = (short) ((buf[i * span + 2] & 0xff) | (buf[i * span + 3] << 8));
190 | short w = (short) Math.min(32767, Math.max(-32767, b + v * 8000));
191 | buf[i * span + 2] = (byte) (w & 0xff);
192 | buf[i * span + 3] = (byte) (w >> 8);
193 | }
194 | }
195 |
196 | }
197 | befClock = curClock;
198 |
199 | }
200 |
201 | public void sync() {
202 | double cpuClock = nes.getCpu().getFrequency();
203 | long cur = nes.getCpu().getMasterClock();
204 | int adv_clock = (int) (cur - befSync);
205 |
206 | // update 4 channels
207 | for (int j = 0; j < 4; j++) {
208 | ChState cc = sch[j];
209 | // length counter
210 | if (cc.enable && cc.lengthEnable) {
211 | double length_clk = cpuClock / 60.0;
212 | cc.lengthClk += adv_clock;
213 | int dec = (int) (cc.lengthClk / length_clk);
214 | cc.lengthClk -= length_clk * dec;
215 | cc.length = Math.max(0, cc.length - dec);
216 | }
217 | }
218 | // update DMC
219 | if (sdmc.enable) {
220 | sdmc.clk += adv_clock;
221 | int dec = (int) (sdmc.clk / sdmc.waveLength);
222 | sdmc.clk -= dec * sdmc.waveLength;
223 |
224 | int rest = sdmc.shiftCount + sdmc.length * 8 - dec;
225 | if (rest <= 0) { // end playback
226 | if ((sdmc.playbackMode & 1) != 0) { // loop
227 | sdmc.length = rest / 8;
228 | while (sdmc.length < 0)
229 | sdmc.length += sdmc.lengthLatch;
230 | sdmc.shiftCount = 0;
231 | } else {
232 | sdmc.enable = false;
233 | if (sdmc.playbackMode == 2) { // IRQ occur
234 | sdmc.irq = true;
235 | nes.getCpu().setIrq(true);
236 | }
237 | }
238 | } else {
239 | sdmc.length = rest / 8;
240 | sdmc.shiftCount = rest % 8;
241 | }
242 | }
243 |
244 | befSync = cur;
245 | }
246 |
247 | private class ChState {
248 | boolean enable;
249 |
250 | int waveLength;
251 |
252 | boolean lengthEnable;
253 | int length;
254 | double lengthClk;
255 |
256 | int volume;
257 | int envelopeRate;
258 | boolean envelopeEnable;
259 | double envelopeClk;
260 |
261 | boolean sweepEnable;
262 | int sweepRate;
263 | boolean sweepMode;
264 | int sweepShift;
265 | double sweepClk;
266 | boolean sweepPausing;
267 |
268 | int duty;
269 |
270 | int linearLatch;
271 | int linearCounter;
272 | boolean holdnote;
273 | int counterStart;
274 | double linearClk;
275 |
276 | boolean randomType;
277 |
278 | int step;
279 | double stepClk;
280 | int shiftRegister;
281 | }
282 |
283 | private ChState[] ch = null;
284 | private ChState[] sch = null;
285 |
286 | private class DmcState {
287 | boolean enable;
288 | boolean irq;
289 |
290 | int playbackMode;
291 | int waveLength;
292 | double clk;
293 |
294 | int counter;
295 | int length;
296 | int lengthLatch;
297 | short adr;
298 | short adrLatch;
299 | int shiftReg;
300 | int shiftCount;
301 | int dacLsb;
302 | };
303 |
304 | private DmcState dmc = new DmcState();
305 | private DmcState sdmc = new DmcState();
306 |
307 | static final int SQ1 = 0;
308 | static final int SQ2 = 1;
309 | static final int TRI = 2;
310 | static final int NOI = 3;
311 | static final int DMC = 4;
312 |
313 | final static int convTable[] = { 0x002, 0x004, 0x008, 0x010, 0x020, 0x030, 0x040, 0x050, 0x065, 0x07F, 0x0BE, 0x0FE, 0x17D, 0x1FC, 0x3F9, 0x7F2, };
314 |
315 | final static int lengthTbl[] = { 0x05, 0x06, 0x0A, 0x0C, 0x14, 0x18, 0x28, 0x30, 0x50, 0x60, 0x1E, 0x24, 0x07, 0x08, 0x0E, 0x10, };
316 |
317 | final static int dacTable[] = { 0xD60, 0xBE0, 0xAA0, 0xA00, 0x8F0, 0x7F0, 0x710, 0x6B0, 0x5F0, 0x500, 0x470, 0x400, 0x350, 0x2A0, 0x240, 0x1B0, };
318 |
319 | void doWrite(ChState[] ch, DmcState dmc, short adr, byte bdat) {
320 | int cn = (adr & 0x1f) / 4;
321 | ChState cc = null;
322 | if (cn < 4)
323 | cc = ch[cn];
324 |
325 | int dat = bdat & 0xff;
326 |
327 | switch (adr) {
328 | case 0x4000:
329 | case 0x4004:
330 | case 0x400C:
331 | cc.envelopeEnable = (dat & 0x10) == 0;
332 | if (cc.envelopeEnable) {
333 | cc.volume = 0xf;
334 | cc.envelopeRate = dat & 0xf;
335 | } else
336 | cc.volume = dat & 0xf;
337 | cc.lengthEnable = (dat & 0x20) == 0;
338 | cc.duty = dat >> 6;
339 | cc.envelopeClk = 0;
340 | break;
341 | case 0x4008:
342 | cc.linearLatch = dat & 0x7f;
343 | cc.holdnote = (dat & 0x80) != 0;
344 | break;
345 |
346 | case 0x4001:
347 | case 0x4005:
348 | cc.sweepShift = dat & 7;
349 | cc.sweepMode = (dat & 0x8) != 0;
350 | cc.sweepRate = (dat >> 4) & 7;
351 | cc.sweepEnable = (dat & 0x80) != 0;
352 | cc.sweepClk = 0;
353 | cc.sweepPausing = false;
354 | break;
355 | case 0x4009:
356 | case 0x400D: // unused
357 | break;
358 |
359 | case 0x4002:
360 | case 0x4006:
361 | case 0x400A:
362 | cc.waveLength = (cc.waveLength & ~0xff) | dat;
363 | break;
364 | case 0x400E: {
365 | cc.waveLength = convTable[dat & 0xf] - 1;
366 | cc.randomType = (dat & 0x80) != 0;
367 | break;
368 | }
369 | case 0x4003:
370 | case 0x4007:
371 | case 0x400B:
372 | case 0x400F: {
373 | if (cn != 3)
374 | cc.waveLength = (cc.waveLength & 0xff) | ((dat & 0x7) << 8);
375 | if ((dat & 0x8) == 0)
376 | cc.length = lengthTbl[dat >> 4];
377 | else
378 | cc.length = (dat >> 4) == 0 ? 0x7f : (dat >> 4);
379 | if (cn == 2)
380 | cc.counterStart = 1;
381 |
382 | if (cc.envelopeEnable) {
383 | cc.volume = 0xf;
384 | cc.envelopeClk = 0;
385 | }
386 | break;
387 | }
388 |
389 | case 0x4010: {
390 | dmc.playbackMode = dat >> 6;
391 | dmc.waveLength = dacTable[dat & 0xf] / 8;
392 | if ((dat >> 7) == 0)
393 | dmc.irq = false;
394 | break;
395 | }
396 | case 0x4011:
397 | dmc.dacLsb = dat & 1;
398 | dmc.counter = (dat >> 1) & 0x3f;
399 | break;
400 | case 0x4012:
401 | dmc.adrLatch = (short) ((dat << 6) | 0xC000);
402 | break;
403 | case 0x4013:
404 | dmc.lengthLatch = (dat << 4) + 1;
405 | break;
406 |
407 | case 0x4015:
408 | ch[0].enable = (dat & 1) != 0;
409 | if (!ch[0].enable)
410 | ch[0].length = 0;
411 | ch[1].enable = (dat & 2) != 0;
412 | if (!ch[1].enable)
413 | ch[1].length = 0;
414 | ch[2].enable = (dat & 4) != 0;
415 | if (!ch[2].enable)
416 | ch[2].length = 0;
417 | ch[3].enable = (dat & 8) != 0;
418 | if (!ch[3].enable)
419 | ch[3].length = 0;
420 |
421 | if ((dat & 0x10) != 0) {
422 | if (!dmc.enable) {
423 | dmc.adr = dmc.adrLatch;
424 | dmc.length = dmc.lengthLatch;
425 | dmc.shiftCount = 0;
426 | }
427 | dmc.enable = true;
428 | } else
429 | dmc.enable = false;
430 | dmc.irq = false;
431 | break;
432 | }
433 | }
434 |
435 | final static int sqWav[][] = { { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, { 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
436 | { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 }, };
437 |
438 | double sqProduce(ChState cc, double clk) {
439 | cc.stepClk += clk;
440 | double ret = (0.5 - sqWav[cc.duty][cc.step]);
441 | double term = cc.waveLength + 1;
442 | if (cc.stepClk >= term) {
443 | int t = (int) (cc.stepClk / term);
444 | cc.stepClk -= term * t;
445 | cc.step = (cc.step + t) % 16;
446 | }
447 | return ret;
448 | }
449 |
450 | final static int triWav[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, };
451 |
452 | double triProduce(ChState cc, double clk) {
453 | cc.stepClk += clk;
454 | double ret = (triWav[cc.step] / 16.0 - 0.5);
455 | double term = cc.waveLength + 1;
456 | if (cc.stepClk >= term) {
457 | int t = (int) (cc.stepClk / term);
458 | cc.stepClk -= term * t;
459 | cc.step = (cc.step + t) % 32;
460 | }
461 | return ret;
462 | }
463 |
464 | double noiProduce(ChState cc, double clk) {
465 | cc.stepClk += clk;
466 | double ret = 0.5 - (cc.shiftRegister >> 14);
467 | double term = cc.waveLength + 1;
468 |
469 | while (cc.stepClk >= term) {
470 | cc.stepClk -= term;
471 | int t = cc.shiftRegister;
472 | if (cc.randomType)
473 | cc.shiftRegister = ((t << 1) | (((t >> 14) ^ (t >> 8)) & 1)) & 0x7fff;
474 | else
475 | cc.shiftRegister = ((t << 1) | (((t >> 14) ^ (t >> 13)) & 1)) & 0x7fff;
476 | }
477 | return ret;
478 | }
479 |
480 | double dmcProduce(double clk) {
481 | if (!dmc.enable)
482 | return ((((dmc.counter << 1) | dmc.dacLsb) - 64) / 32.0);
483 |
484 | dmc.clk += clk;
485 | while (dmc.clk > dmc.waveLength) {
486 | dmc.clk -= dmc.waveLength;
487 | if (dmc.shiftCount == 0) {
488 | if (dmc.length == 0) { // is end?
489 | if ((dmc.playbackMode & 1) != 0) { // loop mode
490 | dmc.adr = dmc.adrLatch;
491 | dmc.length = dmc.lengthLatch;
492 | } else {
493 | dmc.enable = false;
494 | if (dmc.playbackMode == 2) { // occur IRQ
495 | dmc.irq = true;
496 | // nes.getCpu().setIrq(true); //
497 | // actually, IRQ occurs at sync()
498 | }
499 | return ((((dmc.counter << 1) | dmc.dacLsb) - 64) / 32.0);
500 | }
501 | }
502 | dmc.shiftCount = 8;
503 | dmc.shiftReg = nes.getMbc().read(dmc.adr);
504 | if (dmc.adr == 0xFFFF)
505 | dmc.adr = (short) 0x8000;
506 | else
507 | dmc.adr++;
508 | dmc.length--;
509 | }
510 |
511 | int b = dmc.shiftReg & 1;
512 | if (b == 0 && dmc.counter != 0) // decrement
513 | dmc.counter--;
514 | if (b == 1 && dmc.counter != 0x3F)
515 | dmc.counter++;
516 | dmc.counter &= 0x3f;
517 | dmc.shiftCount--;
518 | dmc.shiftReg >>= 1;
519 | }
520 | return ((((dmc.counter << 1) | dmc.dacLsb) - 64) / 32.0);
521 | }
522 |
523 | private class WriteDat {
524 | WriteDat(long clk, short adr, byte dat) {
525 | this.clk = clk;
526 | this.adr = adr;
527 | this.dat = dat;
528 | }
529 |
530 | long clk;
531 | short adr;
532 | byte dat;
533 | };
534 |
535 | private Queue writeQueue = new LinkedList();
536 | private long befClock;
537 | private long befSync;
538 |
539 | private Nes nes;
540 |
541 |
542 |
543 | public Apu getCopy() {
544 |
545 |
546 |
547 |
548 |
549 | return null;
550 | }
551 |
552 | public void loadState(SaveState saveState) {
553 | Apu copy = saveState.getApuData();
554 |
555 | }
556 | }
557 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/jp/tanakh/bjne/nes/Mapper.java:
--------------------------------------------------------------------------------
1 | package jp.tanakh.bjne.nes;
2 |
3 | import jp.tanakh.bjne.nes.Renderer.SoundInfo;
4 |
5 | public interface Mapper {
6 | int mapperNo();
7 |
8 | void reset();
9 |
10 | void write(short adr, byte dat);
11 |
12 | void hblank(int line);
13 |
14 | void audio(SoundInfo info);
15 |
16 | // void serialize(StateData sd){}
17 | }
18 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/jp/tanakh/bjne/nes/MapperAdapter.java:
--------------------------------------------------------------------------------
1 | package jp.tanakh.bjne.nes;
2 |
3 | import jp.tanakh.bjne.nes.Renderer.SoundInfo;
4 |
5 | public abstract class MapperAdapter implements Mapper {
6 |
7 | @Override
8 | public void audio(SoundInfo info) {
9 | }
10 |
11 | @Override
12 | public void hblank(int line) {
13 | }
14 |
15 | @Override
16 | public void reset() {
17 | }
18 |
19 | @Override
20 | public void write(short adr, byte dat) {
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/jp/tanakh/bjne/nes/MapperMaker.java:
--------------------------------------------------------------------------------
1 | package jp.tanakh.bjne.nes;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 | import java.lang.reflect.Constructor;
6 | import java.net.JarURLConnection;
7 | import java.net.URL;
8 | import java.util.ArrayList;
9 | import java.util.Enumeration;
10 | import java.util.jar.JarEntry;
11 | import java.util.jar.JarFile;
12 |
13 | public class MapperMaker {
14 | @SuppressWarnings("unchecked")
15 | public static Mapper makeMapper(int num, Nes n) {
16 | Class>[] classes;
17 | try {
18 | classes = getClasses("jp.tanakh.bjne.nes.mapper");
19 | if (classes == null)
20 | return null;
21 |
22 | for (Class> c : classes) {
23 | try {
24 | Class mc = (Class) c;
25 | Constructor ctor = mc.getConstructor(Nes.class);
26 | Mapper ret = ctor.newInstance(n);
27 | if (ret.mapperNo() == num)
28 | return ret;
29 | } catch (Exception e) {
30 | }
31 | }
32 |
33 | } catch (Exception e) {
34 | return null;
35 | }
36 | return null;
37 | }
38 |
39 | public static Class>[] getClasses(String pckgname)
40 | throws ClassNotFoundException, IOException {
41 | ClassLoader cld = Thread.currentThread().getContextClassLoader();
42 | if (cld == null)
43 | return null;
44 | String path = pckgname.replace('.', '/');
45 | URL resource = cld.getResource(path);
46 | if (resource == null)
47 | return null;
48 |
49 | ArrayList> classes = new ArrayList>();
50 |
51 | if (resource.getProtocol() == "jar") {
52 | JarURLConnection conn = (JarURLConnection) resource
53 | .openConnection();
54 | JarFile jar = conn.getJarFile();
55 | for (Enumeration en = jar.entries(); en.hasMoreElements();) {
56 | JarEntry entry = en.nextElement();
57 | String entryName = entry.getName();
58 | if (!entryName.matches(path + "/.*.class"))
59 | continue;
60 | String className = entryName.replaceAll("/", ".").replace(
61 | ".class", "");
62 | classes.add(Class.forName(className));
63 | }
64 | } else {
65 | File directory = new File(resource.getFile());
66 | if (!directory.exists())
67 | return null;
68 |
69 | String[] files = directory.list();
70 | for (int i = 0; i < files.length; i++)
71 | if (files[i].endsWith(".class"))
72 | classes.add(Class.forName(pckgname + '.'
73 | + files[i].replace(".class", "")));
74 | }
75 |
76 | Class>[] ret = new Class>[classes.size()];
77 | classes.toArray(ret);
78 | return ret;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/jp/tanakh/bjne/nes/Mbc.java:
--------------------------------------------------------------------------------
1 | package jp.tanakh.bjne.nes;
2 |
3 | import java.io.BufferedWriter;
4 | import java.io.File;
5 | import java.io.FileOutputStream;
6 | import java.io.IOException;
7 | import java.io.OutputStreamWriter;
8 | import java.util.Arrays;
9 |
10 | public class Mbc {
11 | public Mbc(Nes n) {
12 | nes = n;
13 | rom = vrom = null;
14 | isVram = false;
15 | }
16 |
17 | public void reset() {
18 | Arrays.fill(ram, (byte) 0x00);
19 |
20 | rom = nes.getRom().getRom();
21 | vrom = nes.getRom().getChr();
22 | sram = nes.getRom().getSram();
23 | vram = nes.getRom().getVram();
24 |
25 | for (int i = 0; i < 4; i++)
26 | mapRom(i, i);
27 | if (vrom != null)
28 | for (int i = 0; i < 8; i++)
29 | mapVrom(i, i);
30 | else
31 | for (int i = 0; i < 8; i++)
32 | mapVram(i, i);
33 |
34 | sramEnabled = true;
35 | }
36 |
37 | public void mapRom(int page, int val) {
38 | romPage[page] = (val % (nes.getRom().romSize() * 2)) * 0x2000;
39 | }
40 |
41 | public void mapVrom(int page, int val) {
42 | if (vrom != null) {
43 | chrPage[page] = (val % (nes.getRom().chrSize() * 8)) * 0x0400;
44 | isVram = false;
45 | }
46 | }
47 |
48 | public void mapVram(int page, int val) {
49 | // TODO : VRAM size
50 | chrPage[page] = (val % 8) * 0x400;
51 | isVram = true;
52 | }
53 |
54 | public byte read(short adr) {
55 |
56 |
57 | /*
58 | //this ram dump is for debugging only
59 | //if this is uncommented for more than one frame the emulator will run too slow to draw
60 |
61 | try{
62 | dumpRAM(ram);
63 | }catch(IOException e)
64 | {
65 | e.printStackTrace();
66 | }
67 | */
68 |
69 |
70 | switch ((adr & 0xffff) >> 11) {
71 | case 0x00:
72 | case 0x01:
73 | case 0x02:
74 | case 0x03: // 0x0000 - 0x1FFF
75 | return ram[adr & 0x7ff];
76 | case 0x04:
77 | case 0x05:
78 | case 0x06:
79 | case 0x07: // 0x2000 - 0x3FFF
80 | return nes.getRegs().read(adr);
81 | case 0x08:
82 | case 0x09:
83 | case 0x0A:
84 | case 0x0B: // 0x4000 - 0x5FFF
85 | if (adr < 0x4020)
86 | return nes.getRegs().read(adr);
87 | return 0; // TODO : Expanision ROM
88 | case 0x0C:
89 | case 0x0D:
90 | case 0x0E:
91 | case 0x0F: // 0x6000 - 0x7FFF
92 | if (sramEnabled)
93 | return sram[adr & 0x1fff];
94 | else
95 | return 0x00;
96 | case 0x10:
97 | case 0x11:
98 | case 0x12:
99 | case 0x13: // 0x8000 - 0x9FFF
100 | return rom[romPage[0] + (adr & 0x1fff)];
101 | case 0x14:
102 | case 0x15:
103 | case 0x16:
104 | case 0x17: // 0xA000 - 0xBFFF
105 | return rom[romPage[1] + (adr & 0x1fff)];
106 | case 0x18:
107 | case 0x19:
108 | case 0x1A:
109 | case 0x1B: // 0xC000?0xDFFF
110 | return rom[romPage[2] + (adr & 0x1fff)];
111 | case 0x1C:
112 | case 0x1D:
113 | case 0x1E:
114 | case 0x1F: // 0xE000?0xFFFF
115 | return rom[romPage[3] + (adr & 0x1fff)];
116 | }
117 | return 0x00;
118 | }
119 |
120 | private synchronized void dumpRAM(byte[] ram) throws IOException{
121 |
122 | File fout = new File("ramdump.txt");
123 |
124 | if (!fout.exists()) {
125 | fout.createNewFile();
126 | }
127 |
128 | FileOutputStream fos = new FileOutputStream(fout);
129 |
130 | BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(fos));
131 |
132 | bw.write( byteArrayToHex(ram) );
133 |
134 |
135 | /*for (int i = 0; i < ram.length; i++) {
136 | bw.write(ram[i]);
137 | bw.newLine();
138 | }
139 | */
140 |
141 | bw.close();
142 |
143 | System.out.println("Done with RAM dump");
144 | }
145 |
146 | public static String byteArrayToHex(byte[] a) {
147 | StringBuilder sb = new StringBuilder(a.length * 2);
148 | for(byte b: a)
149 | {
150 | sb.append(String.format("%02x", b & 0xff));
151 | sb.append(' ');
152 | }
153 | return sb.toString();
154 | }
155 |
156 | public void write(short adr, byte dat) {
157 | switch ((adr & 0xffff) >> 11) {
158 | case 0x00:
159 | case 0x01:
160 | case 0x02:
161 | case 0x03: // 0x0000 - 0x1FFF
162 | ram[adr & 0x7ff] = dat;
163 | break;
164 | case 0x04:
165 | case 0x05:
166 | case 0x06:
167 | case 0x07: // 0x2000 - 0x3FFF
168 | nes.getRegs().write(adr, dat);
169 | break;
170 | case 0x08:
171 | case 0x09:
172 | case 0x0A:
173 | case 0x0B: // 0x4000 - 0x5FFF
174 | if (adr < 0x4020)
175 | nes.getRegs().write(adr, dat);
176 | else if (nes.getMapper() != null)
177 | nes.getMapper().write(adr, dat);
178 | break;
179 | case 0x0C:
180 | case 0x0D:
181 | case 0x0E:
182 | case 0x0F: // 0x6000 - 0x7FFF
183 | if (sramEnabled)
184 | sram[adr & 0x1fff] = dat;
185 | else if (nes.getMapper() != null)
186 | nes.getMapper().write(adr, dat);
187 | break;
188 |
189 | case 0x10:
190 | case 0x11:
191 | case 0x12:
192 | case 0x13: // 0x8000 - 0xFFFF
193 | case 0x14:
194 | case 0x15:
195 | case 0x16:
196 | case 0x17:
197 | case 0x18:
198 | case 0x19:
199 | case 0x1A:
200 | case 0x1B:
201 | case 0x1C:
202 | case 0x1D:
203 | case 0x1E:
204 | case 0x1F:
205 | if (nes.getMapper() != null)
206 | nes.getMapper().write(adr, dat);
207 | break;
208 | }
209 | }
210 |
211 | public byte readChrRom(short adr) {
212 | if (isVram)
213 | return vram[chrPage[(adr >> 10) & 7] + (adr & 0x03ff)];
214 | else
215 | return vrom[chrPage[(adr >> 10) & 7] + (adr & 0x03ff)];
216 | }
217 |
218 | public void writeChrRom(short adr, byte dat) {
219 | if (isVram)
220 | vram[chrPage[(adr >> 10) & 7] + (adr & 0x03ff)] = dat;
221 | else
222 | vrom[chrPage[(adr >> 10) & 7] + (adr & 0x03ff)] = dat;
223 | }
224 |
225 | private byte[] rom, vrom, sram, vram;
226 | private byte[] ram = new byte[0x800];
227 |
228 | // byte[][] romPage = new byte[4][];
229 | // byte[][] chrPage = new byte[8][];
230 | private int[] romPage = new int[4];
231 | private int[] chrPage = new int[8];
232 |
233 | private boolean isVram;
234 | private boolean sramEnabled;
235 |
236 | private Nes nes;
237 |
238 |
239 |
240 | public Mbc getCopy() {
241 | Mbc copy = new Mbc(nes);
242 |
243 |
244 |
245 | System.arraycopy(ram, 0, copy.ram, 0, copy.ram.length);
246 | System.arraycopy(romPage, 0, copy.romPage, 0, copy.romPage.length);
247 | System.arraycopy(chrPage, 0, copy.chrPage, 0, copy.chrPage.length);
248 |
249 |
250 | copy.rom = new byte[rom.length];
251 | System.arraycopy(rom, 0, copy.rom, 0, copy.rom.length);
252 |
253 | copy.vrom = new byte[vrom.length];
254 | System.arraycopy(vrom, 0, copy.vrom, 0, copy.vrom.length);
255 |
256 | copy.sram = new byte[sram.length];
257 | System.arraycopy(sram, 0, copy.sram, 0, copy.sram.length);
258 |
259 | copy.vram = new byte[vram.length];
260 | System.arraycopy(vram, 0, copy.vram, 0, copy.vram.length);
261 |
262 | copy.isVram = this.isVram;
263 | copy.sramEnabled = this.sramEnabled;
264 |
265 |
266 | return copy;
267 | }
268 |
269 | public void loadState(SaveState saveState) {
270 | Mbc copy = saveState.getMbc();
271 |
272 | System.arraycopy(copy.ram, 0, ram, 0, copy.ram.length);
273 | System.arraycopy(copy.romPage, 0, romPage, 0, copy.romPage.length);
274 | System.arraycopy(copy.chrPage, 0, chrPage, 0, copy.chrPage.length);
275 |
276 |
277 |
278 | System.arraycopy(copy.rom, 0, rom, 0, copy.rom.length);
279 | System.arraycopy(copy.vrom, 0, vrom, 0, copy.vrom.length);
280 | System.arraycopy(copy.sram, 0, sram, 0, copy.sram.length);
281 | System.arraycopy(copy.vram, 0, vram, 0, copy.vram.length);
282 |
283 |
284 | for (int i = 0; i < 4; i++)
285 | mapRom(i, i);
286 | if (vrom != null)
287 | for (int i = 0; i < 8; i++)
288 | mapVrom(i, i);
289 | else
290 | for (int i = 0; i < 8; i++)
291 | mapVram(i, i);
292 |
293 |
294 | this.isVram = copy.isVram;
295 | this.sramEnabled = copy.sramEnabled;
296 | }
297 | }
298 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/jp/tanakh/bjne/nes/Nes.java:
--------------------------------------------------------------------------------
1 | package jp.tanakh.bjne.nes;
2 |
3 | import java.io.IOException;
4 | import java.util.HashMap;
5 |
6 | import com.starflask.JavaNESBrain.data.PixelDataListener;
7 |
8 | import jp.tanakh.bjne.nes.Renderer.ScreenInfo;
9 |
10 | public class Nes {
11 | public Nes(Renderer r) {
12 | renderer = r;
13 | cpu = new Cpu(this);
14 | apu = new Apu(this);
15 | ppu = new Ppu(this);
16 | mbc = new Mbc(this);
17 | regs = new Regs(this);
18 | rom = new Rom(this);
19 | mapper = null;
20 | }
21 |
22 | public boolean audioEnabled = true;
23 |
24 | public void load(String fname) throws IOException {
25 | rom.load(fname);
26 | mapper = MapperMaker.makeMapper(rom.mapperNo(), this);
27 | if (mapper == null)
28 | throw new IOException(String.format("unsupported mapper: #%d", rom.mapperNo()));
29 | reset();
30 | }
31 |
32 | public boolean checkMapper() {
33 | return mapper != null;
34 | }
35 |
36 |
37 |
38 |
39 | SaveState[] saveStates = new SaveState[5];
40 |
41 | public void saveState(int stateNumber) {
42 | saveStates[stateNumber] = new SaveState(regs,mbc,cpu,apu,ppu);
43 |
44 | }
45 |
46 |
47 | public int requestedLoadState = -1;
48 |
49 | public void requestLoadState(int state) {
50 | requestedLoadState = state;
51 |
52 | }
53 |
54 | ROMEventListener romEventListener;
55 |
56 | public void loadState(int stateNumber) {
57 |
58 | if(requestedLoadState >= 0)
59 | {
60 | //rom.loadState( saveStates[stateNumber] );
61 | mbc.loadState( saveStates[stateNumber] );
62 | cpu.loadState( saveStates[stateNumber] );
63 | regs.loadState( saveStates[stateNumber] );
64 | apu.loadState( saveStates[stateNumber] );
65 | ppu.loadState( saveStates[stateNumber] );
66 |
67 | requestedLoadState = -1;
68 |
69 | if(romEventListener!=null)
70 | {
71 | romEventListener.onStateLoaded();
72 | }
73 | }
74 |
75 | }
76 |
77 | public void registerROMEventListener(ROMEventListener romEventListener)
78 | {
79 | this.romEventListener=romEventListener;
80 | }
81 |
82 | public void reset() {
83 | // reset rom & mbc first
84 | rom.reset();
85 | mbc.reset();
86 |
87 | // reset mapper
88 | mapper.reset();
89 |
90 | // reset rest
91 | cpu.reset();
92 | apu.reset();
93 | ppu.reset();
94 | regs.reset();
95 |
96 | System.out.println("Reset virtual machine ...");
97 | }
98 |
99 | long frameCount = 0;
100 |
101 |
102 |
103 | public long getFrameCount() {
104 | return frameCount;
105 | }
106 |
107 | public void execFrame() {
108 | // CPU clock is 1.7897725MHz
109 | // 1789772.5 / 60 / 262 = 113.85...
110 | // 114 cycles per line?
111 | // 1789772.5 / 262 / 114 = 59.922 fps ?
112 |
113 |
114 |
115 | if(requestedLoadState > -1)
116 | {
117 | loadState(requestedLoadState);
118 | }
119 |
120 | Renderer.ScreenInfo scri = renderer.requestScreen(256, 240);
121 | Renderer.SoundInfo sndi = renderer.requestSound();
122 | Renderer.InputInfo inpi = renderer.requestInput(2, 8);
123 |
124 |
125 | if (sndi != null && audioEnabled) {
126 | apu.genAudio(sndi);
127 | renderer.outputSound(sndi);
128 | }
129 |
130 |
131 |
132 | if (inpi != null)
133 | {
134 | regs.setInput(inpi.buf);
135 | }
136 |
137 | if(externalGamepadBuffer != null)
138 | {
139 |
140 | regs.setInput( externalGamepadBuffer );
141 | }
142 |
143 |
144 | regs.setVBlank(false, true);
145 | regs.startFrame();
146 | for (int i = 0; i < 240; i++) {
147 | if (mapper != null)
148 | mapper.hblank(i);
149 | regs.startScanline();
150 | if (scri != null)
151 | ppu.render(i, scri);
152 | ppu.spriteCheck(i);
153 | apu.sync();
154 | cpu.exec(114);
155 | regs.endScanline();
156 | }
157 |
158 | if ((regs.getFrameIrq() & 0xC0) == 0)
159 | cpu.setIrq(true);
160 |
161 | for (int i = 240; i < 262; i++) {
162 | if (mapper != null)
163 | mapper.hblank(i);
164 | apu.sync();
165 | if (i == 241) {
166 | regs.setVBlank(true, false);
167 | cpu.exec(0); // one extra op will execute after VBLANK
168 | regs.setVBlank(regs.getIsVBlank(), true);
169 | cpu.exec(114);
170 | } else
171 | cpu.exec(114);
172 | }
173 |
174 | if (scri != null)
175 | {
176 | renderer.outputScreen(scri);
177 |
178 | broadcastScreenOutputData(scri);
179 | }
180 |
181 | frameCount++;
182 | }
183 |
184 |
185 | PixelDataListener pixelDataListener;
186 | private void broadcastScreenOutputData(ScreenInfo scri) {
187 | if(pixelDataListener!=null)
188 | {
189 | pixelDataListener.outputScreen(scri);
190 | }
191 | }
192 |
193 | public void registerPixelDataListener(PixelDataListener pixelDataListener)
194 | {
195 | this.pixelDataListener=pixelDataListener;
196 | }
197 |
198 | public Rom getRom() {
199 | return rom;
200 | }
201 |
202 | public Cpu getCpu() {
203 | return cpu;
204 | }
205 |
206 | public Ppu getPpu() {
207 | return ppu;
208 | }
209 |
210 | public Apu getApu() {
211 | return apu;
212 | }
213 |
214 | public Mbc getMbc() {
215 | return mbc;
216 | }
217 |
218 | public Regs getRegs() {
219 | return regs;
220 | }
221 |
222 | public Mapper getMapper() {
223 | return mapper;
224 | }
225 |
226 | private Rom rom;
227 | private Cpu cpu;
228 | private Ppu ppu;
229 | private Apu apu;
230 | private Mbc mbc;
231 | private Regs regs;
232 | private Mapper mapper;
233 |
234 | private Renderer renderer;
235 |
236 | int[] externalGamepadBuffer;
237 | public void setGamepadInput(int[] buf)
238 | {
239 | externalGamepadBuffer = buf ;
240 | }
241 |
242 |
243 | }
244 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/jp/tanakh/bjne/nes/Ppu.java:
--------------------------------------------------------------------------------
1 | package jp.tanakh.bjne.nes;
2 |
3 | import java.util.Arrays;
4 |
5 | public class Ppu {
6 | public Ppu(Nes n) {
7 | nes = n;
8 | initPalette();
9 | }
10 |
11 | public void reset() {
12 | Arrays.fill(sprram, (byte) 0x00);
13 | Arrays.fill(nameTable[0], (byte) 0x00);
14 | Arrays.fill(nameTable[1], (byte) 0x00);
15 | Arrays.fill(nameTable[2], (byte) 0x00);
16 | Arrays.fill(nameTable[3], (byte) 0x00);
17 | Arrays.fill(palette, (byte) 0x00);
18 |
19 | if (nes.getRom().isFourScreen())
20 | setMirroring(MirrorType.FOUR_SCREEN);
21 | else if (nes.getRom().mirror() == Rom.MirrorType.HORIZONTAL)
22 | setMirroring(MirrorType.HOLIZONTAL);
23 | else if (nes.getRom().mirror() == Rom.MirrorType.VERTICAL)
24 | setMirroring(MirrorType.VERTICAL);
25 | else
26 | setMirroring(MirrorType.FOUR_SCREEN);
27 | }
28 |
29 | public void setMirroring(MirrorType mt) {
30 | switch (mt) {
31 | case HOLIZONTAL:
32 | setMirroring(0, 0, 1, 1);
33 | break;
34 | case VERTICAL:
35 | setMirroring(0, 1, 0, 1);
36 | break;
37 | case FOUR_SCREEN:
38 | setMirroring(0, 1, 2, 3);
39 | break;
40 | case SINGLE_SCREEN:
41 | setMirroring(0, 0, 0, 0);
42 | break;
43 | }
44 | }
45 |
46 | public void setMirroring(int m0, int m1, int m2, int m3) {
47 | namePage[0] = nameTable[m0];
48 | namePage[1] = nameTable[m1];
49 | namePage[2] = nameTable[m2];
50 | namePage[3] = nameTable[m3];
51 | }
52 |
53 | public void render(int line, Renderer.ScreenInfo scri) {
54 | Arrays.fill(buf, palette[0]);
55 |
56 | if (nes.getRegs().getBgVisible())
57 | renderBG(line, buf);
58 | if (nes.getRegs().getSpriteVisible())
59 | renderSPR(line, buf);
60 |
61 | if (scri.bpp == 24 || scri.bpp == 32) { // full color
62 | int inc = scri.bpp / 8;
63 | int dest = scri.pitch * line;
64 | for (int i = 0; i < 256; i++) {
65 | int c = nesPalette24[buf[i + 8] & 0x3f];
66 | scri.buf[dest + 0] = (byte) (c & 0xff);
67 | scri.buf[dest + 1] = (byte) ((c >> 8) & 0xff);
68 | scri.buf[dest + 2] = (byte) ((c >> 16) & 0xff);
69 | dest += inc;
70 | }
71 | } else if (scri.bpp == 16) { // high color
72 | int dest = scri.pitch * line;
73 | for (int i = 0; i < 256; i++) {
74 | short c = nesPalette16[buf[i + 8] & 0x3f];
75 | scri.buf[dest + 0] = (byte) (c & 0xff);
76 | scri.buf[dest + 1] = (byte) ((c >> 8) & 0xff);
77 | dest += 2;
78 | }
79 | } else {
80 | // not supported
81 | }
82 | }
83 |
84 | public byte[] getSpriteRam() {
85 | return sprram;
86 | }
87 |
88 | public byte[][] getNameTable() {
89 | return nameTable;
90 | }
91 |
92 | public byte[][] getNamePage() {
93 | return namePage;
94 | }
95 |
96 | public byte[] getPalette() {
97 | return palette;
98 | }
99 |
100 | public enum MirrorType {
101 | HOLIZONTAL, VERTICAL, FOUR_SCREEN, SINGLE_SCREEN,
102 | }
103 |
104 | public void serialize() {
105 | // TODO
106 | }
107 |
108 | // -----
109 |
110 | private void initPalette() {
111 | int[][] palDat = new int[][] { { 0x75, 0x75, 0x75 },
112 | { 0x27, 0x1B, 0x8F }, { 0x00, 0x00, 0xAB },
113 | { 0x47, 0x00, 0x9F }, { 0x8F, 0x00, 0x77 },
114 | { 0xAB, 0x00, 0x13 }, { 0xA7, 0x00, 0x00 },
115 | { 0x7F, 0x0B, 0x00 }, { 0x43, 0x2F, 0x00 },
116 | { 0x00, 0x47, 0x00 }, { 0x00, 0x51, 0x00 },
117 | { 0x00, 0x3F, 0x17 }, { 0x1B, 0x3F, 0x5F },
118 | { 0x00, 0x00, 0x00 }, { 0x05, 0x05, 0x05 },
119 | { 0x05, 0x05, 0x05 },
120 |
121 | { 0xBC, 0xBC, 0xBC }, { 0x00, 0x73, 0xEF },
122 | { 0x23, 0x3B, 0xEF }, { 0x83, 0x00, 0xF3 },
123 | { 0xBF, 0x00, 0xBF }, { 0xE7, 0x00, 0x5B },
124 | { 0xDB, 0x2B, 0x00 }, { 0xCB, 0x4F, 0x0F },
125 | { 0x8B, 0x73, 0x00 }, { 0x00, 0x97, 0x00 },
126 | { 0x00, 0xAB, 0x00 }, { 0x00, 0x93, 0x3B },
127 | { 0x00, 0x83, 0x8B }, { 0x11, 0x11, 0x11 },
128 | { 0x09, 0x09, 0x09 }, { 0x09, 0x09, 0x09 },
129 |
130 | { 0xFF, 0xFF, 0xFF }, { 0x3F, 0xBF, 0xFF },
131 | { 0x5F, 0x97, 0xFF }, { 0xA7, 0x8B, 0xFD },
132 | { 0xF7, 0x7B, 0xFF }, { 0xFF, 0x77, 0xB7 },
133 | { 0xFF, 0x77, 0x63 }, { 0xFF, 0x9B, 0x3B },
134 | { 0xF3, 0xBF, 0x3F }, { 0x83, 0xD3, 0x13 },
135 | { 0x4F, 0xDF, 0x4B }, { 0x58, 0xF8, 0x98 },
136 | { 0x00, 0xEB, 0xDB }, { 0x66, 0x66, 0x66 },
137 | { 0x0D, 0x0D, 0x0D }, { 0x0D, 0x0D, 0x0D },
138 |
139 | { 0xFF, 0xFF, 0xFF }, { 0xAB, 0xE7, 0xFF },
140 | { 0xC7, 0xD7, 0xFF }, { 0xD7, 0xCB, 0xFF },
141 | { 0xFF, 0xC7, 0xFF }, { 0xFF, 0xC7, 0xDB },
142 | { 0xFF, 0xBF, 0xB3 }, { 0xFF, 0xDB, 0xAB },
143 | { 0xFF, 0xE7, 0xA3 }, { 0xE3, 0xFF, 0xA3 },
144 | { 0xAB, 0xF3, 0xBF }, { 0xB3, 0xFF, 0xCF },
145 | { 0x9F, 0xFF, 0xF3 }, { 0xDD, 0xDD, 0xDD },
146 | { 0x11, 0x11, 0x11 }, { 0x11, 0x11, 0x11 } };
147 |
148 | for (int i = 0; i < 0x40; i++)
149 | nesPalette24[i] = palDat[i][0] | (palDat[i][1] << 8)
150 | | (palDat[i][2] << 16);
151 | }
152 |
153 | private byte readNameTable(short adr) {
154 | return namePage[((adr) >> 10) & 3][(adr) & 0x3ff];
155 | }
156 |
157 | private byte readPatTable(short adr) {
158 | return nes.getMbc().readChrRom(adr);
159 | }
160 |
161 | private void renderBG(int line, byte[] buf) {
162 | Regs r = nes.getRegs();
163 | int x_ofs = r.getPpuAdrX() & 7;
164 | int y_ofs = (r.getPpuAdrV() >> 12) & 7;
165 | short name_adr = (short) (r.getPpuAdrV() & 0xfff);
166 | short pat_adr = (short) (r.getBgPatAdr() ? 0x1000 : 0x0000);
167 |
168 | int ix = -x_ofs;
169 | for (int i = 0; i < 33; i++, ix += 8) {
170 | byte tile = readNameTable((short) (0x2000 + name_adr));
171 |
172 | byte l = readPatTable((short) (pat_adr + (tile & 0xff) * 16 + y_ofs));
173 | byte u = readPatTable((short) (pat_adr + (tile & 0xff) * 16 + y_ofs + 8));
174 |
175 | int tx = name_adr & 0x1f, ty = (name_adr >> 5) & 0x1f;
176 | short attr_adr = (short) ((name_adr & 0xC00) + 0x3C0
177 | + ((ty & ~3) << 1) + (tx >> 2));
178 | int aofs = ((ty & 2) == 0 ? 0 : 4) + ((tx & 2) == 0 ? 0 : 2);
179 | int attr = ((readNameTable((short) (0x2000 + attr_adr)) >> aofs) & 3) << 2;
180 |
181 | for (int j = 7; j >= 0; j--) {
182 | int t = ((l & 1) | (u << 1)) & 3;
183 | if (t != 0)
184 | buf[8 + ix + j] = (byte) (0x40 | palette[t | attr]);
185 | l >>= 1;
186 | u >>= 1;
187 | }
188 |
189 | if ((name_adr & 0x1f) == 0x1f)
190 | name_adr = (short) ((name_adr & ~0x1f) ^ 0x400);
191 | else
192 | name_adr++;
193 | }
194 | }
195 |
196 | private void renderSPR(int line, byte[] buf) {
197 | int spr_height = nes.getRegs().getSpriteSize() ? 16 : 8;
198 | int pat_adr = nes.getRegs().getSpritePatAdr() ? 0x1000 : 0x0000;
199 |
200 | for (int i = 0; i < 64; i++) {
201 | int spr_y = (sprram[i * 4 + 0] & 0xff) + 1;
202 | int attr = sprram[i * 4 + 2] & 0xff;
203 |
204 | if (!(line >= spr_y && line < spr_y + spr_height))
205 | continue;
206 |
207 | boolean is_bg = ((attr >> 5) & 1) != 0;
208 | int y_ofs = line - spr_y;
209 | int tile_index = sprram[i * 4 + 1] & 0xff;
210 | int spr_x = sprram[i * 4 + 3] & 0xff;
211 | int upper = (attr & 3) << 2;
212 |
213 | boolean h_flip = (attr & 0x40) == 0;
214 | int sx = h_flip ? 7 : 0, ex = h_flip ? -1 : 8, ix = h_flip ? -1 : 1;
215 |
216 | if ((attr & 0x80) != 0)
217 | y_ofs = spr_height - 1 - y_ofs;
218 |
219 | short tile_adr;
220 | if (spr_height == 16)
221 | tile_adr = (short) ((tile_index & ~1) * 16
222 | + ((tile_index & 1) * 0x1000) + (y_ofs >= 8 ? 16 : 0) + (y_ofs & 7));
223 | else
224 | tile_adr = (short) (pat_adr + tile_index * 16 + y_ofs);
225 |
226 | byte l = readPatTable(tile_adr);
227 | byte u = readPatTable((short) (tile_adr + 8));
228 |
229 | for (int x = sx; x != ex; x += ix) {
230 | int lower = (l & 1) | ((u & 1) << 1);
231 | if (lower != 0 && (buf[8 + spr_x + x] & 0x80) == 0) {
232 | if (!is_bg || (buf[8 + spr_x + x] & 0x40) == 0)
233 | buf[8 + spr_x + x] = palette[0x10 | upper | lower];
234 | buf[8 + spr_x + x] |= 0x80;
235 | }
236 | l >>= 1;
237 | u >>= 1;
238 | }
239 | }
240 | }
241 |
242 | public void spriteCheck(int line) {
243 | if (nes.getRegs().getSpriteVisible()) {
244 | int spr_y = (sprram[0] & 0xff) + 1;
245 | int spr_height = nes.getRegs().getSpriteSize() ? 16 : 8;
246 | int pat_adr = nes.getRegs().getSpritePatAdr() ? 0x1000 : 0x0000;
247 |
248 | if (line >= spr_y && line < spr_y + spr_height) {
249 | int y_ofs = line - spr_y;
250 | int tile_index = sprram[1] & 0xff;
251 | if ((sprram[2] & 0x80) != 0)
252 | y_ofs = spr_height - 1 - y_ofs;
253 | short tile_adr;
254 | if (spr_height == 16)
255 | tile_adr = (short) ((tile_index & ~1) * 16
256 | + ((tile_index & 1) * 0x1000)
257 | + (y_ofs >= 8 ? 16 : 0) + (y_ofs & 7));
258 | else
259 | tile_adr = (short) (pat_adr + tile_index * 16 + y_ofs);
260 | byte l = readPatTable(tile_adr);
261 | byte u = readPatTable((short) (tile_adr + 8));
262 | if (l != 0 || u != 0)
263 | nes.getRegs().setSprite0Occur(true);
264 | }
265 | }
266 | }
267 |
268 | private byte[] sprram = new byte[0x100];
269 | private byte[][] nameTable = new byte[4][0x400];
270 | private byte[][] namePage = new byte[4][];
271 | private byte[] palette = new byte[0x20];
272 |
273 | private int[] nesPalette24 = new int[0x40];
274 | private short[] nesPalette16 = new short[0x40];
275 |
276 | private byte[] buf = new byte[256 + 16];
277 |
278 | Nes nes;
279 |
280 | public Ppu getCopy() {
281 |
282 | Ppu copy = new Ppu(nes);
283 |
284 | System.arraycopy(sprram, 0, copy.sprram, 0, sprram.length);
285 | System.arraycopy(palette, 0, copy.palette, 0, palette.length);
286 | System.arraycopy(nesPalette24, 0, copy.nesPalette24, 0, nesPalette24.length);
287 | System.arraycopy(nesPalette24, 0, copy.nesPalette24, 0, nesPalette24.length);
288 | System.arraycopy(buf, 0, copy.buf, 0, buf.length);
289 |
290 | for(int i=0;i<4;i++)
291 | {
292 | System.arraycopy(nameTable[i], 0, copy.nameTable[i], 0, nameTable[i].length);
293 |
294 | // System.arraycopy(namePage[i], 0, copy.namePage[i], 0, namePage[i].length);
295 | }
296 |
297 |
298 |
299 | return copy;
300 | }
301 |
302 | public void loadState(SaveState saveState) {
303 | Ppu copy =saveState.getPpuData();
304 |
305 | System.arraycopy(copy.sprram, 0, sprram, 0, copy.sprram.length);
306 | System.arraycopy(copy.palette, 0, palette, 0, copy.palette.length);
307 | System.arraycopy(copy.nesPalette24, 0, nesPalette24, 0, copy.nesPalette24.length);
308 | System.arraycopy(copy.nesPalette24, 0, nesPalette24, 0, copy.nesPalette24.length);
309 | System.arraycopy(copy.buf, 0, buf, 0, copy.buf.length);
310 |
311 |
312 | for(int i=0;i<4;i++)
313 | {
314 | System.arraycopy(copy.nameTable[i], 0, nameTable[i], 0, copy.nameTable[i].length);
315 | // System.arraycopy(copy.namePage[i], 0, namePage[i], 0, copy.namePage[i].length);
316 | }
317 |
318 |
319 | }
320 | }
321 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/jp/tanakh/bjne/nes/ROMEventListener.java:
--------------------------------------------------------------------------------
1 | package jp.tanakh.bjne.nes;
2 |
3 | public interface ROMEventListener {
4 | public void onStateLoaded();
5 | }
6 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/jp/tanakh/bjne/nes/Regs.java:
--------------------------------------------------------------------------------
1 | package jp.tanakh.bjne.nes;
2 |
3 | public class Regs {
4 | public Regs(Nes n) {
5 | nes = n;
6 | }
7 |
8 | public void reset() {
9 | nmiEnable = false;
10 | ppuMaster = false; // unused
11 | spriteSize = false;
12 | bgPatAdr = spritePatAdr = false;
13 | ppuAdrIncr = false;
14 | nameTblAdr = 0;
15 |
16 | bgColor = 0;
17 | spriteVisible = bgVisible = false;
18 | spriteClip = bgClip = false;
19 | colorDisplay = false;
20 |
21 | isVBlank = false;
22 | sprite0Occur = spriteOver = false;
23 | vramWriteFlag = false;
24 |
25 | sprramAdr = 0;
26 | ppuAdrT = ppuAdrV = ppuAdrX = 0;
27 | ppuAdrToggle = false;
28 | ppuReadBuf = 0;
29 |
30 | joypadStrobe = false;
31 | joypadReadPos[0] = joypadReadPos[1] = 0;
32 |
33 | joypadSign[0] = 0x1;
34 | joypadSign[1] = 0x2;
35 |
36 | frameIrq = 0xFF;
37 | }
38 |
39 | public void setVBlank(boolean b, boolean nmi) {
40 | isVBlank = b;
41 | if (nmi && (!isVBlank || nmiEnable))
42 | nes.getCpu().setNmi(isVBlank);
43 | if (b)
44 | sprite0Occur = false;
45 | }
46 |
47 | public void startFrame() {
48 | if (bgVisible || spriteVisible)
49 | ppuAdrV = ppuAdrT;
50 | }
51 |
52 | public void startScanline() {
53 | if (bgVisible || spriteVisible)
54 | ppuAdrV = (short) ((ppuAdrV & 0xfbe0) | (ppuAdrT & 0x041f));
55 | }
56 |
57 | public void endScanline() {
58 | if (bgVisible || spriteVisible) {
59 | if (((ppuAdrV >> 12) & 7) == 7) {
60 | ppuAdrV &= ~0x7000;
61 | if (((ppuAdrV >> 5) & 0x1f) == 29)
62 | ppuAdrV = (short) ((ppuAdrV & ~0x03e0) ^ 0x800);
63 | else if (((ppuAdrV >> 5) & 0x1f) == 31)
64 | ppuAdrV &= ~0x03e0;
65 | else
66 | ppuAdrV += 0x20;
67 | } else
68 | ppuAdrV += 0x1000;
69 | }
70 | }
71 |
72 | public boolean drawEnabled() {
73 | return spriteVisible || bgVisible;
74 | }
75 |
76 | public void setInput(int[] dat) {
77 | for (int i = 0; i < 16; i++)
78 | {
79 | padDat[i / 8][i % 8] = dat[i] != 0 ? true : false;
80 | }
81 | }
82 |
83 | private static boolean _bit(int x, int n) {
84 | return ((x >> n) & 1) != 0;
85 | }
86 |
87 | private static int _set(boolean b, int n) {
88 | return ((b ? 1 : 0) << n);
89 | }
90 |
91 | public byte read(short adr) {
92 | // System.out.printf("[%04X] ->\n", adr & 0xffff);
93 | if ((adr & 0xffff) >= 0x4000) {
94 | switch (adr & 0xffff) {
95 | case 0x4016: // Joypad #1 (RW)
96 | case 0x4017: // Joypad #2 (RW)
97 | {
98 | int padNum = adr - 0x4016;
99 | int readPos = joypadReadPos[padNum];
100 | byte ret;
101 | if (readPos < 8) // ƒpƒbƒhƒf�[ƒ^
102 | ret = (byte) (padDat[padNum][readPos] ? 1 : 0);
103 | else if (readPos < 16) // Ignored
104 | ret = 0;
105 | else if (readPos < 20) // Signature
106 | ret = (byte) ((joypadSign[padNum] >> (readPos - 16)) & 1);
107 | else
108 | ret = 0;
109 | joypadReadPos[padNum]++;
110 | if (joypadReadPos[padNum] == 24)
111 | joypadReadPos[padNum] = 0;
112 | return ret;
113 | }
114 | case 0x4015:
115 | return (byte) (nes.getApu().read((short) 0x4015) | (((frameIrq & 0xC0) == 0) ? 0x40
116 | : 0));
117 | default:
118 | return nes.getApu().read(adr);
119 | }
120 | }
121 |
122 | switch (adr & 7) {
123 | case 0: // PPU Control Register #1 (W)
124 | case 1: // PPU Control Register #2 (W)
125 | case 3: // SPR-RAM Address Register (W)
126 | case 4: // SPR-RAM I/O Register (W)
127 | case 5: // VRAM Address Register #1 (W2)
128 | case 6: // VRAM Address Register #2 (W2)
129 | System.out.printf("read #%4x\n", adr);
130 | return 0;
131 |
132 | case 2: { // PPU Status Register (R)
133 | byte ret = (byte) (_set(isVBlank, 7) | _set(sprite0Occur, 6)
134 | | _set(spriteOver, 5) | _set(vramWriteFlag, 4));
135 | setVBlank(false, true);
136 | ppuAdrToggle = false;
137 | return ret;
138 | }
139 | case 7: { // VRAM I/O Register (RW)
140 | byte ret = ppuReadBuf;
141 | ppuReadBuf = read2007();
142 | return ret;
143 | }
144 | }
145 |
146 | return 0;
147 | }
148 |
149 | public void write(short adr, byte dat) {
150 | // System.out.printf("[%04X] <- %02X\n", adr & 0xffff, dat & 0xff);
151 | if ((adr & 0xffff) >= 0x4000) {
152 | switch (adr & 0xffff) {
153 | case 0x4014: // Sprite DMA Register (W)
154 | {
155 | byte[] sprram = nes.getPpu().getSpriteRam();
156 | for (int i = 0; i < 0x100; i++)
157 | sprram[i] = nes.getMbc().read((short) ((dat << 8) | i));
158 | }
159 | break;
160 | case 0x4016: // Joypad #1 (RW)
161 | {
162 | boolean newval = (dat & 1) != 0;
163 | if (joypadStrobe && !newval) // ‚½‚¿‰º‚èƒGƒbƒW‚ÅƒŠƒZƒbƒg
164 | joypadReadPos[0] = joypadReadPos[1] = 0;
165 | joypadStrobe = newval;
166 | }
167 | break;
168 | case 0x4017: // Joypad #2 (RW)
169 | frameIrq = dat;
170 | break;
171 | default:
172 | nes.getApu().write(adr, dat);
173 | break;
174 | }
175 | return;
176 | }
177 |
178 | switch (adr & 7) {
179 | case 0: // PPU Control Register #1 (W)
180 | nmiEnable = _bit(dat, 7);
181 | ppuMaster = _bit(dat, 6);
182 | spriteSize = _bit(dat, 5);
183 | bgPatAdr = _bit(dat, 4);
184 | spritePatAdr = _bit(dat, 3);
185 | ppuAdrIncr = _bit(dat, 2);
186 | // name_tbl_adr =dat&3;
187 | ppuAdrT = (short) ((ppuAdrT & 0xf3ff) | ((dat & 3) << 10));
188 | break;
189 | case 1: // PPU Control Register #2 (W)
190 | bgColor = dat >> 5;
191 | spriteVisible = _bit(dat, 4);
192 | bgVisible = _bit(dat, 3);
193 | spriteClip = _bit(dat, 2);
194 | bgClip = _bit(dat, 1);
195 | colorDisplay = _bit(dat, 0);
196 | break;
197 | case 2: // PPU Status Register (R)
198 | // what should I do...?
199 | System.out.println("*** write to $2002");
200 | break;
201 | case 3: // SPR-RAM Address Register (W)
202 | sprramAdr = dat;
203 | break;
204 | case 4: // SPR-RAM I/O Register (W)
205 | nes.getPpu().getSpriteRam()[(sprramAdr++) & 0xff] = dat;
206 | break;
207 | case 5: // VRAM Address Register #1 (W2)
208 | ppuAdrToggle = !ppuAdrToggle;
209 | if (ppuAdrToggle) {
210 | ppuAdrT = (short) ((ppuAdrT & 0xffe0) | ((dat & 0xff) >> 3));
211 | ppuAdrX = (short) (dat & 7);
212 | } else {
213 | ppuAdrT = (short) ((ppuAdrT & 0xfC1f) | (((dat & 0xff) >> 3) << 5));
214 | ppuAdrT = (short) ((ppuAdrT & 0x8fff) | ((dat & 7) << 12));
215 | }
216 | break;
217 | case 6: // VRAM Address Register #2 (W2)
218 | ppuAdrToggle = !ppuAdrToggle;
219 | if (ppuAdrToggle)
220 | ppuAdrT = (short) ((ppuAdrT & 0x00ff) | ((dat & 0x3f) << 8));
221 | else {
222 | ppuAdrT = (short) ((ppuAdrT & 0xff00) | (dat & 0xff));
223 | ppuAdrV = ppuAdrT;
224 | }
225 | break;
226 | case 7: // VRAM I/O Register (RW)
227 | write2007(dat);
228 | break;
229 | }
230 | }
231 |
232 | private byte read2007() {
233 | short adr = (short) (ppuAdrV & 0x3fff);
234 | ppuAdrV += ppuAdrIncr ? 32 : 1;
235 | if (adr < 0x2000)
236 | return nes.getMbc().readChrRom(adr);
237 | else if (adr < 0x3f00)
238 | return nes.getPpu().getNamePage()[(adr >> 10) & 3][adr & 0x3ff];
239 | else {
240 | if ((adr & 3) == 0)
241 | adr &= ~0x10;
242 | return nes.getPpu().getPalette()[adr & 0x1f];
243 | }
244 | }
245 |
246 | private void write2007(byte dat) {
247 | short adr = (short) (ppuAdrV & 0x3fff);
248 | ppuAdrV += ppuAdrIncr ? 32 : 1;
249 | if (adr < 0x2000) // CHR-ROM
250 | nes.getMbc().writeChrRom(adr, dat);
251 | else if (adr < 0x3f00) // name table
252 | nes.getPpu().getNamePage()[(adr >> 10) & 3][adr & 0x3ff] = dat;
253 | else { // palette
254 | if ((adr & 3) == 0)
255 | adr &= ~0x10; // mirroring
256 | nes.getPpu().getPalette()[adr & 0x1f] = (byte) (dat & 0x3f);
257 | }
258 | }
259 |
260 | public short getPpuAdrX() {
261 | return ppuAdrX;
262 | }
263 |
264 | public short getPpuAdrV() {
265 | return ppuAdrV;
266 | }
267 |
268 | public boolean getBgPatAdr() {
269 | return bgPatAdr;
270 | }
271 |
272 | public boolean getSpriteSize() {
273 | return spriteSize;
274 | }
275 |
276 | public boolean getSpritePatAdr() {
277 | return spritePatAdr;
278 | }
279 |
280 | public boolean getSpriteVisible() {
281 | return spriteVisible;
282 | }
283 |
284 | public boolean getBgVisible() {
285 | return bgVisible;
286 | }
287 |
288 | public int getFrameIrq() {
289 | return frameIrq;
290 | }
291 |
292 | public boolean getIsVBlank() {
293 | return isVBlank;
294 | }
295 |
296 | public void setSprite0Occur(boolean b) {
297 | this.sprite0Occur = b;
298 | }
299 |
300 | private boolean nmiEnable;
301 | private boolean ppuMaster;
302 | private boolean spriteSize;
303 | private boolean bgPatAdr, spritePatAdr;
304 | private boolean ppuAdrIncr;
305 | private int nameTblAdr;
306 |
307 | private int bgColor;
308 | private boolean spriteVisible, bgVisible;
309 | private boolean spriteClip, bgClip;
310 | private boolean colorDisplay;
311 |
312 | private boolean isVBlank;
313 | private boolean sprite0Occur, spriteOver;
314 | private boolean vramWriteFlag;
315 |
316 | private byte sprramAdr;
317 | private short ppuAdrT, ppuAdrV, ppuAdrX;
318 | private boolean ppuAdrToggle;
319 | private byte ppuReadBuf;
320 |
321 | private boolean joypadStrobe;
322 | private int[] joypadReadPos = new int[2];
323 | private int[] joypadSign = new int[2];
324 | private int frameIrq;
325 |
326 | private boolean[][] padDat = new boolean[2][8];
327 |
328 |
329 |
330 |
331 |
332 |
333 | private Nes nes;
334 |
335 |
336 |
337 | public Regs getCopy() {
338 |
339 | Regs copy = new Regs(nes);
340 |
341 | copy.nmiEnable = this.nmiEnable;
342 | copy.spriteSize = this.spriteSize;
343 | copy.bgPatAdr = this.bgPatAdr;
344 | copy.spritePatAdr = this.spritePatAdr;
345 | copy.ppuAdrIncr = this.ppuAdrIncr;
346 | copy.nameTblAdr = this.nameTblAdr;
347 |
348 |
349 | copy.bgColor = this.bgColor;
350 | copy.spriteVisible = this.spriteVisible;
351 | copy.bgVisible = this.bgVisible;
352 | copy.spriteClip = this.spriteClip;
353 | copy.bgClip = this.bgClip;
354 | copy.colorDisplay = this.colorDisplay;
355 |
356 | copy.isVBlank = this.isVBlank;
357 | copy.sprite0Occur = this.sprite0Occur;
358 | copy.spriteOver = this.spriteOver;
359 | copy.vramWriteFlag = this.vramWriteFlag;
360 |
361 | copy.sprramAdr = this.sprramAdr;
362 | copy.ppuAdrT = this.ppuAdrT;
363 | copy.ppuAdrV = this.ppuAdrV;
364 | copy.ppuAdrX = this.ppuAdrX;
365 | copy.ppuAdrToggle = this.ppuAdrToggle;
366 | copy.ppuReadBuf = this.ppuReadBuf;
367 |
368 | copy.joypadStrobe = this.joypadStrobe;
369 | copy.frameIrq = this.frameIrq;
370 |
371 |
372 |
373 | return copy;
374 | }
375 |
376 | public void loadState(SaveState saveState) {
377 | Regs copy = saveState.getRegsData();
378 |
379 | this.nmiEnable = copy.nmiEnable;
380 | this.spriteSize = copy.spriteSize;
381 | this.bgPatAdr = copy.bgPatAdr;
382 | this.spritePatAdr = copy.spritePatAdr;
383 | this.ppuAdrIncr = copy.ppuAdrIncr;
384 | this.nameTblAdr = copy.nameTblAdr;
385 |
386 |
387 | this.bgColor = copy.bgColor;
388 | this.spriteVisible = copy.spriteVisible;
389 | this.bgVisible = copy.bgVisible;
390 | this.spriteClip = copy.spriteClip;
391 | this.bgClip = copy.bgClip;
392 | this.colorDisplay = copy.colorDisplay;
393 |
394 | this.isVBlank = copy.isVBlank;
395 | this.sprite0Occur = copy.sprite0Occur;
396 | this.spriteOver = copy.spriteOver;
397 | this.vramWriteFlag = copy.vramWriteFlag;
398 |
399 | this.sprramAdr = copy.sprramAdr;
400 | this.ppuAdrT = copy.ppuAdrT;
401 | this.ppuAdrV = copy.ppuAdrV;
402 | this.ppuAdrX = copy.ppuAdrX;
403 | this.ppuAdrToggle = copy.ppuAdrToggle;
404 | this.ppuReadBuf = copy.ppuReadBuf;
405 |
406 | this.joypadStrobe = copy.joypadStrobe;
407 | this.frameIrq = copy.frameIrq;
408 |
409 |
410 | }
411 | }
412 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/jp/tanakh/bjne/nes/Renderer.java:
--------------------------------------------------------------------------------
1 | package jp.tanakh.bjne.nes;
2 |
3 | public interface Renderer {
4 | public class ScreenInfo {
5 | public byte[] buf;
6 | public int width;
7 | public int height;
8 | public int pitch;
9 | public int bpp;
10 | }
11 |
12 | public class SoundInfo {
13 | public byte[] buf;
14 | public int freq;
15 | public int bps;
16 | public int ch;
17 | public int sample;
18 | }
19 |
20 | public class InputInfo {
21 | public int[] buf;
22 | }
23 |
24 | public ScreenInfo requestScreen(int width, int height);
25 |
26 | public SoundInfo requestSound();
27 |
28 | public InputInfo requestInput(int padCount, int buttonCount);
29 |
30 | public void outputScreen(ScreenInfo info);
31 |
32 | public void outputSound(SoundInfo info);
33 |
34 | public void outputMessage(String msg);
35 | }
36 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/jp/tanakh/bjne/nes/Rom.java:
--------------------------------------------------------------------------------
1 | package jp.tanakh.bjne.nes;
2 |
3 | import java.io.File;
4 | import java.io.FileInputStream;
5 | import java.io.IOException;
6 | import java.util.HashMap;
7 |
8 | public class Rom {
9 |
10 | public Rom() {
11 | }
12 |
13 | public Rom(Nes n) {
14 | }
15 |
16 | public void reset() {
17 |
18 | }
19 |
20 | public void release() {
21 | romDat = null;
22 | chrDat = null;
23 | sram = null;
24 | vram = null;
25 | }
26 |
27 | String filename = "";
28 |
29 | public void load(String fname) throws IOException {
30 | release();
31 |
32 | if(fname.length() > 2)
33 | {
34 | filename = fname.substring(fname.lastIndexOf(File.separatorChar)+1);
35 | }
36 |
37 | System.out.println("filename: "+filename);
38 |
39 | FileInputStream is = new FileInputStream(fname);
40 | byte[] dat = new byte[is.available()];
41 | is.read(dat, 0, dat.length);
42 |
43 | if (!(dat[0] == 'N' && dat[1] == 'E' && dat[2] == 'S' && dat[3] == '\u001A'))
44 | throw new IOException("rom signature is invalid");
45 |
46 | prgPageCnt = dat[4] & 0xff;
47 | chrPageCnt = dat[5] & 0xff;
48 |
49 | mirroring = (dat[6] & 1) != 0 ? MirrorType.VERTICAL
50 | : MirrorType.HORIZONTAL;
51 | sramEnable = (dat[6] & 2) != 0;
52 | trainerEnable = (dat[6] & 4) != 0;
53 | fourScreen = (dat[6] & 8) != 0;
54 |
55 | mapper = ((dat[6] & 0xff) >> 4) | (dat[7] & 0xf0);
56 |
57 | int romSize = 0x4000 * prgPageCnt;
58 | int chrSize = 0x2000 * chrPageCnt;
59 |
60 | romDat = new byte[romSize];
61 | if (chrSize != 0)
62 | chrDat = new byte[chrSize];
63 | sram = new byte[0x2000];
64 | vram = new byte[0x2000];
65 |
66 | if (romSize > 0)
67 | System.arraycopy(dat, 16, romDat, 0, romSize);
68 | if (chrSize > 0)
69 | System.arraycopy(dat, 16 + romSize, chrDat, 0, chrSize);
70 |
71 | System.out.printf("Cartridge information:\n");
72 | System.out.printf("%d KB rom, %d KB vrom\n", romSize / 1024,
73 | chrSize / 1024);
74 | System.out.printf("mapper #%d\n", mapper);
75 | System.out.printf("%s mirroring\n",
76 | mirroring == MirrorType.VERTICAL ? "vertical" : "holizontal");
77 | System.out.printf("sram : %s\n", sramEnable ? "Y" : "N");
78 | System.out.printf("trainer : %s\n", trainerEnable ? "Y" : "N");
79 | System.out.printf("four screen : %s\n", fourScreen ? "Y" : "N");
80 | }
81 |
82 |
83 |
84 |
85 |
86 | public void loadState(SaveState saveState) {
87 |
88 | /*
89 | if (romSize() > 0)
90 | System.arraycopy(saveState.getRom().romDat, 16, romDat, 0, romSize());
91 | if (chrSize() > 0)
92 | System.arraycopy(saveState.getRom().chrDat, 16 + romSize(), chrDat, 0, chrSize() );
93 |
94 | System.arraycopy(saveState.getRom().sram, 0, sram, 0, 0x2000);
95 |
96 | System.arraycopy(saveState.getRom().vram, 0, vram, 0, 0x2000);
97 | */
98 |
99 | }
100 |
101 |
102 | public byte[] getRom() {
103 | return romDat;
104 | }
105 |
106 | public byte[] getChr() {
107 | return chrDat;
108 | }
109 |
110 | public byte[] getSram() {
111 | return sram;
112 | }
113 |
114 | public byte[] getVram() {
115 | return vram;
116 | }
117 |
118 | public int romSize() {
119 | return prgPageCnt;
120 | }
121 |
122 | public int chrSize() {
123 | return chrPageCnt;
124 | }
125 |
126 | public int mapperNo() {
127 | return mapper;
128 | }
129 |
130 | public boolean hasSram() {
131 | return sramEnable;
132 | }
133 |
134 | public boolean hasTrainer() {
135 | return trainerEnable;
136 | }
137 |
138 | public boolean isFourScreen() {
139 | return fourScreen;
140 | }
141 |
142 | public enum MirrorType {
143 | HORIZONTAL, VERTICAL,
144 | }
145 |
146 | public MirrorType mirror() {
147 | return mirroring;
148 | }
149 |
150 | private int prgPageCnt, chrPageCnt;
151 | private MirrorType mirroring;
152 | private boolean sramEnable, trainerEnable;
153 | private boolean fourScreen;
154 | private int mapper;
155 | private byte[] romDat, chrDat, sram, vram;
156 |
157 | public String getROMName()
158 | {
159 | return filename;
160 | }
161 |
162 | public Rom getCopy()
163 | {
164 | Rom copy = new Rom();
165 |
166 |
167 |
168 | copy.prgPageCnt = this.prgPageCnt;
169 | copy.chrPageCnt = this.chrPageCnt;
170 | copy.mirroring = this.mirroring;
171 |
172 | copy.sramEnable = this.sramEnable;
173 | copy.trainerEnable = this.trainerEnable;
174 | copy.fourScreen = this.fourScreen;
175 |
176 | copy.mapper = this.mapper;
177 |
178 | int romSize = 0x4000 * copy.prgPageCnt;
179 | int chrSize = 0x2000 * copy.chrPageCnt;
180 |
181 | copy.romDat = new byte[romSize];
182 | if (chrSize != 0)
183 | copy.chrDat = new byte[chrSize];
184 | copy.sram = new byte[0x2000];
185 | copy.vram = new byte[0x2000];
186 |
187 | if (romSize > 0)
188 | System.arraycopy(this.romDat, 0, copy.romDat, 0, romSize);
189 | if (chrSize > 0)
190 | System.arraycopy(this.chrDat, 0 , copy.chrDat, 0, chrSize);
191 |
192 | System.arraycopy(this.sram, 0, copy.sram, 0, 0x2000);
193 |
194 | System.arraycopy(this.vram, 0, copy.vram, 0, 0x2000);
195 |
196 | return copy;
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/jp/tanakh/bjne/nes/SaveState.java:
--------------------------------------------------------------------------------
1 | package jp.tanakh.bjne.nes;
2 |
3 | public class SaveState {
4 |
5 | Cpu cpuData;
6 | //Rom romData;
7 | Mbc mbcData;
8 |
9 | Regs regsData;
10 | Apu apuData;
11 |
12 |
13 |
14 | Ppu ppuData;
15 |
16 | public SaveState(Regs regs, Mbc mbc, Cpu cpu, Apu apu, Ppu ppu) {
17 | cpuData = cpu.getCopy();
18 | regsData = regs.getCopy();
19 | mbcData = mbc.getCopy();
20 | apuData = apu.getCopy();
21 | ppuData = ppu.getCopy();
22 | }
23 |
24 |
25 | public Cpu getCpu() {
26 |
27 | return cpuData;
28 | }
29 |
30 |
31 |
32 |
33 | public Regs getRegsData() {
34 | return regsData;
35 | }
36 |
37 |
38 |
39 | public Apu getApuData() {
40 | return apuData;
41 | }
42 |
43 |
44 |
45 | public Ppu getPpuData() {
46 | return ppuData;
47 | }
48 |
49 |
50 | public Mbc getMbc() {
51 |
52 | return mbcData;
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/jp/tanakh/bjne/nes/mapper/CNROM.java:
--------------------------------------------------------------------------------
1 | package jp.tanakh.bjne.nes.mapper;
2 |
3 | import jp.tanakh.bjne.nes.MapperAdapter;
4 | import jp.tanakh.bjne.nes.Nes;
5 |
6 | public class CNROM extends MapperAdapter {
7 | public CNROM(Nes n) {
8 | nes = n;
9 | reset();
10 | }
11 |
12 | @Override
13 | public int mapperNo() {
14 | return 3;
15 | }
16 |
17 | @Override
18 | public void reset() {
19 | for (int i = 0; i < 4; i++)
20 | nes.getMbc().mapRom(i, i);
21 | for (int i = 0; i < 8; i++)
22 | nes.getMbc().mapVrom(i, i);
23 | }
24 |
25 | @Override
26 | public void write(short adr, byte dat) {
27 | for (int i = 0; i < 8; i++)
28 | nes.getMbc().mapVrom(i, (dat & 0xff) * 8 + i);
29 | }
30 |
31 | private Nes nes;
32 | }
33 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/jp/tanakh/bjne/nes/mapper/MMC1.java:
--------------------------------------------------------------------------------
1 | package jp.tanakh.bjne.nes.mapper;
2 |
3 | import jp.tanakh.bjne.nes.MapperAdapter;
4 | import jp.tanakh.bjne.nes.Nes;
5 | import jp.tanakh.bjne.nes.Ppu;
6 |
7 | public class MMC1 extends MapperAdapter {
8 | public MMC1(Nes n) {
9 | nes = n;
10 | reset();
11 | }
12 |
13 | @Override
14 | public int mapperNo() {
15 | return 1;
16 | }
17 |
18 | @Override
19 | public void reset() {
20 | romSize = nes.getRom().romSize();
21 | sizeInKB = romSize * 16;
22 |
23 | cnt = buf = 0;
24 | mirror = false;
25 | oneScreen = false;
26 | switchArea = true;
27 | switchSize = true;
28 | vromSwitchSize = false;
29 | swapBase = 0;
30 | vromPage[0] = vromPage[1] = 1;
31 | romPage[0] = 0;
32 | romPage[1] = romSize - 1;
33 |
34 | setBank();
35 | }
36 |
37 | private static boolean _bit(int x, int n) {
38 | return ((x >> n) & 1) != 0;
39 | }
40 |
41 | @Override
42 | public void write(short sadr, byte sdat){
43 | int adr = sadr & 0xffff;
44 | int dat = sdat & 0xff;
45 |
46 | if ((dat&0x80)!=0){
47 | cnt=0;buf=0;
48 | return;
49 | }
50 | buf|=((dat&1)<=0x8000&&adr<=0x9FFF){ // reg0
57 | mirror =_bit(dat,0);
58 | oneScreen =_bit(dat,1);
59 | switchArea =_bit(dat,2);
60 | switchSize =_bit(dat,3);
61 | vromSwitchSize=_bit(dat,4);
62 |
63 | setMirroring();
64 | }
65 | else if (adr>=0xA000&&adr<=0xBFFF){ // reg1
66 | if (sizeInKB==512){
67 | swapBase=_bit(dat,4)?16:0;
68 | setBank();
69 | }
70 | else if (sizeInKB==1024){
71 | }
72 |
73 | vromPage[0]=dat&0xf;
74 | if (vromSwitchSize){
75 | nes.getMbc().mapVrom(0,vromPage[0]*4);
76 | nes.getMbc().mapVrom(1,vromPage[0]*4+1);
77 | nes.getMbc().mapVrom(2,vromPage[0]*4+2);
78 | nes.getMbc().mapVrom(3,vromPage[0]*4+3);
79 | }
80 | else{
81 | nes.getMbc().mapVrom(0,vromPage[0]*4);
82 | nes.getMbc().mapVrom(1,vromPage[0]*4+1);
83 | nes.getMbc().mapVrom(2,vromPage[0]*4+2);
84 | nes.getMbc().mapVrom(3,vromPage[0]*4+3);
85 | nes.getMbc().mapVrom(4,vromPage[0]*4+4);
86 | nes.getMbc().mapVrom(5,vromPage[0]*4+5);
87 | nes.getMbc().mapVrom(6,vromPage[0]*4+6);
88 | nes.getMbc().mapVrom(7,vromPage[0]*4+7);
89 | }
90 | }
91 | else if (adr>=0xC000&&adr<=0xDFFF){ // reg2
92 | vromPage[1]=dat&0xf;
93 | if (vromSwitchSize){
94 | nes.getMbc().mapVrom(4,vromPage[1]*4);
95 | nes.getMbc().mapVrom(5,vromPage[1]*4+1);
96 | nes.getMbc().mapVrom(6,vromPage[1]*4+2);
97 | nes.getMbc().mapVrom(7,vromPage[1]*4+3);
98 | }
99 |
100 | if (sizeInKB==1024){
101 | }
102 | }
103 | else if (adr>=0xE000){ // reg3
104 | if (switchSize){ // 16K
105 | if (switchArea){
106 | romPage[0]=dat&0xf;
107 | romPage[1]=(romSize-1)&0xf;
108 | }
109 | else{
110 | romPage[0]=0;
111 | romPage[1]=dat&0xf;
112 | }
113 | }
114 | else{ // 32K
115 | romPage[0]=dat&0xe;
116 | romPage[1]=(dat&0xe)|1;
117 | }
118 | setBank();
119 | }
120 | }
121 |
122 | private void setBank() {
123 | nes.getMbc().mapRom(0, (swapBase | romPage[0]) * 2);
124 | nes.getMbc().mapRom(1, (swapBase | romPage[0]) * 2 + 1);
125 | nes.getMbc().mapRom(2, (swapBase | romPage[1]) * 2);
126 | nes.getMbc().mapRom(3, (swapBase | romPage[1]) * 2 + 1);
127 | }
128 |
129 | private void setMirroring() {
130 | if (oneScreen)
131 | nes.getPpu().setMirroring(
132 | mirror ? Ppu.MirrorType.HOLIZONTAL
133 | : Ppu.MirrorType.VERTICAL);
134 | else if (mirror)
135 | nes.getPpu().setMirroring(1, 1, 1, 1);
136 | else
137 | nes.getPpu().setMirroring(0, 0, 0, 0);
138 | }
139 |
140 | int romSize, sizeInKB;
141 | int cnt, buf;
142 | boolean mirror, oneScreen, switchArea, switchSize, vromSwitchSize;
143 | int swapBase;
144 | int[] vromPage = new int[2];
145 | int[] romPage = new int[2];
146 |
147 | private Nes nes;
148 | }
149 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/jp/tanakh/bjne/nes/mapper/MMC3.java:
--------------------------------------------------------------------------------
1 | package jp.tanakh.bjne.nes.mapper;
2 |
3 | import jp.tanakh.bjne.nes.MapperAdapter;
4 | import jp.tanakh.bjne.nes.Nes;
5 | import jp.tanakh.bjne.nes.Ppu;
6 |
7 | /**
8 | * Mapper 4: MMC3
9 | */
10 | public class MMC3 extends MapperAdapter {
11 | public MMC3(Nes n) {
12 | nes = n;
13 | }
14 |
15 | @Override
16 | public int mapperNo() {
17 | return 4;
18 | }
19 |
20 | @Override
21 | public void reset() {
22 | romSize = nes.getRom().romSize();
23 | prgPage[0] = 0;
24 | prgPage[1] = 1;
25 | chrPage[0] = 0;
26 | chrPage[1] = 1;
27 | chrPage[2] = 2;
28 | chrPage[3] = 3;
29 | chrPage[4] = 4;
30 | chrPage[5] = 5;
31 | chrPage[6] = 6;
32 | chrPage[7] = 7;
33 |
34 | prgSwap = false;
35 | chrSwap = false;
36 |
37 | setRom();
38 | setVrom();
39 | }
40 |
41 | private static boolean _bit(int x, int n) {
42 | return ((x >> n) & 1) != 0;
43 | }
44 |
45 | @Override
46 | public void write(short adr, byte bdat) {
47 | int dat = bdat & 0xff;
48 | switch (adr & 0xE001) {
49 | case 0x8000:
50 | cmd = dat & 7;
51 | prgSwap = _bit(dat, 6);
52 | chrSwap = _bit(dat, 7);
53 | break;
54 |
55 | case 0x8001:
56 | switch (cmd) {
57 | case 0: // Select 2 1K VROM pages at PPU $0000
58 | chrPage[0] = dat & 0xfe;
59 | chrPage[1] = (dat & 0xfe) + 1;
60 | setVrom();
61 | break;
62 | case 1: // Select 2 1K VROM pages at PPU $0800
63 | chrPage[2] = dat & 0xfe;
64 | chrPage[3] = (dat & 0xfe) + 1;
65 | setVrom();
66 | break;
67 | case 2: // Select 1K VROM pages at PPU $1000
68 | chrPage[4] = dat;
69 | setVrom();
70 | break;
71 | case 3: // Select 1K VROM pages at PPU $1400
72 | chrPage[5] = dat;
73 | setVrom();
74 | break;
75 | case 4: // Select 1K VROM pages at PPU $1800
76 | chrPage[6] = dat;
77 | setVrom();
78 | break;
79 | case 5: // Select 1K VROM pages at PPU $1C00
80 | chrPage[7] = dat;
81 | setVrom();
82 | break;
83 | case 6: // Select first switchable ROM page
84 | prgPage[0] = dat;
85 | setRom();
86 | break;
87 | case 7: // Select second switchable ROM page
88 | prgPage[1] = dat;
89 | setRom();
90 | break;
91 | }
92 | break;
93 |
94 | case 0xA000:
95 | if (!nes.getRom().isFourScreen())
96 | nes.getPpu().setMirroring(
97 | (dat & 1) != 0 ? Ppu.MirrorType.HOLIZONTAL
98 | : Ppu.MirrorType.VERTICAL);
99 | break;
100 | case 0xA001:
101 | if ((dat & 0x80) != 0)
102 | ; // enable SRAM
103 | else
104 | ; // disable SRAM
105 | break;
106 |
107 | case 0xC000:
108 | irqCounter = dat;
109 | break;
110 | case 0xC001:
111 | irqLatch = dat;
112 | break;
113 |
114 | case 0xE000:
115 | irqEnable = false;
116 | irqCounter = irqLatch;
117 | break;
118 | case 0xE001:
119 | irqEnable = true;
120 | break;
121 | }
122 | }
123 |
124 | @Override
125 | public void hblank(int line) {
126 | if (irqEnable && line >= 0 && line < 239 && nes.getRegs().drawEnabled()) {
127 |
128 | if ((irqCounter--) == 0) {
129 | irqCounter = irqLatch;
130 | nes.getCpu().setIrq(true);
131 | }
132 | }
133 | }
134 |
135 | private void setRom() {
136 | if (prgSwap) {
137 | nes.getMbc().mapRom(0, (romSize - 1) * 2);
138 | nes.getMbc().mapRom(1, prgPage[1]);
139 | nes.getMbc().mapRom(2, prgPage[0]);
140 | nes.getMbc().mapRom(3, (romSize - 1) * 2 + 1);
141 | } else {
142 | nes.getMbc().mapRom(0, prgPage[0]);
143 | nes.getMbc().mapRom(1, prgPage[1]);
144 | nes.getMbc().mapRom(2, (romSize - 1) * 2);
145 | nes.getMbc().mapRom(3, (romSize - 1) * 2 + 1);
146 | }
147 | }
148 |
149 | private void setVrom() {
150 | for (int i = 0; i < 8; i++)
151 | nes.getMbc().mapVrom((i + (chrSwap ? 4 : 0)) % 8, chrPage[i]);
152 | }
153 |
154 | private int romSize;
155 |
156 | private int cmd;
157 | private boolean prgSwap, chrSwap;
158 |
159 | private int irqCounter, irqLatch;
160 | private boolean irqEnable;
161 |
162 | private int[] prgPage = new int[2];
163 | private int[] chrPage = new int[8];
164 |
165 | private Nes nes;
166 | }
167 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/jp/tanakh/bjne/nes/mapper/NullMapper.java:
--------------------------------------------------------------------------------
1 | package jp.tanakh.bjne.nes.mapper;
2 |
3 | import jp.tanakh.bjne.nes.MapperAdapter;
4 | import jp.tanakh.bjne.nes.Nes;
5 |
6 | public class NullMapper extends MapperAdapter {
7 | public NullMapper(Nes n){
8 | }
9 |
10 | @Override
11 | public int mapperNo() {
12 | return 0;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/jp/tanakh/bjne/nes/mapper/Rambo1.java:
--------------------------------------------------------------------------------
1 | package jp.tanakh.bjne.nes.mapper;
2 |
3 | import jp.tanakh.bjne.nes.MapperAdapter;
4 | import jp.tanakh.bjne.nes.Nes;
5 | import jp.tanakh.bjne.nes.Ppu;
6 |
7 | /**
8 | * Mapper 64: Tengen Rambo
9 | *
10 | * PRG is memory for the CPU
11 | * CHR is memory for the picture processing unit
12 | *
13 | */
14 | public class Rambo1 extends MapperAdapter {
15 | public Rambo1(Nes n) {
16 | nes = n;
17 | }
18 |
19 | @Override
20 | public int mapperNo() {
21 | return 64;
22 | }
23 |
24 | @Override
25 | public void reset() {
26 |
27 | romSize = nes.getRom().romSize();
28 | prgPage[0] = 0;
29 | prgPage[1] = 1;
30 | prgPage[2] = 2;
31 | chrPage[0] = 0;
32 | chrPage[1] = 1;
33 | chrPage[2] = 2;
34 | chrPage[3] = 3;
35 | chrPage[4] = 4;
36 | chrPage[5] = 5;
37 | chrPage[6] = 6;
38 | chrPage[7] = 7;
39 | chrPage[8] = 8;
40 | chrPage[9] = 9;
41 | chrPage[15] = 15; //is this right?
42 |
43 | prgSwap = false;
44 | chrSwap = false;
45 |
46 | setRom();
47 | setVrom();
48 | }
49 |
50 | private static boolean _bit(int x, int n) {
51 | return ((x >> n) & 1) != 0;
52 | }
53 |
54 | @Override
55 | public void write(short adr, byte bdat) {
56 | System.out.println("addr" + adr);
57 | System.out.println("bdat" + bdat);
58 |
59 | int dat = bdat & 0xff;
60 | switch (adr & 0xE001) {
61 | case 0x8000:
62 | cmd = dat & 7;
63 | prgSwap = _bit(dat, 6);
64 | chrSwap = _bit(dat, 7);
65 | break;
66 |
67 |
68 |
69 | //BANK SELECT
70 | case 0x8001:
71 | switch (cmd) {
72 | case 0: // Select 2 1K VROM pages at PPU $0000
73 | chrPage[0] = dat & 0xfe;
74 | chrPage[1] = (dat & 0xfe) + 1;
75 | setVrom();
76 | break;
77 | case 1: // Select 2 1K VROM pages at PPU $0800
78 | chrPage[2] = dat & 0xfe;
79 | chrPage[3] = (dat & 0xfe) + 1;
80 | setVrom();
81 | break;
82 | case 2: // Select 1K VROM pages at PPU $1000
83 | chrPage[4] = dat;
84 | setVrom();
85 | break;
86 | case 3: // Select 1K VROM pages at PPU $1400
87 | chrPage[5] = dat;
88 | setVrom();
89 | break;
90 | case 4: // Select 1K VROM pages at PPU $1800
91 | chrPage[6] = dat;
92 | setVrom();
93 | break;
94 | case 5: // Select 1K VROM pages at PPU $1C00
95 | chrPage[7] = dat;
96 | setVrom();
97 | break;
98 | case 6: // Select first switchable ROM page
99 | prgPage[0] = dat;
100 | setRom();
101 | break;
102 | case 7: // Select second switchable ROM page
103 | prgPage[1] = dat;
104 | setRom();
105 | break;
106 | case 8: //Select 1K VROM page at PPU $0400
107 | chrPage[8] = dat;
108 | setVrom();
109 | break;
110 | case 9: //Select 1K VROM page at PPU $0C00
111 | chrPage[9] = dat;
112 | setVrom();
113 | break;
114 | case 15: //Select third switchable ROM page
115 | prgPage[2] = dat;
116 | setRom();
117 | break;
118 | }
119 | break;
120 |
121 | case 0xA000:
122 | if (!nes.getRom().isFourScreen())
123 | nes.getPpu().setMirroring(
124 | (dat & 1) != 0 ? Ppu.MirrorType.HOLIZONTAL
125 | : Ppu.MirrorType.VERTICAL);
126 | break;
127 | case 0xA001:
128 | if ((dat & 0x80) != 0)
129 | ; // enable SRAM
130 | else
131 | ; // disable SRAM
132 | break;
133 |
134 | case 0xC000:
135 | irqCounter = dat;
136 | break;
137 | case 0xC001:
138 | irqLatch = dat;
139 | break;
140 |
141 | case 0xE000:
142 | irqEnable = false;
143 | irqCounter = irqLatch;
144 | break;
145 | case 0xE001:
146 | irqEnable = true;
147 | break;
148 | }
149 | }
150 |
151 | @Override
152 | public void hblank(int line) {
153 | if (irqEnable && line >= 0 && line < 239 && nes.getRegs().drawEnabled()) {
154 |
155 | if ((irqCounter--) == 0) {
156 | irqCounter = irqLatch;
157 | nes.getCpu().setIrq(true);
158 | }
159 | }
160 | }
161 |
162 | //fix me!
163 | private void setRom() {
164 | if (prgSwap) {
165 | nes.getMbc().mapRom(0, (romSize - 1) * 2);
166 | nes.getMbc().mapRom(1, prgPage[1]);
167 | nes.getMbc().mapRom(2, prgPage[0]);
168 | nes.getMbc().mapRom(3, (romSize - 1) * 2 +1);
169 | } else {
170 | nes.getMbc().mapRom(0, prgPage[0]);
171 | nes.getMbc().mapRom(1, prgPage[1]);
172 | nes.getMbc().mapRom(2, (romSize - 1) * 2);
173 | nes.getMbc().mapRom(3, (romSize - 1) * 2 + 1);
174 | }
175 | }
176 |
177 | private void setVrom() {
178 | for (int i = 0; i < 8; i++)
179 | nes.getMbc().mapVrom((i + (chrSwap ? 4 : 0)) % 8, chrPage[i]);
180 | }
181 |
182 | private int romSize;
183 |
184 | private int cmd;
185 | private boolean prgSwap, chrSwap;
186 |
187 | private int irqCounter, irqLatch;
188 | private boolean irqEnable;
189 |
190 | private int[] prgPage = new int[3];
191 | private int[] chrPage = new int[16];
192 |
193 | private Nes nes;
194 | }
195 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/jp/tanakh/bjne/nes/mapper/UNROM.java:
--------------------------------------------------------------------------------
1 | package jp.tanakh.bjne.nes.mapper;
2 |
3 | import jp.tanakh.bjne.nes.MapperAdapter;
4 | import jp.tanakh.bjne.nes.Nes;
5 |
6 | public class UNROM extends MapperAdapter {
7 | public UNROM(Nes n) {
8 | nes = n;
9 | reset();
10 | }
11 |
12 | @Override
13 | public int mapperNo() {
14 | return 2;
15 | }
16 |
17 | @Override
18 | public void reset() {
19 | int romSize = nes.getRom().romSize();
20 | nes.getMbc().mapRom(0, 0);
21 | nes.getMbc().mapRom(1, 1);
22 | nes.getMbc().mapRom(2, (romSize - 1) * 2);
23 | nes.getMbc().mapRom(3, (romSize - 1) * 2 + 1);
24 | }
25 |
26 | @Override
27 | public void write(short adr, byte dat) {
28 | nes.getMbc().mapRom(0, dat * 2);
29 | nes.getMbc().mapRom(1, dat * 2 + 1);
30 | }
31 |
32 | private Nes nes;
33 | }
34 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/jp/tanakh/bjne/nes/mapper/VRC6.java:
--------------------------------------------------------------------------------
1 | package jp.tanakh.bjne.nes.mapper;
2 |
3 | import java.util.LinkedList;
4 | import java.util.Queue;
5 |
6 | import jp.tanakh.bjne.nes.MapperAdapter;
7 | import jp.tanakh.bjne.nes.Nes;
8 | import jp.tanakh.bjne.nes.Ppu;
9 | import jp.tanakh.bjne.nes.Renderer.SoundInfo;
10 |
11 | public class VRC6 extends MapperAdapter {
12 | public VRC6(Nes n) {
13 | nes = n;
14 | }
15 |
16 | @Override
17 | public int mapperNo() {
18 | return 24;
19 | }
20 |
21 | @Override
22 | public void reset() {
23 | int romSize = nes.getRom().romSize();
24 |
25 | nes.getMbc().mapRom(0, 0);
26 | nes.getMbc().mapRom(1, 1);
27 | nes.getMbc().mapRom(2, (romSize - 1) * 2);
28 | nes.getMbc().mapRom(3, (romSize - 1) * 2 + 1);
29 |
30 | irqCount = 0;
31 | irqEnable = 0;
32 | irqLatch = 0;
33 | befClk = 0;
34 |
35 | sq[0] = new SqState();
36 | sq[1] = new SqState();
37 | saw = new SawState();
38 |
39 | writeQueue = new LinkedList();
40 | }
41 |
42 | @Override
43 | public void write(short sadr, byte sdat) {
44 | int adr = sadr & 0xffff;
45 | int dat = sdat & 0xff;
46 |
47 | switch (adr & 0xF003) {
48 | case 0x8000: // Select 16K ROM bank at $8000
49 | nes.getMbc().mapRom(0, dat * 2);
50 | nes.getMbc().mapRom(1, dat * 2 + 1);
51 | break;
52 | case 0xC000: // Select 8K ROM bank at $C000
53 | nes.getMbc().mapRom(2, dat);
54 | break;
55 |
56 | case 0xD000: // Select 1K VROM bank at PPU $0000
57 | nes.getMbc().mapVrom(0, dat);
58 | break;
59 | case 0xD001: // Select 1K VROM bank at PPU $0400
60 | nes.getMbc().mapVrom(1, dat);
61 | break;
62 | case 0xD002: // Select 1K VROM bank at PPU $0800
63 | nes.getMbc().mapVrom(2, dat);
64 | break;
65 | case 0xD003: // Select 1K VROM bank at PPU $0C00
66 | nes.getMbc().mapVrom(3, dat);
67 | break;
68 | case 0xE000: // Select 1K VROM bank at PPU $1000
69 | nes.getMbc().mapVrom(4, dat);
70 | break;
71 | case 0xE001: // Select 1K VROM bank at PPU $1400
72 | nes.getMbc().mapVrom(5, dat);
73 | break;
74 | case 0xE002: // Select 1K VROM bank at PPU $1800
75 | nes.getMbc().mapVrom(6, dat);
76 | break;
77 | case 0xE003: // Select 1K VROM bank at PPU $1C00
78 | nes.getMbc().mapVrom(7, dat);
79 | break;
80 |
81 | case 0xB003:
82 | switch ((dat >> 2) & 3) {
83 | case 0: // Horizontal mirroring
84 | nes.getPpu().setMirroring(Ppu.MirrorType.VERTICAL);
85 | break;
86 | case 1: // Vertical mirroring
87 | nes.getPpu().setMirroring(Ppu.MirrorType.HOLIZONTAL);
88 | break;
89 | case 2: // Mirror page from $2000
90 | nes.getPpu().setMirroring(0, 0, 0, 0);
91 | break;
92 | case 3: // Mirror page from $2400
93 | nes.getPpu().setMirroring(1, 1, 1, 1);
94 | break;
95 | }
96 | break;
97 |
98 | case 0xF000:
99 | irqLatch = dat;
100 | break;
101 | case 0xF001:
102 | irqEnable = dat & 3;
103 | if ((irqEnable & 2) != 0)
104 | irqCount = irqLatch;
105 | break;
106 | case 0xF002:
107 | if ((irqEnable & 1) != 0)
108 | irqEnable |= 2;
109 | else
110 | irqEnable &= 1;
111 | break;
112 |
113 | case 0x9000:
114 | case 0x9001:
115 | case 0x9002:
116 | case 0xA000:
117 | case 0xA001:
118 | case 0xA002:
119 | case 0xB000:
120 | case 0xB001:
121 | case 0xB002:
122 | writeQueue.add(new WriteDat(nes.getCpu().getMasterClock(), sadr,
123 | sdat));
124 | while (writeQueue.size() > 1000) {
125 | WriteDat wd = writeQueue.remove();
126 | sndWrite(wd.adr, wd.dat);
127 | }
128 | break;
129 | }
130 | }
131 |
132 | private void sndWrite(short sadr, byte sdat) {
133 | int adr = sadr & 0xffff;
134 | int dat = sdat & 0xff;
135 | switch (adr & 0xF003) {
136 | case 0x9000:
137 | case 0xA000:
138 | sq[(adr >> 12) - 9].duty = ((dat >> 4) & 7);
139 | sq[(adr >> 12) - 9].volume = dat & 0xF;
140 | sq[(adr >> 12) - 9].gate = (dat >> 7) != 0;
141 | break;
142 | case 0x9001:
143 | case 0xA001:
144 | sq[(adr >> 12) - 9].freq = (sq[(adr >> 12) - 9].freq & ~0xFF) | dat;
145 | break;
146 | case 0x9002:
147 | case 0xA002:
148 | sq[(adr >> 12) - 9].freq = (sq[(adr >> 12) - 9].freq & 0xFF)
149 | | ((dat & 0xF) << 8);
150 | sq[(adr >> 12) - 9].enable = (dat >> 7) != 0;
151 | break;
152 |
153 | case 0xB000:
154 | saw.phase = dat & 0x3F;
155 | break;
156 | case 0xB001:
157 | saw.freq = (saw.freq & ~0xFF) | dat;
158 | break;
159 | case 0xB002:
160 | saw.freq = (saw.freq & 0xFF) | ((dat & 0xF) << 8);
161 | saw.enable = (dat >> 7) != 0;
162 | break;
163 | }
164 | }
165 |
166 | @Override
167 | public void hblank(int line) {
168 | if ((irqEnable & 2) != 0) {
169 | if (irqCount >= 0xFF) {
170 | nes.getCpu().setIrq(true);
171 | irqCount = irqLatch;
172 | } else
173 | irqCount++;
174 | }
175 | }
176 |
177 | @Override
178 | public void audio(SoundInfo info) {
179 | double cpuClk = nes.getCpu().getFrequency();
180 | double sampleClk = cpuClk / info.freq;
181 | long curClk = nes.getCpu().getMasterClock();
182 | int span = info.bps / 8 * info.ch;
183 |
184 | for (int i = 0; i < info.sample; i++) {
185 | // long cur = (curClk - befClk) * i / info.sample + befClk;
186 | long cur = (long) (befClk + sampleClk * i);
187 | while (!writeQueue.isEmpty() && cur >= writeQueue.peek().clk) {
188 | WriteDat wd = writeQueue.remove();
189 | sndWrite(wd.adr, wd.dat);
190 | }
191 |
192 | double d = (sqProduce(sq[0], sampleClk)
193 | + sqProduce(sq[1], sampleClk) + sawProduce(sampleClk)) / 32.0;
194 |
195 | if (info.bps == 16) {
196 | short v = (short) (d * 8000);
197 | info.buf[i * span + 0] = (byte) (v & 0xff);
198 | info.buf[i * span + 1] = (byte) (v >> 8);
199 | if (info.ch == 2) {
200 | info.buf[i * span + 2] = (byte) (v & 0xff);
201 | info.buf[i * span + 3] = (byte) (v >> 8);
202 | }
203 | } else if (info.bps == 8) {
204 | info.buf[i * span + 0] = (byte) (d * 30);
205 | if (info.ch == 2) {
206 | info.buf[i * span + 1] = (byte) (d * 30);
207 | }
208 | }
209 | }
210 |
211 | befClk = curClk;
212 | }
213 |
214 | private int irqLatch, irqCount, irqEnable;
215 |
216 | private class SqState {
217 | int duty, volume;
218 | boolean gate;
219 | int freq;
220 | boolean enable;
221 |
222 | int step;
223 | double clk;
224 | }
225 |
226 | private SqState[] sq = new SqState[2];
227 |
228 | class SawState {
229 | int phase;
230 | int freq;
231 | boolean enable;
232 |
233 | int step;
234 | double clk;
235 | }
236 |
237 | private SawState saw;
238 |
239 | private int sqProduce(SqState sq, double clk) {
240 | if (!sq.enable)
241 | return 0;
242 | if (sq.gate)
243 | return sq.volume;
244 | sq.clk += clk;
245 | int adv = (int) (sq.clk / (sq.freq + 1));
246 | sq.clk -= (sq.freq + 1) * adv;
247 | sq.step = ((sq.step + adv) % 16);
248 | return ((sq.step > sq.duty) ? 1 : 0) * sq.volume;
249 | }
250 |
251 | private int sawProduce(double clk) {
252 | if (!saw.enable)
253 | return 0;
254 | saw.clk += clk;
255 | int adv = (int) (saw.clk / (saw.freq + 1));
256 | saw.clk -= (saw.freq + 1) * adv;
257 | saw.step = ((saw.step + adv) % 7);
258 | return ((saw.step * saw.phase) >> 3) & 0x1f;
259 | }
260 |
261 | private class WriteDat {
262 | WriteDat(long c, short a, byte d) {
263 | clk = c;
264 | adr = a;
265 | dat = d;
266 | }
267 |
268 | long clk;
269 | short adr;
270 | byte dat;
271 | };
272 |
273 | private Queue writeQueue;
274 |
275 | private long befClk;
276 |
277 | private Nes nes;
278 | }
279 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/jp/tanakh/bjne/nes/mapper/tengenrambo1.txt:
--------------------------------------------------------------------------------
1 | +---------------------------+
2 | Mapper 64: Tengen RAMBO-1
3 | +---------------------------+
4 |
5 | +--------------------------------------------------------------------+
6 | This mapper is used on several U.S. unlicensed titles by Tengen.
7 | They include Shinobi, Klax, and Skull & Crossbones. Thanks to D
8 | for hacking this mapper.
9 | +--------------------------------------------------------------------+
10 |
11 | +-------+ +------------------------------------------------------+
12 | $8000 +--- CPxxNNNN
13 | +-------+ 戌 +--+
14 | 戌 +--- Command Number
15 | 戌 0 - Select 2 1K VROM pages at PPU $0000
16 | 戌 1 - Select 2 1K VROM pages at PPU $0800
17 | 戌 2 - Select 1K VROM page at PPU $1000
18 | 戌 3 - Select 1K VROM page at PPU $1400
19 | 戌 4 - Select 1K VROM page at PPU $1800
20 | 戌 5 - Select 1K VROM page at PPU $1C00
21 | 戌 6 - Select first switchable ROM page
22 | 戌 7 - Select second switchable ROM page
23 | 戌 8 - Select 1K VROM page at PPU $0400
24 | 戌 9 - Select 1K VROM page at PPU $0C00
25 | 戌 15 - Select third switchable ROM page
26 | 戌
27 | -------- PRG Address Select Command Number
28 | -#6- -#7- -#15-
29 | 0 - Enable swapping at $8000/$A000/$C000
30 | 1 - Enable swapping at $A000/$C000/$8000
31 |
32 | +--------- CHR Address Select
33 | 0 - Use normal address for commands 0-5
34 | 1 - XOR command 0-5 address with $1000
35 | +------------------------------------------------------+
36 |
37 | +-------+ +----------------------------------------------+
38 | $8001 +--- PPPPPPPP
39 | +-------+ +------+
40 |
41 |
42 | +------- Page Number for Command
43 | Activates the command number
44 | written to bits 0-2 of $8000
45 | +----------------------------------------------+
46 |
47 | +-------+ +----------------------------------------------+
48 | $A000 +--- xxxxxxxM
49 | +-------+
50 |
51 |
52 | +--- Mirroring Select
53 | 0 - Horizontal mirroring
54 | 1 - Vertical mirroring
55 | NOTE: I don't have any confidence in the
56 | accuracy of this information.
57 | +----------------------------------------------+
58 |
59 | Notes: - Two of the 8K ROM banks in the PRG area are switchable.
60 | The last page is "hard-wired" to the last 8K bank in
61 | the cart.
62 | - At reset, all four 8K banks are set to the last 8K bank
63 | in the cart.
64 | - A cart will first write the command and base select number
65 | to $8000, then the value to be used to $8001.
66 | - On carts with VROM, the first 8K of VROM is swapped into
67 | PPU $0000 on reset. On carts without VROM, as always, there
68 | is 8K of VRAM at PPU $0000.
69 |
70 | ****************************************************************************
71 |
72 |
73 | /*
74 |
75 | http://wiki.nesdev.com/w/index.php/RAMBO-1
76 |
77 | * HalfNES by Andrew Hoffman
78 | * Licensed under the GNU GPL Version 3. See LICENSE file
79 | */
80 | package com.grapeshot.halfnes.mappers;
81 |
82 | import com.grapeshot.halfnes.*;
83 |
84 | /**
85 | *
86 | * @author Andrew
87 | */
88 | public class TengenRamboMapper extends Mapper {
89 |
90 | private int whichbank = 0;
91 | private boolean prgconfig = false, chrconfig = false,
92 | chrmode1k = false, irqmode = false;
93 | private int irqctrreload = 0;
94 | private int irqctr = 0;
95 | private boolean irqenable = false;
96 | private boolean irqreload = false;
97 | private int prgreg0 = 0, prgreg1 = 0, prgreg2 = 0;
98 | private int[] chrreg = new int[8];
99 | private boolean interrupted = false;
100 |
101 | @Override
102 | public void loadrom() throws BadMapperException {
103 | //needs to be in every mapper. Fill with initial cfg
104 | super.loadrom();
105 | //on startup:
106 | for (int i = 0; i < 8; ++i) {
107 | prg_map[i] = (1024 * i);
108 | prg_map[i + 8] = (1024 * i);
109 | //yes this actually matters; MMC3 does NOT start up in a random state
110 | //(at least Smash TV and TMNT3 expect certain banks w/o even setting up mapper)
111 | }
112 | for (int i = 1; i <= 32; ++i) {
113 | prg_map[32 - i] = prgsize - (1024 * i);
114 | }
115 |
116 | for (int i = 0; i < 8; ++i) {
117 | chr_map[i] = 0;
118 | }
119 | setprgregs();
120 | //cpuram.setPrgRAMEnable(false);
121 | }
122 |
123 | @Override
124 | public final void cartWrite(int addr, int data) {
125 | if (addr < 0x8000 || addr > 0xffff) {
126 | super.cartWrite(addr, data);
127 | return;
128 | }
129 | //bankswitches here
130 | //different register for even/odd writes
131 | if (((addr & (utils.BIT0)) != 0)) {
132 | //odd registers
133 | if ((addr >= 0x8000) && (addr <= 0x9fff)) {
134 | //bank change
135 | //System.err.println("setting " + whichbank + " " + data + " " + prgconfig);
136 | switch (whichbank) {
137 | case 0:
138 | case 1:
139 | case 2:
140 | case 3:
141 | case 4:
142 | case 5:
143 | chrreg[whichbank] = data;
144 | setupchr();
145 | break;
146 | case 6:
147 | prgreg0 = data;
148 | setprgregs();
149 | break;
150 | case 7:
151 | //bank 7 always swappable, always in same place
152 | prgreg1 = data;
153 | setprgregs();
154 | break;
155 | case 8:
156 | case 9:
157 | //setup extra chr banks;
158 | chrreg[whichbank - 2] = data;
159 | break;
160 | case 0xf:
161 | prgreg2 = data;
162 | setprgregs();
163 | break;
164 | }
165 | } else if ((addr >= 0xA000) && (addr <= 0xbfff)) {
166 | //prg ram write protect
167 | //cpuram.setPrgRAMEnable(!utils.getbit(data, 7));
168 | } else if ((addr >= 0xc000) && (addr <= 0xdfff)) {
169 | //any value here reloads irq counter
170 | irqreload = true;
171 | irqmode = ((data & (utils.BIT0)) != 0);
172 | } else if ((addr >= 0xe000) && (addr <= 0xffff)) {
173 | //iany value here enables interrupts
174 | irqenable = true;
175 | }
176 | } else {
177 | //even registers
178 | if ((addr >= 0x8000) && (addr <= 0x9fff)) {
179 | //bank select
180 | whichbank = data & 0xf;
181 | chrmode1k = ((data & (utils.BIT5)) != 0);
182 | prgconfig = ((data & (utils.BIT6)) != 0);
183 | //if bit is false, 8000-9fff swappable and c000-dfff fixed to 2nd to last bank
184 | //if bit is true, c000-dfff swappable and 8000-9fff fixed to 2nd to last bank
185 | chrconfig = ((data & (utils.BIT7)) != 0);
186 | //if false: 2 2k banks @ 0000-0fff, 4 1k banks in 1000-1fff
187 | //if true: 4 1k banks @ 0000-0fff, 2 2k banks @ 1000-1fff
188 | setupchr();
189 | setprgregs();
190 | } else if ((addr >= 0xA000) && (addr <= 0xbfff)) {
191 | //mirroring setup
192 | if (scrolltype != MirrorType.FOUR_SCREEN_MIRROR) {
193 | setmirroring(((data & (utils.BIT0)) != 0) ? MirrorType.H_MIRROR : MirrorType.V_MIRROR);
194 | }
195 | } else if ((addr >= 0xc000) && (addr <= 0xdfff)) {
196 | //value written here used to reload irq counter _@ end of scanline_
197 | irqctrreload = data;
198 | irqreload = true;
199 | } else if ((addr >= 0xe000) && (addr <= 0xffff)) {
200 | //any value here disables IRQ and acknowledges
201 | if (interrupted) {
202 | --cpu.interrupt;
203 | }
204 | interrupted = false;
205 | irqenable = false;
206 | irqctr = irqctrreload;
207 | }
208 | }
209 | }
210 |
211 | private void setupchr() {
212 | if (chrconfig) {
213 | if (chrmode1k) {
214 | setppubank(1, 0, chrreg[2]);
215 | setppubank(1, 1, chrreg[3]);
216 | setppubank(1, 2, chrreg[4]);
217 | setppubank(1, 3, chrreg[5]);
218 | setppubank(1, 4, chrreg[0]);
219 | setppubank(1, 5, chrreg[6]);
220 | setppubank(1, 6, chrreg[1]);
221 | setppubank(1, 7, chrreg[7]);
222 | } else {
223 | setppubank(1, 0, chrreg[2]);
224 | setppubank(1, 1, chrreg[3]);
225 | setppubank(1, 2, chrreg[4]);
226 | setppubank(1, 3, chrreg[5]);
227 | //Lowest bit of bank number IS IGNORED for the 2k banks
228 | setppubank(2, 4, (chrreg[0] >> 1) << 1);
229 | setppubank(2, 6, (chrreg[1] >> 1) << 1);
230 | }
231 | } else {
232 | if (chrmode1k) {
233 | setppubank(1, 0, chrreg[0]);
234 | setppubank(1, 1, chrreg[6]);
235 | setppubank(1, 2, chrreg[1]);
236 | setppubank(1, 3, chrreg[7]);
237 | setppubank(1, 4, chrreg[2]);
238 | setppubank(1, 5, chrreg[3]);
239 | setppubank(1, 6, chrreg[4]);
240 | setppubank(1, 7, chrreg[5]);
241 | } else {
242 | setppubank(1, 4, chrreg[2]);
243 | setppubank(1, 5, chrreg[3]);
244 | setppubank(1, 6, chrreg[4]);
245 | setppubank(1, 7, chrreg[5]);
246 |
247 | setppubank(2, 0, (chrreg[0] >> 1) << 1);
248 | setppubank(2, 2, (chrreg[1] >> 1) << 1);
249 | }
250 | }
251 | }
252 |
253 | private void setprgregs() {
254 | //no matter what, c000-dfff is last bank
255 | if (!prgconfig) {
256 | //map r6 to first 8k, r7 to 2nd, rf to 3rd
257 | for (int i = 0; i < 8; ++i) {
258 | prg_map[i] = (1024 * (i + (prgreg0 * 8))) % prgsize;
259 | prg_map[i + 8] = (1024 * (i + (prgreg1 * 8))) % prgsize;
260 | prg_map[i + 16] = (1024 * (i + (prgreg2 * 8))) % prgsize;
261 | }
262 | } else {
263 | //map rf to 1st 8k, r6 to 2nd, r7 to 3rd
264 | for (int i = 0; i < 8; ++i) {
265 | prg_map[i] = (1024 * (i + (prgreg2 * 8))) % prgsize;
266 | prg_map[i + 8] = (1024 * (i + (prgreg0 * 8))) % prgsize;
267 | prg_map[i + 16] = (1024 * (i + (prgreg1 * 8))) % prgsize;
268 | }
269 | }
270 | }
271 |
272 | @Override
273 | public void notifyscanline(int scanline) {
274 | if (irqmode) {
275 | return;
276 | }
277 | //Scanline counter
278 | if (scanline > 239 && scanline != 261) {
279 | //clocked on LAST line of vblank and all lines of frame. Not on 240.
280 | return;
281 | }
282 | if (!ppu.mmc3CounterClocking()) {
283 | return;
284 | }
285 | clockscanlinecounter();
286 | }
287 | int remainder;
288 | boolean intnextcycle = false;
289 |
290 | @Override
291 | public void cpucycle(int cycles) {
292 | if (intnextcycle) {
293 | intnextcycle = false;
294 | if (!interrupted) {
295 | ++cpu.interrupt;
296 | interrupted = true;
297 | }
298 | }
299 | if (!irqmode) {
300 | return;
301 | }
302 | remainder += cycles;
303 | for (int i = 0; i < remainder; ++i) {
304 | if ((i & 3) == 0) {
305 | clockscanlinecounter();
306 | }
307 | }
308 | remainder %= 4;
309 | }
310 |
311 | public void clockscanlinecounter() {
312 | if (irqreload) {
313 | irqreload = false;
314 | irqctr = irqctrreload + 1;
315 | } else if (irqctr == 0) {
316 | irqctr = irqctrreload;
317 | } else {
318 | if (--irqctr == 0 && irqenable) {
319 | intnextcycle = true;
320 | }
321 | }
322 | }
323 |
324 | private void setppubank(int banksize, int bankpos, int banknum) {
325 | // System.err.println(banksize + ", " + bankpos + ", "+ banknum);
326 | for (int i = 0; i < banksize; ++i) {
327 | chr_map[i + bankpos] = (1024 * ((banknum) + i)) % chrsize;
328 | }
329 | }
330 | }
--------------------------------------------------------------------------------
/JavaNESBrain/src/jp/tanakh/bjne/ui/AWTRenderer.java:
--------------------------------------------------------------------------------
1 | package jp.tanakh.bjne.ui;
2 |
3 | import java.awt.Frame;
4 | import java.awt.Graphics;
5 | import java.awt.event.KeyAdapter;
6 | import java.awt.event.KeyEvent;
7 | import java.awt.image.BufferedImage;
8 | import java.awt.image.DataBufferByte;
9 |
10 | import javax.sound.sampled.AudioFormat;
11 | import javax.sound.sampled.AudioSystem;
12 | import javax.sound.sampled.DataLine;
13 | import javax.sound.sampled.LineUnavailableException;
14 | import javax.sound.sampled.SourceDataLine;
15 |
16 | import jp.tanakh.bjne.nes.Renderer;
17 |
18 | public class AWTRenderer implements Renderer {
19 |
20 | static final int SCREEN_SIZE_MULTIPLIER = 2;
21 |
22 | private static final int SCREEN_WIDTH = 16*16;
23 | private static final int SCREEN_HEIGHT = 15*16;
24 |
25 | private static final int SAMPLE_RATE = 48000;
26 | private static final int BPS = 16;
27 | private static final int CHANNELS = 2;
28 | private static final int BUFFER_FRAMES = 2;
29 |
30 | private static final int FPS = 60;
31 | private static final int SAMPLES_PER_FRAME = SAMPLE_RATE / FPS;
32 |
33 | private ScreenInfo scri = new ScreenInfo();
34 | private SoundInfo sndi = new SoundInfo();
35 | private InputInfo inpi = new InputInfo();
36 |
37 | private Frame frame;
38 | private BufferedImage image = new BufferedImage(SCREEN_WIDTH,
39 | SCREEN_HEIGHT, BufferedImage.TYPE_3BYTE_BGR);
40 |
41 | private SourceDataLine line;
42 | private int lineBufferSize;
43 |
44 | AWTRenderer(Frame f) throws LineUnavailableException {
45 | frame = f;
46 | frame.addKeyListener(new KeyAdapter() {
47 | @Override
48 | public void keyPressed(KeyEvent e) {
49 | onKey(e.getKeyCode(), true);
50 | }
51 |
52 | @Override
53 | public void keyReleased(KeyEvent e) {
54 | onKey(e.getKeyCode(), false);
55 | }
56 | });
57 |
58 | AudioFormat format = new AudioFormat(SAMPLE_RATE, BPS, CHANNELS, true,
59 | false);
60 | DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
61 | line = (SourceDataLine) AudioSystem.getLine(info);
62 | line.open();
63 | line.start();
64 | lineBufferSize = line.available();
65 |
66 | int bufSamples = SAMPLES_PER_FRAME;
67 |
68 | sndi.bps = 16;
69 | sndi.buf = new byte[bufSamples * (BPS / 8) * CHANNELS];
70 | sndi.ch = 2;
71 | sndi.freq = SAMPLE_RATE;
72 | sndi.sample = bufSamples;
73 |
74 | inpi.buf = new int[16];
75 | }
76 |
77 | @Override
78 | public void outputMessage(String msg) {
79 | System.out.println(msg);
80 | }
81 |
82 | @Override
83 | public ScreenInfo requestScreen(int width, int height) {
84 | if (!(scri.width == width && scri.height == height)) {
85 | scri.width = width;
86 | scri.height = height;
87 | scri.buf = new byte[3 * width * height];
88 | scri.pitch = 3 * width;
89 | scri.bpp = 24;
90 | }
91 | return scri;
92 | }
93 |
94 | @Override
95 | public void outputScreen(ScreenInfo info) {
96 | byte[] bgr = ((DataBufferByte) image.getRaster().getDataBuffer())
97 | .getData();
98 |
99 | for (int i = 0; i < SCREEN_WIDTH * SCREEN_HEIGHT; i++) {
100 | bgr[i * 3] = info.buf[i * 3 + 2];
101 | bgr[i * 3 + 1] = info.buf[i * 3 + 1];
102 | bgr[i * 3 + 2] = info.buf[i * 3 + 0];
103 |
104 | //System.out.println(info.buf[1600]);
105 | }
106 |
107 | int left = frame.getInsets().left;
108 | int top = frame.getInsets().top;
109 | Graphics g = frame.getGraphics();
110 |
111 | g.drawImage(image, left, top, left + SCREEN_WIDTH*SCREEN_SIZE_MULTIPLIER, top + SCREEN_HEIGHT*SCREEN_SIZE_MULTIPLIER,
112 | 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, frame);
113 | }
114 |
115 | @Override
116 | public SoundInfo requestSound() {
117 | if (getSoundBufferState() <= 0)
118 | return sndi;
119 | else
120 | return null;
121 | }
122 |
123 | @Override
124 | public void outputSound(SoundInfo info) {
125 | line.write(info.buf, 0, info.sample * (info.bps / 8) * info.ch);
126 | }
127 |
128 | public int getSoundBufferState() {
129 | int rest = (lineBufferSize - line.available()) / (sndi.bps / 8)
130 | / sndi.ch;
131 | if (rest < SAMPLES_PER_FRAME * BUFFER_FRAMES)
132 | return -1;
133 | if (rest == SAMPLES_PER_FRAME * BUFFER_FRAMES)
134 | return 0;
135 | return 1;
136 | }
137 |
138 | static final int[][] keyDef = {
139 | { KeyEvent.VK_Z, KeyEvent.VK_X, KeyEvent.VK_SHIFT,
140 | KeyEvent.VK_ENTER, KeyEvent.VK_UP, KeyEvent.VK_DOWN,
141 | KeyEvent.VK_LEFT, KeyEvent.VK_RIGHT, },
142 | { KeyEvent.VK_V, KeyEvent.VK_B, KeyEvent.VK_N, KeyEvent.VK_M,
143 | KeyEvent.VK_O, KeyEvent.VK_COMMA, KeyEvent.VK_K,
144 | KeyEvent.VK_L, } };
145 |
146 | private void onKey(int keyCode, boolean press) {
147 | for (int i = 0; i < 2; i++)
148 | for (int j = 0; j < 8; j++)
149 | if (keyCode == keyDef[i][j])
150 | inpi.buf[i * 8 + j] = (press ? 1 : 0);
151 | }
152 |
153 | @Override
154 | public InputInfo requestInput(int padCount, int buttonCount) {
155 | return inpi;
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/jp/tanakh/bjne/ui/BJNEmulator.java:
--------------------------------------------------------------------------------
1 | package jp.tanakh.bjne.ui;
2 |
3 | import java.awt.Button;
4 | import java.awt.Dialog;
5 | import java.awt.FileDialog;
6 | import java.awt.FlowLayout;
7 | import java.awt.Frame;
8 | import java.awt.Label;
9 | import java.awt.Menu;
10 | import java.awt.MenuBar;
11 | import java.awt.MenuItem;
12 | import java.awt.event.ActionEvent;
13 | import java.awt.event.ActionListener;
14 | import java.awt.event.WindowAdapter;
15 | import java.awt.event.WindowEvent;
16 | import java.io.IOException;
17 |
18 | import javax.sound.sampled.LineUnavailableException;
19 | import javax.swing.UIManager;
20 |
21 | import com.starflask.JavaNESBrain.SuperBrain;
22 |
23 | import jp.tanakh.bjne.nes.Cpu;
24 | import jp.tanakh.bjne.nes.Nes;
25 | import jp.tanakh.bjne.nes.ROMEventListener;
26 |
27 | public class BJNEmulator extends Frame {
28 |
29 | private static final long serialVersionUID = 1L;
30 |
31 | private Nes nes = null;
32 | private AWTRenderer r = null;
33 | private FPSTimer timer = new FPSTimer();
34 |
35 | private Object nesLock = new Object();
36 |
37 | SuperBrain superBrain ;
38 |
39 | public static void main(String[] args) {
40 |
41 | try {
42 | UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
43 | } catch (Exception e) {
44 | System.err.println("Could not set system look and feel. ");
45 | }
46 |
47 |
48 |
49 | new BJNEmulator(args.length == 1 ? args[0] : null);
50 | }
51 |
52 | public BJNEmulator(String file) {
53 | // setup main window
54 |
55 | super("Nes Emulator");
56 |
57 |
58 | superBrain = new SuperBrain(this);
59 |
60 | Thread aiThread = new Thread(superBrain, "AI Thread");
61 | aiThread.start(); // Start the thread
62 |
63 |
64 | MenuBar menuBar = new MenuBar();
65 | setMenuBar(menuBar);
66 |
67 | {
68 | Menu menu = new Menu("File");
69 | {
70 | MenuItem item = new MenuItem("Open");
71 | item.addActionListener(new ActionListener() {
72 | @Override
73 | public void actionPerformed(ActionEvent e) {
74 | onOpen();
75 | }
76 | });
77 | menu.add(item);
78 |
79 |
80 | MenuItem aibutton = new MenuItem("Start AI");
81 | aibutton.addActionListener(new ActionListener() {
82 | @Override
83 | public void actionPerformed(ActionEvent e) {
84 | startAI();
85 | }
86 | });
87 | menu.add(aibutton);
88 |
89 |
90 | }
91 | {
92 | MenuItem item = new MenuItem("Exit");
93 | item.addActionListener(new ActionListener() {
94 | @Override
95 | public void actionPerformed(ActionEvent e) {
96 | onExit();
97 | }
98 | });
99 | menu.add(item);
100 | }
101 | menuBar.add(menu);
102 | }
103 | {
104 | Menu menu = new Menu("Settings");
105 | MenuItem mutebutton = new MenuItem("Mute");
106 | mutebutton.addActionListener(new ActionListener() {
107 | @Override
108 | public void actionPerformed(ActionEvent e) {
109 | toggleAudio();
110 | }
111 | });
112 | menu.add(mutebutton);
113 | menuBar.add(menu);
114 | }
115 | {
116 | Menu menu = new Menu("Help");
117 | MenuItem item = new MenuItem("About");
118 | item.addActionListener(new ActionListener() {
119 | @Override
120 | public void actionPerformed(ActionEvent e) {
121 | onAbout();
122 | }
123 | });
124 | menu.add(item);
125 | menuBar.add(menu);
126 | }
127 |
128 | addWindowListener(new WindowAdapter() {
129 | @Override
130 | public void windowClosing(WindowEvent e) {
131 | onExit();
132 | }
133 | });
134 |
135 | initializeNes();
136 |
137 |
138 | if (file != null)
139 | openRom(file);
140 |
141 | setVisible(true);
142 | setVisible(false);
143 | setSize(256 * AWTRenderer.SCREEN_SIZE_MULTIPLIER + getInsets().left + getInsets().right, 240 * AWTRenderer.SCREEN_SIZE_MULTIPLIER
144 | + getInsets().top + getInsets().bottom);
145 | setVisible(true);
146 |
147 |
148 | while(true)
149 | {
150 | stepEmulation();
151 | }
152 |
153 | }
154 |
155 |
156 | protected void toggleAudio() {
157 | nes.audioEnabled = !nes.audioEnabled;
158 | }
159 |
160 |
161 |
162 | protected void startAI() {
163 |
164 | superBrain.build();
165 | }
166 |
167 | final int FPS = 60;
168 |
169 |
170 |
171 | public void stepEmulation() {
172 |
173 | synchronized (nesLock) {
174 | if (nes == null)
175 | {
176 | return;
177 | }
178 |
179 | long start = System.nanoTime();
180 | nes.execFrame();
181 |
182 | if(!nes.audioEnabled)
183 | {
184 | return;
185 | }
186 |
187 |
188 | for (;;) {
189 | int bufStat = r.getSoundBufferState();
190 | if (bufStat < 0)
191 | break;
192 | if (bufStat == 0) {
193 | long elapsed = System.nanoTime() - start;
194 | long wait = (long) (1.0 / FPS - elapsed / 1e-9);
195 | try {
196 | if (wait > 0)
197 | Thread.sleep(wait);
198 | } catch (InterruptedException e) {
199 | }
200 | break;
201 | }
202 | try {
203 | Thread.sleep(1);
204 | } catch (InterruptedException e) {
205 | }
206 | }
207 | // timer.elapse(60);
208 | }
209 | }
210 |
211 | private void initializeNes() {
212 | try {
213 | r = new AWTRenderer(this);
214 | } catch (LineUnavailableException e) {
215 | System.out.println("Cannot initialize Renderer.");
216 | e.printStackTrace();
217 | System.exit(0);
218 | }
219 | }
220 |
221 | private void openRom(String file) {
222 | System.out.println("loading " + file);
223 | synchronized (nesLock) {
224 | try {
225 | nes = new Nes(r);
226 | nes.load(file);
227 |
228 | //romEventListener.onLoad();
229 | } catch (IOException e) {
230 | System.out.println("error: loading " + file + " ("
231 | + e.getMessage() + ")");
232 | nes = null;
233 | }
234 | }
235 | }
236 |
237 | private void onOpen() {
238 | FileDialog d = new FileDialog(this, "Open ROM", FileDialog.LOAD);
239 | d.setVisible(true);
240 | String dir = d.getDirectory();
241 | String file = d.getFile();
242 | openRom(dir + file);
243 | }
244 |
245 | private void onExit() {
246 | System.exit(0);
247 | }
248 |
249 | private class AboutDialog extends Dialog {
250 | private static final long serialVersionUID = 1L;
251 |
252 | AboutDialog(Frame owner) {
253 | super(owner);
254 | setLayout(new FlowLayout());
255 |
256 | add(new Label("Beautiful Japanese Nes Emulator for Java"));
257 | add(new Label("Version 0.2.0"));
258 | add(new Label("(Modded to add NESBrain AI)"));
259 |
260 | Button b = new Button("OK");
261 | b.addActionListener(new ActionListener() {
262 | @Override
263 | public void actionPerformed(ActionEvent e) {
264 | setVisible(false);
265 | }
266 | });
267 | add(b);
268 |
269 | addWindowListener(new WindowAdapter() {
270 | @Override
271 | public void windowClosing(WindowEvent e) {
272 | setVisible(false);
273 | }
274 | });
275 |
276 | setTitle("About");
277 | setSize(270, 200);
278 | }
279 | }
280 |
281 | private void onAbout() {
282 | Dialog dlg = new AboutDialog(this);
283 | dlg.setModal(true);
284 | dlg.setVisible(true);
285 | }
286 |
287 |
288 | public Nes getNES()
289 | {
290 |
291 |
292 | return nes;
293 | }
294 |
295 | public void setGamepadInput(int[] buf )
296 | {
297 |
298 | nes.setGamepadInput(buf);
299 | }
300 |
301 | public String getCurrentRomName() {
302 |
303 | if(nes!=null && nes.getRom()!=null)
304 | {
305 | return nes.getRom().getROMName();
306 | }
307 |
308 | return "none";
309 | }
310 |
311 | ROMEventListener romEventListener;
312 |
313 | public void addROMEventListener(ROMEventListener listener) {
314 | romEventListener = listener;
315 |
316 | }
317 |
318 | }
319 |
--------------------------------------------------------------------------------
/JavaNESBrain/src/jp/tanakh/bjne/ui/FPSTimer.java:
--------------------------------------------------------------------------------
1 | package jp.tanakh.bjne.ui;
2 |
3 | import java.util.Date;
4 |
5 | public class FPSTimer {
6 | FPSTimer() {
7 | reset();
8 | }
9 |
10 | public void reset() {
11 | bef = getTime();
12 | timing = 0;
13 | }
14 |
15 | public boolean elapse(double fps) {
16 | double spf = 1.0 / fps;
17 | timing += getElapsed() - spf;
18 | if (timing > 0) {
19 | if (timing > spf) {
20 | reset();
21 | return true;
22 | }
23 | return false;
24 | } else {
25 | try {
26 | Thread.sleep((long) (-timing * 1000));
27 | } catch (InterruptedException e) {
28 | }
29 | return true;
30 | }
31 | }
32 |
33 | private double getElapsed() {
34 | double cur = getTime();
35 | double ret = cur - bef;
36 | bef = cur;
37 | return ret;
38 | }
39 |
40 | private double getTime() {
41 | return new Date().getTime() / 1000.0;
42 | }
43 |
44 | private double bef = 0;
45 | private double timing = 0;
46 | }
47 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JavaNESBrain
2 |
3 | [Visit the github pages](https://admazzola.github.io/JavaNESBrain)
4 |
5 | A fork of the BJNE Java NES Emulator that includes a Neural Network learning algorithm based on SethBling's MarI/O Lua script to enable the emulator to learn how to play itself. (Eclipse .project file already included, but you can use any IDE)
6 |
7 | At this time, the only supported ROMs are 'Super Mario Bros' and 'Galaga' for the NES. Support for different ROMs can be added by altering the GameDataManager class and telling it which memory addresses to suck the 'tile map' inputs from.
8 |
9 | This is still a WIP! It does work -to an extent- but the emulator saving/loading code (added during this project) still causes corruption at times. Feel free to submit pull requests.
10 |
11 | ####Instructions
12 | Run the main function in the 'SuperBrain' class using JRE 7 32 bit. Once the emulator opens, use the 'open' button in the GUI menu to run the NES ROM of your choice. Use the keyboard to press 'Enter' and start a level. Then, use the 'Start AI' button in the GUI menu and your keyboard controls will be relinquished to the evolving neural network.
13 |
14 | 
15 |
--------------------------------------------------------------------------------