User avatar
RichardRussell
Posts: 1396
Joined: Thu Jun 21, 2012 10:48 am

Re: Introduction to BBC BASIC

Tue Feb 02, 2021 10:04 am

My attention has been drawn to this blog by Alan Pope, in which he describes his experiments in getting a Raspberry Pi 400 to boot straight into BBC BASIC. Looks like he could do with some help with aspects such as speeding up booting and getting it working full-screen.

User avatar
RichardRussell
Posts: 1396
Joined: Thu Jun 21, 2012 10:48 am

Re: Introduction to BBC BASIC

Tue Feb 09, 2021 4:14 pm

This really belongs in the 'Off topic dIscussion' board, where there is already a thread about Conway's Game of Life, but that board has been shut down temporarily because of spam. I'd just like to draw attention to two recent BBC BASIC implementations of the Game of Life which illustrate different aspects of the language. They both operate on a 512x512 grid (262,144 cells).

The first uses shader (GPU) code to do the actual calculations, so BBC BASIC is only being used as a 'wrapper' which compiles and executes the shader code. You may consider that to be cheating, but one of the strengths of BBC BASIC has always been that it comes with an embedded assembler that allows you to incorporate assembly language subroutines when interpreted BASIC isn't fast enough, and you can consider shader code to be a bit like that. Certainly it runs crazily fast, managing to update all 262,144 cells at frame rate on most platforms.

Here is the source code, which will run on a Raspberry Pi (tested on a RPi 4):

Code: Select all

      REM Conway's 'Game of Life': Shader version for BBC BASIC for SDL 2.0
      REM Adapted from various examples at Shadertoy.com by Richard Russell

      REM Size of grid (max = window size, must be power-of-two for WebGL):
      VDU 23,22,512;512;8,16,16,0
      GridW% = 512
      GridH% = 512

      REM Initial random pattern:
      FOR C% = 1 TO 2048 : VDU 32+RND(94) : NEXT
      OSCLI "GSAVE """ + @tmp$ + "initial.bmp"" 0,0," + STR$(GridW%*2) + "," + STR$(GridH%*2)
      CLS

      REM Install libraries:
      INSTALL @lib$ + "shaderlib"

      REM Define constants:
      GL_NEAREST = &2600
      GL_FRAMEBUFFER = &8D40
      GL_COLOR_ATTACHMENT0 = &8CE0

      REM Create arrays and structures:
      DIM Vertex$(10), Fragment$(999), Final$(10), Float{0%,1%}

      REM Fill vertex and fragment shader arrays from DATA statements:
      PROC_readshader(Vertex$())
      PROC_readshader(Fragment$())
      PROC_readshader(Final$())

      REM Initialise window and get its size:
      ON MOVE IF @msg% <> 5 RETURN ELSE PROCcleanup
      VDU 26
      IF POS REM SDL thread sync
      ScrW% = @size.x%
      ScrH% = @size.y%

      REM Ensure cleanup on exit:
      ON CLOSE PROCcleanup : QUIT
      ON ERROR PROCcleanup : MODE 3 : PRINT REPORT$ : END

      REM Initialise shader library:
      PROC_shaderinit(oVertex%, oFragment%)
      SYS `glCreateShader`, GL_FRAGMENT_SHADER, @memhdc% TO oFinal%
      `glGenFramebuffers` = FN_gpa("glGenFramebuffers")
      `glBindFramebuffer` = FN_gpa("glBindFramebuffer")
      `glFramebufferTexture2D` = FN_gpa("glFramebufferTexture2D")
      `glDeleteFramebuffers` = FN_gpa("glDeleteFramebuffers")

      REM Compile shaders:
      PROC_compileshader(oVertex%, Vertex$(), "Vertex")
      PROC_compileshader(oFragment%, Fragment$(), "Fragment")
      PROC_compileshader(oFinal%, Final$(), "Final")

      REM Link shaders:
      oProgram1% = FN_useshaders(oVertex%, oFragment%)
      oProgram2% = FN_useshaders(oVertex%, oFinal%)

      REM Get pointers to uniforms:
      SYS `glGetUniformLocation`, oProgram1%, "iResolution", @memhdc% TO pResolution1%
      SYS `glGetUniformLocation`, oProgram1%, "iChannel0",   @memhdc% TO pChannel0_1%
      SYS `glGetUniformLocation`, oProgram2%, "iResolution", @memhdc% TO pResolution2%
      SYS `glGetUniformLocation`, oProgram2%, "iChannel0",   @memhdc% TO pChannel0_2%

      REM Load and bind initial random texture:
      SYS `glActiveTexture`, GL_TEXTURE0, @memhdc%
      Texture1% = FN_loadtexture1(@tmp$ + "initial.bmp")
      SYS `glTexParameteri`, GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST, @memhdc%
      Texture2% = FN_loadtexture1(@tmp$ + "initial.bmp")
      SYS `glTexParameteri`, GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST, @memhdc%
      IF Texture1% = 0 OR Texture2% = 0 ERROR 100, "Couldn't load initial.bmp"
      SYS `glUniform1i`, pChannel0_1%, 0, @memhdc%
      SYS `glUniform1i`, pChannel0_2%, 0, @memhdc%

      REM Create Framebuffer object:
      DIM align{fbo%}
      SYS `glGenFramebuffers`, 1, align{}, @memhdc%
      oBuffer% = align.fbo%
      IF oBuffer% = 0 ERROR 100, "Couldn't create framebuffer"

      REM Render loop:
      REPEAT
        REM Stage one: render to texture:
        Float.0% = FN_f4(GridW%) : Float.1% = FN_f4(GridH%)
        SYS `glBindFramebuffer`, GL_FRAMEBUFFER, oBuffer%, @memhdc%
        SYS `glFramebufferTexture2D`, GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, \
        \                                     GL_TEXTURE_2D, Texture2%, 0, @memhdc%
        SYS `glBindTexture`, GL_TEXTURE_2D, Texture1%, @memhdc%
        SYS `glUseProgram`, oProgram1%, @memhdc%
        SYS `glUniform2fv`, pResolution1%, 1, Float{}, @memhdc%
        SYS `glDrawArrays`, GL_TRIANGLES, 0, 6, @memhdc%

        REM Stage two: render to canvas:
        Float.0% = FN_f4(ScrW%) : Float.1% = FN_f4(ScrH%)
        SYS `glBindFramebuffer`, GL_FRAMEBUFFER, 0, @memhdc%
        SYS `glBindTexture`, GL_TEXTURE_2D, Texture2%, @memhdc%
        SYS `glUseProgram`, oProgram2%, @memhdc%
        SYS `glUniform2fv`, pResolution2%, 1, Float{}, @memhdc%
        PROC_render

        SWAP Texture1%, Texture2%
      UNTIL FALSE
      END

      DEF PROCcleanup
      ON ERROR OFF
      oBuffer%  += 0  : IF oBuffer%   SYS `glDeleteFramebuffers`, 1, ^oBuffer%, @memhdc%
      Texture1% += 0  : IF Texture1%  SYS `glDeleteTextures`, 1, ^Texture1%, @memhdc%
      Texture2% += 0  : IF Texture2%  SYS `glDeleteTextures`, 1, ^Texture2%, @memhdc%
      oProgram2% += 0 : IF oProgram2% SYS `glDeleteProgram`, oProgram2%, @memhdc%
      oFinal% += 0    : IF oFinal%    SYS `glDeleteShader`, oFinal%, @memhdc%
      oProgram1% += 0 : oVertex% += 0 : oFragment% += 0
      PROC_shaderexit(oProgram1%, oVertex%, oFragment%)
      *REFRESH ON
      ENDPROC

      REM Minimal vertex shader:
      DATA "attribute vec4 vPosition;"
      DATA "void main()"
      DATA "{"
      DATA "gl_Position = vPosition ;"
      DATA "}"
      DATA ""

      REM Intermediate fragment shader (outputs to texture):
      DATA "uniform vec2 iResolution;"
      DATA "uniform sampler2D iChannel0;"

      DATA "int cell( in vec2 p )"
      DATA "{"
      DATA "return (texture2D(iChannel0, p/iResolution.xy).g > 0.5 ) ? 1 : 0;"
      DATA "}"

      DATA "void main()"
      DATA "{"
      DATA "vec2 px = gl_FragCoord.xy;"
      DATA "int k = cell(px+vec2(-1,-1)) + cell(px+vec2(0,-1)) + cell(px+vec2(1,-1))"
      DATA "      + cell(px+vec2(-1, 0))                       + cell(px+vec2(1, 0))"
      DATA "      + cell(px+vec2(-1, 1)) + cell(px+vec2(0, 1)) + cell(px+vec2(1, 1));"
      DATA "int e = cell(px);"
      DATA "float f = (((k==2) && (e==1)) || (k==3)) ? 1.0 : 0.0;"
      DATA "gl_FragColor = vec4( f, f, f, 1.0 );"
      DATA "}"
      DATA ""

      REM Final fragment shader (outputs to canvas):
      DATA "uniform vec2 iResolution;"
      DATA "uniform sampler2D iChannel0;"
      DATA "void main()"
      DATA "{"
      DATA "gl_FragColor = texture2D(iChannel0, gl_FragCoord.xy/iResolution.xy);"
      DATA "}"
      DATA ""
