ZXSimulator

Anything QL Software or Programming Related.
Post Reply
User avatar
bwinkel67
QL Wafer Drive
Posts: 1187
Joined: Thu Oct 03, 2019 2:09 am

ZXSimulator

Post by bwinkel67 »

So with everything being cancelled it gave me some time to start on my fun project. Plan is to take my BASIC interpreter I prototyped on the QL back in the 90's for my Mac product and convert it to ZX81 Basic. I actually modeled it a bit in-between the ZX81 BASIC and SuperBASIC so it shouldn't need too much modification.

Note this is just for fun and I set myself a couple of ground rules: 1) should run on an unexpanded QL, and 2) will use Digital 'C' to write it. Goal is to be able to run up to 16K programs written for the ZX81 in BASIC with no machine code support. It will not emulate the ZX81 and in fact it will likely only support LOAD, and RUN commands (i.e. not editing/writing BASIC programs in the simulator, just run things).

So I've been testing ways to generate the character set. I may need to learn how to change built-in fonts but for now I played with the block command in Digital 'C' and got some ok results. One good thing is that this will make the simulator work in all graphics mode. I use the JSU ROM and it starts using 8 pixel-height characters to squeeze in everything into the NTSC picture which means a lot of what I wrote in the 90's needed to be reformatted for the 10 point font that is standard in JS ROM. For JSU in monitor mode (F1) it uses 10 points but in TV (F2) it switches to 8 points.

Timing-wise, printing a graphic character is about 1/2 as fast for the alternating pixel pattern (likely the most block calls it would have to do) as a ZX81. I can be more efficient with characters by writing a generalized function that goes through the 6x6 grid (36 bits can be stored in 5 bytes for 2.5K of memory) and for some of the graphics characters I can write specialized functions. The less block calls the closer to real-time I'm guessing. The interpreter itself is pretty fast already and so once I figure out how to write the character set I'll just write a zxprintf function that will replace the fprintf function in Digital 'C' and start spitting out ZX81 font strings and graphics. Also, I only need to focus on 64 characters and the other 64 are just inverted versions which I think I can do by just flipping the paper and ink colors.
ZX81_characters_0x00-3F,_0x80-BF.png
ZX81_characters_0x00-3F,_0x80-BF.png (792 Bytes) Viewed 6070 times
So here I played with some timings on an unexpanded QL:

Code: Select all

/*
 * Author: Michael Jonas (copyright 2020)
 * Date: 3/16/20
 * ZX81 graphics test prototype
 */


#include stdio_h

main ()
{
   int x, y;
   char cstr[80];

   initQLscr();

   cstr[0] = ' ';
   while (cstr[0] != '.')
   {
      for (y=10; y<130; y=y+8)
         for (x=10; x<130; x=x+8)
           pat8(x, y);

      fprintf(stdout, "Pause: ");
      fgets(cstr, 80, stdin);
      cls(stdin);

      for (y=10; y<130; y=y+8)
         for (x=10; x<130; x=x+8)
           pat8b(x, y);

      fprintf(stdout, "Enter '.' to quit: ");
      fgets(cstr, 80, stdin);
      cls(stdin);
   }


}   /* end main */


/* initializes the display */
initQLscr ()
{
   window(stdin,440,124,35,35);
   paper(stdin,7);
   ink(stdin,0);
   border(stdin,2,3);
   mode(4);
}   /* end initQLscr */


/* alternating pattern */
pat8 (x, y)
   int x, y;
{
   int i, j;

   /* clear entire 8x8 square first */
   /* then fill in black parts only */
   block(stdout, 8, 8, x, y, 7);
   for (i=0; i<8; i++)
      for (j=0; j<8; j++)
         if ((j+i)%2 == 0)
            block(stdout, 1, 1, x+j, y+i, 0);
}


/* hard-coded pattern */
pat8b (x, y)
   int x, y;
{
   /* clear entire 8x8 square first */
   /* then fill in black parts only */
   block(stdout, 8, 8, x,   y,   7);

   block(stdout, 1, 1, x,   y,   0);
   block(stdout, 1, 1, x+2, y,   0);
   block(stdout, 1, 1, x+4, y,   0);
   block(stdout, 1, 1, x+6, y,   0);

   block(stdout, 1, 1, x+1, y+1, 0);
   block(stdout, 1, 1, x+3, y+1, 0);
   block(stdout, 1, 1, x+5, y+1, 0);
   block(stdout, 1, 1, x+7, y+1, 0);

   block(stdout, 1, 1, x,   y+2, 0);
   block(stdout, 1, 1, x+2, y+2, 0);
   block(stdout, 1, 1, x+4, y+2, 0);
   block(stdout, 1, 1, x+6, y+2, 0);

   block(stdout, 1, 1, x+1, y+3, 0);
   block(stdout, 1, 1, x+3, y+3, 0);
   block(stdout, 1, 1, x+5, y+3, 0);
   block(stdout, 1, 1, x+7, y+3, 0);

   block(stdout, 1, 1, x,   y+4, 0);
   block(stdout, 1, 1, x+2, y+4, 0);
   block(stdout, 1, 1, x+4, y+4, 0);
   block(stdout, 1, 1, x+6, y+4, 0);

   block(stdout, 1, 1, x+1, y+5, 0);
   block(stdout, 1, 1, x+3, y+5, 0);
   block(stdout, 1, 1, x+5, y+5, 0);
   block(stdout, 1, 1, x+7, y+5, 0);

   block(stdout, 1, 1, x,   y+6, 0);
   block(stdout, 1, 1, x+2, y+6, 0);
   block(stdout, 1, 1, x+4, y+6, 0);
   block(stdout, 1, 1, x+6, y+6, 0);

   block(stdout, 1, 1, x+1, y+7, 0);
   block(stdout, 1, 1, x+3, y+7, 0);
   block(stdout, 1, 1, x+5, y+7, 0);
   block(stdout, 1, 1, x+7, y+7, 0);
}
So timings on unexpanded QL were 10.5 seconds for the for-loop version, 9 seconds for hard-coded version, and 4.7 seconds on ZX81 (code below). So about half-speed. But that's for the worst-case scenario.

