Monday 28 November 2022

My New Mac is an old Mac

 I've picked up an old Powerbook 1400 from eBay, for £65. It's missing a floppy drive / CD-ROM drive, so it's going to be a bit of a challenge getting data in and out of it. I think perhaps at my Dad's house there's a HDI-30 to SCSI converter, but failing that, I can use AppleTalk between my Performa 400 and the Powerbook 1400 or maybe simple serial transfer.

The PowerBook 1400 in question is the PowerPC 603e, 117MHz model without a level 2 cache. So, it's slow. But how slow, is the big question. I tried to find out from LowEndMac. It gave the performance as 

"Performance: 114/137/152 (117/133/166 MHz), MacBench 4 (also 42,076 (117 MHz) Whetstones)"

But when I tried to compare it with other Macs of the era on LowEndMac, particularly the Performa 5200, they all provided benchmarks for different test suites. The 6100/60 was given relative to MacBench 5, MacBench 2 and Speedometer 4. Performa 5200 was just xxx relative to a Mac SE. And so forth.

However, a little while later I came across this reddit page:

https://www.reddit.com/r/VintageApple/comments/q4rg86/performa_620075_benchmarks_compared_to_other_macs/

And it gave a screenshot of a bunch of early PowerPC Macs (and a Quadra 630) benchmarks for MacBench 4!!! Here's the data as a simple table:

Model CPU FPU Disk Mean*
Quadra 630 35 7 73 26
Performa 6200/75(SCSI) 93 95 94 94
Performa 6200/75(IDE) 93 95 121 102
PowerMac 6100/60 100 100 100 100
PowerMac 7200/75 102 117 107 108
PowerBook 1400/117 124 143 92 118
PowerMac 8100/80 142 138 132 137
Performa 6320/120 134 148 161 147
PowerMac 7500/100 162 164 164 163
PowerMac 7200/120 174 202 191 189
Performa 6400/180 184 207 123 167
Performa 6400/200 258 262 163 223
PowerMac 7600/132 251 243 212 235

So, I've used a Performa 5200 before - in fact this was the first PowerPC computer I used and at the time seemed amazingly fast compared with the Performa 400 I'd had previously! The Powerbook 1400 ought to be about 21% faster. I also had a Powerbook 5300 running at 100MHz for a few years when I consolidated my PowerMac 4400 + Powerbook Duo/Full dock setup and apart from the fact that the hard disk failed on the Powerbook 5300, I had found that the Powerbook 5300 was good enough. So, I think the 1400 will be fine too: faster than a first generation 6100 or perhaps even a 6100/66; a 7100/66; 6200 (5200)/75 and even the slowest PCI PowerMac.

Future blog posts will cover the progress I've been making!
[* The mean is a geometric mean where the 3 values are multiplied together, then the cube root is applied]


Wednesday 26 October 2022

ZX81, 1K Hanoi

 In my previous blog-post I described a coding convention for the Intel 8048 and used an embedded implementation of the Towers of Hanoi program as an example. It had a rudimentary user interface; outputting merely the column numbers between each step, which advanced on a button press.

Towers of Hanoi is an interesting toy program, and here we explore a different implementation, targeted at a 1K ZX81.

A standard ZX81 has 8K of built-in ROM + 1K of RAM which is shared between the user program (+variables); the 8K ROM's system variables; the 32x24 screen (which expands from 25 bytes to 793 bytes depending on how much is displayed on it); the 33b printer buffer and the machine stack used by the ROM. All this is described in Chapter 27 of the user manual.

So, in some respects it has less usable memory than an Intel 8048 (because 1K + 64b > 1K-system variables-screen...), but in others it has more (because the ROM is useful library code that would have to be included in the Intel 8048 equivalent). In addition, BASIC is generally less compact than assembler (and ZX81 BASIC is even worse).

The Listing


It doesn't look like there's much here, but a surprising amount thought went into it. You can run a javascript version of a zx81 from here, but many other emulators are available. To prove it will work in 1K, you will need to type POKE 16389,68 [Newline] NEW [Newline] to set the RAMTOP top of BASIC to the end of memory on a 1kB ZX81.

It's not obvious how to type the graphics, but the top line is 2 spaces, then ▗ █ ▙ 6 spaces Graphic Shift 5, 6 spaces Graphic Shift 8. The rest can be deduced fairly easily then.

The application will use 634 bytes. An earlier version used a mere 41 bytes more and this was enough to cause an out of memory error.

Using The Screen