The second version is a 'pure BASIC' implementation, although I've made what use I can of BBC BASIC features such as 'array arithmetic' and structures. In updating and displaying each 'generation' there's not a single loop (not even an unrolled loop), just linear code! On my RPi 4 it runs at about one generation per second (for a 512x512 grid), compared with about 18 per second on my fastest PC:

Code: Select all

      REM Conway's 'Game of Life' in pure BASIC (with some cheating!)
      REM By Richard Russell, http://www.bbcbasic.co.uk/, 13-Jan-2021

      HIMEM = PAGE + 8000000

      REM Set grid size and initialise screen:
      W% = 512 : H% = 512
      VDU 23,22,W%;H%;8,8,16,0
      OFF

      REM Define structures and arrays:
      DIM bm{bfType{l&,h&}, bfSize%, bfReserved%, bfOffBits%, \
      \      biSize%, biWidth%, biHeight%, biPlanes{l&,h&}, biBitCount{l&,h&}, \
      \      biCompression%, biSizeImage%, biXPelsPerMeter%, biYPelsPerMeter%, \
      \      biClrUsed%, biClrImportant%, palette%(255)}, g&(H%-1,W%-1), ft%(9)

      REM Initialise bitmap structure:
      bm.bfType.l& = ASC"B" : bm.bfType.h& = ASC"M"
      bm.bfOffBits% = ^g&(0,0) - bm{} : bm.bfSize% = bm.bfOffBits% + W%*H%
      bm.biSize% = 40 : bm.biWidth% = W% : bm.biHeight% = -H%
      bm.biPlanes.l& = 1 : bm.biBitCount.l& = 8
      bm.palette%(0) = &FF000000 : bm.palette%(1) = &FFFFFFFF

      REM Initialise the grid to a random state:
      FOR Y% = 0 TO H%-1
        FOR X% = 0 TO W%-1
          g&(Y%,X%) = RND(2) - 1
        NEXT
      NEXT

      REM Create 8 copy arrays with gaps:
      G% = W% * 2 + 2 : p%% = 0
      DIM k&(H%-1,W%-1), p%% G%, l&(H%-1,W%-1), p%% G%, m&(H%-1,W%-1), p%% G%, n&(H%-1,W%-1), p%% G%
      DIM o&(H%-1,W%-1), p%% G%, p&(H%-1,W%-1), p%% G%, q&(H%-1,W%-1), p%% G%, r&(H%-1,W%-1), p%% G%

      REM Do some evil descriptor hacking to offset the arrays:
      PROCoffset(k&(),W%+1) : PROCoffset(l&(),W%+1) : PROCoffset(m&(),W%+1) : PROCoffset(n&(),W%+1)
      PROCoffset(o&(),W%+1) : PROCoffset(p&(),W%+1) : PROCoffset(q&(),W%+1) : PROCoffset(r&(),W%+1)

      REM Repeat for each generation:
      GCOL 15
      R% = TIME
      @%=&102010A
      ft%() = 30
      REPEAT
        REM Display grid:
        OSCLI "MDISPLAY " + STR$~bm{}

        REM Make 8 copies of grid:
        k&() = g&() : l&() = g&() : m&() = g&() : n&() = g&()
        o&() = g&() : p&() = g&() : q&() = g&() : r&() = g&()

        REM Offset arrays:
        PROCoffset(k&(),-W%-1) : PROCoffset(l&(),-W%) : PROCoffset(m&(),-W%+1)
        PROCoffset(n&(),-1)    : PROCoffset(o&(),+1)
        PROCoffset(p&(),+W%-1) : PROCoffset(q&(),+W%) : PROCoffset(r&(),+W%+1)

        REM Sum the border cells:
        k&() += l&() + m&() + n&() + o&() + p&() + q&() + r&()

        REM Calculate next state (by Svein Svensson):
        k&() OR= g&()
        g&() = k&() * 80 DIV 240 : REM Relies on 8-bit wraparound

        REM Undo the offsets:
        PROCoffset(k&(),+W%+1) : PROCoffset(l&(),+W%) : PROCoffset(m&(),+W%-1)
        PROCoffset(n&(),+1)    : PROCoffset(o&(),-1)
        PROCoffset(p&(),-W%+1) : PROCoffset(q&(),-W%) : PROCoffset(r&(),-W%-1)

        REM Report speed in title bar:
        T% = (T% + 1) MOD (DIM(ft%(),1) + 1)
        N% = TIME
        ft%(T%) = N% - R%
        R% = N%
        title$ = STR$(1000/SUM(ft%())) + " generations per second"
        IF INKEY$(-256) = "W" THEN
          SYS "SetWindowText", @hwnd%, title$
        ELSE
          SYS "SDL_SetWindowTitle", @hwnd%, title$, @memhdc%
        ENDIF

      UNTIL FALSE
      END

      REM This is non-portable code:
      DEF PROCoffset(RETURN a&(), O%)
      LOCAL i%%, p%%, W%, H% : p%% = PTR(a&())
      W% = DIM(a&(),1) + 1 : H% = DIM(a&(),2) + 1
      IF O% < 0 FOR i%% = p%% + O% + 9 TO p%% + 8 : ?i%% = 0 : NEXT
      IF O% > 0 FOR i%% = p%% + W%*H% + 9 TO p%% + W%*H% + O% + 8 : ?i%% = 0 : NEXT
      p%% += O% : PTR(a&()) = p%%
      ?p%% = 2 : p%%!1 = W% : p%%!5 = H%
      ENDPROC

