Category Archives: Programming

C and returning values quickly or safely. But not both.

WARNING: This article contains a C coding approach that many will find uncomfortable.

In my day job as a mild-mannered embedded C programmer, I am usually too busy maintaining what was created before me to be creating something new for others to maintain after me. There was that one time I had two weeks that were very different, and fun, since they were almost entirely spent “creating” versus “maintaining.”

Today’s quick C tidbit is about getting parameters back from a C function. In C, you only get one thing back — typically a variable type like an int or float or whatever:

int GetTheUltimateAnswer()
{
    return 42;
}

int answer = GetTheUltimateAnswer();
print ("The Ultimate Answer is %d\n", answer);

If you need more than one thing returned, it is common to pass in variables by reference (the address of, or pointer to, the variable in memory) and have the function modify that memory to update the variables:

void GetMinAndMax (int *min, int *max)
{
    *min = 0;
    *max = 100;
}

int min, max;
GetMinAndMax (&min, &max)
printf ("Min is %d and Max is %d\n", min, max);

The moment pointers come in to play, things get very dangerous. But fast.

When passing values in, they get copied in to a new variable:

int variable = 42;

printf ("variable = %d\n", variable);
Function (variable);
printf ("variable = %d\n", variable);

void Function (int x)
{
    x = x + 1;
}

Try it: https://onlinegdb.com/WC3ihCAuj

Above, Function() gets a new variable (called “x” in this case) with the value of the variable that was passed in to the call. The function is like Las Vegas. Anything that happens to that variable inside the function stays inside the function – the variable disappears at the end of the function, while the original variable remains as-was.

C++ changes this, I have learned, so you can pass in variables that can be modified, but I am not a C++ programmer so this post is only about old-skool C.

Pointing to a variable’s memory

By passing in the address of a variable, the function can go to that memory and modify the variable. It will be changed:

int variable = 42;

printf ("variable = %d\n", variable);
Function (&variable);
printf ("variable = %d\n", variable);

void Function (int *x)
{
    *x = *x + 1;
}

Try it: https://onlinegdb.com/Y2Z9WUvFG

Passing by value is slower, since a new variable has to be created. Passing by reference just passes an address and the code uses that address – no new variable is created.

But, using a reference for just for speed is dangerous because the function can modify the variable even if you didn’t want it to. Consider passing in a string buffer, which is a pointer to a series of character bytes:

void PrintError (char *message)
{
    print ("ERROR: %s\n", message);
}

PrintError ("Human Detected");

We do this all the time, but since PrintError() has access to the memory passed in, it could try to modify it. If we passed in a constant string like “Human Detected”, that string would typically be in program memory (though this is not true for Harvard Architecture systems like the PIC and Arduino). At best, an operating system with memory protection would trap that access with an exception and kill the program. At worst, the program would self-modify (which was the case when I learned this on OS-9/6809 back in the late 80s — no memory protection on my TRS-80 CoCo!).

void PrintError (char *message)
{
    message[0] = 42;
}

PrintError ("Human Detected");

Above would likely crash, though if the user had passed in the buffer holding a string, it would just be modified:

void PrintError (char *message)
{
    message[0] = 42;
}

char buffer[80];
strncpy (buffer, "Hello, world!", 80);
printf ("buffer: %s\n", buffer);
PrintError (buffer);
printf ("buffer: %s\n", buffer);

Try it: https://onlinegdb.com/L50JRWYj

And your point is?

My point is — there are certainly times when speed is the most important thing, and it outweighs the potential problems/crashes that could be caused by a bug with code using the pointer. Take for example anything that passes in a buffer:

void UppercaseString (char *buffer)
{
    for (int idx=0; idx<strlen(buffer); idx++)
    {
        buffer[i] = toupper(buffer[I])
    }
}

There are many bad things that could happen here. By using “strlen”, the buffer MUST be a string that has a NIL (‘\0’) byte at the end. This routine could end up trampling through memory uppercasing bytes that are beyond the caller’s string.

It is wise to always add another parameter that is the max size of the buffer:

void UppercaseString (char *buffer, int bufferSize)
{
    for (int idx=0; idx<bufferSize; idx++)
    {
        buffer[i] = toupper(buffer[I])
    }
}

That helps. But it is still up to the compiler to catch the wrong type of pointer being passed in.

int Number = 10;

UppercaseString (&Number, 100);

The compiler should not let you do that, but some may just issue a warning and build it anyway. (This is why I always try to have NO warnings in my code. The more warnings there are, the more likely you will start ignoring them.)

Try #1: Passing by Reference

Suppose we have a function that returns the date and time as individual values (year, month, day, hour, minute and second). Since we cannot get six values back from a function, we first try passing in six variables by reference and having the routine modify them:

void GetDateTime1 (int *year, int *month, int *day,
                   int *hour, int *minute, int *second)
{
    *year = 2023;
    *month = 8;
    *day = 19;
    *hour = 4;
    *minute = 20;
    *second = 0;
}

int year, month, day, hour, minute, second;
GetDateTime1 (&year, &month, &day, &hour, &minute, &second);
printf ("GetDateTime1: %d/%d/%d %02d:%02d:%02d\n",
        year, month, day, hour, minute, second);

That works fine … as long as you know the parameters are “ints” (whatever that is) and not shorts or longs or any other numeric type. This, for example, would be bad:

short year, month, day, hour, minute, second;

GetDateTime1 (&year, &month, &day, &hour, &minute, &second);

Above, we are passing in a short (let’s say that is a 16-bit variable on this system) in to a function that expects an int (let’s say that is a 32-bit signed variable on this system). The function would try to place 32-bits of information at the address of a 16-bit value.

Bad things, as they say, can happen.

Try #2: Passing a structure by reference

Passing in six variable pointers is more work than passing in one, so if we put the values in a structure we could pass in just the pointer to that structure. This has the benefit of making sure the structure is only loaded with values it can handle (unlike passing in an address of something that might be 8, 16, 32 or 64 bits).

typedef struct
{
    int year;
    int month;
    int day;
    int hour;
    int minute;
    int second;
} TimeStruct;

void GetDateTime2 (TimeStruct *timePtr)
{
    timePtr->year = 2023;
    timePtr->month = 8;
    timePtr->day = 19;
    timePtr->hour = 4;
    timePtr->minute = 20;
    timePtr->second = 0;   
}

TimeStruct time;
GetDateTime2 (&time);
printf ("GetDateTime2: %d/%d/%d %02d:%02d:%02d\n",
        time.year, time.month, time.day,
        time.hour, time.minute, time.second);

This should greatly reduce the potential problems since you only have one pointer to screw up, and if you get the type correct (a TimeStruct) the values it contains should be fine since the compiler takes care of trying to set a “uint8_t” to “65535” (a warning, hopefully, and storing 8-bits of that 16-bit value as a “loss of precision”).

Try #3: Returning the address of a static

An approach various standard C library functions take is having some fixed memory allocated inside the function as a static variable, and then returning a pointer to that memory. The user doesn’t make it and therefore isn’t passing in a pointer that could be wrong.

TimeStruct *GetDateTime3 (void)
{
    static TimeStruct s_time;
    
    s_time.year = 2023;
    s_time.month = 8;
    s_time.day = 19;
    s_time.hour = 4;
    s_time.minute = 20;
    s_time.second = 0;

    return &s_time;
}

TimeStruct *timePtr;
timePtr = GetDateTime3 ();  
printf ("GetDateTime3: %d/%d/%d %02d:%02d:%02d\n",
       timePtr->year, timePtr->month, timePtr->day,
       timePtr->hour, timePtr->minute, timePtr->second);

This approach is better, since it gets the speed from using a pointer, and the safety of not being able to get the pointer wrong since the function tells you where it is, not the other way around.

BUT … once you have the address of that static memory, you can modify it.

