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):
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:
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.