BTW, I finally figure out how to convert a .wav file to a .p file and then into text. The last part there are two tools, one is a command line one called zxtools.exe which yields the text below and uses the special escape characters for graphics.

Code: Select all

   10 FOR I=1 TO 14
   20 FOR J=1 TO 15
   30 PRINT "\##";
   40 NEXT J
   50 PRINT
   60 NEXT I


stevepoole
Super Gold Card
Posts: 712
Joined: Mon Nov 24, 2014 2:03 pm

Re: ZXSimulator

Post by stevepoole »

Hi Bwinkel67,
Back in the late '80s I did some character manipulation progams.
Amazingly, sub-script characters can be reduced to less than 4x3 pixels, that is 12 bits, five times less than ROM.
I did a demo for the QLworld magazine,but it was not published, but Quanta have a copy : 'QLWorld_bas'.
The demo contains all sorts of 2D and 3D character stuff... but is too long to print here.
You may wish to take a look ?
Best Wishes,
Steve.


User avatar
tofro
Font of All Knowledge
Posts: 2685
Joined: Sun Feb 13, 2011 10:53 pm
Location: SW Germany

Re: ZXSimulator

Post by tofro »

bwinkel67 wrote: So timings on unexpanded QL were 10.5 seconds for the for-loop version, 9 seconds for hard-coded version, and 4.7 seconds on ZX81 (code below). So about half-speed. But that's for the worst-case scenario.
Your timings can be vastly improved by tackling the problem from another angle:

The QL can easily display 8x8 characters when you provide the proper re-defined character set and patch the console definition block to set x and y char increments to 8.

You might want to have a look into the TK2 commands CHAR_INC and CHAR_USE and create C equivalents for them.

Tobias


ʎɐqǝ ɯoɹɟ ǝq oʇ ƃuᴉoƃ ʇou sᴉ pɹɐoqʎǝʞ ʇxǝu ʎɯ 'ɹɐǝp ɥO
User avatar
NormanDunbar
Forum Moderator
Posts: 2251
Joined: Tue Dec 14, 2010 9:04 am
Location: Leeds, West Yorkshire, UK
Contact:

Re: ZXSimulator

Post by NormanDunbar »

tofro wrote:You might want to have a look into the TK2 commands CHAR_INC and CHAR_USE and create C equivalents for them.
If you need source code, I wrote similar functions (procedures?) in DJToolkit which is on Dilwyn's web site, and also on github at https://github.com/SinclairQL/DJToolkit - if you need source that is.


Cheers,
Norm.


Why do they put lightning conductors on churches?
Author of Arduino Software Internals
Author of Arduino Interrupts

No longer on Twitter, find me on https://mastodon.scot/@NormanDunbar.
User avatar
NormanDunbar
Forum Moderator
Posts: 2251
Joined: Tue Dec 14, 2010 9:04 am
Location: Leeds, West Yorkshire, UK
Contact:

Re: ZXSimulator

Post by NormanDunbar »

Here's the source for set_xinc and set_yinc. It's missing the routines to check if there's a hash, if the channel passed is in the channel table etc, but the rest is there.

Code: Select all

*====================================================================*
* SET_XINC - sets the horozontal spacing for each character in the   *
*            given channel.                                          *
*--------------------------------------------------------------------*
* Uses 4 bytes on the maths stack and doesn't care about returns.    *
*====================================================================*
set_xinc   moveq   #SD_XINC,d7         Flag for SET_XINC
           bra.s   sxy_do_it           Skip


*====================================================================*
* SET_YINC - sets the vertical spacing for each character in the     *
*            given channel.                                          *
*--------------------------------------------------------------------*
* Uses 4 bytes on the maths stack and doesn't care about returns !   *
*====================================================================*
set_yinc   moveq   #SD_YINC,d7         Flag for SET_YINC

sxy_do_it  bsr     count_pars          How many parameters ?
           subq.w  #2,d0               Must be 2
           beq.s   sx_2                More than zero

sx_badpar  moveq   #ERR_BP,d0          Bad parameter

sx_exit    rts                         And quit

sx_2       bsr     check_hash          Try for a hash
           beq.s   sx_badpar           Oops...
           moveq   #get_word,d0        Get params as word integers
           bsr     get_params          Go get them
           bne.s   sx_exit             Oops ...
           moveq   #0,d0               Clear upper word
           move.w  2(a6,a1.l),d1       Get increment size
           move.w  0(a6,a1.l),d0       Get channel number
           bmi.s   sx_badpar           Negative is bad
           bsr     get_chan            Convert to channel id in A0
           bne.s   sx_exit             Oops ...
           moveq   #sd_extop,d0        Prepare to trap the unwary
           move.w  d7,d2               Offset into channel block
           moveq   #timeout,d3         How long you get to do it
           lea.l   sx_extop,a2         Routine to be called
           trap    #3                  Call it
           rts                         Exit with or without errors