TimeStruct *timePtr;
timePtr = GetDateTime3 ();
timePtr->year = 1969;

In a real Date/Time function (like the one in the C library), those variables are populated with the system time when you call the function, so even if the user changed something like this, it would be set back to what it was the next time it was called. But, I can see where there could be issues with other types of functions that just hold on to memory like this.

Plus, it’s always holding on to that memory whether anyone is using it or not. That is a no-no when working on memory constrained systems like an Arduino with 4K of RAM.

Try #4: Returning a copy of a structure

And now the point of today’s ramblings… I rarely have used this, since it’s probably the slowest way to do things, but … you don’t just have to return a date type like and int or a bool or a pointer. You can return a structure, and C will give the caller a copy of the structure.

TimeStruct GetDateTime4 (void)
{
    TimeStruct time;
    
    time.year = 2023;
    time.month = 8;
    time.day = 19;
    time.hour = 4;
    time.minute = 20;
    time.second = 0;

    return time;
}

TimeStruct time;
time = GetDateTime4 ();    
printf ("GetDateTime4: %d/%d/%d %02d:%02d:%02d\n",
       time.year, time.month, time.day,
       time.hour, time.minute, time.second);

Above is possibly the safest way to return data, since no pointers are used. The called makes an new structure variable, and then the function creates a new structure variable and the return copies that structure in to the caller’s structure.

Try it: https://onlinegdb.com/F6rR1V-xb

This is slower, and consumes more memory during the process of making all these copies, BUT it’s far, far safer. Even ChatGPT agrees that, if going to “safe” code, this is the better approach.

And, at my day job, I experimented with this and it’s been working very well. It’s about the closest thing C has to “objects”. I even use it for a BufferStruct so I can pass a buffer around without using a pointer (though internally there is a pointer to the buffer memory). It looks something like this:

#include <stdio.h>
#include <string.h>

typedef struct
{
    char buffer[80];
    char bufferSize;
} BufferStruct;

BufferStruct GetBuffer ()
{
    BufferStruct buf;
    
    strncpy (buf.buffer, "Hello, world!", sizeof(buf.buffer));
    buf.bufferSize = strlen(buf.buffer);
    
    return buf;
}

void ShowBuffer (BufferStruct buf)
{
    printf ("Buffer: %s\n", buf.buffer);
    printf ("Size  : %d\n", buf.bufferSize);
}

int main()
{
    BufferStruct myBuffer;
    myBuffer = GetBuffer ();
    ShowBuffer (myBuffer);

    BufferStruct testBuffer;
    strncpy (testBuffer.buffer, "I put this in here",
             sizeof(testBuffer.buffer));
    testBuffer.bufferSize = strlen (testBuffer.buffer);
    ShowBuffer (testBuffer);
    
    return 0;
}   

The extra overhead may be a problem if you are coding for speed, but doing this trick (while trying not to think about all the extra work and copying the code is doing) gives you a simple way to pass things around without ever using a pointer. You could even do this:

typedef struct
{
    int year;
    int month;
    int day;
    int hour;
    int minute;
    int second;
} TimeStruct;

// Global time values.
int g_year, g_month, g_day, g_hour, g_minute, g_second;

void SetTime (TimeStruct time)
{
    // Pretend we are setting the clock.
    g_year = time.year;
    g_month = time.month;
    g_day = time.day;
    g_hour = time.hour;
    g_minute = time.minute;
    g_second = time.second;
}

TimeStruct GetTime ()
{
    TimeStruct time;

    // Pretend we are reading the clock.
    time.year = g_year;
    time.month = g_month;
    time.day = g_day;
    time.hour = g_hour;
    time.minute = g_minute;
    time.second = g_second;

    return time;
}

TimeStruct time;

time.year = 2023;
time.month = 8;
time.day = 19;
time.hour = 12;
time.minute = 4;
time.second = 20;
SetTime (time);

...

time = GetTime ();

And now a certain percentage of C programmers who stumble in to this article should be having night terrors at what is going on here.

Until next time…

Tackling the Logiker 2023 Vintage Computing Christmas Challenge – part 4

See also: part 1, part 2, part 3 and part 4.

If I would have told my teenage-self that one day I would be corresponding with Radio Shack ROM-Pak game programmer, Rick Adams, I would have not believed myself. Rick became a very well known name in the CoCo community, and I remember seeing his name in the title screen of Radio Shack ROM-Paks. Temple of ROM was one I played at Radio Shack, but never owned. I just recall thinking it was like a fancy version of my all-time favorite Atari VCS game, Adventure.

But I digress…

It started with a message containing a screen shot:

Rick had simplified the program by breaking up the pattern in to sections he could GOSUB to and print a few lines. It was simple, and smaller than printing the whole thing.

“Why line 2046?” I asked. Apparently, the PDP-8 BASIC he was using had that as the highest line number. This was one of many restrictions this version of BASIC had. Through our chat, I learned it had a limit to the length of a string, and could not do things like MID$(A$,1,1)=”A” which were methods I had been toying with.

The next thing he sent me was optimizing this pattern further, breaking it up in to just one part of the diamond shape:

1 DIM A$(4)
10 FOR I = 1 TO 4
20 READ A$(I)
30 NEXT I
40 FOR I = 1 TO 4
50 PRINT A$(I);A$(I);A$(I)
60 NEXT I
70 FOR I = 3 TO 1 STEP -1
80 PRINT A$(I);A$(I);A$(I)
90 NEXT I
300 DATA " * "
301 DATA " * * "
302 DATA " * *"
303 DATA "* "
2046 END

This proof-of-concept code worked by loading this pattern in to an array. It can print the top array string three times across the screen, then continue through the pattern. Then, for the reverse diamond, print the array backwards. This version was clever, though it left off the right-most asterisk of the bottom/middle row, which could be handled by adding an extra IF/PRINT for those lines. Neat!

But, even when packing everything together (the PDP-8 BASIC has its own way of combining lines using a backslash), it did not look like it would be the smallest approach to this challenge.

A bit later, I received this screen shot:

This, my friends, is what a programmer comes up with when they are tired of typing in Microsoft BASIC. Rick has a front-end (pre-processor?) that allows him to write nicely readable (for BASIC) code and then have it converted to Microsoft BASIC for loading on a CoCo.

I typed that in (well, not literally) to a CoCo and saw that it worked. But what is it doing? Let’s look at a packed CoCo version of this code he provided (adjusted to work on the 40 column screen):

1 WIDTH40:GOSUB2:FORK=1TO3:FORI=1TO3:GOSUB2:NEXTI:FORI=2TO0STEP-1:GOSUB2:NEXTI:NEXTK:END
2 A$=STRING$(20,32):FORJ=4TO16STEP6:MID$(A$,J-I)="*":MID$(A$,J+I)="*":NEXTJ:PRINTA$:RETURN

This looks like a similar approach that Jason Pittman had mentioned he was working on — drawing the pattern as if it was two asterisks that just get further apart as they move down the line (then closer together as it continues).

Rick’s version appears to work by starting with an empty string – generated using the STRING$(20,32) for a string of 20 CHR$(32) spaces. As it goes forward through the loop, it changes the character in the string at position J+I to be an asterisk, then the character at position J-I to be an asterisk . At the top/bottom of each diamond, the offset is 0, so it is just putting the asterisk on top of itself.

Let me break this apart, closer to his original pre-preprocessed source. I will put some extra spaces in to space out the loops:

1 GOSUB 100
2 FOR K=1 TO 3
3 FOR I=1 TO 3
4 GOSUB 100
5 NEXT I
6 FOR I=2 TO 0 STEP -1
7 GOSUB 100
8 NEXT I
9 NEXT K
10 END
100 A$=STRING$(20,32)
101 FOR J=4 TO 16 STEP 6
102 MID$(A$,J-I)="*"
103 MID$(A$,J+I)="*"
104 NEXT J
105 PRINT A$
106 RETURN