The most significant change to the ZX81 version is to use the screen to provide a graphical representation of the puzzle. Because we still need to save space, it's essential to use the ZX81 block graphics (rather than whole characters) and I chose to move a ring by unplotting a ring's old position while plotting its new position rather than, e.g. animating the movement (which would have been very slow on the ZX81 anyway).

In the first version I used loops and plot commands to generate the puzzle, but it uses less memory to print the puzzle out directly. I could save another 4 bytes by assigning the graphics for the spaces and the poles to R$ and substituting R$ at the end of lines 10 to 30.

I also save a bit of space by calculating the minimum pole spacing. It looks like there isn't enough room between poles, but this isn't correct,  because at maximum we only ever need space for a 7 pixel wide ring and a 6 pixel wide ring. Therefore 7+1+6+1=15 pixels is enough between the poles.

This means the graphics take up: (8+15+15+7)/2=23 chars for row 4, 22 chars for row 3; 21 chars for row 2 and 20 chars for row 1(because the end of the higher rows are only ever filled with shorter rings). That's 86 bytes in total. The Column->Column moves are also displayed and this takes 4 bytes.

Moving The Rings

This is relatively simple: we have a string, R$, which is used as a byte array (to save space) to hold the number of occupied rings on each column. The width of each ring to move is determined by the current level: 1 for level 0 up to 7 for level 6. S and D determine the start and end coordinate. We plot to the ring we move to, while unplotting the ring we move from except for when x=0 in order to leave the pole. At the end we adjust the number of occupied rings, -1 for the source ring and +1 for the destination ring.

Memory-Saving Techniques

This program uses the normal ZX81 BASIC memory saving techniques. '0', '1' and '3' are replaced by NOT PI, SGN PI and INT PI to save 5 bytes each time. Values in the printable character range are replaced by CODE "x", saving 3 or 4 bytes each time; while other values use VAL "nnn" to save 3 bytes each time. This also applies to line numbers, so that placing the Hanoi function itself below line 1000 saves several bytes.

Using R$ as a byte array involves a number of clumsy VAL, CODE and CHR$ conversions, but replacing R$ with a DIM R(D) array would end up costing another 9 bytes, so it's a net saving to use R$.

Hanoi Algorithm Change

It turns out we can make the Hanoi algorithm itself less recursive than in the Intel 8048 version. In that version we pushed the source and destination columns on each call, but in fact that was done to demonstrate how to manage the data structure.

It's not necessary to do that. The original source and destination values can be restored after each recursive call, because 6-S-D is a reversible operation. Similarly, because L is decremented at the beginning of each call (instead of passing L-1 as a parameter to each call), then by incrementing it at the end of the function, it too, doesn't need to be saved.

Conclusion

The constraints and capabilities of running Hanoi on a different platform and language present challenges and opportunities which this ZX81 implementation amply demonstrates, not in the least by the 4:30 minutes of patience needed to fully run it for 7 rings (vs <1s for the Intel 8048 version). Finally, this implementation circumvents the ZX81 BASIC's lack of support for stack data structures to reduce the amount of recursive memory needed, which begs the question: how much recursion is really needed to implement the algorithm?

Saturday 22 October 2022

Intel 8048 Coding Guide

This is a short guide on programming the largely obsolete Intel 8048 series of Microcontrollers from the viewpoint of a conventional coding paradigm. It describes how to essentially hand compile programs notionally written in a high-level language into assembler using a consistent methodology.

We shall chose a relatively simple toy program: a towers of Hanoi application. It uses 4 LEDs for output at each step (the source column and the destination column); a single button; some simple expressions; recursion (only up to 6 levels) and a simple interrupt routine to read the button.

Register Usage

The datasheets for the 8048 series shows it is an accumulator architecture, with direct access to 8 registers [r0 to r7] with which the accumulator can perform arithmetic / logic operations and indirect access to the rest of RAM via r0 and r1, with which it can also perform a similar set of ALU operations.

So, the convention here is to use r2 to r7 for parameters, locals and temporaries; while r0 and r1 point to globals. Thus, accessing a RAM location (e.g. x) takes 2 instructions:

    mov r0,#x
    mov a,@r0

Because r0 and r1 can both be used as pointers, we can cache up to 2 globals at any one time, e.g. b^=c =>
    mov r0,#b
    mov a,@r0
    mov r1,#c
    xrl a,@r1
    mov @r0,a ;7b