*====================================================================*
* EXTOP routine for SET_XINC & SET_YINC, this simply stores the data *
* in D1.W, the new character increment, at the offset in D2.W, which *
* is either SD_XINC or SD_YINC, relative to the base of the channel  *
* definition block which QDOS is kind enough to pass in A0.L on entry*
* to this routine.                                                   *
*====================================================================*
sx_extop   move.w  d1,0(a0,d2.w)       Store the new increment
           moveq   #0,d0               There are no errors
           rts                         And exit

Cheers,
Norm.


Why do they put lightning conductors on churches?
Author of Arduino Software Internals
Author of Arduino Interrupts

No longer on Twitter, find me on https://mastodon.scot/@NormanDunbar.
User avatar
bwinkel67
QL Wafer Drive
Posts: 1187
Joined: Thu Oct 03, 2019 2:09 am

Re: ZXSimulator

Post by bwinkel67 »

So I spent a little time looking at the ZX81 characters and it looks like at most the letters, numbers, and symbols are 7x7 (always leaving the first row and last column free):
ZX81-letters-digits-symbols.png
ZX81-letters-digits-symbols.png (1.83 KiB) Viewed 5991 times
I created a mask (enlarged 4x below) and it looks like only 40 pixels are activated so using 5 bytes to store each letter, number, and symbol should work:
ZX81-letters-digits-symbols-mask.png
ZX81-letters-digits-symbols-mask.png (1.01 KiB) Viewed 5990 times
My initial attempt will be to do this using the block function since then I can quickly get it up and running, see how slow it is and look to making it speedier. I realize that likely I may have to do some assembly coding to muck with the character set but for now I just want to get my BASIC interpreter to look like a ZX81, even if it's slow.


User avatar
bwinkel67
QL Wafer Drive
Posts: 1187
Joined: Thu Oct 03, 2019 2:09 am

Re: ZXSimulator

Post by bwinkel67 »

Ok, I have the character set working (this excludes the 11 graphics characters). Not optimal speed-wise but only 5 bytes for 64 bits of information storage-wise (270 bytes). Give it a try and you'll see the ZX81 displayed. Next I'll integrate it into the BASIC interpreter just to see how fast it runs displaying the new font.
test2.zip
(4.39 KiB) Downloaded 213 times
Here is the main code:

Code: Select all

/*
 * Author: Michael Jonas (copyright 2020)
 * Date: 3/17/20
 * ZX81 graphics test #2 prototype */

/* 
Our bitmap looks like this:

0100001
 100001
 111111
 100001
 100001
 100001
  000
 */


#include stdio_h

#define MAXCOL   440
#define MAXROW   124

#define SIZE     5

char mask[8] = { 128, 64, 32, 16, 8, 4, 2, 1 };

char chQUO[SIZE] = { 0x24, 0x90, 0x00, 0x00, 0x00 };
char chPOU[SIZE] = { 0x1C, 0x8F, 0x88, 0x21, 0xF8 };
char chDOL[SIZE] = { 0x08, 0xFA, 0x8F, 0x8A, 0xF9 };
char chCOL[SIZE] = { 0x00, 0x01, 0x00, 0x00, 0x40 };
char chQUE[SIZE] = { 0x3D, 0x08, 0x42, 0x00, 0x20 };
char chOPA[SIZE] = { 0x04, 0x20, 0x82, 0x08, 0x10 };
char chCPA[SIZE] = { 0x20, 0x41, 0x04, 0x10, 0x80 };
char chGRE[SIZE] = { 0x00, 0x40, 0x81, 0x08, 0x40 };
char chLES[SIZE] = { 0x00, 0x10, 0x84, 0x08, 0x10 };
char chEQU[SIZE] = { 0x00, 0x03, 0xE0, 0x3E, 0x00 };
char chPLU[SIZE] = { 0x00, 0x20, 0x8F, 0x88, 0x20 };
char chMIN[SIZE] = { 0x00, 0x00, 0x0F, 0x80, 0x00 };
char chAST[SIZE] = { 0x00, 0x50, 0x8F, 0x88, 0x50 };
char chDIV[SIZE] = { 0x00, 0x08, 0x42, 0x10, 0x80 };
char chSEM[SIZE] = { 0x00, 0x40, 0x00, 0x10, 0x44 };
char chCOM[SIZE] = { 0x00, 0x00, 0x00, 0x08, 0x22 };
char chPER[SIZE] = { 0x00, 0x00, 0x00, 0x18, 0x60 };
char chSPA[SIZE] = { 0x00, 0x00, 0x00, 0x00, 0x00 };