Line 100 – The subroutine creates an A$ of 20 space characters.

Line 101 – The FOR loop of 4 TO 16 STEP 6 will produce values of 4, 10 and 16. That matches the top spacing of the peak of the diamond shapes in the pattern:

.........11111111112
12345678901234567890
* * *
* * * * * *
* * * * * *
* * * *

Line 102 – at position J-I (I is set by a FOR loop before it GOSUBs to this routine) will be places an asterisk at that location inside the A$. The outer FOR I loop is 1 TO 3, calling this routine each time, so it would look like this:

I=1, J=4, 10, 16 (producing 4-1, 10-1 and 16-1 ... 3, 9 and 15).
"..*.....*.....*....."

I=2, J=4, 10, 16 (producing 4-2, 10-2 and 16-2 ... 2, 8 and 14).
".*.....*.....*......"

I=3, J=4, 10, 16 (producing 4-3, 10-3 and 16-3 ... 1, 7 and 13).
"*.....*.....*......."

Line 103 – this line does the same thing, but uses the position of J+I so the asterisk moves to the right each time:

I=1, J=4, 10, 16 (producing 4+1, 10+1 and 16+1 ... 5, 11 and 17)
"....*.....*.....*..."

I=2, J=4, 10, 16 (producing 4+2, 10+2 and 16+2 ... 6, 12 and 18)
".....*.....*.....*.."

I=3, J=4, 10, 16 (producing 4+3, 10+3 and 16+3 ... 7, 13 and 19)
"......*.....*.....*."

Since these two lines are adding asterisks to the same A$, the results actually look like this:

"..*.*...*.*...*.*..."
".*...*.*...*.*...*.."
"*.....*.....*.....*."

Line 104 – is the NEXT for the FOR/J, so it goes through all three entries (4, 10 and 16).

Line 105 – prints the modified string.

Line 106 – returns back to the main code.

You will notice that calling this routine like this misses the top line. Let’s look at the start of the program to see how it gets there:

Line 1 – This initial GOSUB to 100 is what draws that top line. When the program starts, all variables are zero. So the routine enteres with I set to 0, which would make J+I and J-I just be the J value, putting an asterisk at the 4, 10, and 16 positions:

"...*.....*.....*...."

Well. That’s clever.

Line 2 – This is just a FOR loop to draw the pattern three times.

Line 3 – This is the FOR loop that draws the top part of the diamond, but since I always starts at 0, it doesn’t draw the “top” row — that was done by LINE 1, and then when it draws the reverse/bottom of the diamond it will finish that pattern, which is the last row and start of the next diamond. (Confused yet?)

Line 4 – Draw the line for the top part of the diamond (inside the I loop of 1 to 3).

Line 5 – This is the NEXT for the top-to-bottom I loop.

Line 6 – This is a second I loop, that goes from 2 to 0.

Line 7 – Calling the GOSUB routine, so now it will be drawing like this:

I=2, J=4, 10, 16 (producing 4+2, 10+2 and 16+2 ... 6, 12 and 18)
".....*.....*.....*.."

I=1, J=4, 10, 16 (producing 4+1, 10+1 and 16+1 ... 5, 11 and 17)
"....*.....*.....*..."

I=0, J=4, 10, 16 (producing 4+0, 10+0 and 16+0 ... 4, 10 and 16)
"...*.....*.....*...."

And the second part of the GOSUB doing the right side of the pyramid shape:

I=2, J=4, 10, 16 (producing 4-2, 10-2 and 16-2 ... 2, 8 and 14)
".*.....*.....*......"

I=1, J=4, 10, 16 (producing 4-1, 10-1 and 16-1 ... 3, 9 and 15)
"..*.....*.....*....."

I=0, J=4, 10, 16 (producing 4-0, 10-0 and 16-0 ... 4, 10 and 16)
"...*.....*.....*...."

Together, the strings end up as:

".*...*.*...*.*...*.."
"..*.*...*.*...*.*..."
"...*.....*.....*...."

And that draws the bottom of the diamond!

Line 8 – The NEXT for the I (bottom of diamond) loop.

Line 9 – The NEXT for the “do it three times” loop.

Line 10 – END, so it won’t try to run the subroutine again.

Wow, that’s cool.

What do you think?

More to come…?

Tackling the Logiker 2023 Vintage Computing Christmas Challenge – part 3

See also: part 1, part 2, part 3 and part 4.

I have to admit, this year’s Logiker challenge stumped me. I had a few “clever” ideas on how to reproduce the pattern, but the code was larger than just printing out the pattern from arrays of strings.

Meanwhile, Jason Pittman kept posting revisions of his concept in the comments. In a response to part 2, he shared this:

This is the last attempt I came up with. It uses the same “-3 to 2 … ABS()” from the other example I sent on part 1. I think one potential trick here is to treat it as if you are holding a rubber stamp that stamps out three asterisks that are six spaces apart. You want to make this pattern by stamping it twice on each line. You’re keeping track of a starting position on each row (1027 + 32 for each row) and an offset to add and subtract to the starting position for each row. On the first line, the offset is zero, so you stamp it twice on top of itself at the starting position. On the next line, the offset is 1, so you stamp it twice, but one time you add -1 and the other time you add +1 to the starting position. Does this make any sense?

1 CLS:P=1027:FORC=1TO3:FORX=-3TO2:FORS=0TO12STEP6:POKEP-3+ABS(X)+S,106:POKEP+3-ABS(X)+S,106:NEXT:P=P+32:NEXT:NEXT:GOTO1

– Jason Pittman

This was an optimization of his original approach, but it had one limitation that might prevent it from solving the challenge: The CoCo’s 32×16 screen is too small to display the entire image, and POKEing characters on the screen would be limited to just 16 lines of text. He was aware of this, and his program does POKE the full image, but it is POKEing past the end of the visible screen. Would this count? RUNning the program displays the pattern over and over again (which was done to avoid having a new line with an endless loop GOTO):

POKEing past the visible screen works here because I am emulating a Disk Extended BASIC CoCo, and the memory after the text screen is reserved for four pages of PMODE high resolution graphics. But, I suspect, if I ran this on a cassette based CoCo, it might be POKEing in to memory used for the BASIC program itself.

Perhaps the CoCo 3 could help, since it has 40×25 and 80×25 text modes? Jason tried that:

I may play around with LPOKE. This should get it on the 40 column screen. I bet there is a crafty way to (a) not do the last line manually outside of the loops (b) remove one of the FOR loops (c) Shoot, there’s probably some crafty wizard way to do it in one FOR loop with logical operators, but I wouldn’t ever find it.

1 WIDTH40:FORZ=0TO12STEP6:FORX=-3TO2:FORS=0TO12STEP6:LOCATEABS(X)+S,Z+X+4:PRINT”*”;:LOCATE6-ABS(X)+S,Z+X+4:PRINT”*”;:NEXT:NEXT:NEXT:FORX=3TO15STEP6:LOCATE X,19:PRINT”*”;:NEXT

– Jason Pittman

In this version, Jason uses LOCATE(x,y) to position the cursor. That is what the CoCo 3 used instead of PRINT@ for text positioning. And it works!

It also feels better to use built-in BASIC text commands versus POKEing in to memory.

But he wasn’t done! He added this version:

10 WIDTH 40
20 FOR S = -15 TO 33 STEP 6
30 FOR X = 0 TO 18
40 IF S+X < 19 AND S+X >= 0 THEN LOCATE S+X,X:PRINT "*";
41 IF S-X >= 0 AND S-X < 19 THEN LOCATE S-X,X:PRINT "*";
50 NEXT X
60 NEXT S
70 GOTO 70

