├── 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 (xy) 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 | ![preview](https://cloud.githubusercontent.com/assets/6249263/8437567/d564abbe-1f2d-11e5-8457-15d470e0a294.PNG) 15 | --------------------------------------------------------------------------------