diff --git a/app/src/main/kotlin/android/emu6502/CPU.kt b/app/src/main/kotlin/android/emu6502/CPU.kt index b82c414..85f90b7 100644 --- a/app/src/main/kotlin/android/emu6502/CPU.kt +++ b/app/src/main/kotlin/android/emu6502/CPU.kt @@ -9,14 +9,16 @@ import android.os.Handler import android.os.HandlerThread import android.util.Log import java.util.HashMap +import java.util.concurrent.CountDownLatch -class CPU(val memory: Memory) { - private val handlerThread = HandlerThread("Screencast Thread") - private val handler: Handler +class CPU(val memory: Memory) : Display.Callbacks { + private val bgHandlerThread = HandlerThread("Screencast Thread") + private val bgHandler: Handler + private var lock: CountDownLatch? = null init { - handlerThread.start() - handler = Handler(handlerThread.getLooper()) + bgHandlerThread.start() + bgHandler = Handler(bgHandlerThread.getLooper()) } // Accumulator @@ -96,12 +98,12 @@ class CPU(val memory: Memory) { fun run() { isRunning = true - innerRun() + bgHandler.post { innerRun() } } private fun innerRun() { (0..97).forEach { execute() } - handler.postDelayed({ innerRun() }, 10) + bgHandler.postDelayed({ innerRun() }, 10) } private fun execute() { @@ -137,7 +139,20 @@ class CPU(val memory: Memory) { fun stop() { isRunning = false - handler.removeCallbacks(null) + bgHandler.removeCallbacks(null) + } + + fun reset() { + var i = 0 + while (i < 0x600) { + memory.set(i++, 0x00) + } + A = 0 + Y = 0 + X = 0 + PC = 0x600 + SP = 0xff + P = 0x30 } fun popByte(): Int { @@ -324,4 +339,13 @@ class CPU(val memory: Memory) { } return flags.toString() } + + override fun onUpdate() { + lock = CountDownLatch(1) + lock?.await() + } + + override fun onDraw() { + lock?.countDown() + } } \ No newline at end of file diff --git a/app/src/main/kotlin/android/emu6502/Display.kt b/app/src/main/kotlin/android/emu6502/Display.kt index f0a9db6..b7a7427 100644 --- a/app/src/main/kotlin/android/emu6502/Display.kt +++ b/app/src/main/kotlin/android/emu6502/Display.kt @@ -8,21 +8,25 @@ import android.graphics.Point import android.util.AttributeSet import android.util.Log import android.view.View -import java.util.ArrayList +import java.util +import java.util.* open class Display : View { private val numX = 32 private val numY = 32 + private val matrix = Array(32, { IntArray(32) }) + private val palette = arrayOf( "#000000", "#ffffff", "#880000", "#aaffee", "#cc44cc", "#00cc55", "#0000aa", "#eeee77", "#dd8855", "#664400", "#ff7777", "#333333", "#777777", "#aaff66", "#0088ff", "#bbbbbb") - private val drawingCache = ArrayList() private val paint: Paint private val bgPaint: Paint + private val TAG = "Display" + private var listener: Callbacks? = null constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { paint = Paint() @@ -30,29 +34,50 @@ open class Display : View { bgPaint.setColor(Color.BLACK); } + fun setOnDisplayCallback(callback: Callbacks) { + listener = callback; + } + open fun updatePixel(addr: Int, value: Int) { + val offsetAddr = addr - 0x200 + val x = offsetAddr % 32 + val y = Math.floor((offsetAddr / 32).toDouble()).toInt() val color = palette[value] - val x = (addr - 0x200) % 32 - val y = Math.floor(((addr - 0x200) / 32).toDouble()) - drawingCache.add(Pixel(Point(x.toInt(), y.toInt()), Color.parseColor(color))) + matrix[x][y] = Color.parseColor(color) postInvalidate() + listener?.onUpdate() } override fun onDraw(canvas: Canvas) { val pixelSize = getWidth() / numX - canvas.drawRect(0f, 0f, getWidth().toFloat(), getHeight().toFloat(), bgPaint) - - for (pixel in drawingCache) { - val right = (pixel.point.x * pixelSize).toFloat() - val top = (pixel.point.y * pixelSize).toFloat() - paint.setColor(pixel.color) - canvas.drawRect(right, top, right + pixelSize, top + pixelSize, paint) + matrix.forEachIndexed { i, _ -> + matrix[i].forEachIndexed { j, _ -> + val color = matrix[i][j] + val right = (i * pixelSize).toFloat() + val top = (j * pixelSize).toFloat() + if (color != 0) { + paint.setColor(color) + canvas.drawRect(right, top, right + pixelSize, right + pixelSize, paint) + } else { + canvas.drawRect(right, top, right + pixelSize, right + pixelSize, bgPaint) + } + } } - - drawingCache.clear() + listener?.onDraw() } - class Pixel(val point: Point, val color: Int) { + interface Callbacks { + fun onUpdate() + fun onDraw() + } + + fun reset() { + matrix.forEachIndexed { i, _ -> + matrix[i].forEachIndexed { j, _ -> + matrix[i][j] = 0 + } + } + postInvalidate() } } diff --git a/app/src/main/kotlin/android/emu6502/Emulator.kt b/app/src/main/kotlin/android/emu6502/Emulator.kt index bef99d5..db149b1 100644 --- a/app/src/main/kotlin/android/emu6502/Emulator.kt +++ b/app/src/main/kotlin/android/emu6502/Emulator.kt @@ -2,8 +2,17 @@ package android.emu6502 import android.emu6502.instructions.Symbols -final class Emulator(display: Display) { +final class Emulator(val display: Display) { val memory = Memory(display) val cpu = CPU(memory) val assembler = Assembler(memory, Symbols()) + + init { + display.setOnDisplayCallback(cpu) + } + + fun reset() { + display.reset() + cpu.reset() + } } \ No newline at end of file diff --git a/app/src/main/kotlin/android/emu6502/Memory.kt b/app/src/main/kotlin/android/emu6502/Memory.kt index d5954f8..432fc55 100644 --- a/app/src/main/kotlin/android/emu6502/Memory.kt +++ b/app/src/main/kotlin/android/emu6502/Memory.kt @@ -1,6 +1,6 @@ package android.emu6502 -import java.util.* +import java.util.concurrent.CountDownLatch class Memory(private val display: Display) { private val mem = IntArray(65536) diff --git a/app/src/main/kotlin/android/emu6502/app/MainActivity.kt b/app/src/main/kotlin/android/emu6502/app/MainActivity.kt index 4901af2..914de6e 100644 --- a/app/src/main/kotlin/android/emu6502/app/MainActivity.kt +++ b/app/src/main/kotlin/android/emu6502/app/MainActivity.kt @@ -4,6 +4,7 @@ import android.emu6502.Display import android.emu6502.Emulator import android.emu6502.R import android.os.Bundle +import android.support.design.widget.CoordinatorLayout import android.support.design.widget.FloatingActionButton import android.support.design.widget.Snackbar import android.support.v7.app.ActionBar @@ -13,7 +14,7 @@ import android.view.Menu import android.view.MenuItem import android.view.View import android.widget.Button -import android.widget.LinearLayout +import android.widget.FrameLayout import android.widget.TextView import butterknife.bindView @@ -26,14 +27,16 @@ public class MainActivity : AppCompatActivity() { val txtSP: TextView by bindView(R.id.SP) val txtPC: TextView by bindView(R.id.PC) val txtFlags: TextView by bindView(R.id.PC) + val displayWrapper: FrameLayout by bindView(R.id.display_wrapper) val display: Display by bindView(R.id.display) val txtInstructions: TextView by bindView(R.id.txtInstructions) val fabRun: FloatingActionButton by bindView(R.id.fabRun) - val layoutContent: LinearLayout by bindView(R.id.layout_content) + val layoutContent: CoordinatorLayout by bindView(R.id.layout_content) val btnLeft: Button by bindView(R.id.arrowLeft) val btnRight: Button by bindView(R.id.arrowRight) val btnUp: Button by bindView(R.id.arrowUp) val btnDown: Button by bindView(R.id.arrowDown) + val btnReset: Button by bindView(R.id.btnReset) private var emulator: Emulator? = null @@ -47,7 +50,7 @@ public class MainActivity : AppCompatActivity() { ab.setDisplayHomeAsUpEnabled(true) fabRun.setOnClickListener { - display.setVisibility(View.VISIBLE) + displayWrapper.setVisibility(View.VISIBLE) emulator = Emulator(display) val emu: Emulator = emulator as Emulator emu.assembler.assembleCode(txtInstructions.getText().toString().splitBy("\n")) @@ -57,6 +60,11 @@ public class MainActivity : AppCompatActivity() { emu.cpu.run() } + btnReset.setOnClickListener { + val emu: Emulator = emulator as Emulator + emu.reset() + } + val onClickButton = { code: Int -> if (emulator != null) { val emu = emulator as Emulator diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 2ec636d..d589187 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,4 +1,5 @@ @@ -41,11 +41,18 @@ android:text="define appleL $00 ; screen location of apple, low byte\ndefine appleH $01 ; screen location of apple, high byte\ndefine snakeHeadL $10 ; screen location of snake head, low byte\ndefine snakeHeadH $11 ; screen location of snake head, high byte\ndefine snakeBodyStart $12 ; start of snake body byte pairs\ndefine snakeDirection $02 ; direction (possible values are below)\ndefine snakeLength $03 ; snake length, in bytes\n\n; Directions (each using a separate bit)\ndefine movingUp 1\ndefine movingRight 2\ndefine movingDown 4\ndefine movingLeft 8\n\n; ASCII values of keys controlling the snake\ndefine ASCII_w $77\ndefine ASCII_a $61\ndefine ASCII_s $73\ndefine ASCII_d $64\n\n; System variables\ndefine sysRandom $fe\ndefine sysLastKey $ff\n\n\n jsr init\n jsr loop\n\ninit:\n jsr initSnake\n jsr generateApplePosition\n rts\n\n\ninitSnake:\n lda #movingRight ;start direction\n sta snakeDirection\n\n lda #4 ;start length (2 segments)\n sta snakeLength\n \n lda #$11\n sta snakeHeadL\n \n lda #$10\n sta snakeBodyStart\n \n lda #$0f\n sta $14 ; body segment 1\n \n lda #$04\n sta snakeHeadH\n sta $13 ; body segment 1\n sta $15 ; body segment 2\n rts\n\n\ngenerateApplePosition:\n ;load a new random byte into $00\n lda sysRandom\n sta appleL\n\n ;load a new random number from 2 to 5 into $01\n lda sysRandom\n and #$03 ;mask out lowest 2 bits\n clc\n adc #2\n sta appleH\n\n rts\n\n\nloop:\n jsr readKeys\n jsr checkCollision\n jsr updateSnake\n jsr drawApple\n jsr drawSnake\n jsr spinWheels\n jmp loop\n\n\nreadKeys:\n lda sysLastKey\n cmp #ASCII_w\n beq upKey\n cmp #ASCII_d\n beq rightKey\n cmp #ASCII_s\n beq downKey\n cmp #ASCII_a\n beq leftKey\n rts\nupKey:\n lda #movingDown\n bit snakeDirection\n bne illegalMove\n\n lda #movingUp\n sta snakeDirection\n rts\nrightKey:\n lda #movingLeft\n bit snakeDirection\n bne illegalMove\n\n lda #movingRight\n sta snakeDirection\n rts\ndownKey:\n lda #movingUp\n bit snakeDirection\n bne illegalMove\n\n lda #movingDown\n sta snakeDirection\n rts\nleftKey:\n lda #movingRight\n bit snakeDirection\n bne illegalMove\n\n lda #movingLeft\n sta snakeDirection\n rts\nillegalMove:\n rts\n\n\ncheckCollision:\n jsr checkAppleCollision\n jsr checkSnakeCollision\n rts\n\n\ncheckAppleCollision:\n lda appleL\n cmp snakeHeadL\n bne doneCheckingAppleCollision\n lda appleH\n cmp snakeHeadH\n bne doneCheckingAppleCollision\n\n ;eat apple\n inc snakeLength\n inc snakeLength ;increase length\n jsr generateApplePosition\ndoneCheckingAppleCollision:\n rts\n\n\ncheckSnakeCollision:\n ldx #2 ;start with second segment\nsnakeCollisionLoop:\n lda snakeHeadL,x\n cmp snakeHeadL\n bne continueCollisionLoop\n\nmaybeCollided:\n lda snakeHeadH,x\n cmp snakeHeadH\n beq didCollide\n\ncontinueCollisionLoop:\n inx\n inx\n cpx snakeLength ;got to last section with no collision\n beq didntCollide\n jmp snakeCollisionLoop\n\ndidCollide:\n jmp gameOver\ndidntCollide:\n rts\n\n\nupdateSnake:\n ldx snakeLength\n dex\n txa\nupdateloop:\n lda snakeHeadL,x\n sta snakeBodyStart,x\n dex\n bpl updateloop\n\n lda snakeDirection\n lsr\n bcs up\n lsr\n bcs right\n lsr\n bcs down\n lsr\n bcs left\nup:\n lda snakeHeadL\n sec\n sbc #$20\n sta snakeHeadL\n bcc upup\n rts\nupup:\n dec snakeHeadH\n lda #$1\n cmp snakeHeadH\n beq collision\n rts\nright:\n inc snakeHeadL\n lda #$1f\n bit snakeHeadL\n beq collision\n rts\ndown:\n lda snakeHeadL\n clc\n adc #$20\n sta snakeHeadL\n bcs downdown\n rts\ndowndown:\n inc snakeHeadH\n lda #$6\n cmp snakeHeadH\n beq collision\n rts\nleft:\n dec snakeHeadL\n lda snakeHeadL\n and #$1f\n cmp #$1f\n beq collision\n rts\ncollision:\n jmp gameOver\n\n\ndrawApple:\n ldy #0\n lda sysRandom\n sta (appleL),y\n rts\n\n\ndrawSnake:\n ldx #0\n lda #1\n sta (snakeHeadL,x) ; paint head\n \n ldx snakeLength\n lda #0\n sta (snakeHeadL,x) ; erase end of tail\n rts\n\n\nspinWheels:\n ldx #0\nspinloop:\n nop\n nop\n dex\n bne spinloop\n rts\n\n\ngameOver:" android:textSize="10sp"/> - + android:background="@android:color/black" + android:visibility="gone"> + + + diff --git a/app/src/test/java/android/emu6502/CPUTest.java b/app/src/test/java/android/emu6502/CPUTest.java index 207d65a..fcc5be0 100644 --- a/app/src/test/java/android/emu6502/CPUTest.java +++ b/app/src/test/java/android/emu6502/CPUTest.java @@ -34,7 +34,7 @@ public class CPUTest { "LDA #$08", "STA $0202"); assembler.assembleCode(lines); - cpu.execute(); + cpu.run(); assertThat(cpu.getA(), equalTo(0x08)); assertThat(cpu.getX(), equalTo(0x00)); assertThat(cpu.getY(), equalTo(0x00)); @@ -51,7 +51,7 @@ public class CPUTest { "ADC #$c4 ;Add the hex value $c4 to the A register", "BRK ;Break - we're done"); assembler.assembleCode(lines); - cpu.execute(); + cpu.run(); assertThat(cpu.getA(), equalTo(0x84)); assertThat(cpu.getX(), equalTo(0xC1)); assertThat(cpu.getY(), equalTo(0x00)); @@ -72,7 +72,7 @@ public class CPUTest { "STX $0201", "BRK"); assembler.assembleCode(lines); - cpu.execute(); + cpu.run(); assertThat(cpu.getA(), equalTo(0x00)); assertThat(cpu.getX(), equalTo(0x03)); assertThat(cpu.getY(), equalTo(0x00)); @@ -92,7 +92,7 @@ public class CPUTest { "there:", "STA $0200"); assembler.assembleCode(lines); - cpu.execute(); + cpu.run(); assertThat(cpu.getA(), equalTo(0x03)); assertThat(cpu.getX(), equalTo(0x00)); assertThat(cpu.getY(), equalTo(0x00)); @@ -120,7 +120,7 @@ public class CPUTest { "end:", "BRK"); assembler.assembleCode(lines); - cpu.execute(); + cpu.run(); assertThat(cpu.getA(), equalTo(0x00)); assertThat(cpu.getX(), equalTo(0x05)); assertThat(cpu.getY(), equalTo(0x00)); @@ -134,7 +134,7 @@ public class CPUTest { "define a_dozen $0c ; a constant", "LDX #a_dozen ; equivalent to \"LDX #$0c\""); assembler.assembleCode(lines); - cpu.execute(); + cpu.run(); assertThat(cpu.getA(), equalTo(0x00)); assertThat(cpu.getX(), equalTo(0x0C)); assertThat(cpu.getY(), equalTo(0x00)); @@ -372,7 +372,7 @@ public class CPUTest { " rts", "gameOver:", "\n"); assembler.assembleCode(lines); - cpu.execute(); + cpu.run(); assertThat(cpu.getA(), equalTo(0x1f)); assertThat(cpu.getX(), equalTo(0xff)); assertThat(cpu.getY(), equalTo(0x00));