char ch0[SIZE] = { 0x3D, 0x1C, 0xB4, 0xE2, 0xF0 };
char ch1[SIZE] = { 0x18, 0xA0, 0x82, 0x08, 0xF8 };
char ch2[SIZE] = { 0x3D, 0x08, 0x2F, 0x41, 0xF8 };
char ch3[SIZE] = { 0x3D, 0x08, 0xC0, 0xC2, 0xF0 };
char ch4[SIZE] = { 0x08, 0x62, 0x92, 0x7E, 0x20 };
char ch5[SIZE] = { 0x7F, 0x07, 0xC0, 0xC2, 0xF0 };
char ch6[SIZE] = { 0x3D, 0x07, 0xD0, 0xC2, 0xF0 };
char ch7[SIZE] = { 0x7E, 0x08, 0x42, 0x10, 0x40 };
char ch8[SIZE] = { 0x3D, 0x0B, 0xD0, 0xC2, 0xF0 };
char ch9[SIZE] = { 0x3D, 0x0C, 0x2F, 0x82, 0xF0 };

char chA[SIZE] = { 0x3D, 0x0C, 0x3F, 0xC3, 0x08 };
char chB[SIZE] = { 0x7D, 0x0F, 0xD0, 0xC3, 0xF0 };
char chC[SIZE] = { 0x3D, 0x0C, 0x10, 0x42, 0xF0 };
char chD[SIZE] = { 0x79, 0x14, 0x30, 0xC5, 0xE0 };
char chE[SIZE] = { 0x7F, 0x07, 0xD0, 0x41, 0xF8 };
char chF[SIZE] = { 0x7F, 0x07, 0xD0, 0x41, 0x00 };
char chG[SIZE] = { 0x3D, 0x0C, 0x13, 0xC2, 0xF0 };
char chH[SIZE] = { 0x43, 0x0F, 0xF0, 0xC3, 0x08 };
char chI[SIZE] = { 0x3E, 0x20, 0x82, 0x08, 0xF8 };
char chJ[SIZE] = { 0x02, 0x08, 0x30, 0xC2, 0xF0 };
char chK[SIZE] = { 0x45, 0x27, 0x12, 0x45, 0x08 };
char chL[SIZE] = { 0x41, 0x04, 0x10, 0x41, 0xF8 };
char chM[SIZE] = { 0x43, 0x9D, 0xB0, 0xC3, 0x08 };
char chN[SIZE] = { 0x43, 0x8D, 0x32, 0xC7, 0x08 };
char chO[SIZE] = { 0x3D, 0x0C, 0x30, 0xC2, 0xF0 };
char chP[SIZE] = { 0x7D, 0x0C, 0x3F, 0x41, 0x00 };
char chQ[SIZE] = { 0x3D, 0x0C, 0x34, 0xCA, 0xF0 };
char chR[SIZE] = { 0x7D, 0x0C, 0x3F, 0x45, 0x08 };
char chS[SIZE] = { 0x3D, 0x03, 0xC0, 0xC2, 0xF0 };
char chT[SIZE] = { 0xFE, 0x41, 0x04, 0x10, 0x40 };
char chU[SIZE] = { 0x43, 0x0C, 0x30, 0xC2, 0xF0 };
char chV[SIZE] = { 0x43, 0x0C, 0x30, 0xA4, 0x60 };
char chW[SIZE] = { 0x43, 0x0C, 0x30, 0xDB, 0x98 };
char chX[SIZE] = { 0x42, 0x91, 0x86, 0x25, 0x08 };
char chY[SIZE] = { 0x83, 0x12, 0x84, 0x10, 0x40 };
char chZ[SIZE] = { 0x7E, 0x10, 0x84, 0x21, 0xF8 };


int row = 0;
int col = 0;


main ()
{
   char ch;

   initQLscr();

   while (1)
   {
      ch = getchar();
      if (ch == 10)
      {
         col = 0;
         row = row + 8;
         if (row > MAXROW)
         {
            cls(stdout);
            row = 0;
         }
      }
      else if (ch == '!')
         break;
      else
         ZXputc(stdout, toupper(ch));
   }

}   /* end main */


/* initializes the display */
initQLscr ()
{
   window(stdin,MAXCOL,MAXROW,35,35);
   paper(stdin,7);
   ink(stdin,0);
   border(stdin,2,3);
   mode(4);
}   /* end initQLscr */


ZXputc(fp, ch)
   int fp; char ch;
{
   ZXatChar(fp, ch, col, row);

   col = col + 8;
   if (col > MAXCOL)
   {
      col = 0;
      row = row + 8;
      if (row > MAXROW)
      {
         cls(fp);
         row = 0;
      }
   }
}


ZXatChar (fp, ch, x, y)
   int fp; char ch; int x, y;
{
   int r, c, i;

   block(fp, 8, 8, x, y, 7);

   i = 0;
   c = 0;
   for (r = 1; r < 7; r++)
   {
      while (c < 7)
      {
         if (isBlack(ch, i++))
            block(fp, 1, 1, x+c, y+r, 0);
         c++;
      }
      c = 1;
   }
  
   for (c=2; c<5; c++)
      if (isBlack(ch, i++))
         block(fp, 1, 1, x+c, y+r, 0);
}


