1
0
Fork 0
mirror of https://github.com/ethauvin/chip-8.git synced 2025-04-26 09:17:12 -07:00

First commit

This commit is contained in:
Cedric Beust 2020-08-22 11:59:50 -07:00
commit ee139eba9b
55 changed files with 1728 additions and 0 deletions

View file

@ -0,0 +1,235 @@
package com.beust.chip8
import com.beust.jcommander.JCommander
import com.beust.jcommander.Parameter
import javafx.application.Application
import javafx.beans.property.SimpleBooleanProperty
import javafx.beans.property.SimpleStringProperty
import javafx.event.EventHandler
import javafx.fxml.FXMLLoader
import javafx.fxml.Initializable
import javafx.scene.Scene
import javafx.scene.control.Button
import javafx.scene.control.Label
import javafx.scene.control.ScrollPane
import javafx.scene.control.TextField
import javafx.scene.input.KeyCode
import javafx.scene.input.KeyEvent
import javafx.scene.layout.AnchorPane
import javafx.scene.layout.GridPane
import javafx.scene.layout.VBox
import javafx.stage.FileChooser
import javafx.stage.Stage
import java.io.File
import java.net.URL
import java.nio.file.Paths
import java.util.*
import kotlin.system.exitProcess
class Controller : Initializable {
var currentRomPath: SimpleStringProperty? = null
var computer: Computer? = null
var pauseButton: Button? = null
override fun initialize(location: URL?, resources: ResourceBundle?) {
println("Initializing controller")
}
}
class MyFxApp : Application() {
private val computer = Computer()
private var pauseButton: Button? = null
private var disassembly: GridPane? = null
private val isPaused = SimpleBooleanProperty().apply {
addListener { _, _, newVal -> computer.apply {
// Computer is paused
// Update the pause button label
pauseButton?.text = if (newVal) "Resume" else "Pause"
// Display the disassembly
updateDisassembly(disassembly!!, computer.disassemble())
}
}}
/**
* Display the disassembly.
*/
private fun updateDisassembly(disassembly: GridPane, listing: List<Computer.AssemblyLine>) {
disassembly.children.clear()
listing.forEachIndexed() { row, al ->
var column = 0
Label(al.counter.h).let { label ->
GridPane.setConstraints(label, column++, row)
disassembly.children.add(label)
}
Label(al.byte0.h + " " + al.byte1.h).let { label ->
GridPane.setConstraints(label, column++, row)
disassembly.children.add(label)
}
Label(al.name).let { label ->
GridPane.setConstraints(label, column++, row)
disassembly.children.add(label)
}
}
}
private val currentRomName = SimpleStringProperty()
private val currentRomPath = SimpleStringProperty().apply {
addListener { _, _, newVal -> computer.apply {
// Restart computer
stop()
loadRom(File(newVal))
// Update the rom name
val start = newVal.lastIndexOf(File.separatorChar)
val end = newVal.lastIndexOf(".ch8")
val romName = if (end == -1) newVal.substring(start + 1)
else newVal.substring(start + 1, end)
currentRomName.set(romName)
}}
}
override fun start(primaryStage: Stage) {
val keyListener = object: KeyListener {
override fun onKey(key: Int?) {
computer.keyboard.key = key
}
}
primaryStage.title = "CHIP-8"
val url = this::class.java.classLoader.getResource("main.fxml")
val loader = FXMLLoader(url)
val res = url.openStream()
val root = loader.load<AnchorPane>(res)
pauseButton = root.lookup("#pause") as Button
val scrollPane = root.lookup("#disassemblyScrollPane") as ScrollPane
disassembly = scrollPane.content as GridPane
val controller = loader.getController<Controller>()
controller.let {
it.currentRomPath = currentRomPath
it.computer = computer
it.pauseButton = pauseButton
}
computer.listener = object: ComputerListener {
override fun onKey(key: Int?) {
computer.keyboard.key = key
}
override fun onPause() {
isPaused.set(true)
}
override fun onStart() {
isPaused.set(false)
}
}
val scene = Scene(root)
val bp = root.lookup("#emulator") as VBox
bp.children.add(computer.display.pane)
primaryStage.scene = scene
primaryStage.show()
// Load rom button
(root.lookup("#loadRom") as Button).setOnAction {
computer.pause()
val romFile = FileChooser().run {
initialDirectory = File("roms")
showOpenDialog(Stage())
}
println("Picked file $romFile")
if (romFile != null) {
currentRomPath.set(romFile.absolutePath)
}
}
// Pause button
pauseButton?.setOnAction {
if (computer.paused) {
computer.start()
} else {
computer.pause()
}
}
// CPU clock button
with((root.lookup("#cpuClock") as TextField)) {
fun updateClock() {
computer.cpuClockHz = text.toLong()
}
text = computer.cpuClockHz.toString()
onAction = EventHandler { e ->
updateClock()
}
focusedProperty().addListener { _, _, onFocus ->
if (!onFocus) {
updateClock()
}
}
}
// Current ROM
val currentRomLabel = root.lookup("#currentRom") as Label
currentRomLabel.textProperty().bind(currentRomName)
scene.setOnKeyPressed { event: KeyEvent ->
when(event.code) {
KeyCode.Q -> {
primaryStage.close()
computer.stop()
exitProcess(0)
}
KeyCode.P -> {
computer.pause()
}
else -> {
if (computer.paused) computer.start()
else {
try {
val key = Integer.parseInt(event.code.char, 16)
keyListener.onKey(key)
} catch(ex: NumberFormatException) {
println("Can't parse key " + event.code.char + ", ignoring")
}
}
}
}
}
scene.setOnKeyReleased { event: KeyEvent ->
keyListener.onKey(null)
}
val spaceInvaders = Paths.get("roms", "Space Invaders [David Winter].ch8")
val breakout = Paths.get("roms", "Breakout [Carmelo Cortez, 1979].ch8")
val file3 = Paths.get("roms", "Tetris [Fran Dachille, 1991].ch8")
val file4 = Paths.get("roms", "Clock Program [Bill Fisher, 1981].ch8")
val file5 = Paths.get("roms", "IBM Logo.ch8")
val keypad = Paths.get("roms", "Keypad Test [Hap, 2006].ch8")
currentRomPath.set(spaceInvaders.toFile().absolutePath)
// val rom = file2.toFile() // if (arg.rom != null) File(arg.rom) else file3.toFile()
// chip8.run(rom)
}
}
fun main(args: Array<String>) {
if (args.isNotEmpty()) {
class Arg {
@Parameter(names = arrayOf("--rom"))
var rom: String? = null
@Parameter(names = arrayOf("--start"))
var start: String? = "200"
}
val arg = Arg()
JCommander.newBuilder().addObject(arg).args(args).build()
val computer = Computer(sound = false)
val start = Integer.parseInt(arg.start!!, 16)
val listing = computer.disassemble(start)
println("=== " + arg.rom + " PC=0x" + Integer.toHexString(start))
listing.forEach { println(it) }
} else {
Application.launch(MyFxApp::class.java)
}
}