This one draws the same pattern, but in a very different way. It draws diagonal lines going down from the points at the top. Try it! It’s cool!

And, then this odd one, which creates the pattern by drawing the asterisks RANDOMLY, eventually getting them all on the screen.

0 WIDTH40
1 X=RND(3)*6:Y=RND(3)*6:RX=RND(7)-4:RY=ABS(RX)-3:D=RND(2)*2-3:LOCATE X+RX,Y+RY*D:PRINT"*";:GOTO 1

Nice job, Jason!

But it is going to make my brain hurt to understand how this works…

Meanwhile, I received a message from Rick Adams on Facebook with an implementation he was working on for a (much more limited) PDP-8 BASIC.

To be continued…

Tackling the Logiker 2023 Vintage Computing Christmas Challenge – part 2

See also: part 1, part 2, part 3 and part 4.

As I write this, I have no idea how to make this work. Producing this pattern:

   *     *     *
* * * * * *
* * * * * *
* * * *
* * * * * *
* * * * * *
* * *
* * * * * *
* * * * * *
* * * *
* * * * * *
* * * * * *
* * *
* * * * * *
* * * * * *
* * * *
* * * * * *
* * * * * *
* * *

…seems like it should be simple. Three asterisks, then six, then six, then four, then six, then six, then three… Spaces that go five, three, one, zero and then back up. Unhelpful.

But, it’s also just one pattern repeated across the screen three times…

   *
* *
* *
* *

And then it’s reversed, so I think if we can do the above, we can do the whole pattern. We see spaces of three, two, one, zero on the left, and zero, one, three, and five in the inside.

Color BASIC does not have a SPC() option (my VIC-20 did, I think) for outputting spaces, but TAB() will go to a specific column. Maybe we can figure out which column the asterisks should be in:

.........1111111111
1234567890123456789
* * *
* * * * * *
* * * * * *
* * * *

This gives us 4, 10 and 16. Then 3 and 5, 9 and 11, and 15 and 17. Then 2 and 6, 8 and 12, and 14 and 18. Finally, 1 and 7 and 13 and 19. I don’t know why, but I kind of like thinking about it as tab positions.

10 FOR SP=3 TO 0 STEP-1
20 PRINT TAB(SP);"*";TAB(6-SP);
30 IF SP<3 THEN PRINT "*";
40 PRINT
50 NEXT

That would give us one of the pyramid shapes. To complete the bottom, we’d do another FOR/NEXT loop. At least, that’s what I would do. BUT, in a comment to part 1, Jason Pittman had a smarter idea:

Awesome! I’ve got an idea on this one but I’m not going to jump ahead this year and I’m just going to follow along.One thought here is that you could combine the two print loops on 100 and 110 by coming up with a series that goes “0 1 2 3 2 1”. I did it by replacing 100 and 110 with this: “100 FOR A=-3 TO 2:PRINT A$(ABS(ABS(A)-3)):NEXT”

Or, you could shorten that a little if you reverse the direction of the array (so that it looks like “VVV”) and use “100 FOR A=-3 TO 2:PRINT A$(ABS(A)):NEXT” – Jason Pittman

I could print one diamond like this:

10 FOR A=-3 TO 3:SP=ABS(A)
20 PRINT TAB(SP);"*";TAB(6-SP);
30 IF SP<3 THEN PRINT "*";
40 PRINT
50 NEXT

That prints almost the entire diamond, except for the final asterisk. Because, if I wanted to print three of them, I’d do this in a loop, then print the final asterisk row at the end.

Unfortunately, as I start going down this rabbit hole, I find the code of loops and such ends up looking larger than some much simpler approaches, like one shown to my by Rick Adams. His code was written for a PDP-8 BASIC, which lacks things like ELSE and MID$. His technique was to have strings representing parts of the pyramid:

"   *  "
" * * "
" * *"
"* "

…then to print each string three times across the screen. This produced:

"   *     *     *  "
" * *  * *  * * "
" * * * * * *"
"* * * "

…and then do it backwards. There is a missing “*” on the right, that gets printed with an “IF”. In a chat, we bounced around some ideas to shrink the code, but looking at his approach, it seems everything I try to do gets larger:

  • Try “run length encode” where DATA statements represent the spaces. Print that many spaces, then an asterisk, and repeat.
  • Try DATA statements showing the positions of the asterisks. DATA is large than a string.
  • Try simulating a “SET(x,y)” to draw it, but using PRINT@ on the CoCo. Alas, the CoCo 32×16 screen is too small to fit the whole pattern, so even if this was smaller, it would still require extra code at the end to scroll the screen and print the final few lines (as the top portion scrolls off). BUT, using a CoCo 3 40/80 column screen would work using LOCATE x,y instead. But still, larger.

Is there an elegant solution to this challenge that doesn’t involve just PRINTing strings?

We shall continue… Next time…

Tackling the Logiker 2023 Vintage Computing Christmas Challenge – part 1

See also: part 1, part 2, part 3 and part 4.

Special thanks to Jason Pittman for mentioning this year’s challenge in a comment…

Logiker is at it again, with a 2023 retro-programming Christmas challenge:

???? Vintage Computing Christmas Challenge (VC³) 2023 ???? – Logiker

This year, the pattern looks like this:

   *     *     *
* * * * * *
* * * * * *
* * * *
* * * * * *
* * * * * *
* * *
* * * * * *
* * * * * *
* * * *
* * * * * *
* * * * * *
* * *
* * * * * *
* * * * * *
* * * *
* * * * * *
* * * * * *
* * *

This image is 19×19, so while it will fit on a Radio Shack Color Computer 1/2 screen width-wise, it’s a bit too tall to fit height-wise. The challenge allows for it to scroll off the screen, which is something we had to do for past challenges.

Logiker 2023 Challenge pattern.

I can think of a number of ways to approach this.

The pattern is made up of only four unique lines, so you could print them A B C D B C A B C D and so on. There’s probably a simple way to do that with a FOR/NEXT loop and an array of those four lines.

10 CLS
50 A$(0)=" * * *
60 A$(1)=" * * * * * *
70 A$(2)=" * * * * * *
80 A$(3)="* * * *
90 FOR I=1 TO 3
100 FOR A=0 TO 3:PRINT A$(A):NEXT
110 FOR A=2 TO 1 STEP-1:PRINT A$(A):NEXT
120 NEXT
130 PRINT A$(0)
333 GOTO 333

If we had a larger screen (like the 40 or 80 column screens on the Color Computer 3), we could use LOCATE x,y to plot the pattern using some line drawing type math.

We could try the RLE (run length encoding) compression from past years to see if we could compress it down to spaces and characters.

We could try using math to figure out a pattern.

These all seem fun.

I hope to find some time to experiment. I don’t plan to “enter,” since one of the asks for the challenge is to not share your work until after the challenge ends.

More to come…

Color BASIC overflow bug – same as Commodore’s?

I just saw a tweet from Robin @ 8-Bit Show And Tell concerning a bug in Commodore BASIC that existed in the PET, C64 and VIC-20.

VAL() takes a string and converts it in to a floating point numerical variable. The value of “1E39” is a number in scientific notation, and this appears to cause a problem.

In Microsoft BASIC, the notation “1E39” represents the number 1 multiplied by 10 raised to the power of 39. This is also known as scientific notation, where the “E” indicates the exponent to which the base (10 in this case) is raised. So, “1E39” is equal to 1 * 10^39, which is an extremely large number:

1E39 = 1 * 10^39 = 1000000000000000000000000000000000000000

This number has 39 zeros after the 1, making it a very large value.

– ChatGPT

I was curious to see what the CoCo’s Color BASIC did, so I tried it…

It appears that port of BASIC to the 6809 also ported over this bug. Anyone want to take a look at the source and see what the issue is?

…to be continued, maybe…

