Sunday, September 21, 2014

Commodore 128 BASIC 7.0 - graphics programming.

     I have never owned Commodore 128 computer in my youth. I recently purchased two C-128-s from e-bay due to my fascination with CP/M operating system. However there is plenty more to do with this versatile machine than just CP/M or playing games. Compared to its famous predecessor, model 64, it is in many aspects a better microcomputer. Not only it is 100% compatible with C64, making it possible to use all that huge code base created for C-64. It incorporates also a Z-80 microprocessor, making it compatible with CP/M - a huge business oriented software library exists for that operating system. And of course C-128 offers its own improved C-128 native mode with bigger memory (128 KB, banked) and better memory management thanks to the MMU on chip and new graphic facilities, like 80-column text mode (also very useful in CP/M which by default runs in 80-column mode). Last weekend I explored its built in BASIC. Anyone who owned C-64 and made first steps in programming using built in BASIC on that machine knows full well how inadequate that dialect was. Based on Microsoft's version 2, there were no machine specific keywords to take advantage of the built in hardware. All that had to be done with peek'n'poke trickery. No structural constructs available as well. Shortly speaking - a mess and it wasn't easy to create a structured readable program. The program also had to take more space in memory and was slower due to all that extra peek and pokes programmer had to use to generate sounds or create graphics on the screen. There were few options available, all at the cost - switching to machine language (difficult and long development cycle), using extended BASIC dialects (at the cost of memory available for code and variables, sometimes speed) or using different than BASIC languages (also at the cost of memory and/or speed).
Brilliant engineers at Commodore tried to address most of these issues in this next model (and as it turned out later, the last 8-bit computer by Commodore). BASIC 7.0 is much improved comparing to version 2.0 seen in C-64. It offers keywords that take advantage of the built in graphic and sound hardware as well as memory management (bank selection with BANK command) without the need of remembering the addresses of all the involved hardware registers. The new BASIC also adds some structural constructs like multiple line IF/THEN/BEGIN/BEND decision branches, open-ended (with optional condition) flow/loop control constructs like DO/WHILE/LOOP, DO/LOOP/WHILE, DO/UNTIL/LOOP, DO/LOOP/UNTIL in addition to standard BASIC's FOR/NEXT loop. C-128 offers so much more than C-64 that in fact I put it on my permanent retro-computer desk and replaced C-64 that was there before. Now I have 3-in-1 system (C-64, CP/M and C-128) taking up only slightly more space than C-64.

To do a little BASIC 7.0 exercise, I decided to write a simple analog clock application that would use the basic graphic capabilities of the machine. I will add sound/alarm abilities to it later, it should be as easy as graphics part with all the extensions that BASIC 7.0 provides.

To switch C-128 to high resolution mode (or a bitmap mode), BASIC command GRAPHIC is used.

Formats: GRAPHIC mode, [clear], [split]
                 GRAPHICS CLR

Where mode can be one of:

0 - 40 columns (composite or monochrome monitor, tv set),
1 - hi-res graphics (composite or monochrome monitor, tv set),
2 - hi-res graphics (split screen, the same type of output like 0 or 1),
3 - multicolor graphics (the same output as above),
4 - multicolor graphics (split screen, the same output as above),
5 - 80 columns (RGB or monochrome monitor).

I did not mention before that C-128 features 2 separate video output ports, the composite/TV and RGB. Composite/TV output is of the same format as C-64 video output, that provides 40 column text and high-resolution (320x200) graphics mode as well as other graphics modes (multicolor). Technically it is possible for C-128 to work with two monitors at the same time, one RGB and one composite (or a TV set). This is the setup that I have. User can freely switch between these modes in immediate mode and in program mode where application can output text to 80-column (RGB) monitor while drawing graphics on the composite monitor or alternate text output to both monitors by switching the modes with GRAPHIC command. I think this is pretty cool for a 8-bit 30 years old computer to be able to do that.
The "GRAPHICS CLR" command is NOT a screen clearing command. It clears and de-allocates memory used for graphics operations reclaiming it back for BASIC. To clear any given graphics mode, use flag clear in the GRAPHIC command or command SCNCLR.
You may want to use "GRAPHIC CLR" if you run out of memory for BASIC and you're no longer using hi-res graphics.
Flag split lets user have graphics and text on the same screen, thus allowing to present text output of the application in the more convenient text mode while having hi-res picture on top of it as well as allowing the standard text input from user (INPUT command). The number used in split tells BASIC interpreter at which line number the split screen starts. The text can also be mixed with graphics on the hi-res screen, but BASIC command PRINT can not be used for that - CHAR command is the graphics command designed for that purpose.