isBlack(ch, i)
   char ch; int i;
{
   switch (ch)
   {
      case '"':
         return chQUO[i/8] & mask[i%8];
      case '#':
         return chPOU[i/8] & mask[i%8];
      case '$':
         return chDOL[i/8] & mask[i%8];
      case ':':
         return chCOL[i/8] & mask[i%8];
      case '(':
         return chOPA[i/8] & mask[i%8];
      case ')':
         return chCPA[i/8] & mask[i%8];
      case '>':
         return chGRE[i/8] & mask[i%8];
      case '<':
         return chLES[i/8] & mask[i%8];
      case '=':
         return chEQU[i/8] & mask[i%8];
      case '+':
         return chPLU[i/8] & mask[i%8];
      case '-':
         return chMIN[i/8] & mask[i%8];
      case '*':
         return chAST[i/8] & mask[i%8];
      case '/':
         return chDIV[i/8] & mask[i%8];
      case ';':
         return chSEM[i/8] & mask[i%8];
      case ',':
         return chCOM[i/8] & mask[i%8];
      case '.':
         return chPER[i/8] & mask[i%8];
      case ' ':
         return chSPA[i/8] & mask[i%8];
      case '0':
         return ch0[i/8] & mask[i%8];
      case '1':
         return ch1[i/8] & mask[i%8];
      case '2':
         return ch2[i/8] & mask[i%8];
      case '3':
         return ch3[i/8] & mask[i%8];
      case '4':
         return ch4[i/8] & mask[i%8];
      case '5':
         return ch5[i/8] & mask[i%8];
      case '6':
         return ch6[i/8] & mask[i%8];
      case '7':
         return ch7[i/8] & mask[i%8];
      case '8':
         return ch8[i/8] & mask[i%8];
      case '9':
         return ch9[i/8] & mask[i%8];
      case 'A':
         return chA[i/8] & mask[i%8];
      case 'B':
         return chB[i/8] & mask[i%8];
      case 'C':
         return chC[i/8] & mask[i%8];
      case 'D':
         return chD[i/8] & mask[i%8];
      case 'E':
         return chE[i/8] & mask[i%8];
      case 'F':
         return chF[i/8] & mask[i%8];
      case 'G':
         return chG[i/8] & mask[i%8];
      case 'H':
         return chH[i/8] & mask[i%8];
      case 'I':
         return chI[i/8] & mask[i%8];
      case 'J':
         return chJ[i/8] & mask[i%8];
      case 'K':
         return chK[i/8] & mask[i%8];
      case 'L':
         return chL[i/8] & mask[i%8];
      case 'M':
         return chM[i/8] & mask[i%8];
      case 'N':
         return chN[i/8] & mask[i%8];
      case 'O':
         return chO[i/8] & mask[i%8];
      case 'P':
         return chP[i/8] & mask[i%8];
      case 'Q':
         return chQ[i/8] & mask[i%8];
      case 'R':
         return chR[i/8] & mask[i%8];
      case 'S':
         return chS[i/8] & mask[i%8];
      case 'T':
         return chT[i/8] & mask[i%8];
      case 'U':
         return chU[i/8] & mask[i%8];
      case 'V':
         return chV[i/8] & mask[i%8];
      case 'W':
         return chW[i/8] & mask[i%8];
      case 'X':
         return chX[i/8] & mask[i%8];
      case 'Y':
         return chY[i/8] & mask[i%8];
      case 'Z':
         return chZ[i/8] & mask[i%8];
      default:
         return chQUE[i/8] & mask[i%8];
   }
}



User avatar
bwinkel67
QL Wafer Drive
Posts: 1187
Joined: Thu Oct 03, 2019 2:09 am

Re: ZXSimulator

Post by bwinkel67 »

Here is the full ZX81 character set test program, both executable and Digital 'C' source code. To recompile it you simply type (assuming a win1_ device that it is all located in):

exec win1_cc;"win1_test3_c"
exec win1_cg;"win1_test3 win1_test3 -nc"
test3.zip
(5.26 KiB) Downloaded 205 times
The code below (included in zip file). You'll see that for the inverted 64 character (the ZX81 has 128) I just flipped the paper and ink values from 7/0 to 0/7. The only place I didn't do that for is the simple block ones since it was faster drawing the combination of 4 solid quarter-blocks than playing with inverse video.

Code: Select all

/*
 * Author: Michael Jonas (copyright 2020)
 * Date: 3/18/20
 * ZX81 graphics test #3 prototype */

/* 
Our bitmap looks like this:

0100001
 100001
 111111
 100001
 100001
 100001
  000
 */


#include stdio_h

#define MAXCOL   440
#define MAXROW   124

#define LASTCOL  428
#define LASTROW  116

#define SIZE     5

char mask[8] = { 128, 64, 32, 16, 8, 4, 2, 1 };

char chQUO[SIZE] = { 0x24, 0x90, 0x00, 0x00, 0x00 };
char chPOU[SIZE] = { 0x1C, 0x8F, 0x88, 0x21, 0xF8 };
char chDOL[SIZE] = { 0x08, 0xFA, 0x8F, 0x8A, 0xF9 };
char chCOL[SIZE] = { 0x00, 0x01, 0x00, 0x00, 0x40 };
char chQUE[SIZE] = { 0x3D, 0x08, 0x42, 0x00, 0x20 };
char chOPA[SIZE] = { 0x04, 0x20, 0x82, 0x08, 0x10 };
char chCPA[SIZE] = { 0x20, 0x41, 0x04, 0x10, 0x80 };
char chGRE[SIZE] = { 0x00, 0x40, 0x81, 0x08, 0x40 };
char chLES[SIZE] = { 0x00, 0x10, 0x84, 0x08, 0x10 };
char chEQU[SIZE] = { 0x00, 0x03, 0xE0, 0x3E, 0x00 };
char chPLU[SIZE] = { 0x00, 0x20, 0x8F, 0x88, 0x20 };
char chMIN[SIZE] = { 0x00, 0x00, 0x0F, 0x80, 0x00 };
char chAST[SIZE] = { 0x00, 0x50, 0x8F, 0x88, 0x50 };
char chDIV[SIZE] = { 0x00, 0x08, 0x42, 0x10, 0x80 };
char chSEM[SIZE] = { 0x00, 0x40, 0x00, 0x10, 0x44 };
char chCOM[SIZE] = { 0x00, 0x00, 0x00, 0x08, 0x22 };
char chPER[SIZE] = { 0x00, 0x00, 0x00, 0x18, 0x60 };
char chSPA[SIZE] = { 0x00, 0x00, 0x00, 0x00, 0x00 };

