OK, I just finished writing a Chip-8 implementation in Scala and connected a 4x4 keypad to my Raspberry, so now I can actually play games there!

I did hit an annoying issue here, though... The fact that:

  • The screen refresh logic is slow (I think this is a limitation if I2C)
  • Chip8 doesn't have any kind of VSync

Means that I need to simulate the CPU at a high-ish clock rate (10KHz) so that I don't spend a bunch of time drawing intermediate states. I think this might cause issue in some games (In my local simulator, 1KHz seems to be the soft spot for most games).

As an aside, this is the second time that I give Chip8 a try (I gave a talk about that in 2016: https://joaocosta.eu/Talks/Chip8/#/)

Turns out I was making things way too complex back then by trying to be smart... This time I went "Ints everywhere and immutability goes brrrr" and it was super easy to implement. 😂

João Costa shared 6 days ago
João Costa shared 9 days ago

Working a bit more on Scala Native + Raspberry PI GPIO .

I have some code to:

  • Display arbitrary images to an OLED
  • Perform an action (e.g. change an image) when a button is pushed
  • Control a LED
  • Fetch data from an HTTP server (OK, this part is not yet working because I run out of RAM while compiling)

So, I have all the building blocks for a small project:

  • Periodically fetch notifications from a server
  • Turn the LED on when there are unread notifications
  • Show some details about the OLED screen
  • Clear the notifications with a button press

But, I just noticed that I don't have any concrete project like that in mind. 😐

It also doesn't help that the screen is tiny (128x64 and about the size of my thumb), so the amount of text I can show is a bit limited unless I use a font for ants. 😂

So I think I'll let things simmer for a bit until I figure out what to do with this.

I'm back from some two weeks in Mexico, so I can go back to playing with my Raspberry.

Got I2C communications to work and rendering to a SSD1306 with Scala Native.

I admit that I'm a bit confused about the coordinate system (and the way pixels are sent to the screen in vertical batches along the horizontal axis doesn't help), but it works, and I think if I play around with some initialization flags things should be more intuitive.

For now, I'm happy that it's working. 🙂

João Costa shared a month ago
João Costa shared a month ago

Got some Scala bindings for WiringPi thanks to SN-Bindgen (plus some manual adjustments)

https://gist.github.com/JD557/c81ad69f7506c70a5c26e9ddec24db27

Took me quite some time to get things to work... but at least I got a blinking LED today 😅 :

import wiringpi.all.*

println("Starting")
wiringPiSetup()

val led = 25

pinMode(led, OUTPUT)

while(true) {
  println("ON")
  digitalWrite(led, HIGH)
  Thread.sleep(1000)
  println("OFF")
  digitalWrite(led, LOW)
  Thread.sleep(1000)
}

I do need to figure out how to cross compile before I try something more complex... Scala Native compilation on a Raspberry Pi 3 B is SLOW! 😬

GIF

I've been flashing some Raspberry Pis recently and damn, this new Raspberry Pi Imager 2 (https://www.raspberrypi.com/news/a-new-raspberry-pi-imager/) is awesome.

Not only does the wizard includes a bunch of images (so no more searching for ISOs of common distros) it let's me configure credentials, SSH and Wi-Fi from the get go. That's a huge time saver for the initial setup. 🔥

João Costa shared a month ago
João Costa shared a month ago

I know that this is kind of creepy in the "being spied sense", but I kind of love how, after searching/buying any maker adjacent thing (e.g. 3d printer filament, smart home devices...), ad networks start treating me like the coolest guy ever. 😎

I buy a couple of small things and immediately start getting ads like:

  • "Buy this CNC mill!"
  • "What you need is a laser engraver. Perfect for all your projects!"
  • "Replace your soldering iron with our fully featured soldering kit!"
  • "Look at this pocket radiation detector/spectrometer!"
  • "Call us to print your PCBs!"

As if I had a huge workshop and my latest "big project" wasn't "adding a switch to a LED strip". 😅

João Costa shared a month ago
João Costa shared a month ago

This weekend I was playing a bit with displacement filters, with the help of https://www.smashingmagazine.com/2021/09/deep-dive-wonderful-world-svg-displacement-filtering/

It's actually not that hard to get some cool effects from this. However, it's a bit of a shame that nice distortions require some big gaussian blur kernel... I need to see if I can get similar effects by precomputing the blured images and just blending them with transparency.

GIF
João Costa shared 3 months ago
João Costa shared 3 months ago

I've been dusting off my PS2 lately - the DVD player was a goner a long time ago, but with the new PSBBN patches it's trivial to launch some of the old games from an SSD for that nostalgia hit.

One thing that I don't remember being as annoying is the lack of a "Quit" button in games and a "Shutdown" button in the main menu.

I remember finding it odd when the next generation of consoles added a home button (it's not a PC, why would you want to go back to the main menu?), but in hindsight, that's super helpful 😄 .

(I know that there's some homebrew to add a shutdown button to the main menu, but it's kind of useless if I need to reset the console to get back to it)

  • Want to stop playing? Get up!
  • Want to change games? Get up! (To be fair, back then you would have to do that anyway)
  • Want to load a different save file? Get up!
  • Want to change settings? Get up!

Scastie finally supports Scala 3 libraries with Scala.js.🔥

I was playing around with the idea of interactive-ish graphic tutorials: https://scastie.scala-lang.org/JD557/sXkQ2yQsSpqoQgXounejXQ/4

Playing around with Ordered Dithering and Bayer Matrices.

Interactive demo with multiple dithering sizes (2x2, 4x4, 8x8 and 16x16): https://joaocosta.eu/Demos/Dither/

This was much simpler than I thought. The whole dithering logic is just:

def buildBayerMatrix(n: Int): Vector[Vector[Int]] =
  if (n <= 1) Vector(Vector(0))
  else {
    val mn2 = buildBayerMatrix(n / 2) // M_(n/2)

    val q1 = mn2.map(_.map(_ * 4)) // 4*M_(n/2)
    val q2 = q1.map(_.map(_ + 2)) // 4*M_(n/2) + 2*J_(n/2)
    val q3 = q1.map(_.map(_ + 3)) // 4*M_(n/2) + 3*J_(n/2)
    val q4 = q1.map(_.map(_ + 1)) // 4*M_(n/2) + 1*J_(n/2)

    q1.zip(q2).map(_ ++ _) ++ q3.zip(q4).map(_ ++ _)
  }

def buildDitherSurface(n: Int): RamSurface = {
  val mask = buildBayerMatrix(n)
  val max  = n * n
  RamSurface(mask.map(_.map(x => Color.grayscale((x * 255) / max))))
}

def ditherImage(image: Surface, ditherMask: Surface): Surface =
  image.view.zipWith(
    ditherMask.view.repeating,
    (color, mask) =>
      Color(
        if (color.r >= mask.r) 255 else 0,
        if (color.g >= mask.g) 255 else 0,
        if (color.b >= mask.b) 255 else 0
      )
  )
Ordered Dithering Demo joaocosta.eu