ejolson
Posts: 12318
Joined: Tue Mar 18, 2014 11:47 am

Re: Introduction to BBC BASIC

Tue Feb 09, 2021 4:36 pm

RichardRussell wrote:
Tue Feb 09, 2021 4:14 pm
This really belongs in the 'Off topic dIscussion' board, where there is already a thread about Conway's Game of Life, but that board has been shut down temporarily because of spam. I'd just like to draw attention to two recent BBC BASIC implementations of the Game of Life which illustrate different aspects of the language. They both operate on a 512x512 grid (262,144 cells).

The first uses shader (GPU) code to do the actual calculations, so BBC BASIC is only being used as a 'wrapper' which compiles and executes the shader code. You may consider that to be cheating, but one of the strengths of BBC BASIC has always been that it comes with an embedded assembler that allows you to incorporate assembly language subroutines when interpreted BASIC isn't fast enough, and you can consider shader code to be a bit like that. Certainly it runs crazily fast, managing to update all 262,144 cells at frame rate on most platforms.

Here is the source code, which will run on a Raspberry Pi (tested on a RPi 4):

Code: Select all

      REM Conway's 'Game of Life': Shader version for BBC BASIC for SDL 2.0
      REM Adapted from various examples at Shadertoy.com by Richard Russell

      REM Size of grid (max = window size, must be power-of-two for WebGL):
      VDU 23,22,512;512;8,16,16,0
      GridW% = 512
      GridH% = 512

      REM Initial random pattern:
      FOR C% = 1 TO 2048 : VDU 32+RND(94) : NEXT
      OSCLI "GSAVE """ + @tmp$ + "initial.bmp"" 0,0," + STR$(GridW%*2) + "," + STR$(GridH%*2)
      CLS

      REM Install libraries:
      INSTALL @lib$ + "shaderlib"

      REM Define constants:
      GL_NEAREST = &2600
      GL_FRAMEBUFFER = &8D40
      GL_COLOR_ATTACHMENT0 = &8CE0

      REM Create arrays and structures:
      DIM Vertex$(10), Fragment$(999), Final$(10), Float{0%,1%}

      REM Fill vertex and fragment shader arrays from DATA statements:
      PROC_readshader(Vertex$())
      PROC_readshader(Fragment$())
      PROC_readshader(Final$())

      REM Initialise window and get its size:
      ON MOVE IF @msg% <> 5 RETURN ELSE PROCcleanup
      VDU 26
      IF POS REM SDL thread sync
      ScrW% = @size.x%
      ScrH% = @size.y%

      REM Ensure cleanup on exit:
      ON CLOSE PROCcleanup : QUIT
      ON ERROR PROCcleanup : MODE 3 : PRINT REPORT$ : END

      REM Initialise shader library:
      PROC_shaderinit(oVertex%, oFragment%)
      SYS `glCreateShader`, GL_FRAGMENT_SHADER, @memhdc% TO oFinal%
      `glGenFramebuffers` = FN_gpa("glGenFramebuffers")
      `glBindFramebuffer` = FN_gpa("glBindFramebuffer")
      `glFramebufferTexture2D` = FN_gpa("glFramebufferTexture2D")
      `glDeleteFramebuffers` = FN_gpa("glDeleteFramebuffers")

      REM Compile shaders:
      PROC_compileshader(oVertex%, Vertex$(), "Vertex")
      PROC_compileshader(oFragment%, Fragment$(), "Fragment")
      PROC_compileshader(oFinal%, Final$(), "Final")

      REM Link shaders:
      oProgram1% = FN_useshaders(oVertex%, oFragment%)
      oProgram2% = FN_useshaders(oVertex%, oFinal%)

      REM Get pointers to uniforms:
      SYS `glGetUniformLocation`, oProgram1%, "iResolution", @memhdc% TO pResolution1%
      SYS `glGetUniformLocation`, oProgram1%, "iChannel0",   @memhdc% TO pChannel0_1%
      SYS `glGetUniformLocation`, oProgram2%, "iResolution", @memhdc% TO pResolution2%
      SYS `glGetUniformLocation`, oProgram2%, "iChannel0",   @memhdc% TO pChannel0_2%

      REM Load and bind initial random texture:
      SYS `glActiveTexture`, GL_TEXTURE0, @memhdc%
      Texture1% = FN_loadtexture1(@tmp$ + "initial.bmp")
      SYS `glTexParameteri`, GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST, @memhdc%
      Texture2% = FN_loadtexture1(@tmp$ + "initial.bmp")
      SYS `glTexParameteri`, GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST, @memhdc%
      IF Texture1% = 0 OR Texture2% = 0 ERROR 100, "Couldn't load initial.bmp"
      SYS `glUniform1i`, pChannel0_1%, 0, @memhdc%
      SYS `glUniform1i`, pChannel0_2%, 0, @memhdc%

      REM Create Framebuffer object:
      DIM align{fbo%}
      SYS `glGenFramebuffers`, 1, align{}, @memhdc%
      oBuffer% = align.fbo%
      IF oBuffer% = 0 ERROR 100, "Couldn't create framebuffer"

      REM Render loop:
      REPEAT
        REM Stage one: render to texture:
        Float.0% = FN_f4(GridW%) : Float.1% = FN_f4(GridH%)
        SYS `glBindFramebuffer`, GL_FRAMEBUFFER, oBuffer%, @memhdc%
        SYS `glFramebufferTexture2D`, GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, \
        \                                     GL_TEXTURE_2D, Texture2%, 0, @memhdc%
        SYS `glBindTexture`, GL_TEXTURE_2D, Texture1%, @memhdc%
        SYS `glUseProgram`, oProgram1%, @memhdc%
        SYS `glUniform2fv`, pResolution1%, 1, Float{}, @memhdc%
        SYS `glDrawArrays`, GL_TRIANGLES, 0, 6, @memhdc%

        REM Stage two: render to canvas:
        Float.0% = FN_f4(ScrW%) : Float.1% = FN_f4(ScrH%)
        SYS `glBindFramebuffer`, GL_FRAMEBUFFER, 0, @memhdc%
        SYS `glBindTexture`, GL_TEXTURE_2D, Texture2%, @memhdc%
        SYS `glUseProgram`, oProgram2%, @memhdc%
        SYS `glUniform2fv`, pResolution2%, 1, Float{}, @memhdc%
        PROC_render

        SWAP Texture1%, Texture2%
      UNTIL FALSE
      END

      DEF PROCcleanup
      ON ERROR OFF
      oBuffer%  += 0  : IF oBuffer%   SYS `glDeleteFramebuffers`, 1, ^oBuffer%, @memhdc%
      Texture1% += 0  : IF Texture1%  SYS `glDeleteTextures`, 1, ^Texture1%, @memhdc%
      Texture2% += 0  : IF Texture2%  SYS `glDeleteTextures`, 1, ^Texture2%, @memhdc%
      oProgram2% += 0 : IF oProgram2% SYS `glDeleteProgram`, oProgram2%, @memhdc%
      oFinal% += 0    : IF oFinal%    SYS `glDeleteShader`, oFinal%, @memhdc%
      oProgram1% += 0 : oVertex% += 0 : oFragment% += 0
      PROC_shaderexit(oProgram1%, oVertex%, oFragment%)
      *REFRESH ON
      ENDPROC

      REM Minimal vertex shader:
      DATA "attribute vec4 vPosition;"
      DATA "void main()"
      DATA "{"
      DATA "gl_Position = vPosition ;"
      DATA "}"
      DATA ""

      REM Intermediate fragment shader (outputs to texture):
      DATA "uniform vec2 iResolution;"
      DATA "uniform sampler2D iChannel0;"

      DATA "int cell( in vec2 p )"
      DATA "{"
      DATA "return (texture2D(iChannel0, p/iResolution.xy).g > 0.5 ) ? 1 : 0;"
      DATA "}"

      DATA "void main()"
      DATA "{"
      DATA "vec2 px = gl_FragCoord.xy;"
      DATA "int k = cell(px+vec2(-1,-1)) + cell(px+vec2(0,-1)) + cell(px+vec2(1,-1))"
      DATA "      + cell(px+vec2(-1, 0))                       + cell(px+vec2(1, 0))"
      DATA "      + cell(px+vec2(-1, 1)) + cell(px+vec2(0, 1)) + cell(px+vec2(1, 1));"
      DATA "int e = cell(px);"
      DATA "float f = (((k==2) && (e==1)) || (k==3)) ? 1.0 : 0.0;"
      DATA "gl_FragColor = vec4( f, f, f, 1.0 );"
      DATA "}"
      DATA ""

      REM Final fragment shader (outputs to canvas):
      DATA "uniform vec2 iResolution;"
      DATA "uniform sampler2D iChannel0;"
      DATA "void main()"
      DATA "{"
      DATA "gl_FragColor = texture2D(iChannel0, gl_FragCoord.xy/iResolution.xy);"
      DATA "}"
      DATA ""