char ch0[SIZE] = { 0x3D, 0x1C, 0xB4, 0xE2, 0xF0 };
char ch1[SIZE] = { 0x18, 0xA0, 0x82, 0x08, 0xF8 };
char ch2[SIZE] = { 0x3D, 0x08, 0x2F, 0x41, 0xF8 };
char ch3[SIZE] = { 0x3D, 0x08, 0xC0, 0xC2, 0xF0 };
char ch4[SIZE] = { 0x08, 0x62, 0x92, 0x7E, 0x20 };
char ch5[SIZE] = { 0x7F, 0x07, 0xC0, 0xC2, 0xF0 };
char ch6[SIZE] = { 0x3D, 0x07, 0xD0, 0xC2, 0xF0 };
char ch7[SIZE] = { 0x7E, 0x08, 0x42, 0x10, 0x40 };
char ch8[SIZE] = { 0x3D, 0x0B, 0xD0, 0xC2, 0xF0 };
char ch9[SIZE] = { 0x3D, 0x0C, 0x2F, 0x82, 0xF0 };

char chA[SIZE] = { 0x3D, 0x0C, 0x3F, 0xC3, 0x08 };
char chB[SIZE] = { 0x7D, 0x0F, 0xD0, 0xC3, 0xF0 };
char chC[SIZE] = { 0x3D, 0x0C, 0x10, 0x42, 0xF0 };
char chD[SIZE] = { 0x79, 0x14, 0x30, 0xC5, 0xE0 };
char chE[SIZE] = { 0x7F, 0x07, 0xD0, 0x41, 0xF8 };
char chF[SIZE] = { 0x7F, 0x07, 0xD0, 0x41, 0x00 };
char chG[SIZE] = { 0x3D, 0x0C, 0x13, 0xC2, 0xF0 };
char chH[SIZE] = { 0x43, 0x0F, 0xF0, 0xC3, 0x08 };
char chI[SIZE] = { 0x3E, 0x20, 0x82, 0x08, 0xF8 };
char chJ[SIZE] = { 0x02, 0x08, 0x30, 0xC2, 0xF0 };
char chK[SIZE] = { 0x45, 0x27, 0x12, 0x45, 0x08 };
char chL[SIZE] = { 0x41, 0x04, 0x10, 0x41, 0xF8 };
char chM[SIZE] = { 0x43, 0x9D, 0xB0, 0xC3, 0x08 };
char chN[SIZE] = { 0x43, 0x8D, 0x32, 0xC7, 0x08 };
char chO[SIZE] = { 0x3D, 0x0C, 0x30, 0xC2, 0xF0 };
char chP[SIZE] = { 0x7D, 0x0C, 0x3F, 0x41, 0x00 };
char chQ[SIZE] = { 0x3D, 0x0C, 0x34, 0xCA, 0xF0 };
char chR[SIZE] = { 0x7D, 0x0C, 0x3F, 0x45, 0x08 };
char chS[SIZE] = { 0x3D, 0x03, 0xC0, 0xC2, 0xF0 };
char chT[SIZE] = { 0xFE, 0x41, 0x04, 0x10, 0x40 };
char chU[SIZE] = { 0x43, 0x0C, 0x30, 0xC2, 0xF0 };
char chV[SIZE] = { 0x43, 0x0C, 0x30, 0xA4, 0x60 };
char chW[SIZE] = { 0x43, 0x0C, 0x30, 0xDB, 0x98 };
char chX[SIZE] = { 0x42, 0x91, 0x86, 0x25, 0x08 };
char chY[SIZE] = { 0x83, 0x12, 0x84, 0x10, 0x40 };
char chZ[SIZE] = { 0x7E, 0x10, 0x84, 0x21, 0xF8 };


int row = 0;
int col = 0;


main ()
{
   char ch;

   initQLscr();

   while (1)
   {
      ch = getchar();

      if (ch == 10)
      {
         col = 0;
         row = row + 8;
         if (row > LASTROW)
         {
            cls(stdout);
            row = 0;
         }
         continue;
      }
      else if (ch == '!')
         break;
      else if (ch == '\\')
      {
         ZXgraphics(stdout, getchar(), getchar());
      }
      else if (ch == '%')
         ZXputc(stdout, toupper(getchar()), 7, 0);  /* invert ink and paper */
      else
         ZXputc(stdout, toupper(ch), 0, 7);   /* normal ink and paper */

      adjustCursor(stdout);
   }

}   /* end main */


/* initializes the display */
initQLscr ()
{
   window(stdin,MAXCOL,MAXROW,35,35);
   paper(stdin,7);
   ink(stdin,0);
   border(stdin,2,3);
   mode(4);
}   /* end initQLscr */