To draw a circle in one of the bitmap modes, BASIC command CIRCLE can be used:

Format: CIRCLE color_source, x_center, y_center, x_radius, y_radius, [start_angle], [end_angle], [rotation], [segment]

Computer must be in proper graphics mode to be able to draw a circle.

The color source is a number from 0 to 3, as in the COLOR command:

0 - 40-column (composite) background,
1 - 40-column foreground,
2 - multicolor 1,
3 - multicolor 2.

The COLOR command stores a color into the color source register.

Format: COLOR color_source, color_number

Please refer to the C-128 system guide for the colors and their assigned numbers.

BASIC command DRAW puts a line on the hi-res screen.

Formats: DRAW color_source, x1, y1 [TO x2, y2]
                 DRAW TO x, y

DRAW is used to draw points and lines in the hi-res and multicolor modes. It can draw a line from one specified location (x1, y1) to another (x2, y2) or from the current graphic cursor position to the specified coordinates (DRAW TO). If the 1st format is used but the 2nd set of coordinates is ommited (TO x2, y2), a point is turned on. The color_source as in COLOR and CIRCLE commands tells BASIC which color to use for a point/line. This can also be used to remove existing points or lines, when we draw with background as a color source (color_source=0). You can also specify relative coordinates by supplying a distance and an angle. This method of locating a point requires to use semicolon in the command.

Example: GRAPHIC 1:LOCATE 50,50:DRAW 1,25;90

will plot a point at coordinates 75,50.

Example: DRAW 0,160,100 to 50;hr%*(360/12)+(mi%/15)*(360/48)

will plot a line from point at coordinates 160,100, length=50 at the angle calculated from hr% and mi% variables (this particular example shows how to draw a small hand of the analog clock).

Without further ado, let me present the BASIC code that will display graphical (very simplified) analog clock image that is updated every minute (except seconds which are drawn more often) based on the system time TI$. I used petcat program (part of the WinVICE Commodore emulator software) to convert native BASIC code (PRG) to the text format.

petcat -70 -o analogclock.txt  -- analogclock.prg

10 rem analog clock v1.0
20 rem (c) by marek karcz 2014
30 rem all rights reserved
40 rem
50 rem variables
60 hr%=0:mi%=0:se%=0:pr%=0:hm%=0
70 hr$="":mi$="":se$="":hm$=""
80 input "set time (hhmmss) ";tm$
90 ti$=tm$
900 goto 10000
1000 rem initgr
1005 scnclr
1010 graphic 1,1:width 2
1020 color 0,1:color 1,6:color 5,6
1030 circle 1,160,100,110,90
1040 return
1050 rem draw short hand
1060 draw 1,160,100 to 50;hr%*(360/12)+(mi%/15)*(360/48)
1070 return
1080 rem draw long hand
1090 draw 1,160,100 to 90;mi%*(360/60)
1110 return
1130 rem eraseshorthand
1140 locate 160,100
1150 draw 0,160,100 to 50;hr%*(360/12)+(mi%/15)*(360/48)
1160 return
1170 rem eraselonghand
1180 draw 0,160,100 to 90;mi%*(360/60)
1190 return
1200 rem gethr
1210 hr$=mid$(ti$,1,2)
1220 hr%=val(hr$)
1225 if hr%>11 then hr%=hr%-12
1230 return
1240 rem getmi
1250 mi$=mid$(ti$,3,2)
1260 mi%=val(mi$)
1270 return
1280 rem getsec
1290 se$=mid$(ti$,5,2)
1300 se%=val(se$)
1310 return
1320 rem draw seconds
1330 char 1,0,24,se$,1
1340 return
1350 rem gethrmi
1360 hm$=mid$(ti$,1,4)
1370 hm%=val(hm$)
1380 return
10000 rem main
10010 gosub 1000
10015 gosub 1200:gosub 1240:gosub 1280:rem get hr%,mi%,se%
10017 rem gosub 1050:gosub 1090:rem draw hands
10020 do
10025 gosub 1350:get hrmi
10030 if pr%<>hm% then begin:
10040 gosub 1130:gosub 1170:rem erase hands
10050 gosub 1200:gosub 1240:rem get hr%,mi%
10060 gosub 1050:gosub 1090:rem draw hands
10070 bend:
10080 gosub 1280:rem get se%
10090 gosub 1320:rem draw seconds
10100 pr%=hm%
10110 loop:rem loop forever
20000 end

Thanks to support for graphics modes in BASIC, code is short and clear.
Because image speaks 1,000 words, here are some pictures of this code in action:

Thank you for your time.


COMPUTE!'s 128 Programmer's Guide, ISBN 0-87455-031-9.

Marek Karcz

No comments:

Post a Comment