TIL: You can build C in Microsoft Visual Studio

I feel dumb for not knowing this, but I was under the impression that today’s Visual Studio only built things like C#, C++, etc. — since those are the things listed when you make a new project.

I happened to ask ChatGPT about building C in Studio, and it told me I could just make C++ project and save the file out as a .c and get a C project. I had no idea.

#TheMoreYouKnow

Though, it’s many more steps to do this than, say, popping out to https://www.onlinegdb.com/online_c_compiler, just to do a quick test, but it’s useful.

I have only tried this under Windows, but I plan to see if the Mac version of Visual Studio supports the same. I’ve tried to get C code building in VS Code, but it’s klunky (makefiles!).

Tackling the Logiker 2022 Vintage Computing Christmas Challenge – part 7

See also: part 1, part 2, part 3, part 4, part 5, part 6 and part 7.

Updates:

  • 2022-12-30 – Update to Jason’s final version to make it two bytes smaller.

In this final (?) installment, I wanted to share some other approaches that were taken to by members of the CoCo community draw this:

…including one that immediately was smaller than the version I did.

Rick Adams – PDP8/I

Early on, a version was shared by legendary CoCo programmer Rick Adams. His version was not for the CoCo – he chose to do it “in a very primitive BASIC, BASIC8 on a simulated PDP8/I running the TSS8 OS”…

0 'RICK ADAMS
12 FOR B = 1 TO 4
14 GOSUB 2000
20 NEXT B
22 C = 0
24 D = 0
30 FOR I = 1 TO 9
32 READ A, B
34 GOSUB 1000
36 NEXT I
50 FOR B = 4 TO 1 STEP -1
52 GOSUB 2000
58 NEXT B
200 DATA 0, 17, 1, 15, 2, 13, 3, 11, 4, 9, 3, 11, 2, 13, 1, 15, 0, 17
300 STOP
1000 PRINT TAB(A);
1010 FOR J = 1 TO B
1020 PRINT "*";
1030 NEXT J
1040 PRINT TAB(A + B + C);
1050 FOR J = 1 TO D
1060 PRINT "*";
1070 NEXT J
1080 PRINT
1090 RETURN
2000 A = 4
2002 D = B
2010 C = 9 - 2 * B
2020 GOSUB 1000
2030 RETURN
2046 END

I am unfamiliar with the BASIC on this machine, but at least it doesn’t require using “LET“. This version can run on the CoCo as well, and correctly reproduces the pattern.

Jim Gerrie – MC-10/CoCo

Next, take a look a this one by MC-10 BASIC-meister, Jim Gerrie:

Jim Gerrie’s fancier solution

His approach uses DATA statements and then draws the star in an interesting way.

Jason Pittman

In the comments on an earlier installment, Jason shared his attempt. His approach was realizing that the shape was just “four overlapping right triangles.”

1 FORX=64TO416STEP32:L=X/32:T$=STRING$(L,42):PRINT@X-28,T$;:PRINT@(X-19-L),T$;:PRINT@544-X+4,T$;:PRINT@557-X-L,T$;:NEXT:GOTO1

This version is just 100 bytes! Due to the CoCo’s 32 column screen being too short, it doesn’t draw the top and end lines of the pattern, so it wouldn’t meet the challenge requirements. To fix that, he needed to add an IF:

1 FORX=32TO416STEP32:L=X/32:T$=STRING$(L,42):PRINT@X-28,T$;:PRINT@(X-19-L),T$;:IF X>32THEN PRINT@544-X+4,T$;:PRINT@557-X-L,T$;
2 NEXT
3 GOTO3

Since the CoC 3 also has a 40×24 and 80×24 screen, the entire pattern could fit on those screens. Version three looked like this:

1 WIDTH40:FORX=1TO13:L$=STRING$(X,42):LOCATE14-X,X:PRINTL$;:LOCATE14-X,18-X:PRINTL$;:LOCATE5+L,X:PRINTL$;:LOCATE5,18-X:PRINTL$;:NEXT:GOTO1

That one is a mere 88 bytes! And, the GOTO1 at the end is just to make it keep redrawing, else it stops near the top and would print the “OK” in the middle of the pattern.

I’d say the “WIDTH40:” is not required, since you could just say “run this from the 40 column screen.” And, to keep the loop, starting on LINE 0 allows just saying “GOTO” with no line number:

0 FORX=1TO13:L$=STRING$(X,42):LOCATE14-X,X:PRINTL$;:LOCATE14-X,18-X:PRINTL$;:LOCATE5+L,X:PRINTL$;:LOCATE5,18-X:PRINTL$;:NEXT:GOTO

By my count, that turns in to 83 bytes! Amazing.

UPDATE: L. Curtis Boyle pointed out there was an unnecessary “+L” left in the code, which can be removed to make this 81 bytes. More amazing!

0 FORX=1TO13:L$=STRING$(X,42):LOCATE14-X,X:PRINTL$;:LOCATE14-X,18-X:PRINTL$;:LOCATE5,X:PRINTL$;:LOCATE5,18-X:PRINTL$;:NEXT:GOTO

Here is what it looks like, though I paused it to capture the full image:

Please read his comments to part 1 for more background and earlier versions he shared.

I’m really blown away by this.

Are we done? Is this as small as it gets?

Unless there are more ideas, I think that is the end.

Merry Christmas, everyone!

Tackling the Logiker 2022 Vintage Computing Christmas Challenge – part 6

See also: part 1, part 2, part 3, part 4, part 5, part 6 and part 7.

Just when I thought I was out… they pull me back in.

Michael Corleon, Godfather III

Sometimes clever isn’t as good as brute force. In this installment, I’ll present a hybrid approach to the challenge of displaying the Logiker 2022 holiday image.

Instead of writing code to handle each section of the pattern, perhaps taking the simpler approach of just doing Run Length Encoding (sorta) might be smaller. I do not know where I first learned about RLE, but I implemented a simple version in Sub-Etha Software’s graphical “CoCoFEST Simulation” text adventure back in the early 1990s. The images in the graphical adventure were large, and I came up with a simple way to represent repeated data in them. (I do not know if someone gave me this idea, or if I worked it out on my own; in those pre-Internet days, knowledge was alot more organic.)

Basically, RLE looks for repeating sequences and replaces them with a count value and the sequence. The Wikipedia article gives this example:

Consider a screen containing plain black text on a solid white background. There will be many long runs of white pixels in the blank space, and many short runs of black pixels within the text. A hypothetical scan line, with B representing a black pixel and W representing white, might read as follows:

WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWBWWWWWWWWWWWWWW

With a run-length encoding (RLE) data compression algorithm applied to the above hypothetical scan line, it can be rendered as follows:

12W1B12W3B24W1B14W

– https://en.wikipedia.org/wiki/Run-length_encoding

Above, if there are twelve “W” characters in a row, you would encode the value 12 followed by the “W”.

I wrote a simple program that will input a string and then scan it looking for repeating characters. When it finds a new character, it prints the count of the previous characters as well as what character that was.

0 ' RLE.BAS
10 LINE INPUT "STRING:";A$
20 LC=ASC(LEFT$(A$,1)):L=1
30 FOR I=2 TO LEN(A$)
40 C=ASC(MID$(A$,I,1))
50 IF C<>LC THEN PRINT L;LC:L=0:LC=C
60 L=L+1:NEXT
70 PRINT L;LC

I pasted this code in to chat.openai.com and it correctly recognized it as an RLE routine! Here is how it described my program:

This looks like a BASIC program that encodes a string using run-length encoding (RLE). RLE is a simple form of data compression, where runs of repeated characters are replaced by a count and the character. For example, the string “AAABBCCC” would be encoded as “3A2B3C”.