ZXprintf(fp, str)
   int fp; char str[];
{
   int i;

   for (i=0; str[i]!='\0'; i++)
   {
      ZXputc(fp, str[i], 0, 7);
      adjustCursor(fp);
   }
}


ZXputc(fp, ch, ink, pap)
   int fp; char ch; int ink, pap;
{
   int i, x, y;

   block(fp, 8, 8, col, row, pap);

   i = 0;
   x = 0;
   for (y = 1; y < 7; y++)
   {
      while (x < 7)
      {
         if (isBlack(ch, i++))
            block(fp, 1, 1, col+x, row+y, ink);
         x++;
      }
      x = 1;
   }
  
   for (x=2; x<5; x++)
      if (isBlack(ch, i++))
         block(fp, 1, 1, col+x, row+7, ink);
}


ZXgraphics(fp, ch1, ch2)
   int fp; char ch1, ch2;
{
   switch (ch1)
   {
      case '#':
         if (ch2 == '#')
            { ZXgrey(fp, 1, 1, 0, 7); return; }
         break;
      case '~':
         if (ch2 == '~')
            { ZXgrey(fp, 1, 0, 0, 7); return; }
         break;
      case ',':
         if (ch2 == ',')
            { ZXgrey(fp, 0, 1, 0, 7); return; }
         break;
      case '@':
         if (ch2 == '@')
            { ZXgrey(fp, 1, 1, 7, 0); return; }
         break;
      case '!':
         if (ch2 == '!')
            { ZXgrey(fp, 1, 0, 7, 0); return; }
         break;
      case ';':
         if (ch2 == ';')
            { ZXgrey(fp, 0, 1, 7, 0); return; }
         break;
      case ' ':
         if (ch2 == ':')
            { ZXblock(fp, 0, 1, 0, 1); return; }
         if (ch2 == '\'')
            { ZXblock(fp, 0, 1, 0, 0); return; }
         if (ch2 == '.')
            { ZXblock(fp, 0, 0, 0, 1); return; }
         if (ch2 == ' ')
            { ZXputc(fp, ' ', 0, 7); return; }
               return;
         break;
      case ':':
         if (ch2 == ':')
            { ZXputc(fp, ' ', 7, 0); return; }
         if (ch2 == '\'')
            { ZXblock(fp, 1, 1, 1, 0); return; }
         if (ch2 == '.')
            { ZXblock(fp, 1, 0, 1, 1); return; }
         if (ch2 == ' ')
            { ZXblock(fp, 1, 0, 1, 0); return; }
         break;
      case '\'':
         if (ch2 == ':')
            { ZXblock(fp, 1, 1, 0, 1); return; }
         if (ch2 == '\'')
            { ZXblock(fp, 1, 1, 0, 0); return; }
         if (ch2 == '.')
            { ZXblock(fp, 1, 0, 0, 1); return; }
         if (ch2 == ' ')
            { ZXblock(fp, 1, 0, 0, 0); return; }
         break;
      case '.':
         if (ch2 == ':')
            { ZXblock(fp, 0, 1, 1, 1); return; }
         if (ch2 == '\'')
            { ZXblock(fp, 0, 1, 1, 0); return; }
         if (ch2 == '.')
            { ZXblock(fp, 0, 0, 1, 1); return; }
         if (ch2 == ' ')
            { ZXblock(fp, 0, 0, 1, 0); return; }
         break;
   }

   /* Question mark for unknown characters */
   ZXputc(fp, '?', 0, 7);
}


ZXgrey(fp, up, lo, ink, pap)
   int fp, up, lo, ink, pap;
{
   int x, y;

   block(fp, 8, 8, col, row, pap);

   if (up)
   {
      for (y=0; y<4; y++)
         for (x=0; x<8; x++)
            if ((x+y)%2 == 0)
               block(fp, 1, 1, col+x, row+y, ink);
   }
   if (lo)
   {
      for (y=4; y<8; y++)
         for (x=0; x<8; x++)
            if ((x+y)%2 == 0)
               block(fp, 1, 1, col+x, row+y, ink);
   }
}


ZXblock(fp, ul, ur, ll, lr)
   int fp, ul, ur, ll, lr;
{
   block(fp, 8, 8, col, row, 7);

   if (ul)
      block(fp, 4, 4, col, row, 0);
   if (ur)
      block(fp, 4, 4, col+4, row, 0);
   if (ll)
      block(fp, 4, 4, col, row+4, 0);
   if (lr)
      block(fp, 4, 4, col+4, row+4, 0);
}


adjustCursor(fp)
   int fp;
{
   col = col + 8;
   if (col > LASTCOL)
   {
      col = 0;
      row = row + 8;
      if (row > LASTROW)
      {
         cls(fp);
         row = 0;
      }
   }
}