The second version is a 'pure BASIC' implementation, although I've made what use I can of BBC BASIC features such as 'array arithmetic' and structures. In updating and displaying each 'generation' there's not a single loop (not even an unrolled loop), just linear code! On my RPi 4 it runs at about one generation per second (for a 512x512 grid), compared with about 18 per second on my fastest PC:

Code: Select all

      REM Conway's 'Game of Life' in pure BASIC (with some cheating!)
      REM By Richard Russell, http://www.bbcbasic.co.uk/, 13-Jan-2021

      HIMEM = PAGE + 8000000

      REM Set grid size and initialise screen:
      W% = 512 : H% = 512
      VDU 23,22,W%;H%;8,8,16,0
      OFF

      REM Define structures and arrays:
      DIM bm{bfType{l&,h&}, bfSize%, bfReserved%, bfOffBits%, \
      \      biSize%, biWidth%, biHeight%, biPlanes{l&,h&}, biBitCount{l&,h&}, \
      \      biCompression%, biSizeImage%, biXPelsPerMeter%, biYPelsPerMeter%, \
      \      biClrUsed%, biClrImportant%, palette%(255)}, g&(H%-1,W%-1), ft%(9)

      REM Initialise bitmap structure:
      bm.bfType.l& = ASC"B" : bm.bfType.h& = ASC"M"
      bm.bfOffBits% = ^g&(0,0) - bm{} : bm.bfSize% = bm.bfOffBits% + W%*H%
      bm.biSize% = 40 : bm.biWidth% = W% : bm.biHeight% = -H%
      bm.biPlanes.l& = 1 : bm.biBitCount.l& = 8
      bm.palette%(0) = &FF000000 : bm.palette%(1) = &FFFFFFFF

      REM Initialise the grid to a random state:
      FOR Y% = 0 TO H%-1
        FOR X% = 0 TO W%-1
          g&(Y%,X%) = RND(2) - 1
        NEXT
      NEXT

      REM Create 8 copy arrays with gaps:
      G% = W% * 2 + 2 : p%% = 0
      DIM k&(H%-1,W%-1), p%% G%, l&(H%-1,W%-1), p%% G%, m&(H%-1,W%-1), p%% G%, n&(H%-1,W%-1), p%% G%
      DIM o&(H%-1,W%-1), p%% G%, p&(H%-1,W%-1), p%% G%, q&(H%-1,W%-1), p%% G%, r&(H%-1,W%-1), p%% G%

      REM Do some evil descriptor hacking to offset the arrays:
      PROCoffset(k&(),W%+1) : PROCoffset(l&(),W%+1) : PROCoffset(m&(),W%+1) : PROCoffset(n&(),W%+1)
      PROCoffset(o&(),W%+1) : PROCoffset(p&(),W%+1) : PROCoffset(q&(),W%+1) : PROCoffset(r&(),W%+1)

      REM Repeat for each generation:
      GCOL 15
      R% = TIME
      @%=&102010A
      ft%() = 30
      REPEAT
        REM Display grid:
        OSCLI "MDISPLAY " + STR$~bm{}

        REM Make 8 copies of grid:
        k&() = g&() : l&() = g&() : m&() = g&() : n&() = g&()
        o&() = g&() : p&() = g&() : q&() = g&() : r&() = g&()

        REM Offset arrays:
        PROCoffset(k&(),-W%-1) : PROCoffset(l&(),-W%) : PROCoffset(m&(),-W%+1)
        PROCoffset(n&(),-1)    : PROCoffset(o&(),+1)
        PROCoffset(p&(),+W%-1) : PROCoffset(q&(),+W%) : PROCoffset(r&(),+W%+1)

        REM Sum the border cells:
        k&() += l&() + m&() + n&() + o&() + p&() + q&() + r&()

        REM Calculate next state (by Svein Svensson):
        k&() OR= g&()
        g&() = k&() * 80 DIV 240 : REM Relies on 8-bit wraparound

        REM Undo the offsets:
        PROCoffset(k&(),+W%+1) : PROCoffset(l&(),+W%) : PROCoffset(m&(),+W%-1)
        PROCoffset(n&(),+1)    : PROCoffset(o&(),-1)
        PROCoffset(p&(),-W%+1) : PROCoffset(q&(),-W%) : PROCoffset(r&(),-W%-1)

        REM Report speed in title bar:
        T% = (T% + 1) MOD (DIM(ft%(),1) + 1)
        N% = TIME
        ft%(T%) = N% - R%
        R% = N%
        title$ = STR$(1000/SUM(ft%())) + " generations per second"
        IF INKEY$(-256) = "W" THEN
          SYS "SetWindowText", @hwnd%, title$
        ELSE
          SYS "SDL_SetWindowTitle", @hwnd%, title$, @memhdc%
        ENDIF

      UNTIL FALSE
      END

      REM This is non-portable code:
      DEF PROCoffset(RETURN a&(), O%)
      LOCAL i%%, p%%, W%, H% : p%% = PTR(a&())
      W% = DIM(a&(),1) + 1 : H% = DIM(a&(),2) + 1
      IF O% < 0 FOR i%% = p%% + O% + 9 TO p%% + 8 : ?i%% = 0 : NEXT
      IF O% > 0 FOR i%% = p%% + W%*H% + 9 TO p%% + W%*H% + O% + 8 : ?i%% = 0 : NEXT
      p%% += O% : PTR(a&()) = p%%
      ?p%% = 2 : p%%!1 = W% : p%%!5 = H%
      ENDPROC