The program starts by prompting the user to input a string, and then assigns the first character of the string to the variable LC (which stands for “last character”). The variable L is set to 1, and then a FOR loop is used to iterate through the remaining characters in the string.

For each character in the string, the program checks if it is different from the last character. If it is, it prints the length of the previous run and the character itself, and then resets the length and last character variables. If the character is the same as the last character, the length is incremented. This continues until the end of the string is reached, at which point the final run is printed.

For example, if the input string is “AAABBCCC”, the output would be:

3 65
2 66
3 67

This indicates that there were three instances of the character “A” (ASCII value 65), two instances of the character “B” (ASCII value 66), and three instances of the character “C” (ASCII value 67).

– chat.openai.com

Wow. Just wow.

Here is an example of the program running:

RLE (Run Length Encoding) in BASIC

Since the Logiker Challenge image only contains spaces and asterisks, we don’t need a full RLE to encode it. I chose to use alternating numbers to indicate how many spaces, then how many asterisks.

Ultimately, I tried to save code space by breaking the image in to sections, and using two main routines to print that section within FOR/NEXT loops.

But … is that overkill? Instead of using multiple PRINT routines, what if I only needed one? By expanding the image data so each line covers the entire width of the CoCo’s 32-column screen, I could do away with the “end of line” markers in the data, and replace them with a larger series of spaces that goes from the end of the data on that line to the start of the data on the next line:

[12345678901234567890123456789012]
"           *       *            "
"           **     **            "
"           ***   ***            "
"           **** ****            "
"       *****************        "
"        ***************         "
"         *************          "
"          ***********           "
"           *********            "
"          ***********           "
"         *************          "
"        ***************         "
"       *****************        "
"           **** ****            "
"           ***   ***            "
"           **     **            "
"           *       *            "
[12345678901234567890123456789012]

Above, at the end of the first line’s asterisks, there are 12 spaces to the end of that line. For the next line, there are 11 spaces to get to the start of the next asterisks. That means after printing the last asterisks in line 1 we can just print 23 spaces and be at the start of the next line.

Assuming we start with a SPACE then an ASTERISK then a SPACE and do on, the data for the first two lines would look like this:

11 - print11 spaces
1 - print 1 asterisk
7 - print 7 spaces
1 - print 1 asterisk
23 - print 23 spaces (to move to the start of data in the second line)
2 - print 2 asterisks
5 - print 5 spaces
2 - print 2 asterisks
...and so on...

I was going to convert all the PRINT lines of the original version I started with to DATA statements and write a program to count this for me, but that sounded complicated. I just counted, and came up with the following numbers:

11 1 7 1 23 2 5 2 3 3 23 4 4 4 23 5 16 15 16 17 18 16 5 2 3 3 23 4 4 4 23 5 18 2 5 2 1 1 7 1

I could store those in a DATA statement:

DATA 11,1,7,1,23,2,5,2,3,3,23,4,4,4,23,5,16,15,16,17,18,16,5,2,3,3,23,4,4,4,23,5,18,2,5,2,1,1,7,1

But, that takes up alot of room. There is a comma between each number, so for 50 numbers we’d be adding 49 commas, basically doubling the size of the data. Also, two digit numbers like 10 take up two bytes. I thought about using HEX numbers (0-15 turns in to 0-F) but the data has some values that are larger than 15 (the highest value that fits in a single character of a HEX value).

HEX is BASE-16 (0-F to represent 0-15) and what I really need is at least BASE-23 (0-23, the larger number I need). Since there are 26 letters in the alphabet, I could use all of them and get BASE-26 leaving me room to spare!

If A=1, B=2 and so on, the above series of numbers could be turned in to:

K A G A W B E B W C C C W D A D S Q P O R M T K V I V K T M R O P Q S D A D W C C C W B E B W A G A

I could then turn those in to DATA:

DATA K,A,G,A,W,B,E,B,W,C,C,C,W,D,A,D,S,Q,P,O,R,M,T,K,V,I,V,K,T,M,R,O,P,Q,S,D,A,D,W,C,C,C,W,B,E,B,W,A,G,A

…and read them as a string (READ A$) and then convert that string to a number by subtracting 63 (ASCII for A is 64, so if I read an A and get 64, subtracting 63 turns that in to 1):

READ A$
V=ASC(A$)-64

While this saves a byte for every number that was two digits, the extra code to convert from ASCII to a number may be larger than what we saved.

Since we have 49 commas, we could get rid of those and add code to parse a long string. As long as that code is smaller than 49 bytes, we come out ahead.

DATA KAGAWBEBWCCCWDADSQPORMTKVIVKTMROPQSDADWCCCWBEBWAGA

Now I could read that as a string and parse it in to numbers:

0 'STRTONUM.BAS
10 READ A$
20 FOR I=1 TO LEN(A$)
30 PRINT ASC(MID$(A$,I,1))-64;
40 NEXT
50 DATA KAGAWBEBWCCCWDADSQPORMTKVIVKTMROPQSDADWCCCWBEBWAGA

And, if I want to use that series of numbers in a loop that prints alternating strings of spaces and asterisks, I don’t even need to bother with it being in a DATA statement. I could just embed it directly in the MID$() command and hard code the lengthof the string, like this:

0 'STRTONUM2.BAS
20 FOR I=1 TO 50
30 PRINT ASC(MID$("KAGAWBEBWCCCWDADSQPORMTKVIVKTMROPQSDADWCCCWBEBWAGA",I,1))-64;
40 NEXT

And if I can do that, the only thing left is to figure out when to print a space and when to print an asterisks.

An easy way to do that is looking at the I variable in the FOR/NEXT loop. As it counts from 1 to 2 to 3 to 4, I can use AND to check bit 1. For odd numbers, that bit is set. For even numbers, it is not.

0 = 0000000
1 = 0000001
2 = 0000010
3 = 0000011
4 = 0000100
5 = 0000101
...and so on...

This means a simple check for “I AND 1” in an IF statement can help me decide which to print. Something like:

IF (I AND 1) THEN PRINT space ELSE PRINT asterisk

That gets me to something like this:

0 ' LOGIKER-ALPHA2.BAS
10 FORI=1TO50
20 L=ASC(MID$("KAGAWBEBWCCCWDADSQPORMTKVIVKTMROPQSDADWCCCWBEBWAGA",I))-64
30 IF I AND 1 THEN PRINT STRING$(L,32); ELSE PRINT STRING$(L,42);
40 NEXT

Perhaps I can get rid of one of those PRINT STRING$ commands… Since I know a space is ASCII 32 and an asterisk is ASCII 42, I could start with the 32 and add 10 if it’s the asterisk case. To do that, I need to see the result that comes back from AND:

PRINT 1 AND 1
1

PRINT 2 AND 1
0

So if the condition is TRUE (bit 1 is set, meaning the value is odd), I get a 1. If the condition is FALSE (bit 1 is clear, meaning the value is even), I get a 0.

Since I want to print spaces on the odd values, I need to use the 1 (odd) to mean 32, and the 0 (even) to mean 42. I’ll reverse my logic a bit and always start with 42 (asterisks) and multiply it by 10 times the result of (I AND 1). Something like this should work:

0 ' LOGIKER-ALPHA3.BAS
10 FOR I=1 TO 50
20 L=ASC(MID$("KAGAWBEBWCCCWDADSQPORMTKVIVKTMROPQSDADWCCCWBEBWAGA",I))-64
30 PRINT STRING$(L,42-(I AND 1)*10);
40 NEXT

And that gives me the pattern I want, with far less code. I can remove unneeded spaces and combine everything in to one line and see how big it is.

Unneeded Spaces

A quick thing about unneeded spaces. There are spaces that BASIC itself doesn’t need, but the tokenizer that turns what you type in to the program DO need. For example:

FOR I=100 TO 5000