isBlack(ch, i)
   char ch; int i;
{
   switch (ch)
   {
      case '"':
         return chQUO[i/8] & mask[i%8];
      case '#':
         return chPOU[i/8] & mask[i%8];
      case '$':
         return chDOL[i/8] & mask[i%8];
      case ':':
         return chCOL[i/8] & mask[i%8];
      case '(':
         return chOPA[i/8] & mask[i%8];
      case ')':
         return chCPA[i/8] & mask[i%8];
      case '>':
         return chGRE[i/8] & mask[i%8];
      case '<':
         return chLES[i/8] & mask[i%8];
      case '=':
         return chEQU[i/8] & mask[i%8];
      case '+':
         return chPLU[i/8] & mask[i%8];
      case '-':
         return chMIN[i/8] & mask[i%8];
      case '*':
         return chAST[i/8] & mask[i%8];
      case '/':
         return chDIV[i/8] & mask[i%8];
      case ';':
         return chSEM[i/8] & mask[i%8];
      case ',':
         return chCOM[i/8] & mask[i%8];
      case '.':
         return chPER[i/8] & mask[i%8];
      case ' ':
         return chSPA[i/8] & mask[i%8];
      case '0':
         return ch0[i/8] & mask[i%8];
      case '1':
         return ch1[i/8] & mask[i%8];
      case '2':
         return ch2[i/8] & mask[i%8];
      case '3':
         return ch3[i/8] & mask[i%8];
      case '4':
         return ch4[i/8] & mask[i%8];
      case '5':
         return ch5[i/8] & mask[i%8];
      case '6':
         return ch6[i/8] & mask[i%8];
      case '7':
         return ch7[i/8] & mask[i%8];
      case '8':
         return ch8[i/8] & mask[i%8];
      case '9':
         return ch9[i/8] & mask[i%8];
      case 'A':
         return chA[i/8] & mask[i%8];
      case 'B':
         return chB[i/8] & mask[i%8];
      case 'C':
         return chC[i/8] & mask[i%8];
      case 'D':
         return chD[i/8] & mask[i%8];
      case 'E':
         return chE[i/8] & mask[i%8];
      case 'F':
         return chF[i/8] & mask[i%8];
      case 'G':
         return chG[i/8] & mask[i%8];
      case 'H':
         return chH[i/8] & mask[i%8];
      case 'I':
         return chI[i/8] & mask[i%8];
      case 'J':
         return chJ[i/8] & mask[i%8];
      case 'K':
         return chK[i/8] & mask[i%8];
      case 'L':
         return chL[i/8] & mask[i%8];
      case 'M':
         return chM[i/8] & mask[i%8];
      case 'N':
         return chN[i/8] & mask[i%8];
      case 'O':
         return chO[i/8] & mask[i%8];
      case 'P':
         return chP[i/8] & mask[i%8];
      case 'Q':
         return chQ[i/8] & mask[i%8];
      case 'R':
         return chR[i/8] & mask[i%8];
      case 'S':
         return chS[i/8] & mask[i%8];
      case 'T':
         return chT[i/8] & mask[i%8];
      case 'U':
         return chU[i/8] & mask[i%8];
      case 'V':
         return chV[i/8] & mask[i%8];
      case 'W':
         return chW[i/8] & mask[i%8];
      case 'X':
         return chX[i/8] & mask[i%8];
      case 'Y':
         return chY[i/8] & mask[i%8];
      case 'Z':
         return chZ[i/8] & mask[i%8];
      default:
         return chQUE[i/8] & mask[i%8];
   }
}

This is modeled after the following escape codes, graphics, and formatting conventions for inverse video and graphics characters:

http://freestuff.grok.co.uk/zxtext2p/index.html

Next I will work on merging it to the BASIC interpreter to see what the first draft will look like. I think I only have integer BASIC implemented on the QL so it will initially be more of an ZX80 simulator before I get the rest working. Still, can't wait to write a simple ZX80/81 BASIC program and see it run on the QL under this.


User avatar
bwinkel67
QL Wafer Drive
Posts: 1187
Joined: Thu Oct 03, 2019 2:09 am

Re: ZXSimulator

Post by bwinkel67 »

Here is version 1.0 of ZXSimulator -- first piece of new QL software during COVID-19 self social distancing :-). It's pretty slow right now but is only 38K in size, so pretty small footprint and runs easily on an unexpanded QL.
zx.zip
(20.74 KiB) Downloaded 211 times
You can load the following code via (assumes file resides on MDV2): LOAD "MDV2_TEST_BAS"

Code: Select all

10 for i=1 to 13
20 FOR J=1 TO 15
30 PRINT "\##";
40 NEXT J
50 PRINT
60 NEXT I
If you LIST you you will see that ZXSimulator transforms it into the ZX81 character set (i.e. the "\##").

Still lots of work to do and the BASIC does not presently match up with the ZX81 BASIC so expect the unexpected. Any feedback and/or help is appreciated.

P.S. Ctrl-C stops a running program.


User avatar
NormanDunbar
Forum Moderator
Posts: 2251
Joined: Tue Dec 14, 2010 9:04 am
Location: Leeds, West Yorkshire, UK
Contact:

Re: ZXSimulator

Post by NormanDunbar »

I gave it a try out, not bad!

Question, when I ran the test_bas program, it does give a checkerboard pattern, but it doesn't look like the old ZX-81 checkerboard patter I remember, I wonder if it's correct? This is the listing from inside the ZX emulator, running in QPC 4.05, on Linux Mint 19.3 64bit:
ZX-81.2.png
ZX-81.2.png (6.3 KiB) Viewed 5852 times
I did a quick test too, printing all the ZX-81 characterset from 0 to 255, this is what I got but I see none of the special "graphics" characters in there, should I have seen them, or is that a "still to come"?

ZX81.png
ZX81.png (6.5 KiB) Viewed 5852 times
Cheers,
Norm.


Why do they put lightning conductors on churches?
Author of Arduino Software Internals
Author of Arduino Interrupts

No longer on Twitter, find me on https://mastodon.scot/@NormanDunbar.
Post Reply