A few years ago, I shared here my experience with the Vectrex32 card for the GCE Vectrex vector video game system (here and here). Today, I will share my experience and thoughts about the PiTrex! The project is the brainchild of Graham Toal, Kevin Koster, James Churchill, and Malban. A few good links are: here, and here, and you can buy one from here.

To simplify, the PiTrex is a Raspberry Pi Zero HAT that connects to the Vectrex cartridge port. From a user point of view, it is a Vectrex cartridge – assuming you bought or printed one – with a connector to insert a Pi ZERO (or Zero 2) on top of it. Plug it into the Vectrex; yes, you need one and power it up. The first boot time is a bit longer, so be patient and don’t think you screwed up something. A menu is displayed, and you can navigate it. More on this later.

Yet another way of thinking about the PiTrex is an interface to use the Vectrex vector display, BIOS, and game controller for the Pi Zero. This is where the comparison with the Vectrex32 is the most accurate. I remember describing the latter as a co-processor for the Vectrex. As of today, I would compare the Vectrex32 to a locked-down version of the PiTrex. Let me explain more. The Vectrex32 allows you to write GS BASIC code on your computer and upload it to the Vectrex32, which performs the magic of running it on the Vectrex. Of course, the BASIC has adapted keywords to support the hardware that makes the Vectrex so remarkable: the vector display! That’s it. It works well, is supported, and you can do a lot without hurting yourself with the underlying mess.

Well, the PiTrex gives you access to all the mess 😊. Even more than you wish for, at least during the current development phase of the software layers. Indeed, when you are running bare-metal, that’s what you have. But, since the beginning of the project, a lot has been done by Alban & co. First, the GS BASIC 1.27 has been ported to Pi Zero/PiTrex, and I could save my Bézier curve program to the SD card of the PiTrex (in a dedicated BASIC folder); boot the PiTrex, and run it flawlessly! Beautiful. This already means that you can develop GS BASIC programs for the Vectrex on two platforms. It also means that you can reach a larger community with your code. True, the GS BASIC port to PiTrex is not supported, but it should work fine for standard stuff. Regardless, choice is good, and compatibility makes it better.

The community seems to spend its time on two significant problems. The first one – arguably the most exciting and essential – consists in providing the low-level vector graphics, sounds, and IO routines/libraries on top of the bare metal. Ah, did I mention that all this is done in C and ASM? Yeah, C, that’s super cool. Again, now you have the choice: BASIC or C, you pick. Examples of such vector libraries can be found here and more on the bare-metal environment here. The second body of work is the porting of game frameworks, so you, as a user, can run raster games on your beloved vector console. You may have already spotted the ELITE game port. There are many of them in the works, including MAME, video and audio players

If you plan to grab a PiTrex, buy the Pi Zero (1 or 2 – but with no performance gains) with the GPIO header installed (the H version). The gang recommends the W (with Wi-Fi) version as it may be used in future features. And this is what excites me about the project; you have a fullfledged Pi Zero and many of its resources available to you—starting with the humongous amount of memory vs. the venerable one kilobyte of the Vectrex. Nothing should prevent you from creating an open world to explore! I linked several videos I captured. Unfortunately, as usual with the Vectrex, they don’t give it justice (from a rendering point of view), and the quality is poor because of all these reflections – and my lack of skills as a videographer. Enjoy!

' bezier.bas: draws a Bezier curve, use joystip up & down top change draw step.

' Define display configuration.
scale = 80
intensity = 70
frame_rate = 20

' Define various UI configuration.
anchor_size = 4
control_size = 1
instructions = { _
   {-50, 90, "INSTRUCTIONS"}, _ 
   {-80, 80, "JOYSTICK UP TO +1 BEZIER STEPS"}, _ 
   {-80, 70, "JOYSTICK DOWN TO -1 BEZIER STEPS"}, _ 
   {-80, 60, "MIN STEP IS 1 - MAX STEP IS 80."} _

' Bn functions are respectively first, second, third and fourth 
' Bernstein derivations to compute the quadratic B-Spline.
function B1(t) 
   return ((t) * (t) * (t)) 

function B2(t) 
   return (3 * (t) * (t) * (1 - (t)))

function B3(t)
   return (3 * (t) * (1 - (t)) * (1 - (t)))

function B4(t)
   return ((1 - (t)) * (1 - (t)) * (1 - (t)))

sub draw_square(x, y, s)
   move = MoveSprite(x, y)
   square = LinesSprite( _
      { _
         { MoveTo, s, s }, _
         { DrawTo, -s, s}, _
         { DrawTo, -s, -s }, _
         { DrawTo, s, -s }, _
         { DrawTo, s, s } _
      } _
   call ReturnToOriginSprite()

sub draw_segment(x1, y1, x2, y2)
   segment = LinesSprite( _
      { _
         { MoveTo, x1, y1 }, _
         { DrawTo + $F0, x2, y2} _
      } _
   call ReturnToOriginSprite()

sub draw_bezier_curve(x1, y1, x2, y2, x3, y3, x4, y4, s)
   if s > 0.0 then 
      call ReturnToOriginSprite()
      call draw_square(x1+anchor_size, y1, anchor_size)
      call draw_square(x2, y2, control_size)
      call draw_segment(x1+anchor_size, y1, x2, y2)
      call draw_square(x3, y3, control_size)
      call draw_square(x4, y4, anchor_size)
      call draw_segment(x3, y3, x4, y4)
      i = 0.0
      lastx = x4
      lasty = y4
      d = 1.0 / s;
      curve = {{ MoveTo, x1, y1 }}
         x = Int(x1 * B1(i) + x2 * B2(i) + x3 * B3(i) + x4 * B4(i))
         y = Int(y1 * B1(i) + y2 * B2(i) + y3 * B3(i) + y4 * B4(i))
         move = {{ MoveTo, lastx, lasty}, { DrawTo, x, y }}
         curve = AppendArrays(curve, move)
         i = i + d
         lastx = x
         lasty = y
     until i >= 1.0
     move = {{ DrawTo, x1, y1 }}
     curve = AppendArrays(curve, move)
     bezier = LinesSprite(curve)

' Driver code to demonstrate use of draw_bezier_curve().
x = 4
o = 80.0
s = 20
sd = 1
sil = 1
sul = 80

while true
   d = 0
   for i = -50.0 to 50.0 step x

      ' Display instructions.
      textSize = {25, 4} '{40, 5}
      call TextSizeSprite(textSize) 
      call TextListSprite(instructions)

      ' Display current steps.
      textSize = {40, 5}
      current_steps = {{-50, 150, "STEPS: " + s}} 
      call TextSizeSprite(textSize) 
      call TextListSprite(current_steps)

      ' Setup display for the Bezier curve.
      call IntensitySprite(intensity)
      call SetFrameRate(frame_rate)
      call ScaleSprite(scale)

      ' Draw the updated Bezier curve.
      call draw_bezier_curve( _
         -o, -d, _
         -o+d, o+d, _
         o-d/4, -o, _
         o-1.5*d, o-d, _
         s _

      ' Prepare for next frame & update steps -- if needed.
      d = d + 5
      controls = WaitForFrame(JoystickDigital, Controller1, JoystickY)
      if controls[1, 2] > 0 then
         s = s + sd
         if s > sul then 
            s = sul
      elseif controls[1, 2] < 0 then
         s = s - sd
         if s < sil then 
            s = sil 
      call ClearScreen()
      call ReturnToOriginSprite()

   next i