None of those spaces are needed, because BASIC knows where a keyword ends (FOR) and can tell the variable will be whatever is there before the “=”. The same is true for the numbers, since it can tell where a number ends and know to look for “TO”.

FORI=100TO5000

BUT, if you were using variables in that loop…

FOR I=B TO E

…and you took the spaces out:

FORI=BTOE

…how does BASIC know what your variable is? Is it “B”? Or “BT”? Or maybe “BTOE”? You will get an “?SN ERROR” if you try that because BASIC sees a non-number after the “=” and switches to parsing it as if it were a variable. To get around this, we have to put a space after it like this:

FORI=B TOE

That allows the tokenizer to work fine.

However If you were manually creating the BASIC program by packing bytes together in a file, you could omit that space and it will run just fine. Utilities such as Carl England’s CRUNCH do this trick to save a byte. BUT, if you were to CRUNCH the program then try to EDIT that line, you’d no longer have code that would run because updating the line requires it to be re-tokenized. #TheMoreYouKnow

Why is that important?

I mention this because in my above program, I wanted to remove spaces from this line:

30 PRINT STRING$(L,42-(I AND 1)*10);

I can remove all but one, since I need a space between “I” and “AND” for the same reason I just mentioned:

30 PRINTSTRING$(L,42-(I AND1)*10);

But… instead of “I AND 1” I could change it to “1 AND I” and get the same result, but no longer need the space because BASIC can tell where a number stops:

30 PRINTSTRING$(L,42-(1ANDI)*10);

And that, my friends, is how you save one more byte.

Would it be possible to also get rid of those parenthesis? Right now, I need to take my asterisk value (42) and subtract either 0 or 10. I need the results of “1 AND I” multiplied by 10, and if I removed the parens…

42-1 AND I*10

…BASIC would do the math first (42-1 and I*10) and if “I” was 3 at the time, I would get this:

42-1 AND 3*10
41 AND 30

…and that’s not at all what we want.

Can it be done? I moved things around but it really looks like the result of “1 AND I” has to be in parens. Can you figure a way to save those two bytes?

With that said, I present this version:

10 FOR I=1 TO 50
20 L=ASC(MID$("KAGAWBEBWCCCWDADSQPORMTKVIVKTMROPQSDADWCCCWBEBWAGA",I))-64
30 PRINT STRING$(L,42-(1ANDI)*10);
40 NEXT

…which can be packed in to this version:

10 FORI=1TO50:L=ASC(MID$("KAGAWBEBWCCCWDADSQPORMTKVIVKTMROPQSDADWCCCWBEBWAGA",I))-64:PRINTSTRING$(L,42-(1ANDI)*10);:NEXT

And that shows up as 114 bytes!

Oh, one thing I should also mention — during last year’s challenge, a comment was made about how ASC() works. If you give it a string, it returns the ASCII value of the first character. So ASC(“A”) returns 64, just like ASC(“ALLEN”) does. They said instead of using MID$(A$,I,1) to get one character, you can leave off that third parameterand MID$ returns the rest of the string:

A$="HELLO"
PRINT MID$(A$,2,1)
C

PRINT MID$(A$,2)
ELLO

If we were trying to print or use just one letter, we need that third parameter. But since I am passing it in to ASC, I could still give it the longer string and it would work fine:

PRINT ASC("E")
69

PRINT ASC("ELLO")
69

Thus, I can leave off that third parameter and save the two bytes that “,1” took up.

Neat!

Are we done? Can we save any more?

Until next time…

Tackling the Logiker 2022 Vintage Computing Christmas Challenge – part 5

See also: part 1, part 2, part 3, part 4, part 5, part 6 and part 7.

The challenge continues. From humble beginnings of using PRINT, to fancier methods of encoding the image as a series of spaces and asterisks, we eventually ended up with an even fancier method that used only 1/4 of the image data to represent the entire symmetrical image.

That approach could work for any image that is symmetrical vertically and horizontally, and typically general purpose routines are not as small as custom routines that know what they will be doing.

Knowing what we now know…

WIth that said, looking at this image, there is another shortcut that I missed:

The entire image is centered over one column… This means the amount of spaces on the left is unimportant — we just need to center the following lines:

*       *
**     **
***   ***
**** ****
*****************
***************
*************
***********
*********
***********
*************
***************
*****************
**** ****
***   ***
**     **
*       *

And, since we know it’s symmetrical both vertically and horizontally, we really only need to worry about this:

*    | <- 1 asterisk, 4 spaces
**   | <- 2 asterisks, 3 spaces
***  | <- 3 asterisks, 2 space
**** | <- 4 asterisks, 1 space
*****************| 17
***************| 15
*************| 13
***********| 11
*********| 9

I’m not sure what the pattern is as I type this, but I am expecting there is one. Here is a quick program that prints the rows of the shape using FOR/NEXT loops (uncentered):

0 ' LOGIKER13.BAS
10 FOR I=1 TO 4
20 PRINT STRING$(I,"*");STRING$(1+(4-I)*2," ");STRING$(I,"*")
30 NEXT
40 FOR I=17 TO 9 STEP-2
50 PRINT STRING$(I,"*")
60 NEXT
70 FOR I=11 TO 17 STEP 2
80 PRINT STRING$(I,"*")
90 NEXT
100 FOR I=4 TO 1 STEP-1
110 PRINT STRING$(I,"*");STRING$(1+(4-I)*2," ");STRING$(I,"*")
120 NEXT

If each of those lines were centered, we’d have our shape. Let’s try that by creating a string for the row, and then using the LEN() of that string to know how to center it using TAB().

0 ' LOGIKER14.BAS
10 FOR I=1 TO 4
20 A$=STRING$(I,"*")+STRING$(1+(4-I)*2," ")+STRING$(I,"*")
25 PRINT TAB(16-LEN(A$)/2);A$
30 NEXT
40 FOR I=17 TO 9 STEP-2
50 A$=STRING$(I,"*")
55 PRINT TAB(16-LEN(A$)/2);A$
60 NEXT
70 FOR I=11 TO 17 STEP 2
80 A$=STRING$(I,"*")
85 PRINT TAB(16-LEN(A$)/2);A$
90 NEXT
100 FOR I=4 TO 1 STEP-1
110 A$=STRING$(I,"*")+STRING$(1+(4-I)*2," ")+STRING$(I,"*")
115 PRINT TAB(16-LEN(A$)/2);A$
120 NEXT
130 GOTO 130

That produces our desired shape (though it does leave a blank line at the end, which our original version avoided by having a semi-colon on the PRINT and just breaking lines when we went to the next one).

The first thing I see it that the centering code on line 25, 55, 85 and 115 is the same. Subroutine!

0 ' LOGIKER15.BAS
10 FOR I=1 TO 4
20 A$=STRING$(I,"*")+STRING$(1+(4-I)*2," ")+STRING$(I,"*")
25 GOSUB 150
30 NEXT
40 FOR I=17 TO 9 STEP-2
50 A$=STRING$(I,"*")
55 GOSUB 150
60 NEXT
70 FOR I=11 TO 17 STEP 2
80 A$=STRING$(I,"*")
85 GOSUB 150
90 NEXT
100 FOR I=4 TO 1 STEP-1
110 A$=STRING$(I,"*")+STRING$(1+(4-I)*2," ")+STRING$(I,"*")
115 GOSUB 150
120 NEXT
130 GOTO 130
150 PRINT TAB(16-LEN(A$)/2);A$:RETURN

Next, we see that the string building code for the top and bottom are the same, so 20 and 110 are the same (it’s the value of I that changes how it prints), and then 50 and 80 are the same. Subroutines!

