Fixes display update routine

This commit is contained in:
Felipe Lima 2015-06-29 23:15:43 -07:00
parent 75b72abe81
commit fef8112fb3
7 changed files with 112 additions and 39 deletions

View File

@ -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()
}
}

View File

@ -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<Pixel>()
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()
}
}

View File

@ -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()
}
}

View File

@ -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)

View File

@ -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

View File

@ -1,4 +1,5 @@
<android.support.design.widget.CoordinatorLayout
android:id="@+id/layout_content"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
@ -22,7 +23,6 @@
</android.support.design.widget.AppBarLayout>
<LinearLayout
android:id="@+id/layout_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
@ -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.emu6502.Display
android:id="@+id/display"
<FrameLayout
android:id="@+id/display_wrapper"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"/>
android:background="@android:color/black"
android:visibility="gone">
<android.emu6502.Display
android:id="@+id/display"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
</android.support.v7.widget.CardView>

View File

@ -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));