From 114c376af7556af7f1b2158d5b4185a5f1e369dc Mon Sep 17 00:00:00 2001
From: Can
Date: Mon, 3 Feb 2025 22:11:01 +0100
Subject: feat: some tests

---
 README.md               |  9 +++++++
 src/cpu.effekt          | 22 +++++++++-------
 src/main.effekt         |  2 +-
 src/renderers/js.effekt |  1 +
 src/test.effekt         | 69 ++++++++++++++++++++++++++++++++++++++++++++++---
 5 files changed, 88 insertions(+), 15 deletions(-)

diff --git a/README.md b/README.md
index 2aa608a..8206d4d 100644
--- a/README.md
+++ b/README.md
@@ -49,6 +49,15 @@ effekt src/main.effekt --backend js-web --includes .
 This will then produce a `main.html` and `main.js` file in the `out` directory. You can open the `main.html` file in your browser to run the emulator.
 
 Chip-8 ROMs can be found online and loaded into the emulator by clicking the "Load ROM" button in the GUI.
+If you want to enable audio, you can do so by clicking the "Enable Audio" button in the GUI.
+
+## Keyboard Layout
+
+| 1 (1) | 2 (2) | 3 (3) | 4 (C) |
+| ----- | ----- | ----- | ----- |
+| Q (4) | W (5) | E (6) | R (D) |
+| A (7) | S (8) | D (9) | F (E) |
+| Z (A) | X (0) | C (B) | V (F) |
 
 ## Resources
 
diff --git a/src/cpu.effekt b/src/cpu.effekt
index c2a929e..bd441fd 100644
--- a/src/cpu.effekt
+++ b/src/cpu.effekt
@@ -17,7 +17,16 @@ CPU Cycle and Effects:
 6. Wait for next cycle
 */
 
-extern io def getNow(): Int = jsWeb "Date.now()"
+extern io def getNow(): Int = 
+  js "Date.now()"
+  chez "(current-milliseconds)"
+  llvm """
+    %time = call i64 @time(ptr null)
+    %ms = mul i64 %time, 1000
+    ret i64 %ms
+  """
+  vm "effekt::getNow()"
+
 interface CPU {
   def initCPU(rom: ByteArray): Unit
   def cycleCPU(): Unit
@@ -75,7 +84,6 @@ def makeCPU() {r: Renderer} = {
         val key_ = r.getKeyPressed().getOrElse { "P" }
         if (key_ != "P") {
           key = convertKey(key_)
-          r.log("Key pressed: " ++ show(key))
         }
         last_key_update_time = currentTime
       }
@@ -84,8 +92,6 @@ def makeCPU() {r: Renderer} = {
         val h = ram.getAddr(pc)
         val l = ram.getAddr(pc + 1)
         val inst = bitwiseOr(bitwiseShl(h.toInt(), 8), l.toInt())
-        r.log("PC: " ++ show(pc))
-        r.log("Instruction: " ++ show(inst))
 
         // Decode
         val opcode = bitwiseShr(bitwiseAnd(inst, 61440), 12)
@@ -184,11 +190,9 @@ def makeCPU() {r: Renderer} = {
                 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 and VF = LSB of VX
               case 6 => {
@@ -203,11 +207,9 @@ def makeCPU() {r: Renderer} = {
                 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 and VF = MSB of VX
               case 14 => {
diff --git a/src/main.effekt b/src/main.effekt
index befe021..fd9f7c0 100644
--- a/src/main.effekt
+++ b/src/main.effekt
@@ -1,4 +1,4 @@
-module main // must be named same as the file!
+module main
 
 import src/cpu
 import src/renderers/js
diff --git a/src/renderers/js.effekt b/src/renderers/js.effekt
index 3292481..f1da8e6 100644
--- a/src/renderers/js.effekt
+++ b/src/renderers/js.effekt
@@ -23,6 +23,7 @@ extern jsWeb """
   function log(msg) {
     const message = new Date().toLocaleTimeString() + ' - ' + msg + ' \n'
     console.log(message);
+    document.getElementById('logs').textContent += message;
   }
 
   function romCheck() {
diff --git a/src/test.effekt b/src/test.effekt
index f605d19..7048bab 100644
--- a/src/test.effekt
+++ b/src/test.effekt
@@ -1,10 +1,71 @@
 module src/test
 
 import test
-import src/lib
+import src/cpu
+import src/renderer
+import bytearray
+import array
 
-def main() = mainSuite("lib") {
-  test("Hello world") {
-    assertEqual("Hello, world!", "Hello, world!")
+// Mock renderer for testing
+def makeMockRenderer() = {
+  var screen: Array[Bool] = allocate(32 *64)
+  var lastKey: String = "P"
+  var beeping: Bool = false
+  new Renderer {
+  def init(run: (ByteArray) => Unit at {io, global}) = ()
+  def clear() = screen = allocate(32 *64)
+  def draw(x: Int, y: Int, color: String) = {
+    screen.get(y).set(x, color == "white")
+  }
+  def fill(color: String) = {
+    screen = allocate(32 *64, color == "white")
+  }
+  def get(x: Int, y: Int): Bool = screen(y)(x)
+  def update(f: () => Unit at {io, global}) = ()
+  def log(msg: String) = ()
+  def getKeyPressed() = Some(lastKey)
+  def beep() = { beeping = true }
+  def stopBeep() = { beeping = false }
   }
 }
+def main() = mainSuite("CHIP-8") {
+  // CPU Tests
+  
+    test("Key conversion") {
+      assertEqual(convertKey("1"), 1)
+      assertEqual(convertKey("2"), 2)
+      assertEqual(convertKey("v"), 15)
+      assertEqual(convertKey("invalid"), -1)
+  
+    test("CPU initialization") {
+      val renderer = makeMockRenderer()
+      val cpu = makeCPU() { renderer }
+      val testRom = allocate(10)
+      cpu.initCPU(testRom)
+      // Add assertions here for initial state
+    }
+
+    test("Basic instructions") {
+      val renderer = makeMockRenderer()
+      val cpu = makeCPU() { renderer }
+      val testRom = allocate(4)
+      // Set up a simple instruction in ROM
+      testRom.unsafeSet(0, 96) // 0x60
+      testRom.unsafeSet(1, 66) // 0x42
+      cpu.initCPU(testRom)
+      cpu.cycleCPU()
+      
+    }
+  }
+
+  // Renderer Tests
+  
+    test("Screen operations") {
+      val renderer = makeMockRenderer()
+      renderer.clear()
+      renderer.draw(0, 0, "white")
+      assert(renderer.get(0, 0))
+      renderer.draw(0, 0, "black")
+      assert(renderer.get(0, 0), false)
+    }
+}
\ No newline at end of file
-- 
cgit v1.2.3