Becomes a straight-forward translation. If b and c had been in r2 and r3 it would have become just
    mov a,r2
    xrl a,r3
    mov r2,a ;3 bytes instead of 7b.

The 8048 has a second bank of registers, these will only be used within interrupts. That way, we never have to save context and we can be sure that nothing we do with r0' to r7' will affect the main program.

This means we can now write the interrupt-handling code, which debounces a button press. If we assume the 8048 runs at 8MHz, then the timer will overflow every 8MHz/15/32/256 = 65Hz, which is a reasonable period for debounce. The algorithm is fairly simple, on every overflow interrupt we simply shift in the button press value to a holding register and if the bottom 4 bits are 1100, then this means that the button was seen as being debounced and off ('11') then debounced and on ('00') so a button press has occurred and the button press bit is set. The key input routine waits for that bit to be set, then clears it.

    org 7 ;start tmr interrupt at its address.
TmrInt:
    sel rb1
    in A,p1 ;button in bit 0.
    roc A ;now in carry.
    mov A,r2 ;button press history
    rlc A ;now bottom
    mov r2,A
    anl #12 ;
    xrl #12 ;zero => button!
    jnz Tmr10
    clr f0
    cpl f0 ;F0= button result.
Tmr10:
    sel rb0 ;back to main reg set
    retr ;return from int. 16b.

Key: ;wait for key.
    jf0 Key10
    jump Key ;still clear
Key10:
    clr f0 ;ready for next keypress.
    ret ;Return. 6b.

TmrInit:
    mov a,#1
    orl a,p1
    out p1,a
    sel rb1
    mov r2,#0 ;button had been 'pressed'
    sel rb0
    en tcnti
    strt cnt ;65Hz.
    ret ;11b.

;16+6+11 = 33b.

Data Structures And Stack Frames

The Tower of Hanoi recursive program is fairly simple. If we want to move all the rings from column a to column c; we first move all the rings above from column a to column b; then move a ring from column a to column c, then move all the rings above from column b to column c.

Normally, because a 8048 program is poor at handling indexed data structures, it will be best to map stack frames to static stack frames. However, for this application We'll use r2 to r7 as locals and r0 as a stack pointer. The stack pointer will push down from the top of RAM and we'll assume it's an 8048 with 64b of RAM, so r0 starts at 64. The function is equivalent to the following 'C' function:

void Hanoi(uint8_t aLevel, uint8_t aFrom, uint8_t aToo)
{
    if(aLevel>0) {
        Hanoi(aLevel-1, aFrom, aToo^aFrom);
    }
    Out(p1,(((aFrom<<2)|aToo)<<1)|1);
    Key();
    if(aLevel>0) {
        Hanoi(aLevel-1, aFrom^aToo, aToo);
    }
}

I tend to choose caller-saved conventions, so the first thing Hanoi will do is save from and too; and restore them at the end. We'll assume a 6 level Hanoi, with columns numbered as 0, to 2. We can always compute the via as 3^from^to. e.g. 0 to 2 => 3^0^2 => 1. 0 =>1 is 3^1^0 => 2. 1 to 2 => 3^1^2 => 0.

Hanoi ;r2=aLevel, r3=aFrom, r4=aToo.
    mov a,r3
    dec r0
    mov @r0,a ;push aFrom
    mov a,r4
    dec r0
    mov @r0,a ;push aToo.
    dec r2 ;if the result is 0
    mov a,r2 ;
    JZ Hanoi10
    mov a, #3
    xrl a,r3
    xrl a,r4
    mov r4,a ;from source to via.
    call Hanoi ;recurse.
Hanoi10:
    mov a,r3
    rl a
    rl a
    orl a,r4
    clr c
    cpl c ;set c.
    rlc a ;because bit 0=button input.
    out p1,a ;output the move
    call Key ;wait for button
    mov a,r2 ;level==0?
    jz Hanoi20
Hanoi20:
    mov a,#3
    xrl a,r3
    xrl a,r4
    mov r3,a ;move via to dest.
    call Hanoi
    inc r2 ;restore level above.
    mov a,@r0 ;restore from and too at the end.
    inc r0
    mov r4,a
    mov a,@r0
    inc r0
    mov r3,a
    ret ;45b

Thus it can be seen that the implementation is very simple. Initialisation involves setting up the timer interrupt and the initial Hanoi call:

    org 0;reset
    jmp Main
Main:
    call TmrInit
    mov r0,#64 ;sp
    mov r2,#6 ;6 levels
    mov r3,#0
    mov r4,#2
    call Hanoi