The shader code looks like the simplest example of such things I've seen ever and the best example of GPU computing I've seen on the Raspberry Pi! Which version did you write first, the GPU or CPU code?
Last edited by ejolson on Tue Feb 09, 2021 5:06 pm, edited 1 time in total.

User avatar
RichardRussell
Posts: 1396
Joined: Thu Jun 21, 2012 10:48 am

Re: Introduction to BBC BASIC

Tue Feb 09, 2021 4:55 pm

ejolson wrote:
Tue Feb 09, 2021 4:36 pm
Which version did you write first, the GPU or CPU code?
I did the GPU version first, not least because it's not entirely original. As noted in the comments, I used a few different implementations at Shadertoy.com as inspiration, borrowing what I liked and throwing away what I didn't!

User avatar
cpcbegin
Posts: 251
Joined: Sun Mar 08, 2015 3:48 pm
Location: Costa del Sol, Spain

Re: Introduction to BBC BASIC

Tue Feb 09, 2021 6:00 pm

You can also install BBC Basic in any PC with GNU/Linux with this script:
https://gitlab.com/cpcbegin/retromultiinstaller
(Debian/Ubuntu family).
Trucos y recetas para raspberry pi:
http://malagaoriginal.blogspot.com.es/search/label/raspberry%20pi

User avatar
RichardRussell
Posts: 1396
Joined: Thu Jun 21, 2012 10:48 am

Re: Introduction to BBC BASIC

Tue Feb 09, 2021 6:35 pm

cpcbegin wrote:
Tue Feb 09, 2021 6:00 pm
You can also install BBC Basic in any PC with GNU/Linux with this script:
What version is that? Presumably, given the 'retro' tag, it's something ancient! Of course I wouldn't want BBC BASIC's heritage to be forgotten, especially in this 40th Anniversary year, but personally I can't see the attraction of 'retro computing'. :roll:

Both of the Game of Life implementations above should run in BBC BASIC for SDL 2.0 on any of the supported platforms (Windows, Linux, MacOS, Raspbian, Android, iOS, in-browser). They will also run in BBC BASIC for Windows but the BB4W version of the shader library is required; it can be downloaded from here.

User avatar
RichardRussell
Posts: 1396
Joined: Thu Jun 21, 2012 10:48 am

Re: Introduction to BBC BASIC

Thu Mar 04, 2021 9:33 am