0 ' LOGIKER16.BAS
10 FOR I=1 TO 4
20 GOSUB 200
25 GOSUB 150
30 NEXT
40 FOR I=17 TO 9 STEP-2
50 GOSUB 250
55 GOSUB 150
60 NEXT
70 FOR I=11 TO 17 STEP 2
80 GOSUB 250
85 GOSUB 150
90 NEXT
100 FOR I=4 TO 1 STEP-1
110 GOSUB 200
115 GOSUB 150
120 NEXT
130 GOTO 130
150 PRINT TAB(16-LEN(A$)/2);A$:RETURN
200 A$=STRING$(I,"*")+STRING$(1+(4-I)*2," ")+STRING$(I,"*"):RETURN
250 A$=STRING$(I,"*"):RETURN

Next, I notice the subroutines of 200 and 250 both have the centering PRINT called after them, so maybe we change it up a bit…

0 ' LOGIKER17.BAS
10 FOR I=1 TO 4
20 GOSUB 200
30 NEXT
40 FOR I=17 TO 9 STEP-2
50 GOSUB 250
60 NEXT
70 FOR I=11 TO 17 STEP 2
80 GOSUB 250
90 NEXT
100 FOR I=4 TO 1 STEP-1
110 GOSUB 200
120 NEXT
130 GOTO 130
200 A$=STRING$(I,"*")+STRING$(1+(4-I)*2," ")+STRING$(I,"*"):GOTO 300
250 A$=STRING$(I,"*")
300 PRINT TAB(16-LEN(A$)/2);A$:RETURN

What else? The FOR/NEXT loops are basically all the same, except for the start and end value and the step value… Maybe we could come up with a way to have only one, and feed it those values using DATA statements?

10 FOR I=1 TO 4
...
40 FOR I=17 TO 9 STEP-2
...
70 FOR I=11 TO 17 STEP 2
...
100 FOR I=4 TO 1 STEP-1

500 DATA 1,4,1
510 DATA 17,9,-2
520 DATA 11,17,2
530 DATA 4,1,-1

If they all went to the same GOSUB routine this would be easy, but they don’t. The go 200, 250, 250, 200. We could add a fourth element in the DATA that tells it which routine to go to and “IF X=1 THEN GOSUB Y ELSE GOSUB Z” or something. That adds more code. Perhaps we don’t need the DATA since we know it alternates? Still, we’d have to track it ourselves with an IF or something. For now, let’s just try this:

0 ' LOGIKER18.BAS
10 FOR J=1 TO 4
20 READ A,B,C,D
30 FOR I=A TO B STEP C
40 IF D=0 THEN GOSUB 200 ELSE GOSUB 250
50 NEXT I
60 NEXT J
70 GOTO 70
200 A$=STRING$(I,"*")+STRING$(1+(4-I)*2," ")+STRING$(I,"*"):GOTO 300
250 A$=STRING$(I,"*")
300 PRINT TAB(16-LEN(A$)/2);A$:RETURN
500 DATA 1,4,1,0
510 DATA 17,9,-2,1
520 DATA 11,17,2,1
530 DATA 4,1,-1,0

And that still produces your original shape. But is it any smaller?

In part 4, we had a version that (using my default XRoar emulator running DISK EXTENDED COLOR BASIC) showed 22499 bytes free after loading. This new version shows 22567 bytes free. So yes, it is smaller! And, we can pack those lines and make it even smaller than that. (And NEXT doesn’t near the variable — in fact, using “NEXT I” is slower than just saying “NEXT” so I’ll remove those here as well.)

0 ' LOGIKER19.BAS
10 FOR J=1 TO 4:READ A,B,C,D:FOR I=A TO B STEP C:IF D=0 THEN GOSUB 200 ELSE GOSUB 250
50 NEXT:NEXT
70 GOTO 70
200 A$=STRING$(I,"*")+STRING$(1+(4-I)*2," ")+STRING$(I,"*"):GOTO 300
250 A$=STRING$(I,"*")
300 PRINT TAB(16-LEN(A$)/2);A$:RETURN:DATA 1,4,1,0,17,9,-2,1,11,17,2,1,4,1,-1,0

That version shows me 22609 free, which is even smaller — and we could still make this a bit smaller by getting rid of unnecessary spaces in the code.

Side note: I am being lazy and just showing the BASIC “PRINT MEM” values rather than calculating the actual size of the program. On my configuration, 22823 is how much memory is there on startup. So, 22823-22609 shows that this program is 214 bytes. It uses more memory for the strings when running, but I don’t think that matters for this challenge.

What else can we do to save a few bytes? Well, STRING$() takes two parameters. The first is the count of how many times to repeat the second parameter. The second parameter can be a quoted character like “*”, or a number like 42 (the ASCII value of the asterisk). 42 is one by smaller than “*” so we can do that as well as use 32 (the ASCII value for space) instead of ” “:

200 A$=STRING$(I,42)+STRING$(1+(4-I)*2,32)+STRING$(I,42):GOTO 300
250 A$=STRING$(I,42)

Another thing we know is that in the shape there are always the same number of spaces before the top and bottom sections, so we really don’t need to center it. We could just hard code a PRINT TAB for that instead of building a string and calling a center subroutine:

200 PRINT TAB(11);STRING$(I,42);STRING$(1+(4-I)*2,32);STRING$(I,42)
210 RETURN

The middle section is similar. Since we know the length, we could calculate how many spaces to tab using that number:

250 PRINT TAB(16-I/2);STRING$(I,42)
260 RETURN

And that removes a subroutine, leaving us with this (not line packed yet):

0 ' LOGIKER20.BAS
10 FOR J=1 TO 4
20 READ A,B,C,D
30 FOR I=A TO B STEP C
40 IF D=0 THEN GOSUB 200 ELSE GOSUB 250
50 NEXT
60 NEXT
70 GOTO 70
200 PRINT TAB(11);STRING$(I,42);STRING$(1+(4-I)*2,32);STRING$(I,42)
210 RETURN
250 PRINT TAB(16-I/2);STRING$(I,42)
260 RETURN
500 DATA 1,4,1,0
510 DATA 17,9,-2,1
520 DATA 11,17,2,1
530 DATA 4,1,-1,0

Two FOR/NEXT loops, a READ, an IF, and two PRINT subroutines.

Maybe we don’t need those subroutines, now that we have an “IF” in line 40 that decides what to do?

0 ' LOGIKER21.BAS
10 FOR J=1 TO 4
20 READ A,B,C,D
30 FOR I=A TO B STEP C
40 IF D=0 THEN PRINT TAB(11);STRING$(I,42);STRING$(1+(4-I)*2,32);STRING$(I,42) ELSE PRINT TAB(16-I/2);STRING$(I,42)
50 NEXT
60 NEXT
70 GOTO 70
500 DATA 1,4,1,0
510 DATA 17,9,-2,1
520 DATA 11,17,2,1
530 DATA 4,1,-1,0

That’s an ugly line 40, but it got rid of two GOSUBs and two RETURNS. Plus every line takes up 5 bytes, so by removing two of those (each subroutine must start on its own line) we saved even more.

Not only is this smaller, it’s faster, too, since it doesn’t have to spend time seeking through the program looking for a subroutine line number for each line it prints.

If we pack this version, removing unnecessary spaces and semicolons and such, and remove the “GOTO” loop (the challenge does not require the screen to be cleared, and says it can return to a prompt after running), we get:

0 ' LOGIKER22.BAS (don't include this line)

1 FORJ=1TO4:READA,B,C,D:FORI=A TOB STEPC:IFD=0THENPRINTTAB(11)STRING$(I,42)STRING$(1+(4-I)*2,32)STRING$(I,42)ELSEPRINTTAB(16-I/2)STRING$(I,42)
2 NEXT:NEXT:DATA1,4,1,0,17,9,-2,1,11,17,2,1,4,1,-1,0

151 bytes of BASIC code!

Is there more that can be done? Comment with your ideas! I think I’m out of them, here.

Until next time…