aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorCan2025-01-31 02:38:56 +0100
committerCan2025-01-31 02:38:56 +0100
commit7beb6fd52a4fad8e914a061eb3816cdff206dc76 (patch)
treea00bc7e6490d0828327be7cf9d39f511bcce2db5 /src
parentba86b9db29b1cbf3bebea70d98655967ba9da770 (diff)
feat: finally everything works
Diffstat (limited to 'src')
-rw-r--r--src/cpu.effekt129
-rw-r--r--src/renderer.effekt2
-rw-r--r--src/renderers/js.effekt40
3 files changed, 116 insertions, 55 deletions
diff --git a/src/cpu.effekt b/src/cpu.effekt
index f347ddd..d61d33b 100644
--- a/src/cpu.effekt
+++ b/src/cpu.effekt
@@ -71,14 +71,13 @@ def makeCPU() {r: Renderer} = {
def cycleCPU() = {
val currentTime = getNow()
var key: Int = -1 // Key pressed
- if (currentTime - last_key_update_time >= 16) { // 60Hz key updates
- // Update keys
+ if (currentTime - last_key_update_time >= 1) {
val key_ = r.getKeyPressed().getOrElse { "P" }
if (key_ != "P") {
- last_key_update_time = currentTime
key = convertKey(key_)
r.log("Key pressed: " ++ show(key))
}
+ last_key_update_time = currentTime
}
if (currentTime - last_instruction_run_time >= 2) { // ~500Hz
@@ -103,7 +102,6 @@ def makeCPU() {r: Renderer} = {
// Clear the screen
case 224 => {
r.clear()
- r.log("Clearing the screen...")
pc = pc + 2
}
// Return from a subroutine
@@ -112,7 +110,8 @@ def makeCPU() {r: Renderer} = {
pc = stack.popFront().getOrElse { 0 }
}
} else {
- r.log("Unknown Instruction: " ++ show(inst))
+ // 0nnn - SYS addr - Jump to a machine code routine at nnn
+ pc = nnn
}
}
// Jump to address NNN
@@ -130,7 +129,6 @@ def makeCPU() {r: Renderer} = {
pc = pc + 2
}
pc = pc + 2
- r.log("Skipping next instruction...")
}
// Skip next instruction if VX != NN
case 4 => {
@@ -148,59 +146,77 @@ def makeCPU() {r: Renderer} = {
}
// Set VX = NN
case 6 => {
- v.set(x, nn)
+ v.unsafeSet(x, nn.toByte())
pc = pc + 2
}
// Set VX = VX + NN
case 7 => {
- v.set(x, v.unsafeGet(x).toInt() + nn)
+ v.unsafeSet(x, (v.unsafeGet(x).toInt() + nn).toByte())
pc = pc + 2
}
case 8 => {
n match {
// Set VX = VY
case 0 => {
- v.set(x, v.unsafeGet(y))
+ v.unsafeSet(x, v.unsafeGet(y))
}
// Set VX = VX OR VY
case 1 => {
- v.set(x, bitwiseOr(v.unsafeGet(x).toInt(), v.unsafeGet(y).toInt()))
+ v.unsafeSet(x, bitwiseOr(v.unsafeGet(x).toInt(), v.unsafeGet(y).toInt()).toByte())
}
// Set VX = VX AND VY
case 2 => {
- v.set(x, bitwiseAnd(v.unsafeGet(x).toInt(), v.unsafeGet(y).toInt()))
+ v.unsafeSet(x, bitwiseAnd(v.unsafeGet(x).toInt(), v.unsafeGet(y).toInt()).toByte())
}
// Set VX = VX XOR VY
case 3 => {
- v.set(x, bitwiseXor(v.unsafeGet(x).toInt(), v.unsafeGet(y).toInt()))
+ v.unsafeSet(x, bitwiseXor(v.unsafeGet(x).toInt(), v.unsafeGet(y).toInt()).toByte())
}
// Set VX = VX + VY, set VF = carry
case 4 => {
- r.log("Adding" ++ show(v.unsafeGet(x)) ++ " and " ++ show(v.unsafeGet(y)))
val sum = v.unsafeGet(x).toInt() + v.unsafeGet(y).toInt()
- r.log("Sum: " ++ show(sum) ++ " Carry: " ++ show(if (sum > 255) 1 else 0))
- v.set(15, if (sum > 255) 1 else 0)
- v.set(x, sum.toByte())
+ v.unsafeSet(x, sum.toByte())
+ v.unsafeSet(15, (if (sum > 255) 1 else 0).toByte())
+
}
// Set VX = VX - VY, set VF = NOT borrow
case 5 => {
- v.set(15, if (v.unsafeGet(x).toInt() > v.unsafeGet(y).toInt()) 1 else 0)
- v.set(x, v.unsafeGet(x).toInt() - v.unsafeGet(y).toInt())
+ val vx = v.unsafeGet(x).toInt()
+ val vy = v.unsafeGet(y).toInt()
+ val sub = vx - vy
+ r.log(show(vx) ++ "(" ++ show(x) ++ ") - " ++ show(vy) ++ "(" ++ show(y) ++ ") = " ++ show(sub) ++ "with borrow of " ++ show(if (vx > vy) 1 else 0))
+ v.unsafeSet(x, sub.toByte())
+ v.unsafeSet(15, (if (vx > vy) 1 else 0).toByte())
+ r.log("vx is now " ++ show(v.unsafeGet(x)))
+ r.log("vF is now " ++ show(v.unsafeGet(15)))
}
- // Set VX = VX SHR 1
+ // Set VX = VX SHR 1 and VF = LSB of VX
case 6 => {
- v.set(15, bitwiseAnd(v.unsafeGet(x).toInt(), 1))
- v.set(x, bitwiseShr(v.unsafeGet(x).toInt(), 1))
+ val vx = v.unsafeGet(x).toInt()
+ val lsb = bitwiseAnd(vx, 1)
+ val vx_ = bitwiseShr(vx, 1)
+ v.unsafeSet(x, vx_.toByte())
+ v.unsafeSet(15, lsb.toByte())
}
// Set VX = VY - VX, set VF = NOT borrow
case 7 => {
- v.set(15, if (v.unsafeGet(y).toInt() > v.unsafeGet(x).toInt()) 1 else 0)
- v.set(x, v.unsafeGet(y).toInt() - v.unsafeGet(x).toInt())
+ val vx = v.unsafeGet(x).toInt()
+ val vy = v.unsafeGet(y).toInt()
+ val sub = vy - vx
+ r.log(show(vy) ++ "(" ++ show(y) ++ ") - " ++ show(vx) ++ "(" ++ show(x) ++ ") = " ++ show(sub) ++ "with borrow of " ++ show(if (vy > vx) 1 else 0))
+ v.unsafeSet(x, sub.toByte())
+ v.unsafeSet(15, (if (vy > vx) 1 else 0).toByte())
+ r.log("vx is now " ++ show(v.unsafeGet(x)))
+ r.log("vF is now " ++ show(v.unsafeGet(15)))
}
- // Set VX = VX SHL 1
+ // Set VX = VX SHL 1 and VF = MSB of VX
case 14 => {
- v.set(15, bitwiseAnd(v.unsafeGet(x).toInt(), 128))
- v.set(x, bitwiseShl(v.unsafeGet(x).toInt(), 1))
+ val vx = v.unsafeGet(x).toInt()
+ val msb = bitwiseAnd(vx, 128)
+ val vx_ = vx * 2
+ val vf = bitwiseShr(msb, 7)
+ v.unsafeSet(x, vx_.toByte())
+ v.unsafeSet(15, vf.toByte())
}
} else {
r.log("Unknown Instruction: " ++ show(inst))
@@ -227,7 +243,7 @@ def makeCPU() {r: Renderer} = {
// Set VX = random byte AND NN
case 12 => {
val randInt: Int = floor(random() * 255.0)
- v.set(x, bitwiseAnd(randInt, nn))
+ v.unsafeSet(x, bitwiseAnd(randInt, nn).toByte())
pc = pc + 2
}
// Display n-byte sprite starting at memory location I at (VX, VY), set VF = collision
@@ -236,38 +252,57 @@ def makeCPU() {r: Renderer} = {
val vy = v.unsafeGet(y).toInt()
var collision = false
var yline = 0
+
while (yline < n) {
val pixel = ram.getAddr(i + yline)
var xline = 0
+
while (xline < 8) {
- if (bitwiseAnd(pixel.toInt(), bitwiseShr(128, xline)) != 0) {
- if (r.get(vx + xline, vy + yline)) {
+ // Get if the current sprite pixel is set (1)
+ val spritePixel = bitwiseAnd(bitwiseShr(pixel.toInt(), 7 - xline), 1) == 1
+
+ if (spritePixel) {
+ val x = mod(vx + xline, 64)
+ val y = mod(vy + yline, 32)
+
+ // Get current screen pixel
+ val screenPixel = r.get(x, y)
+
+ // XOR operation
+ if (screenPixel == spritePixel) {
+ // If both pixels are on, turn it off and set collision
+ r.draw(x, y, "black")
collision = true
+ } else if (screenPixel != spritePixel) {
+ // If screen pixel is off and sprite pixel is on, turn it on
+ r.draw(x, y, "white")
}
- r.draw(vx + xline, vy + yline)
- // r.log("Drawing at: " ++ show(vx + xline) ++ ", " ++ show(vy + yline))
}
xline = xline + 1
}
yline = yline + 1
}
- v.set(15, if (collision) 1 else 0)
+
+ v.unsafeSet(15, (if (collision) 1 else 0).toByte())
pc = pc + 2
}
case 14 => {
- r.log("nn: " ++ show(nn))
nn match {
// Skip next instruction if key with the value of VX is not pressed
case 161 => {
- r.log("Key: " ++ show(key) ++ " VX: " ++ show(v.unsafeGet(x)))
- if (key != -1 && key != v.unsafeGet(x).toInt()) {
+ val vx = v.unsafeGet(x).toInt()
+ if (key != v.unsafeGet(x).toInt()) {
+ pc = pc + 4
+ } else {
pc = pc + 2
}
}
// Skip next instruction if key with the value of VX is pressed
case 158 => {
- r.log("Key: " ++ show(key) ++ " VX: " ++ show(v.unsafeGet(x)))
+ val vx = v.unsafeGet(x).toInt()
if (key != -1 && key == v.unsafeGet(x).toInt()) {
+ pc = pc + 4
+ } else {
pc = pc + 2
}
}
@@ -279,29 +314,35 @@ def makeCPU() {r: Renderer} = {
nn match {
// Set VX = delay timer value
case 7 => {
- v.set(x, delay)
+ v.unsafeSet(x, delay)
+ pc = pc + 2
}
// Wait for a key press, store the value of the key in VX
case 10 => {
- if (key != -1) {
- v.set(x, key.toByte())
+ if (key >= 0) {
+ v.unsafeSet(x, key.toByte())
+ pc = pc + 2
}
}
// SET delay timer = VX
case 21 => {
delay = v.unsafeGet(x)
+ pc = pc + 2
}
// Set sound timer = VX
case 24 => {
sound = v.unsafeGet(x)
+ pc = pc + 2
}
// SET I = I + VX
case 30 => {
i = i + v.unsafeGet(x).toInt()
+ pc = pc + 2
}
// Set I = location of sprite for digit VX
case 41 => {
i = v.unsafeGet(x).toInt() * 5
+ pc = pc + 2
}
// Store BCD representation of VX in memory locations I, I+1, and I+2
case 51 => {
@@ -309,6 +350,7 @@ def makeCPU() {r: Renderer} = {
ram.setAddr(i, (vx / 100).toByte())
ram.setAddr(i + 1, mod((vx / 10), 10).toByte())
ram.setAddr(i + 2, mod(vx, 10).toByte())
+ pc = pc + 2
}
// Store registers V0 through VX in memory starting at location I
case 85 => {
@@ -317,21 +359,22 @@ def makeCPU() {r: Renderer} = {
ram.setAddr(i + index, v.unsafeGet(index))
index = index + 1
}
- i = i + x + 1
+ // i = i + x + 1
+ pc = pc + 2
}
// Read registers V0 through VX from memory starting at location I
case 101 => {
var index = 0
while (index <= x) {
- v.set(index, ram.getAddr(i + index))
+ v.unsafeSet(index, ram.getAddr(i + index))
index = index + 1
}
- i = i + x + 1
+ // i = i + x + 1
+ pc = pc + 2
}
} else {
r.log("Unknown Instruction: " ++ show(inst))
}
- pc = pc + 2
}
} else {
r.log("Unknown Instruction: " ++ show(inst))
diff --git a/src/renderer.effekt b/src/renderer.effekt
index b4d5343..9aca29d 100644
--- a/src/renderer.effekt
+++ b/src/renderer.effekt
@@ -11,7 +11,7 @@ import bytearray
interface Renderer {
def init(run: (ByteArray) => Unit at {io, global}): Unit
def clear(): Unit
- def draw(x: Int, y: Int): Unit
+ def draw(x: Int, y: Int, color: String): Unit
def fill(color: String): Unit
def get(x: Int, y: Int): Bool
def update(f: () => Unit at {io, global}): Unit
diff --git a/src/renderers/js.effekt b/src/renderers/js.effekt
index 4aea494..05802e6 100644
--- a/src/renderers/js.effekt
+++ b/src/renderers/js.effekt
@@ -14,13 +14,13 @@ extern io def loadRom(): ByteArray = jsWeb "rom ? rom : new Uint8Array(0)"
extern io def requestAnimationFrame {callback: () => Unit}: Unit = jsWeb "requestAnimationFrame(() => $effekt.runToplevel((ks) => ${box callback}(ks)))"
extern jsWeb """
let rom = undefined;
- let key = undefined;
+ let keyStates = new Map(); // Change to track multiple keys
+ let lastKeyUpdate = performance.now();
+
// Global Log Function
function log(msg) {
const message = new Date().toLocaleTimeString() + ' - ' + msg + ' \n'
console.log(message);
- // Logging with timestamp
- // document.getElementById('logs').innerText += message;
}
function romCheck() {
@@ -31,8 +31,24 @@ extern jsWeb """
}
function keyRegister() {
+ // Track key state changes
document.addEventListener('keydown', (event) => {
- window.key = event.key;
+ event.preventDefault();
+ const key = event.key.toLowerCase();
+ keyStates.set(key, true);
+ lastKeyUpdate = performance.now();
+ });
+
+ document.addEventListener('keyup', (event) => {
+ event.preventDefault();
+ const key = event.key.toLowerCase();
+ keyStates.delete(key);
+ lastKeyUpdate = performance.now();
+ });
+
+ // Prevent key repeats
+ document.addEventListener('keypress', (event) => {
+ event.preventDefault();
});
}
"""
@@ -100,11 +116,11 @@ def eventHandler {onClick: (ByteArray) => Unit}: Unit = {
def clear(): Unit = fill("black")
// Draw at (x, y) on the screen
-extern io def draw(x: Int, y: Int): Unit = jsWeb """
+extern io def draw(x: Int, y: Int, color: String): Unit = jsWeb """
(() => {
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
- ctx.fillStyle = 'white';
+ ctx.fillStyle = ${color};
ctx.fillRect(10 * ${x}, 10 * ${y}, 10, 10);
})();
"""
@@ -130,14 +146,16 @@ extern io def get(x: Int, y: Int): Bool = jsWeb """
extern io def update(f: () => Unit at {io, global}): Unit = jsWeb """
setInterval(() => {
$effekt.runToplevel((ks) => ${f}(ks));
- }, 1000 / 120);
+ }, 1000 / 240);
"""
extern io def getKeyPressed(): String = jsWeb """
(() => {
- const key = window.key;
- window.key = undefined;
- return key;
+ // Return first pressed key or "P" if no keys pressed
+ for (const [key, pressed] of keyStates.entries()) {
+ if (pressed) return key;
+ }
+ return "P";
})();
"""
@@ -146,7 +164,7 @@ namespace JSRenderer {
def makeRenderer: Renderer = new Renderer {
def init(run) = init(run)
def clear() = clear()
- def draw(x: Int, y: Int) = draw(x, y)
+ def draw(x: Int, y: Int, color: String) = draw(x, y, color)
def update(f: () => Unit at {io, global}) = update(f)
def log(msg: String) = log(msg)
def fill(color: String) = fill(color)