I've released version 1.20a of BBC BASIC for SDL 2.0 - the cross-platform programming language for Windows, MacOS, Linux, Raspbian, Android, iOS and in-browser. The changes in this version are as follows:

  1. BASIC Interpreter / Run Time Engine

    Fixed FOR...NEXT loops misbehaving with negative non-integer STEP on ARM editions (Raspberry Pi, Android, iOS).

    Fixed *KEY not correctly handling strings such as |!|H (ARM and 64-bit editions only).

  2. IDEs and Utilities

    Fixed compiler.bbc incorrectly crunching structure members in rare circumstances.

    Fixed SDLIDE.bbc crashing in MacOS if the Tab key was pressed.

  3. Libraries

    Modified webgllib.bbc to be 64-bit compatible (it can be used on platforms other than in-browser).

    Modified filedlg.bbc so that you can type a full (absolute) path into the File name box.

    Modified menulib.bbc so that drop-down menus can be hidden from the menu bar (by setting their title to an empty string).

    Fixed editbox.bbc not reliably setting the 'changed' flag, and mispositioning the caret on blank lines.

  4. Example Programs

    Modified Ceefax.bbc to add function key shortcuts (f1-f4) for the coloured buttons; also fixed a crash if a page number with a leading zero was entered.

    Modified sudoku.bbc so the timeout only applies to the 'Count' option, not to 'Solve', so it can solve any puzzle even on slow machines.
This version may be downloaded, for all the supported platforms, from the usual location. The GitHub repository has been updated (used to build the MacOS, Raspbian, Android, iOS, 64-bit Linux and in-browser editions, currently).

User avatar
RichardRussell
Posts: 1396
Joined: Thu Jun 21, 2012 10:48 am

Re: Introduction to BBC BASIC

Thu Mar 04, 2021 10:55 pm

RichardRussell wrote:
Thu Mar 04, 2021 9:33 am
Modified webgllib.bbc to be 64-bit compatible (it can be used on platforms other than in-browser).
That is perhaps worth emphasising. webgllib.bbc was written to support 3D graphics in the in-browser edition of BBCSDL, because WebGL doesn't have the Fixed Function Pipeline (in OpenGL and OpenGL ES) that the other editions rely on. To make it work it was necessary to use shaders instead (and I was pleased to be able to achieve that, despite a very limited experience of shader programming).

It was only after I'd got it going that I realised that it actually worked better than the other libraries (ogllib.bbc and gleslib.bbc) in that lighting calculations are performed per pixel rather than per vertex. This can dramatically improve the realism of the end result, especially in cases when spotlights are used, and/or there are specular reflections.

Fortunately not only does it work better in those respects, it runs on all the other platforms (Windows, MacOS, Linux, Raspbian, Android, iOS) too! So if you want to take advantage of the improved rendering quality simply INSTALL webgllib instead of ogllib. It's not quite 100% compatible but it comes close (you'll notice a particularly dramatic effect if you perform the substitution in skaters.bbc!).

User avatar
RichardRussell
Posts: 1396
Joined: Thu Jun 21, 2012 10:48 am

Re: Introduction to BBC BASIC

Fri Mar 12, 2021 2:09 pm

I have updated the Console Mode editions of BBC BASIC to version 0.32. The changes in this version are:

  • Fixed the *KEY command not accepting (e.g.) |!|H meaning character code 136.

  • Fixed a bug in the Raspbian edition (only) causing FOR...NEXT to misbehave with a negative, non-integer, STEP.

  • Added new example programs calendar.bbc and sortdemo.bbc.
Version 0.32 may be downloaded from the usual place:
* The Mac M1 ('Apple silicon') edition does not have a usable assembler because Apple enforces the Hardened Runtime on that platform.

User avatar
RichardRussell
Posts: 1396
Joined: Thu Jun 21, 2012 10:48 am

Re: Introduction to BBC BASIC

Fri Mar 26, 2021 6:07 pm

Here's a YouTube video of my BBC BASIC Pinball program running on a Raspberry Pi 4. The program is fewer than 300 lines (about 10 Kbytes) and relies heavily on Box2D for the physics and OpenGL for the 3D graphics. Although Box2D is only 2D, it's appropriate for an inclined plane (which is what Pinball effectively is).

I think this is a good example of what 'modern' BBC BASIC can achieve with relatively little code. I'd be interested to get a feel for how it compares with other more 'popular' languages. Could you do the same thing in as little Python code, for example, and would it run on as many platforms (Windows, MacOS, Linux, Android, iOS and in-browser)?

ejolson
Posts: 12318
Joined: Tue Mar 18, 2014 11:47 am

Re: Introduction to BBC BASIC

Sun Mar 28, 2021 4:52 pm

RichardRussell wrote:
Fri Mar 26, 2021 6:07 pm
Here's a YouTube video of my BBC BASIC Pinball program running on a Raspberry Pi 4. The program is fewer than 300 lines (about 10 Kbytes) and relies heavily on Box2D for the physics and OpenGL for the 3D graphics. Although Box2D is only 2D, it's appropriate for an inclined plane (which is what Pinball effectively is).

I think this is a good example of what 'modern' BBC BASIC can achieve with relatively little code. I'd be interested to get a feel for how it compares with other more 'popular' languages. Could you do the same thing in as little Python code, for example, and would it run on as many platforms (Windows, MacOS, Linux, Android, iOS and in-browser)?
How difficult would it be to make lights blink on the simulated pinball machine?

User avatar
RichardRussell
Posts: 1396
Joined: Thu Jun 21, 2012 10:48 am

Re: Introduction to BBC BASIC

Sun Mar 28, 2021 7:19 pm

ejolson wrote:
Sun Mar 28, 2021 4:52 pm
How difficult would it be to make lights blink on the simulated pinball machine?
Not trivial, unfortunately. The most obvious way would probably be to separate out, as independent 3D objects, the items that you want to light up. Then you would be able to change either the associated texture or the associated material to achieve the wanted effect. But currently only the ball, flippers and plunger are separate objects (for obvious reasons); everything else is one big 3D model.

An easier method, but one which would be very wasteful of memory, would be to create a set of different textures corresponding to all the different possible patterns of lights. But if there are n independent lights, you'd need to create 2^n different textures, each of which is 2048x2048 pixels (16 Mbytes)! But if there are only a few lights and you've got enough GPU texture memory available it would be straightforward.