Main10:
    jmp Main10;14b

Thus we now have a complete implementation with user interaction, display, interrupts, expressions, data structures, locals, and recursion. It takes only 7+33+45+14 bytes = 103b in total. Note, at the time of writing, the Hanoi application hasn't been tested.

Static Stack Frame Algorithm.

The Static stack frame algorithm needs to be computed by hand. First we work out the call tree for the application. Secondly, for each function we allocate a call level to it based on the deepest call to that function. Thirdly, for each call level we allocate the number of bytes = the maximum number of stack bytes needed over the set of functions at that call level. Fourthly, we set the start address for the higher call level to the start of spare RAM and each lower call level to the start address of the previous call level + the stack bytes calculated in step 3.

Conclusion

Although the instruction set for the 8048 family is fairly comprehensive for an early 8-bit MCU and its multiple source interrupt feature makes it far better than the contemporary PIC 1655, the limited and clumsy access to memory outside of a local set of 8 registers makes coding challenging. Many of these problems were fixed by its successor, the Intel 8051.

Nevertheless, some fairly simple coding techniques provide for a fairly straight-forward and efficient coding convention.

Sunday 8 May 2022

A Tale Of Two Banners: VIC-20

 I previously posted about a Banner program I wrote for the 40th anniversary of the ZX Spectrum. One of my friend's followers tweeted that he always thought my friend was a Commodore C64 owner, who could PEEK and POKE with the best of them.

This set me thinking - what would a Commodore C64 version be like? And then, because a C64 version would be too easy, what would an unexpanded VIC-20 version be like? For sure, it's more challenging than a ZX Spectrum version.

Here's a bunch of reasons why:

  • The VIC-20 has a smaller screen area, just 22 x 23 characters; so an 8x5 character banner can't be done with 8x8 pixel characters.
  • The VIC-20 has no graphics commands. It can redefine the character set and that's about it. It can't easily PRINT AT any location on the screen.
  • The VIC-20 supports an ink colour per character, but only a global paper colour. That's because it only has 4-bits per colour byte instead of 8-bits (which gives room for an ink + paper per character). Therefore, I can't use the same colour trick as the ZX Spectrum.
  • The VIC-20's INKEY$ function (GET stringVar$) doesn't return proper ASCII upper and lower case characters, but PETSCII codes (weird).
  • The VIC-20 fouls up the character set pointer when you press Shift+[C=].
Nevertheless, I was able to do it, and here I'll describe how:














Smaller Screen Area

The VIC-20 has a smaller screen area, and if I understand it correctly, the screen can't be more than 512 characters (though they can be double-height!). Normally the screen is 22x23 characters, which isn't enough to fit 8 characters across made up from Battenberg, 4x4 pixels each. You'd need 32 characters across for that. However, it's almost enough to support 8 characters across made up from 6x6 block graphic fonts from 4x4 pixel Battenberg graphics.

And... the VIC-20 screen size can be redefined. By making it 24x21 there's room for 8 characters across x 7 characters down, even more than the ZX Spectrum!

Of course, on a VIC-20 it has to be done using POKEs:

1000 A=7504:POKE 36864,10: POKE 36867,42: POKE 36866,152


So, 36867 is the number of rows, *2 in bits 1..6, Address 36866 is the number of columns in bits 0..6. The default values were 46 and 150 respectively, so I changed them to 21*2 for 21 rows and 152 for 24 columns.

Where does all the information about the POKEs come from? Well the most concise information I've found is from here, an extensive resource on the VIC-20 memory map.

The values can be directly poked in, though I'd start with changing the dimension that gets smaller, so that the screen area is always <512b.

Finally, we need to adjust the left-hand side of the screen so that it's better centred. Address 36864 does that and changing it to 10 was found by experimentation.

There Are No Graphics Commands

However, the VIC-20 can display graphics characters, and there are Battenberg block graphics characters inherited from the Commodore PET. Strangely, and unlike the ZX81 or ZX Spectrum, they don't have a very logical order. Instead, in the sequence I'd use, the codes are:

9000 DATA 32, 124, 126, 226, 108, 225, 127, 251

9010 DATA 123, 255, 97, 236, 98, 254, 252, 160


Given that we have all the Block character graphics selected, all we need to do now is define the character set in terms of them. Unfortunately, that's not trivial either.  The first thing I did was to take a 6x6 bitmapped character set I'd used for a FIGnition example program:


 

