Yet another method, which could work for the 'mushrooms' at least, would be to add some narrow-beam spotlights, each of which illuminates just one mushroom. Then you could just wind the associated light up and down. But currently there's a maximum of five lights, which limits the applicability of this technique.

All in all it would be an interesting challenge for somebody to tackle.

User avatar
RichardRussell
Posts: 1396
Joined: Thu Jun 21, 2012 10:48 am

Re: Introduction to BBC BASIC

Mon Mar 29, 2021 1:49 am

RichardRussell wrote:
Sun Mar 28, 2021 7:19 pm
Yet another method, which could work for the 'mushrooms' at least, would be to add some narrow-beam spotlights
I've now experimented with that technique. To be effective it's necessary to turn down the overall lighting level considerably to make the 'lit' object really show up. Whilst that's no doubt quite true to life, it does make the whole thing a bit dim:
pinball_spotlight.jpg
pinball_spotlight.jpg (251.26 KiB) Viewed 15421 times

Puffergas
Posts: 255
Joined: Thu Dec 19, 2019 12:16 am

Re: Introduction to BBC BASIC

Thu Apr 01, 2021 5:08 pm

RichardRussell wrote:
Fri Mar 26, 2021 6:07 pm
Here's a YouTube video of my BBC BASIC Pinball program running on a Raspberry Pi 4.
That is impressive. Vary impressive, indeed.

User avatar
RichardRussell
Posts: 1396
Joined: Thu Jun 21, 2012 10:48 am

Re: Introduction to BBC BASIC

Fri Apr 16, 2021 1:38 pm

I've released version 1.21a of BBC BASIC for SDL 2.0 - the cross-platform programming language for Windows, MacOS, Linux, Raspbian, Android, iOS and in-browser. The changes in this version are as follows:

  1. BASIC Interpreter / Run Time Engine

    Mapped keyboard shortcuts Cmd+A, Cmd+C, Cmd+F, Cmd+V, Cmd+X and Cmd+Z to their Ctrl equivalents for improved compatibility with Apple Macs.

    Fixed a couple of minor differences between the 32-bit x86 editions and the ARM/64-bit editions.

  2. IDEs and Utilities

    Added compiler directives to SDLIDE.bbc to simplify building a standalone version in BB4W.

    Modified memusage.bbc, profiler.bbc, searchin.bbc and sdldebug.bbc to be compatible with BB4W as well as BBCSDL.

  3. Libraries

    Added box2ddbg.bbc to support Box2D Debug Graphics, largely compatible with the equivalent BB4W library.

    Fixed a bug in box2dlib.bbc affecting Gear Joints on 64-bit platforms.

    Fixed a bug in dlglib.bbc causing the trackbar control not to scale to the specified range.

    Fixed a bug in treeview.bbc causing mouse clicks not to be reliably detected.

  4. Example Programs

    Added pinball.bbc (in examples/physics) which demonstrates combining Box2D and 3D rendering.

    Added voronoi.bbc (in examples/graphics/) which is another shader demo.

    Modified recorder.bbc to increase the range of level adjustment (100% is now 'mid-range').

    Modified the other demos in examples/physics to take advantage of the box2ddbg library (press D to display the Debug Graphics).
This version may be downloaded, for all the supported platforms, from the usual location. The GitHub repository has been updated (used to build the MacOS, Raspbian, Android, iOS, 64-bit Linux and in-browser editions, currently).

User avatar
scruss
Posts: 5831
Joined: Sat Jun 09, 2012 12:25 pm
Location: Toronto, ON

Re: Introduction to BBC BASIC

Fri Apr 16, 2021 8:44 pm

Thanks, Richard! I always appreciate your new releases!
‘Remember the Golden Rule of Selling: “Do not resort to violence.”’ — McGlashan.
Pronouns: he/him

User avatar
RichardRussell
Posts: 1396
Joined: Thu Jun 21, 2012 10:48 am

Re: Introduction to BBC BASIC

Mon Apr 19, 2021 5:20 pm

I have updated the Console Mode editions of BBC BASIC to version 0.33. The changes in this version are:

  • QUIT n now sets the exit code (n must be greater than or equal to zero).

  • FOR...NEXT loop variables are now promoted to a float if they go outside the 64-bit integer range.

  • Fixed a bug which could affect assigning to a sub-string using RIGHT$(s$)=, i.e. with the second parameter omitted.

  • Added a new example program sudoku.bbc which is an adaptation of the Sudoku solver supplied with BB4W and BBCSDL.
Version 0.33 may be downloaded from the usual place:

* The Mac M1 ('Apple silicon') edition does not have a usable assembler because Apple enforces the Hardened Runtime on that platform.

miab3
Posts: 5
Joined: Mon Jul 01, 2019 12:50 pm

Re: Introduction to BBC BASIC

Fri Apr 23, 2021 8:07 pm

Thank you very much.

Why is the "compiled" version faster?

For example, such a simple test:

1 PRINT "START"
2 T=TIME
3 A=1
4 B=0
5 FOR I=0 TO 999
6 FOR J=0 TO 9999
7 A=A+0.000001
8 B=B+SIN(A)
9 NEXT J
10 NEXT I
11 PRINT A
12 PRINT B
13 PRINT"Time taken=";(TIME-T)/100;" seconds"

7.3 seconds and 4.46 seconds

Michał

User avatar
RichardRussell
Posts: 1396
Joined: Thu Jun 21, 2012 10:48 am

Re: Introduction to BBC BASIC

Fri Apr 23, 2021 9:12 pm

miab3 wrote:
Fri Apr 23, 2021 8:07 pm
Why is the "compiled" version faster?
It will be because of the actions of the 'cruncher':

  1. Comments (REM statements) are deleted.
  2. Unnecessary spaces are removed.
  3. Multiple short lines are concatenated into longer lines.
  4. Variables are replaced by embedded pointers.

The resulting program is still interpreted, but the overheads are reduced.

User avatar
RichardRussell
Posts: 1396
Joined: Thu Jun 21, 2012 10:48 am

Re: Introduction to BBC BASIC

Mon Apr 26, 2021 5:24 pm

This simulation of Newton's Cradle is another example of combining 3D graphics with calculations performed by the Box2D physics engine, made easy by the libraries supplied with BBC BASIC for SDL 2.0:

https://www.youtube.com/watch?v=T4TYj8Pt1wc