I have a java program which reads opens the image as a .png and then copies the pixels to an array where they can be subsequently transformed into a different image format.

I needed to be able to transform the character bitmaps so they could be represented in VIC-20 BASIC. I couldn't encode them as proper full bytes, because all 256 symbols can't be typed. I could have encoded them as 6-bit text, but again, the odd non-ascii use of VIC-20 characters made that more complex. So, I simply encoded them as 4-bit text using the characters A..O and then indexing each character (-65) in an array of Battenberg graphic characters. This meant the 96 printable characters would take up 864 bytes in themselves+ some overhead for the individual lines and BASIC commands, a good chunk the unexpanded VIC-20's 3.5kB memory space! Encoding as 6-bits could would have saved 33%, about 288 bytes.

Unfortunately, it wasn't likely to be feasible to just store the whole font in strings, so I figured that I could store them in DATA statements and then do RESTORE line to point to the right data statement where the character I wanted was defined.

Unfortunately, the VIC-20 only supports RESTORE to the beginning of the program. So, instead - yet again (and this is a common theme) I had to use memory PEEKing. I placed the data statements at the end, and when I'd read all the other data in the setup, I stored the system variable for where the DATA statement pointer was, and then literally PEEKed the right memory location to get the bytes.

It's possible to do a PRINT AT on a VIC-20 by printing the home and cursor control characters. Home is an inverse S, which you can display by literally typing PRINT " and then the home key, because the VIC-20 re-interprets keystrokes within quotes and similarly, you can move the print position to different locations by typing PRINT " and then the cursor keys themselves, for the same reason. This means that the VIC-20's screen editor, which is usually easy to use turns into a pain within quotes, because moving the cursor starts overwriting the rest of the text, so you have to wrestle with it to get it back into proper cursor mode (typing " usually works).

And colours work the same way, you type PRINT " and then a colour key and it will change the INK colour.

So, you can assign these to strings and then print "[HomeKey]";LEFT$(CD$,Y);LEFT$(CR$,X); to get the the right location, but it's fairly slow compared with poking directly into screen memory at 7680+22*row+column and of course, the cursor key technique doesn't work when the screen dimensions have been changed!

So, POKEing the screen is the best solution and you have to poke the colour attribute byte too, because the VIC-20 for some reason doesn't fill it in when it displays spaces. Clear screen, for example (PRINT "[Shift+Home]"; ) doesn't fill the attribute bytes with the current ink colour; it just clears the text bytes.

This is why in the real code I have to clear them explicitly:

FOR F=7680 TO 8183:POKE F,42:NEXT F

And the reason why it's code 42 and not 32 will be explained next:

Producing The Diagonal Stripes

I was pleased with how I generated the diagonal stripes on the ZX Spectrum, as it's a challenge when only 2 colours are allowed per character, and, helpfully enough, the VIC-20 does have a diagonal character!

Yet, doing the same thing on a VIC-20 is several times harder, because only 1 unique foreground colour can be defined per character and clearly we need two. Yet, it is just about possible, but only just!

The solution is that the VIC-20 supports 2-bits per pixel colours on a character-by-character basis, by setting bit 3 of every colour attribute byte. Each bit pair then selects one of four possible colours:

00: Which is the paper colour, bits 7-4 of location 36879.
01: Which is the border colour, bits 3-0 of location 36879.
10: Which is the auxiliary colour, bits 7-4 of location 36878 (the bottom 4 bits are the sound volume level).
11: Which is the ink colour of the character.

This means that one diagonal half can have a choice of 3 possible colours, while the other diagonal half (ink) can have a choice of 7 possible colours. We need to handle 5 colours: the black background (paper), Red, Yellow, Green and Cyan.





Using pairs of pixels also forces us to pair up the rows in the UDGs giving us an effective resolution of 4x4 for each character. You can see that the stripes are more blocky than an ideal 8x8 diagonal would be.

It also means we can't use the standard VIC-20 diagonal graphics character, because we actually need 5 different types of diagonal characters with bit pair combinations of xx/11 and 11/xx. This means we have to allocate space for a character set and in turn that means we can't use the built-in block graphics characters, we have to defines copies of those too. In total we need 16+5 characters (though in fact I used 16+6). In essence, then we need to first allocate space for the graphics characters:

5 POKE 52,29:POKE 51,80:POKE 56,29:POKE 55,80:PRINT CHR$(8);:CLR

Allocate the character set pointer to give us 64 graphics (thus the first character will be at code 64-6-16 = 42) and assign the Auxiliary and background colours.

1100 POKE 36878,112:POKE 36869,255:POKE 646,1:POKE 36879,11:P=7680

Copy over the block graphics from ROM (we could calculate them, but this is easier).

1010 READ P:P=P*8+32768

1015 FOR F=0 TO 7:POKE F+A,PEEK(P+F):NEXT F

1020 A=A+8:IF A<7632 THEN 1010

...

9000 DATA 32, 124, 126, 226, 108, 225, 127, 251

9010 DATA 123, 255, 97, 236, 98, 254, 252, 160


Generate the stripes characters:

1030 READ N,M:FOR F=0 TO 6 STEP 2:POKE A+F,N:POKE A+F+1,N:N=(N*4+M)AND 255:NEXT F

1040 A=A+8:IF A<7680 THEN 1030

...

9020 DATA 2,2,168,0,86,2,169,1,254,2,171,3


Clear the screen the hard way:

1105 FOR F=7680 TO 8183:POKE F,42:NEXT F:PRINT “[Home]”;


Then read the character codes for the stripes and place them at the right locations.

1120 FOR X=0 TO 4:READ N,M:P=8176+X

1130 FOR F=0 TO 7-X:POKE P+30720,M:POKE P,N:P=P-23:NEXT F

1140 NEXT X

...

9030 DATA 58,10,63,10,62,13,61,13,60,8


Ascii Code Conversions & Stopping Case Swapping

You can swap between Capitals + Graphics and Capitals and Lower Case (+ a few graphics) on the VIC-20 using Shift+[C=]. However, this doesn't affect what character codes are read by GET x$. Normal lower-case characters return upper-case ASCII characters and holding down shift gives the same codes + 128.

Fortunately, that's just a simple case of mapping the characters:

111 K$=CHR$((ASC(K$)+(32 AND (K$>=“A” AND K$<=“Z”)))AND 127)

Also, fixing the case swapping issue is fairly easy, it's done by printing a control character: PRINT CHR$(8) in line 5.

Conclusion

Early 80s computers had to be creative with graphics hardware, because the relatively high memory costs limited graphics detail, and lower memory bandwidth limited the range of colours. The ZX Spectrum and VIC-20, at first sight provided a very similar style of graphics, using 1 bit per pixel + an attribute byte for colour per character, but short-cuts in the colour memory (only 4-bits per character instead of 8) added even more limitations.

Consequently, porting a program from one architecture to another often involved a lot of additional work to map or work around the respective limitations. In the case of the VIC-20, a critical aspect of the Banner program (the diagonal red, yellow, green and cyan stripes against a black background) were only made possible by the VIC chip's ability to support 2 bit per pixel multi-colour graphics, plus the ability of one of those colours to be the ink colour at the character. An ordinary 2 bit per pixel graphics mode, such as that offered by the 6847 graphics chip could not have reproduced the stripes, even though, at a minimum, 96x84 pixels graphics would need 2kB of RAM vs the 932 bytes of RAM actually used.

Finally, even the differences in the implementation of what was accepted as the standard microcomputer language: BASIC could have serious ramifications; and often hacking directly into the OS or memory map was the only solution.

The Banner program is a great, and simple way of exploring the architectural differences, and at the end of it, it's fun to type out colourful chunky characters across the whole screen!

The Listing

Finally, here's the listing! There's about 1kB free on the unexpanded VIC-20 once it's been typed in. In VICE it's possible to copy and paste a line at a time, but you need to convert the characters to lower-case first!

5 POKE 52,29:POKE 51,80:POKE 56,29:POKE 55,80:PRINT CHR$(8);:CLR

10 POKE 36869,240:GOSUB 1000

100 FOR X=0 TO 2:BG(X)=PEEK(P+48+X):POKE P+48+X,54:NEXT X

110 GET K$:IF K$=“” THEN 110

111 K$=CHR$((ASC(K$)+(32 AND (K$>=“A” AND K$<=“Z”)))AND 127)

112 F=ASC(K$):IF F<32 AND F<>13 THEN 110

113 FOR X=0 TO 2:POKE P+48+X,BG(X)::NEXT X

116 IF ASC(K$)=13 THEN P=INT((P-7680)/24)*24+7752:GOTO 160

120 C=ASC(K$)-32:C=C0+(C AND 3)*9+INT(C/4)*44

130 I=INT(RND(0)*7)+1

140 FOR Y=0 TO 2: FOR X=0 TO 2:POKE P+X,PEEK(C+X)-23:POKE P+X+30720,I:NEXT X

150 P=P+24:C=C+3:NEXT Y:P=P-69

155 P=P-7680:P=(P-INT(P/24)*24)+INT((P+48)/72)*72+7680

160 IF P>=8184 THEN P=7680

170 GOTO 100

999 POKE 36869,240:POKE 36864,12:POKE 36866,150:POKE 36867,174:STOP

1000 A=7504:POKE 36864,10: POKE 36867,42: POKE 36866,152

1005 DIM BG(3)

1010 READ P:P=P*8+32768

1015 FOR F=0 TO 7:POKE F+A,PEEK(P+F):NEXT F

1020 A=A+8:IF A<7632 THEN 1010

1030 READ N,M:FOR F=0 TO 6 STEP 2:POKE A+F,N:POKE A+F+1,N:N=(N*4+M)AND 255:NEXT F

1040 A=A+8:IF A<7680 THEN 1030

1100 POKE 36878,112:POKE 36869,255:POKE 646,1:POKE 36879,11:P=7680

1105 FOR F=7680 TO 8183:POKE F,42:NEXT F:PRINT “[Home]”;

1120 FOR X=0 TO 4:READ N,M:P=8176+X

1130 FOR F=0 TO 7-X:POKE P+30720,M:POKE P,N:P=P-23:NEXT F

1140 NEXT X

1150 C0=PEEK(65)+256*PEEK(66)+7:P=7680

1999 RETURN

9000 DATA 32, 124, 126, 226, 108, 225, 127, 251

9010 DATA 123, 255, 97, 236, 98, 254, 252, 160

9020 DATA 2,2,168,0,86,2,169,1,254,2,171,3

9030 DATA 58,10,63,10,62,13,61,13,60,8

9100 DATA“AAAAAAAAAAKAACAACAFFAAAAAAANNINNIBBA"

9110 DATA"AOIBOADKAPECEGICBCJIAJMCBCCAKAAAAAAA"

9120 DATA"AJAAKAABAAGAAFAACAIIIFPACCCAKADLCACA"

9130 DATA"AAAAEAACAAAADDCAAAAAAAAAACAAECECACAA"

9140 DATA"JHIOCKBDAEKAAKABDADDIJDADDCDDIBDIDDA"

9150 DATA"EHAONIABALDCDDIDDAEDCLDIBDADDKAJAACA"

9160 DATA"JDIJDIBDAJDIBHCBCAAIAAIAAAAAAAACAECA"

9170 DATA"AJABIAABAMMIMMIAAABIAAJABAAJDIADAACA"

9180 DATA"JLIKDCBDCJDILDKCACLDILDIDDAJDCKAABDC"

9190 DATA"LGAKECDCALDCLDADDCLDCLDACAAJDCKDKBDC"

9200 DATA"KAKLDKCACBLAAKABDAAHCAFABCAFECFGABAC"

9210 DATA"FAAFAABDCOEKKCKCACOAKKGKCACJDIKAKBDA"

9220 DATA"LDILDACAAJDIKGKBDCJDILLACBCJDABDIBDA"

9230 DATA"DLCAKAACAKAKKAKBDAKAKGECACAKAKKKKBBA"

9240 DATA"GECEGACACGECAKAACADHCECADDCALAAKAADA"

9250 DATA"GAAAGAAACAHAAFAADAEGAAAAAAAAAAAAAMMM"

9260 DATA"EDAHCADDCEMAKFABDAOIAKFADCAEIAKAABCA"

9270 DATA"ENAKFABDAEIALDABDAEDAFDABAAEMAGNAEJA"

9280 DATA"OIAKFACBAAIAAIAACAACAAKAECAKAALLACBA"

9290 DATA"KAAKAABCAEIAPFACBAMIAKFACBAEIAKFABCA"

9300 DATA"JGAOJACAAJGAGNAABCAMAFAABAAAMABIADAA"

9310 DATA"FIAFAAADAIEAKFABCAIEAOCACAAIEAPPABCA"

9320 DATA"IEAFKACBAIEAGNAEJAMMAECADDAAJABKAABA"

9330 DATA"AKAAKAACABIAALABAAJJAAAAAAAJHIKHKBDA”



Friday 6 May 2022

A Tale Of Two Banners: ZX

For the 40th Anniversary of the ZX Spectrum's announcement on April 23rd, 1982, I decided to write a little banner program.

Here's an example of what it can do:

May be a cartoon

ZX Spectrum BASIC is blessed with a large number of useful commands to access graphics, so the program was quite easy to write and relatively short:

No photo description available.

No photo description available.

As you can see, there's very little to this program. How does it work?

The main part of the program, obviously is the mechanism for enlarging the characters. This is surprisingly easy. The ZX Spectrum has 16 graphics characters arranged as Battenberg (as in the cake) graphics which occupy character codes 128 to 143.


The ZX Spectrum also has a POINT(x,y) function, which returns the pixel value at coordinates (x,y). By POINT ing at (x,y), (x+1,y), (x, y+1), (x+1, y+1) we can simply multiply each point by the appropriate Battenberg pixel value and add 128 to generate the correct graphics character. This is why you can see a tiny version of the '!' character in the bottom right-hand corner: the standard character is displayed and then it is scanned and the appropriate Battenberg characters are generated.

The program allows for the banner to be edited by simply supporting a carriage return feature and by wrapping the bottom line back to the top line.

The harder part is to generate the stripy coloured lines at the bottom right-hand corner. The difficulty with the ZX Spectrum is that it's not possible for more than 2 different colours (an INK colour and a PAPER colour) to occupy the same character square. So, to make the stripes we have to carefully make sure that each stripe is exactly 1 character wide and map the colours so that the stripe never clashes.

To do this we define a graphic character, UDG "a", whose address in RAM is returned by USR "a" as a diagonal triangle filling the bottom half of the character. This gives us the diagonal stripes by using the previous ink colour as the next paper colour on the next stripe - a classic ZX Spectrum colour trick! (You can see how it works from the image below where alternate diagonal rows are brighter ).

No photo description available.

To make the program a little bit more fun, each character is assigned a random colour and the cursor flashes.  Because the ZX Spectrum came out in a 16kB form, and one of my friends had one at the time, I thought it would be considerate to make sure the program would run on either model (it's so short, I could hardly fail 😉 !).

You can type in the program by going to the ZX Spectrum Javascript website! Here's the keyboard to help you! https://jsspeccy.zxdemo.org .

Following this I thought I'd do the same, for the Commodore VIC-20. It turns out that, despite arguably superior graphics capabilities (well, it can do 2 bits per pixel graphics!), it's far harder (see Part 2).


Sunday 1 May 2022

Gini Sim: Interactive

 In May 2014 I wrote a post on modelling the  Lorenz Curve,  which is an income or wealth curve whose curvature is expressed as the Gini Coefficient. In this model an ideal society has a straight-line curve and the more unequal a society is, the greater the curvature.

The post shows how a pure, free-market economy naturally gives rise to the highest possible gini coefficient, approaching 1 over time. This is the case even if the population involved has no ulterior profit motive and all participants play by the same, equally applied rules. The post provides a program, written in JavaScript which simulates the process, but the program doesn't run, it's just a listing.

Informally, the algorithm works as follows. There are 100 players. At first each player is given the same amount of cash: $10. $1 is randomly taken from the pool of money, and so the player who owned it now has $1 less; then another $1 is picked randomly from the remaining pool and the player who owns that one is now given the previously taken $1. So, usually, one player loses $1 and another player gains $1 (unless the same player gets picked for both steps).

Intuitively you would think that the probabilities would even out. As a player loses money, they are less likely to have money taken from them (and given to them), but likewise, as a player gains money, they are more likely to have money taken from them (and also given to them).

This is not what happens. Instead as players gain money, they are more likely to gain in subsequent transactions. This is because the probabilities change between transactions, in favour of previous winners. For example, consider a situation near the end game where one player has $1 and the remaining player has $99,999. Although 99.999% of the time, the dominant player will have $1 removed, 99.999% it will be returned with another 0.001% of an opportunity that it goes to the lesser player. However, in the 0.001% of the time that the lesser player's $1 is removed, it becomes impossible to receive that $1, and in subsequent plays, they now have a 0% of winning.

In the real world, this corresponds to the way in which larger players, who occupy more of the market, are more likely to be chosen to trade with: thus increasing their market share. In this version, the javascript is embedded in the article itself and thus it can be played live. You can see a Lorenz Curve being mapped out in realtime as it becomes more extreme. A variant allows you to generate interest with a given probability (interest works by leaving the 'loser' with the original $1 they had), but it has no effect on the overall outcome: the richest get richer while the poorest lose everything.




Simulation

Your browser does not support the HTML5 canvas tag.