If you want to run it yourself the source code and other required files can be found here. To operate the simulation press and hold the 1, 2, 4 and/or 5 keys to lift the relevant ball(s); try different combinations and see if you can predict what will happen from the laws of conservation of energy and momentum!

You can change the viewpoint using the cursor keys and PgUp/PgDn to zoom.

cradle_rpi.jpg
cradle_rpi.jpg (122.16 KiB) Viewed 15032 times

User avatar
RichardRussell
Posts: 1396
Joined: Thu Jun 21, 2012 10:48 am

Re: Introduction to BBC BASIC

Fri May 28, 2021 4:52 pm

I've released version 1.22a of BBC BASIC for SDL 2.0 - the cross-platform programming language for Windows, MacOS, Linux, Raspberry Pi OS, Android, iOS and in-browser. The changes in this version are as follows:

  1. BASIC Interpreter / Run Time Engine

    Cmd+left, Cmd+right, Cmd+up and Cmd+down are mapped to Home, End, Ctrl+Home and Ctrl+End, respectively, for improved compatibility with Apple Macs.

    In the in-browser edition *RUN now runs JavaScript code (previously it did nothing useful) e.g.

    Code: Select all

     OSCLI "window.open(""http://www.rtrussell.co.uk"")"

    The QUIT n statement can now accept a parameter of zero (ARM and 64-bit editions only, 32-bit x86 editions always could).

    A few performance optimisations have been carried out (e.g. fixing memory alignment issues).

  2. IDEs and Utilities

    In SDLIDE.bbc Alt+left and Alt+right are mapped to Word Left and Word Right, respectively, for improved compatibility with Apple Macs.

    Added a -P command-line switch to SDLIDE.bbc to activate the profiler.

    The Android and iOS editions report if a later version is available.

  3. Libraries

    Changed gleslib.bbc and ogllib.bbc to call glLightfv by address rather than name.

  4. Example Programs

    Added Newton's Cradle simulation (cradle.bbc in examples/physics).

    Bundled the set of instructive Box2D demos in examples/physics/samples (these all use Debug Draw graphics).

    Updated sudoku.bbc to add Tidy and New Game options (the difficulty of the generated game is very variable!).
This version may be downloaded, for all the supported platforms, from the usual location. The GitHub repository has been updated (used to build the MacOS, Raspbian, Android, iOS, 64-bit Linux and in-browser editions, currently).

User avatar
RichardRussell
Posts: 1396
Joined: Thu Jun 21, 2012 10:48 am

Re: Introduction to BBC BASIC

Fri Jun 11, 2021 2:55 pm

I have updated the Console Mode editions of BBC BASIC to version 0.34. The changes in this version are:

  • I've (hopefully) worked around the apparent bug in MinGW's implementation of the _ftelli64() function.
  • Execution speed has been improved by about 10% by optimising the expression evaluator.
Version 0.34 may be downloaded from the usual place:


User avatar
RichardRussell
Posts: 1396
Joined: Thu Jun 21, 2012 10:48 am

Re: Introduction to BBC BASIC

Fri Jul 09, 2021 1:17 pm

I've released version 1.23a of BBC BASIC for SDL 2.0 - the cross-platform programming language for Windows, MacOS, Linux, Raspberry Pi OS, Android, iOS and in-browser. The changes in this version are as follows:

  1. BASIC Interpreter / Run Time Engine

    An experimental build for the 64-bit Raspberry Pi OS has been added (with many thanks to Simon Willcocks who wrote the AArch64 assembler which made it possible). This runs more than twice as fast as the 32-bit Pi OS edition!

    On a MODE change, the window will be centered only if it would otherwise extend beyond the edge of the desktop.

    If SDL's message system is unavailable (which it currently is on 64-bit Pi OS), fatal errors will be reported in a MODE 7 window.

  2. IDEs and Utilities

    Extended SDLIDE.bbc so Ctrl+Backspace is delete word left and Ctrl+Delete is delete word right. Alt (Option) may be used instead of Ctrl, for compatibility with Apple Macs.

    Extended the Cross Reference utility to report mismatched quotes, parentheses and braces.

    Fixed a couple of minor bugs in the Cruncher, one of which could cause the output file to be larger than it needed to be.

    Updated BBCEdit to version 0.38.1, with thanks to Andy Parkes.

  3. Libraries

    Added msgbox.bbc which provides support for Message Boxes, similar to those available in Windows.

    Updated filedlg to save and restore the VDU state (e.g. colours, viewports).

    Updated the Box2D libraries to be compatible with the 64-bit Raspberry Pi OS.

    Extended sdldebug.bbc and timerlib.bbc to include a 64-bit ARM (AArch64) version of the assembler code.

  4. Example Programs

    Extended hello.bbc to include a 64-bit ARM (AArch64) version of the assembler code.

    Simplified telstar.bbc by using the built-in MODE 7 font in BBCSDL (and MODE7LIB in BB4W).

    Updated several examples to replace SDL's message boxes with BBC BASIC-created ones.
This version may be downloaded, for all the supported platforms, from the usual location. The GitHub repository has been updated (used to build the MacOS, Raspberry Pi, Android, iOS, 64-bit Linux and in-browser editions, currently).

User avatar
RichardRussell
Posts: 1396
Joined: Thu Jun 21, 2012 10:48 am

Re: Introduction to BBC BASIC

Sat Jul 10, 2021 2:07 pm

Here's a YouTube video of David Williams' SubZapII running in BBC BASIC on a Raspberry Pi 4 with 64-bit OS. This is 100% BASIC code (no assembler), with direct calls to the SDL 2.0 API to take advantage of the accelerated 2D graphics.

User avatar
RichardRussell
Posts: 1396
Joined: Thu Jun 21, 2012 10:48 am

Re: Introduction to BBC BASIC

Sun Jul 18, 2021 2:11 pm

I have updated the Console Mode editions of BBC BASIC to version 0.35. The changes in this version are:

  • An edition for the 64-bit Raspberry Pi OS (currently in beta test) has been added. This typically runs more than twice as fast as the 32-bit Raspberry Pi edition.

  • The Mac M1 ('Apple Silicon') edition now has an AArch64 assembler. It has limited usefulness because Apple enforce the Hardened Runtime on this platform, but you could still use it to export assembled code to a file.
Version 0.35 may be downloaded from the usual place:


Return to “Other programming languages”