Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
Tiggum
Oct 24, 2007

Your life and your quest end here.


Carbon dioxide posted:

Is it legal to do a complete for loop in a single line? so FOR blabla : some operation : NEXT

One of the first example programs in my Apple II "service manual" is:
code:
FOR I=1 TO 800:?"A";:NEXT:?"PHEW!"
The lack of a line number means it executes as soon as it's typed. The question mark is equivalent to PRINT. The Apple II displayed text in 40 columns and 23 rows, so when you ran this program it would print 20 rows of A's followed by the word "PHEW!" on its own line and the standard prompt and cursor on the next, with the "program" still displayed at the top of the screen.

pre:
]FOR I=1 TO 800:?"A";:NEXT:?"PHEW!"
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
PHEW!
]▓

Adbot
ADBOT LOVES YOU

ManxomeBromide
Jan 29, 2009

old school

Carbon dioxide posted:

You know, I was thinking and the best BASIC games I've ever seen are probably GORILLA.BAS and NIBBLES.BAS that came with MS-DOS QBasic.

Those are way more complex than anything in this book and you'd probably wouldn't want to copy them from paper.

Anyway, I guess the stuff they do where they have a sort of full screen graphical game where the whole screen updates instead of the terminal just scrolling as it prints things is something for which you need a much more modern computer than anything this book was aiming for.

This motivated me to dig through my old QBasic stuff to see how big they really were. It turns out they're a lot larger than I thought they'd be: NIBBLES.BAS is 720 lines of code, and GORILLAS is around 1150. Most of the lines are blank or comments, though, or very short; unlike the programs we've looked at here, there is little or no combination of multiple statements onto a single line.

Still, it's pretty verbose. Back in the Compute's Gazette LP one of the games studied was "The Viper", which is a Snake clone like NIBBLES.BAS, but which clocks in at 140 lines and was written to run on a much less powerful machine.

You're right that GORILLAS.BAS and NIBBLES.BAS are basically unportable off the PC. GORILLAS used full-screen bitmap graphics and NIBBLES relied heavily on PC-specific character graphics. That said, despite the fact that these two games were written in 1990, a 1981 IBM PC with 512KB of RAM and CGA would play both games. NIBBLES would run flawlessly in that environment (It's built out of character graphics in the 16-color 80x25 text mode that's been there for the entire lifespan of the PC), while GORILLAS really wanted EGA so it could run in a higher resolution with more colors... but it could drop back to a 320x200 CGA mode if necessary. At least some of GORILLAS.BAS is spent on error handling when it discovers that it's running on hardware that can't manage its preferred performance.

So, let's see here:
Program written: 1990
GORILLAS.BAS: runs on a top-of-the-line but not maxed-out PC from 1984
NIBBLES.BAS: runs on a top-of-the-line but not maxed-out PC from 1981

I was going to make a comment about how times sure have changed, but it's 2020, and if you were to target a top-of-the-line game console from 2014, you'd still be targeting the PS4.

So I guess, at least here, times haven't changed in the intervening 30 years.

FredMSloniker
Jan 2, 2008

Why, yes, I do like Kirby games.
There's going to be a brief hiatus in the LP, as I'm planning on going cross-platform with it and want to give the other forums time to catch up. I have the LP Beach in mind, obviously, but if you know of other retro forums where this thread would be welcome, let me know!

FredMSloniker
Jan 2, 2008

Why, yes, I do like Kirby games.
I'm caught up on the LP Beach! Regular service will resume on the morrow. (Remember how I said the next program looked interesting? That's... one word for it.)

FredMSloniker
Jan 2, 2008

Why, yes, I do like Kirby games.




Hoo boy. This program. This is what detractors of BASIC point to when they complain about line numbers and GOTOs. This program was repeatedly hacked on, stuffing lines in willy-nilly and, when the line numbers ran out, actually using GOTO to get to unused line numbers just long enough to do a few things before GOTOing right back! When a program gets this bad, there's only one thing to do: print it out, work through its flow, and type it in from scratch.

At least, that's the way it used to work. These days we have, if nothing else, a text editor that we can flop code around in.

Here's what the program does in broad strokes. After getting the player's starting defense - but wait, there's already a bug! If the player types in an incorrect defense number in line 76, it heads off to line 2010, which is used elsewhere in the program to ask for a new defense number. However, that code assumes a game is already in play, so, among other things, it doesn't actually ask for the opposing team's name.

Anyway. Assuming we avoid that bug, play starts in line 370. Our first visit there is at the start of the game (execution falling through from line 80), but we also go back to this line at the end of the first half and at the start of overtime. The center jump, interestingly, is weighted 60-40 in the opponent's favor; if they get the ball, we head to line 3000, but if the player gets it, we drop through to line 425.

Line 425 is called from various places in the program to print a blank line, before dropping to line 430 - which is also called from various places in the program, this time to ask what shot the player wants to make next. (Some parts of the program want to add something to the beginning of the input line, most obviously line 455, hence the skip.) Line 440 sets P to 0; P is the variable for whose turn it is, and it's 0 for the player and 1 for the computer. We then check that the player entered an integer from 1 to 4 inclusive before going to line 460.

Lines 460 and 480 perform an oddly placed check. T is the number of turns that have passed during the game. The game is supposed to end at turn 100, but to add a touch of uncertainly, we also flip a coin and don't end the game if it comes up heads. Placing the check here, however, seems bad timing - especially since it means the game can only actually end (a) if the player has the ball and (b) after they've tried to make a move. The first may be intended behavior, but the second seems like a bad idea.

We'll come back to line 490 later. Line 1000 is where we start actually processing the player's move. The ON - GOTO structure on that line, as well as the GOTO on line 1030, is an odd way of saying 'if the player made either kind of jump shot, go to 1040; otherwise, go to line 1300'. (If Z, the player's offensive play, isn't 1 or 2, execution falls through the ON - GOTO to the next line.)

Lines 1040 and 1300 are identical, because a bit of code is unnecessarily duplicated here. Lines 1040-1046 advance the game's clock; if T=50, we jump to line 8000, which prints the score at the end of the first half and heads back to line 370 for the second half's center jump. If T=92, we jump to line 1046, which calls the subroutine at 600, which prints the two-minute warning; then we drop to line 1050. If neither are true, line 1043 sends us to line 1050 anyway. If we change line 1042 to just GOSUB 600, we can omit lines 1043 and 1046 entirely. The code over at 1300-1304 can have the exact same improvement applied - or, better yet, we can move the time-advancing code somewhere else that will be called regardless of what play the player calls.

Line 1305, which we reach if the chosen play is not 1 or 2, checks if it's 0, in which case we go to the aforementioned line 2010 (change defense), which heads back to 425 to ask for an actual offensive play. Otherwise, it prints the line of text at 1320 (play 3, lay up) or 1700 (play 4, set shot) - but either way, it winds up at 1330. Plays 1 and 2 don't even get that much difference, showing the line 'jump shot' at line 1050 before heading to line 1060. As it turns out, there are only two actual plays in the player's handbook: 'jump shot' and 'not jump shot'.

From here, the code gets distinctly wiggly. There are five possible results of a shot: the shot is good (all shots score two points), the shot is missed, the shot is blocked, the shooter is fouled, or the shooter is assessed a charging foul. The order in which these possibilities are checked, the odds of success on a given check, and the odds of one side or the other recovering the ball, depend on whether or not the shot was a jump shot and, in some but only some cases, on the active defense.

One way or another, we call the subroutine at 6000 if the player scores, call the subroutine at 4000 if the player is fouled (it handles the two free throws they get), and head to either line 430 (if the player still has the ball) or line 3000 (if the computer gets it).

Over at line 3000, we set P to 1 - it's now the computer's turn - then advance the clock with similar code to that at 1040 - but not identical, because we've hit on another bug. The game doesn't check for the two-minute warning if the computer has the ball - line 3015 exists, to give said warning, but line 3012 jumps over it, and no other line in the code goes there. (It's not the only code that never gets called; line 1180, which is supposed to handle the player being fouled on a jump shot, can't be reached.)

Line 3020 picks a play for the computer in a strange way. Instead of returning an integer in the 1-4 range, it returns a number from 1 to 3.5. As this number is handled in much the same way as the player's numbers, this means that the computer is half as likely to make set shots as it is to make any other play - but, like the player, only 'jump shot' and 'not jump shot' actually matter, so it winds up being an 8:7 bias toward jump shots. Again, there's a complicated wodge of code to determine the result of the given play, sending execution to 425 or 3000, depending. Amazingly, it does reuse the subroutine at 4000 (the only place P is actually checked), but scoring is handled at line 7000.

Lines 5000-5030 and 5100-5140 are most likely a late addition to the code, as they're actually part of the code blocks, such as they are, for lines 3000 and 425, respectively - if a press defense is in play, there's a chance that either team can steal the ball from the other team. The code starting at 8000 is called at the end of the first half, while the code starting at line 491 is called when the game should be over; if the game's drawn, however, it falls through to line 492 which announces overtime, puts some time back on the clock (on line 499), and heads back to the center jump (on line 370).

Line 520, which is where the game is supposed to end, uses a STOP, which shows an error. Line 9999, the last line in the program, contains a doubly-unnecessary END - since you can't actually reach that line.

This is a program that could use some serious optimization. If you stored the opposing team's name in a string array, storing "DARTMOUTH" (or your team name of choice) in the other element of the array, you could consolidate a bunch of '(this team) has the ball'-type text. (We've already got P! We're barely using it!) We could also work on improving the flow of all those random checks - eliminate redundancies, store values that change by play in an array, that sort of thing. While we're at it, we could improve the basic way the game works - create a matrix of values for offensive play vs. defensive play? And let the computer choose its own defense as well as its own offense? Maybe even have specific teams with specific stats? But just tidying up the code would be a huge improvement.

Before I go, let me share just

One Weird Trick

Let's take a look at a piece of code that's representative of something that happens a lot in this program.

code:
445 IF Z<>INT(Z) THEN 455
446 IF Z<0 OR Z>4 THEN 455
447 GOTO 460
455 PRINT "INCORRECT ANSWER.  RETYPE IT. ";:GOTO 430
460 IF RND(1)<.5 THEN 1000
Now let's look at an abstract version of this code:
code:
	IF (this thing is true) THEN (go to label a)
	GOTO (label b)
(label a)
	(do stuff): GOTO (somewhere else)
(label b)
	(do other stuff)
Why do we have a block of branching code where one of the branches jumps immediately after the other when the other does not fall through to that code? That's just untidy. Let's swap the branches.

code:
	IF (this thing is not true) THEN (go to label)
	(do stuff): GOTO (somewhere else)
(label)
	(do other stuff)
See how much simpler the code is when we take advantage of the natural flow? It's not a huge savings in code length -

code:
445 IF Z=INT(Z) AND Z>=0 AND Z<=4 THEN 460
455 PRINT "INCORRECT ANSWER.  RETYPE IT. ";:GOTO 430
460 IF RND(1)<.5 THEN 1000
- but it's easier to understand, and it's a savings that can be applied throughout the program. Whenever possible, work with the flow in BASIC!

FredMSloniker fucked around with this message at 20:26 on Jul 3, 2020

ManxomeBromide
Jan 29, 2009

old school

FredMSloniker posted:

See how much simpler the code is when we take advantage of the natural flow? It's not a huge savings in code length -

code:
445 IF Z=INT(Z) AND Z>=0 AND Z<=4 THEN 460
455 PRINT "INCORRECT ANSWER.  RETYPE IT. ";:GOTO 430
460 IF RND(1)<.5 THEN 1000
- but it's easier to understand, and it's a savings that can be applied throughout the program. Whenever possible, work with the flow in BASIC!

It's easier to understand, but it's treacherous. This burned me in AMAZING a bit. :)

In the modern world we generally think of AND and OR and like it terms that ultimately come from LISP: there's a collection of values, and we want to know of all (AND) or any (OR) of them are true. We are used to a world where the computer goes through the alternatives in order and sees if they are true (or "truthy" - many languages don't explicitly have 'true/false' as separate values and instead have some way of assigning truth or falsity to arbitrary values.) Once it finds something that lets it know the answer right away, we expect the calculation to stop.

BASIC doesn't do this, and that means that if you need to need to check something like
IF (it is safe to even check the value) AND (the value checked has some property) THEN (do the thing)
BASIC will go right ahead and do the dangerous check, and very likely end up crashing at some point with the BASIC equivalents of a NullPointerException or an IndexOutOfBoundsException. In these cases you have to do the chained IF statements.

The formal term for this is that in BASIC, the AND/OR/NOT operators do not short-circuit. The reason they don't, in many but not all BASICs, is kind of interesting: it's because they aren't actually logical operators.

Instead, these are operations that combine the bits in their arguments; AND returns the number that has a 1 bit in every slot that both operands had a 1; OR returns a number with a 1 bit in every slot that either operand had a 1 in; NOT flips all the bits. In a Microsoft BASIC, 10 AND 12 is 8; 10 OR 12 is 14; NOT 10 is - due to the way integer math works on the computers Microsoft BASIC uses - negative 11.

So why does this work at all? Well, it's not really consistent what you'll get out if you print out a statement like PRINT 3>2. But on MS BASICs, it will print out -1. This is the integer with all 1 bits set.

And that is what makes this work. Generally speaking, 0 is false, and all non-zero values are truthy. The problem with this from a logical standpoint is that if you'd like NOT to be negation, now all numbers save -1 are falsy, which means that this program prints out both lines on a C64:

code:
10 IF 3 THEN PRINT "YAY!"
20 IF NOT 3 THEN PRINT "WAIT, WHAT?"
But as long as the only values you are working with are 0 and -1, then there is no difference between bit-level and logic-level operations for determining truth and falsehood.

(This is also why languages in the C tradition use && and || in their logical tests; & and | are the bitwise checks and would suffer from the problems we see here.)

FredMSloniker
Jan 2, 2008

Why, yes, I do like Kirby games.
It can get more degenerate than that. Consider the humble code:

code:
IF A=3 THEN X=X+2 ELSE X=X+5
This code works exactly the same way:

code:
X=X+5+(A=3)*3

FredMSloniker
Jan 2, 2008

Why, yes, I do like Kirby games.
Now that I've thought on your post, ManxomeBromide, I have a few more things to add.

You're right that AND, OR, and NOT are bitwise operators, not logical operators. Any comparison function returns not a boolean 'true' or 'false' but a number representing 'true' or 'false', and those numbers are 'all bits on' (-1 in all BASIC implementations I'm aware of) or 'all bits off' (0, likewise). That said, I have two nits with your nits. One, you should be aware of this and not be using 3 to mean 'true' in the first place, and if you want to know if 3 is or is not 0, you should be making that comparison explicitly. Two, when it comes to having multiple comparisons on one line, the original program started it. :v: (Line 446.)

You're also right that BASIC doesn't short-circuit logical operators (on account of not having them). The most common way this will cause problems is attempting to access an array element that doesn't exist; for instance, if you want to check the four neighbors of an element in a two-dimensional grid, you're going to run into trouble if you try to check A(5,-1). Specifically, you're going to get an ILLEGAL QUANTITY ERROR if your array index is less than zero and a BAD SUBSCRIPT ERROR if your array index is too large. (At least in Commodore V2 BASIC.) That's your IndexOutOfBoundsException. (BASIC doesn't have pointers, so it can't throw a NullPointerException; the closest I can think of is trying to access memory that doesn't exist with PEEK() or POKE, which also gives an ILLEGAL QUANTITY ERROR.)

Anyway. The more you know.



Hey, remember that thing I said about guess-the-number games being as prolific as they are boring? So are remove-the-object games. BATNUM gets credit for exactly two things. One, it was written by John Kemeny, who, as previously mentioned, was one of the two people who created BASIC. (Which means that, if I find any coding sins, I'll be Quite Cross.) Two, it can play a lot of different versions, which means you don't need programs like... er... Twenty-Three Matches. Which is also in this book. As noted above.

Yay.

Looking at the flow, I see a puzzling decision in lines 350-380; if the player enters zero for the number of objects in the pile, the program jumps to line 330 and asks for a different number right away, but if they enter an invalid number of objects, it goes to line 220 and prints a bunch of blank lines first. The error-checking looks pretty solid, though. (It doesn't check if A=B, which is a legal game, but one that only allows one move.)

Line 530 is part of the computer's strategy. In a game where it wants to avoid taking the last object, it wants to force the player to have only one object on their turn. To do this, it needs to force the player to have C+1 objects on their previous turn; that way, no matter how many objects the player takes (from A to B, as established in line 430), the computer can take C-P objects, which will also be in the A to B range, and leave just one. Similarly, the computer wants the player to have C*2+1 objects on the turn before that, C*3+1 objects on the turn before that, and so on. If, on the other hand, the computer wants to take the last object, it wants the number of objects on the player's turn to be C*X, where X is some integer, so that on the player's last turn they can't prevent the computer from taking the last object.

The core game loop is from lines 550-590. Line 550 calls the subroutine at 600, which makes the computer's move; line 560 checks W, which is set to 1 if the game is over, and jumps to line 220 to start a new game if so. Line 570 calls the subroutine at line 810, which makes the player's move (which is why line 540 jumps here if the player makes the first move); line 580 checks again if the game is over. Then line 590 starts the process again until someone wins.

Line 600 starts the subroutine that makes the computer's move. Q is set to N, the number of objects remaining, if M, the flag for whether the computer wants to take the last object or not, is 1 (meaning it wants to); if M isn't 1, Q is set to N-1. Lines 600-620 are kind of a stiff way to do this, and after examining the code, I've realized why: not only is there only one statement per line, but IF - THEN statements are only used for implicit GOTOs! Was that a limitation of the earliest BASICs?

Lines 630-670 determines if the computer has a forced loss, which can only happen if it's trying to avoid taking the last object (M isn't 1) and if N<=A (there are at most as many objects in the pile as the computer is obligated to take). If so, the code prints a loss message, sets the 'the game is over' flag, and returns. Similarly, lines 680-710 determine if the computer can win this turn, which can only happen if it's trying to take the last object (we only get here if M is 1) and N<=B (there are at most as many objects in the pile as the computer is able to take).

Line 720 figures out how many objects the computer wants to take. If it's trying to avoid taking the last object, as we discussed earlier, Q=N-1. The computer wants to take some number of objects, P, so that, at the end of this turn, there will be C*X+1 objects remaining, where X is some integer. That number of objects will also be N-P. Therefore:

C*X+1=N-P
C*X+1+P=N
C*X+P=N-1
C*X+P=Q (since Q=N-1)
P=Q-C*X
P=Q-C*INT(Q/C) (since we can freely choose an integer value for X)

Which is the statement at line 720.

If the computer is trying to take the last object, Q=N; since we want to have C*X objects at the end of the turn, we start with C*X=N-P, or C*X=Q-P, or C*X+P=Q, and proceed from there as above. Either way, line 720 gives the number of pieces the computer wants to take.

But why that X in particular? The answer lies in modulo math. Q-C*INT(Q/C) is another way of writing Q modulo C, that is, the remainder if you divide Q by C. Since the computer wants to reduce the number of objects in the pile by C at a time, and since it would like to take Q objects if only it could, by repeatedly reducing Q by C it will eventually reach a valid move (in theory - see below) - and that repeated subtraction of C from Q is the same as dividing Q by C and taking the remainder.

If the computer has a winning strategy, though, then so does the player. In the game Twenty-Three Matches (A=1, B=3, N=23, M=2), whoever goes first always wins with perfect play. Solving the equation on line 720 gives us that move: two matches. But if we let the player go first, and they take that move, when we come to line 720, N=21, which gives a P of... 0. Which is to say, it'd really much rather it be the player's turn, thanks.

Unfortunately for it, that's not an option, and lines 730-760 enforce that. (I don't think P will ever be greater than B to begin with. I'd also consider changing this code so that, if the computer can't make the move it wants, it makes a random move in hopes of confusing the player.) Line 770 applies the move, line 780 describes the move, and line 790 confirms that the game is not over. (This means we don't have to explicitly reset W at the start of a new game, since this line and its twin, line 1060, in the player code take care of that before the win condition is first checked.) Last but not least, line 800 returns control to the main loop.

Lines 810-820 starts the player's turn handling by asking for a move. Note the cheeky bit of code at lines 830-860. Lines 880-890 handle the player taking fewer objects than they're required to; this is only legal if there are fewer objects remaining than that, and then only if they take all of those objects. If that's the case, we go to line 960 to show if they won or lost; otherwise, it's an illegal move.

If we get to line 910, it's because the player has taken at least as many objects as they're required to. Here we make sure they've taken at most as many objects as they're allowed to, then head to line 940, where we take away that many objects, then check to see if there are now zero objects remaining, in which case we fall through to 960's end-of-game code. Otherwise, we head to 1030, which does a sanity check on the number of items remaining; if we now have a negative number, we put the objects back and send up an illegal move error. Otherwise, we mark the game as not over and return.

The code here is a tiny bit tangled. If we put a check in earlier for if P>N, we wouldn't need to put any objects back in the first place. Adapting the code within the rather draconian restrictions of this flavor of BASIC is left as an exercise for the reader.

That said, this is a perfectly serviceable BASIC game, as should be expected from one of the people that created the language. It's just a shame I've already explained to you how to beat it.

FredMSloniker
Jan 2, 2008

Why, yes, I do like Kirby games.






BASIC Computer Games posted:

BATTLE is based on the popular game Battleship which is primarily played to familiarize people with the location and designation of points on a coordinate plane.

I'm pretty sure that's not why people play Battleship. At any rate, this program is only loosely based on Battleship; for one thing, the computer doesn't actually shoot back. For another thing -

BASIC Computer Games posted:

The main objective and primary educational benefit of BATTLE comes from attempting to decode the bad guys' fleet disposition code. To do this, you must make a comparison between the coded matrix and the actual matrix which you construct as you play the game.

- there's this. The only real 'gameplay' is trying to crack the coded matrix. The problem is that said 'code' is the same every time, so once you've solved it, there's nothing else to do with the program. With that in mind, I'm going to focus on how the coded matrix is made, then move on.

code:
1180 INPUT X,Y

...

1230 R=7-Y
1240 C=X
2150 IF F(R,C)>0 THEN 1290
F() is a two-dimensional array that holds the starting state of the fleet. The first dimension is R, the row, and the second C, the column. We can use whatever variable names we want, of course, but that's what line 2150, which checks for a hit, uses.

A 6x6 grid of row and column values looks like this. (If you use spreadsheets, this should look slightly familiar.)

Illustration 1
(R=1,C=1) (R=1,C=2) (R=1,C=3) (R=1,C=4) (R=1,C=5) (R=1,C=6)
(R=2,C=1) (R=2,C=2) (R=2,C=3) (R=2,C=4) (R=2,C=5) (R=2,C=6)
(R=3,C=1) (R=3,C=2) (R=3,C=3) (R=3,C=4) (R=3,C=5) (R=3,C=6)
(R=4,C=1) (R=4,C=2) (R=4,C=3) (R=4,C=4) (R=4,C=5) (R=4,C=6)
(R=5,C=1) (R=5,C=2) (R=5,C=3) (R=5,C=4) (R=5,C=5) (R=5,C=6)
(R=6,C=1) (R=6,C=2) (R=6,C=3) (R=6,C=4) (R=6,C=5) (R=6,C=6)

Line 1180 gets the coordinates the player wants to shoot. Note that we can ask for more than one variable's worth of input at a time; the values must be separated by commas, and if the player doesn't give enough, the program will prompt for more input. (In Commodore BASIC V2, it does so by printing ??.) It will keep the input already given, though, so if the player only types a value for X, they then type only a value for Y.

The player's input is in Cartesian coordinates. A 6x6 grid of Cartesian coordinates would look like this.

Illustration 2
(X=1,Y=6) (X=2,Y=6) (X=3,Y=6) (X=4,Y=6) (X=5,Y=6) (X=6,Y=6)
(X=1,Y=5) (X=2,Y=5) (X=3,Y=5) (X=4,Y=5) (X=5,Y=5) (X=6,Y=5)
(X=1,Y=4) (X=2,Y=4) (X=3,Y=4) (X=4,Y=4) (X=5,Y=4) (X=6,Y=4)
(X=1,Y=3) (X=2,Y=3) (X=3,Y=3) (X=4,Y=3) (X=5,Y=3) (X=6,Y=3)
(X=1,Y=2) (X=2,Y=2) (X=3,Y=2) (X=4,Y=2) (X=5,Y=2) (X=6,Y=2)
(X=1,Y=1) (X=2,Y=1) (X=3,Y=1) (X=4,Y=1) (X=5,Y=1) (X=6,Y=1)

In Cartesian coordinates, therefore, x increases as you go to the right, and y increases as you go up. However, in a spreadsheet, while the column increases as you go to the right, the row increases as you go down. Lines 1230 and 1240 convert X and Y to R and C.

But that's only half of the picture. The other half is the coded matrix. How does that get printed out?

code:
1050 FOR I=1 TO 6
1051 FOR J=1 TO 6
1052 H(I,J)=F(J,I)
1053 NEXT J
1054 NEXT I
1060 FOR I=1 TO 6
1061 FOR J=1 TO 6
1062 PRINT H(I,J);
1063 NEXT J
1064 PRINT
1065 NEXT I
H() is a two-dimensional array that holds the coded matrix. The loop from line 1060 to line 1065 prints it out row by row, while the loop from line 1061 to line 1063 prints an individual row. If line 1062 printed the values of I and J for a given cell, the output might look something like this:

Illustration 3
(I=1,J=1) (I=1,J=2) (I=1,J=3) (I=1,J=4) (I=1,J=5) (I=1,J=6)
(I=2,J=1) (I=2,J=2) (I=2,J=3) (I=2,J=4) (I=2,J=5) (I=2,J=6)
(I=3,J=1) (I=3,J=2) (I=3,J=3) (I=3,J=4) (I=3,J=5) (I=3,J=6)
(I=4,J=1) (I=4,J=2) (I=4,J=3) (I=4,J=4) (I=4,J=5) (I=4,J=6)
(I=5,J=1) (I=5,J=2) (I=5,J=3) (I=5,J=4) (I=5,J=5) (I=5,J=6)
(I=6,J=1) (I=6,J=2) (I=6,J=3) (I=6,J=4) (I=6,J=5) (I=6,J=6)

Which means that, for H(), I is the row and J is the column (compare to Illustration 1).

The loop from line 1050 to line 1054, meanwhile, is what sets up the coded matrix in the first place. Line 1052 in particular transposes the two dimensions of F() into H(); rows become columns and columns become rows.

So. The player looks at the coded matrix and picks a row and column they want to shoot. They then transpose the row and column, convert them into x and y Cartesian coordinates, and enter them into the game, which converts them back into a row and column and checks them against the fleet grid. Which means that the sample code sheet on page 15 decodes to this:

Illustration 4
0 0 5 5 5 5
0 4 0 0 1 0
0 4 0 6 0 1
2 4 6 0 0 0
2 6 0 0 0 0
6 0 0 3 3 3

You can compare this to the sample output to confirm this.

If there's anything else you want to know about this program, let me know. Otherwise, I'm going to move on to the next program, which is actually fairly interesting-looking - and also nearly two pages of code, so it'll take me a bit to break it down.

FredMSloniker
Jan 2, 2008

Why, yes, I do like Kirby games.

ManxomeBromide, elsewhere posted:

I suppose I should say something about Battle, but it's hard to when it's less a game than a homework assignment that only has one problem and isn't even procgen. Um. Well.

"Commander, we have deciphered the enemy's secret naval code?"
"Yes?"
"It turns out we were holding the paper sideways."

Yeah, the philosophy I've been building as I read through this is finding something worth talking about, no matter how banal the program. Acey Ducey got a line-by-line analysis, since it was the first program; Amazing got a guest breakdown by ManxomeBromide; Animal had an interesting way to build a database; Awari had a learning AI; Bagels actually error-checked its input, unlike most of the programs in here; Banner used the binary representation of numbers to store pixel data; Basketball served as an example of what not to do; and Batnum showed how to solve an entire class of taking-object puzzles. Battle was the first one that stumped me in terms of interesting things to say, and there at least I managed to scratch something out.

Let's take a brief look at a long program.







This is an impressive program, with an accordingly long listing. It's well-commented, makes good use of subroutines, and supports a surprisingly large number of features. That said, I'm not going to look at it for long. Why?

BASIC Computer Games posted:

Cards are automatically reshuffled as the 51st card is reached. For greater realism, you may wish to change this to the 41st card in Line 110. Actually, fanatical purists will want to modify the program so it uses three decks of cards instead of just one.

You know what? Challenge accepted.

C64List

C64List is a program that does a number of useful things. Most prominently, it converts Commodore BASIC V2 programs to and from a variety of formats, including PRG (the way the C64 stores BASIC programs internally; it can be loaded into an emulator or transferred to a real C64) and TXT (what you'd expect: a plain-text version of a program). It also, however, has an LBL format, which removes the line numbers and inserts labels as necessary; I typed in Basketball and converted it from TXT to LBL to get a better view of the program flow (and to confirm that certain lines of code were never visited). As a bonus feature, it supports long variable names, which will get converted into the two-character names that the C64 actually uses.

Here's what ManxomeBromide's rewrite of Amazing looks like when I convert it from TXT to LBL and use the -extractvariables option. (Apologies for the scrolling; that's how this board handles the code tag.)

code:
{var:SuperVariable0000=XM}
{var:SuperVariable0001=YM}
{var:SuperVariable0002=W}
{var:SuperVariable0003=V}
{var:SuperVariable0004=VD}
{var:SuperVariable0005=R}
{var:SuperVariable0006=TI}
{var:SuperVariable0007=S}
{var:SuperVariable0008=C}
{var:SuperVariable0009=Z}
{var:SuperVariable0010=N}
{var:SuperVariable0011=X}
{var:SuperVariable0012=Y}
{var:SuperVariable0013=L$}
{var:SuperVariable0014=M$}
{var:SuperVariable0015=R$}
	PRINT TAB(12);"AMAZING PROGRAM"
	PRINT TAB(11);"CREATIVE COMPUTING":PRINT TAB(9);"MORRISTOWN, NEW JERSEY"
	PRINT:PRINT:PRINT:PRINT
{:40}
	INPUT "WHAT ARE YOUR WIDTH AND LENGTH";{var:SuperVariable0000},{var:SuperVariable0001}
	IF {var:SuperVariable0000}<2 OR {var:SuperVariable0001}<2 THEN PRINT "MEANINGLESS DIMENSIONS. TRY AGAIN.":GOTO {:40}
	DIM {var:SuperVariable0002}({var:SuperVariable0000}+1,{var:SuperVariable0001}+1),{var:SuperVariable0003}({var:SuperVariable0000},{var:SuperVariable0001}),{var:SuperVariable0004}(4)
	{var:SuperVariable0005}=RND(-{var:SuperVariable0006})
	{var:SuperVariable0005}=INT(RND(1)*{var:SuperVariable0000}+1):{var:SuperVariable0007}=1:{var:SuperVariable0002}({var:SuperVariable0005},{var:SuperVariable0007})=1:{var:SuperVariable0008}=2:{var:SuperVariable0009}=0
{:90}
	{var:SuperVariable0010}=0:{var:SuperVariable0011}=0
	IF {var:SuperVariable0005}<>1 AND {var:SuperVariable0002}({var:SuperVariable0005}-1,{var:SuperVariable0007})=0 THEN {var:SuperVariable0010}={var:SuperVariable0010}+1:{var:SuperVariable0004}({var:SuperVariable0010})=1
	IF {var:SuperVariable0007}<>1 AND {var:SuperVariable0002}({var:SuperVariable0005},{var:SuperVariable0007}-1)=0 THEN {var:SuperVariable0010}={var:SuperVariable0010}+1:{var:SuperVariable0004}({var:SuperVariable0010})=2
	IF {var:SuperVariable0005}<>{var:SuperVariable0000}AND {var:SuperVariable0002}({var:SuperVariable0005}+1,{var:SuperVariable0007})=0 THEN {var:SuperVariable0010}={var:SuperVariable0010}+1:{var:SuperVariable0004}({var:SuperVariable0010})=3
	IF ({var:SuperVariable0007}={var:SuperVariable0001}AND {var:SuperVariable0009}=0) OR ({var:SuperVariable0007}<>{var:SuperVariable0001}AND {var:SuperVariable0002}({var:SuperVariable0005},{var:SuperVariable0007}+1)=0) THEN {var:SuperVariable0010}={var:SuperVariable0010}+1:{var:SuperVariable0004}({var:SuperVariable0010})=4
	IF {var:SuperVariable0010}>0 THEN {var:SuperVariable0010}={var:SuperVariable0004}(INT(RND(1)*{var:SuperVariable0010})+1)
	ON {var:SuperVariable0010}+1 GOSUB {:390}, {:400}, {:410}, {:420}, {:430}
	IF {var:SuperVariable0011}=0 THEN {var:SuperVariable0008}={var:SuperVariable0008}+1:GOTO {:210}
{:170}
	IF {var:SuperVariable0005}<>{var:SuperVariable0000}THEN {var:SuperVariable0005}={var:SuperVariable0005}+1:GOTO {:200}
	IF {var:SuperVariable0007}<>{var:SuperVariable0001}THEN {var:SuperVariable0005}=1:{var:SuperVariable0007}={var:SuperVariable0007}+1:GOTO {:200}
	{var:SuperVariable0005}=1:{var:SuperVariable0007}=1
{:200}
	IF {var:SuperVariable0002}({var:SuperVariable0005},{var:SuperVariable0007})=0 THEN {:170}
{:210}
	IF {var:SuperVariable0009}=0 OR {var:SuperVariable0008}<{var:SuperVariable0000}*{var:SuperVariable0001}+1 THEN {:90}
	PRINT CHR$(176);:IF {var:SuperVariable0002}(1,1)=1 THEN PRINT "  ";:GOTO {:240}
	PRINT CHR$(195);CHR$(195);
{:240}
	FOR {var:SuperVariable0011}=2 TO {var:SuperVariable0000}:PRINT CHR$(178);:IF {var:SuperVariable0002}({var:SuperVariable0011},1)=1 THEN PRINT "  ";:GOTO {:260}
	PRINT CHR$(195);CHR$(195);
{:260}
	NEXT {var:SuperVariable0011}:PRINT CHR$(174)
	FOR {var:SuperVariable0012}=1 TO {var:SuperVariable0001}:PRINT CHR$(221);:FOR {var:SuperVariable0011}=1 TO {var:SuperVariable0000}
	IF {var:SuperVariable0003}({var:SuperVariable0011},{var:SuperVariable0012})<2 THEN PRINT "  ";CHR$(221);:GOTO {:300}
	PRINT "   ";
{:300}
	NEXT {var:SuperVariable0011}:PRINT:{var:SuperVariable0013}=CHR$(171):{var:SuperVariable0014}=CHR$(219):{var:SuperVariable0015}=CHR$(179)
	IF {var:SuperVariable0012}={var:SuperVariable0001}THEN {var:SuperVariable0013}=CHR$(173):{var:SuperVariable0014}=CHR$(177):{var:SuperVariable0015}=CHR$(189)
	PRINT {var:SuperVariable0013};:IF {var:SuperVariable0003}(1,{var:SuperVariable0012})=0 OR {var:SuperVariable0003}(1,{var:SuperVariable0012})=2 THEN PRINT CHR$(195);CHR$(195);:GOTO {:340}
	PRINT "  ";
{:340}
	FOR {var:SuperVariable0011}=2 TO {var:SuperVariable0000}:PRINT {var:SuperVariable0014};
	IF {var:SuperVariable0003}({var:SuperVariable0011},{var:SuperVariable0012})=0 OR {var:SuperVariable0003}({var:SuperVariable0011},{var:SuperVariable0012})=2 THEN PRINT CHR$(195);CHR$(195);:GOTO {:370}
	PRINT "  ";
{:370}
	NEXT {var:SuperVariable0011}:PRINT {var:SuperVariable0015}:NEXT {var:SuperVariable0012}
	END
{:390}
	{var:SuperVariable0011}=1:RETURN
{:400}
	{var:SuperVariable0002}({var:SuperVariable0005}-1,{var:SuperVariable0007})={var:SuperVariable0008}:{var:SuperVariable0003}({var:SuperVariable0005}-1,{var:SuperVariable0007})=2:{var:SuperVariable0005}={var:SuperVariable0005}-1:RETURN
{:410}
	{var:SuperVariable0002}({var:SuperVariable0005},{var:SuperVariable0007}-1)={var:SuperVariable0008}:{var:SuperVariable0003}({var:SuperVariable0005},{var:SuperVariable0007}-1)=1:{var:SuperVariable0007}={var:SuperVariable0007}-1:RETURN
{:420}
	{var:SuperVariable0002}({var:SuperVariable0005}+1,{var:SuperVariable0007})={var:SuperVariable0008}:{var:SuperVariable0003}({var:SuperVariable0005},{var:SuperVariable0007})={var:SuperVariable0003}({var:SuperVariable0005},{var:SuperVariable0007})+2:{var:SuperVariable0005}={var:SuperVariable0005}+1:RETURN
{:430}
	IF {var:SuperVariable0007}={var:SuperVariable0001}THEN {:450}
	{var:SuperVariable0002}({var:SuperVariable0005},{var:SuperVariable0007}+1)={var:SuperVariable0008}:{var:SuperVariable0003}({var:SuperVariable0005},{var:SuperVariable0007})={var:SuperVariable0003}({var:SuperVariable0005},{var:SuperVariable0007})+1:{var:SuperVariable0007}={var:SuperVariable0007}+1:RETURN
{:450}
	{var:SuperVariable0009}=1:IF {var:SuperVariable0003}({var:SuperVariable0005},{var:SuperVariable0007})<>0 THEN {var:SuperVariable0003}({var:SuperVariable0005},{var:SuperVariable0007})=3:RETURN
	{var:SuperVariable0003}({var:SuperVariable0005},{var:SuperVariable0007})=1:{var:SuperVariable0005}=1:{var:SuperVariable0007}=1:{var:SuperVariable0008}={var:SuperVariable0008}-1
	IF {var:SuperVariable0002}({var:SuperVariable0005},{var:SuperVariable0007})=0 THEN {var:SuperVariable0011}=1:{var:SuperVariable0008}={var:SuperVariable0008}+1
	RETURN
With a bit of search-and-replace, we can change {var:SuperVariable0000 (note the lack of closing brace) to, say, {var:mazewidth and have something easier to understand in the code. Similarly, we can replace {:40} with {:getwidthandlength} or something. Then we can make edits and convert back to TXT format, or into PRG format to run in VICE or somesuch.

But that's grabbing existing code and messing with it. Let's throw Blackjack out the window and start from scratch.

Blackjack, The Modern Way

I went through a brief gambling phase when I was younger; the local casino had a deal on Tuesdays where, if you bought $20 worth of chips, they'd give you $30, so long as you went through them at least once before cashing out. Blackjack was one of the two games I played (the other being craps), being a game with a relatively slim house advantage. But the house always wins, and when I realized, tracking my games, that I was $100 down from where I'd started, I stopped.

One thing I remember was the machines they had at every table that they would use for shuffling. There was a sort of hopper, called the shoe, in which they had eight decks' worth of shuffled cards, and they'd deposit the discards in another shoe. When they reached a card that had been randomly inserted near the bottom of the shoe, they'd place the remainder of the cards in the shoe and swap it with another shoe that was sitting in a shuffling machine. You could listen to it shuffling as you played the next several hands.

So we're going to need a shuffling machine. Let's make one.

code:
{:freshshoe}
	PRINT "GETTING A FRESH SHOE..."
	FOR {var:temp} = 0 TO 51: {var:shoe}({var:temp}) = {var:decks}: NEXT
	{var:lastcard} = INT(RND(.) * {var:lastcardrange}) + {var:lastcardmaximum}
	{var:totalcards} = 52 * {var:decks}: RETURN
This code will initialize the shoe, which is stored in {var:shoe}(). (Note that the parentheses are outside the curly braces; C64List basically does search-and-replace on variable names, and doesn't care about the difference between A and A(), so keep that in mind.) As I mentioned in my suggestion for improving Acey Deucey, I'm not actually storing the shoe in a shuffled form; instead, I'm storing the number of each card we have. (I'm using the zero index to save memory, which is why they're numbered 0-51.)

We'll set {var:decks} to 8 somewhere early in the program, so we can change it later if we want to change the number of decks used. We'll also DIM {var:shoe}(51).

While we're at it, we'll set {var:lastcardmaximum} to the earliest card we'll consider shuffling at (more precisely, the largest remaining shoe size) and {var:lastcardrange} to the number of cards after that we can possibly wait. (Technically, the number of cards after that plus one, since RND(.) * {var:lastcardrange} will return a number between 0 and not quite {var:lastcardrange}, and INT() rounds down.) In this case, we'll set {var:lastcardmaximum} to 104, or 2 * 52, and {var:lastcardrange} to 52, so the game will shuffle somewhere between six and seven decks in.

Incidentally, we could save ourselves a lot of trouble by going to video blackjack, i.e. having a single deck that got shuffled after every hand. It takes a while to initialize the shoe, which is why we have that PRINT statement.

code:
{:drawacard}
	{var:functionz} = INT(RND(.) * {var:totalcards}): {var:functiona} = 0
{:drawacardloop}
	IF {var:functionz} > {var:shoe}({var:functiona}) THEN {var:functionz} = {var:functionz} - {var:shoe}({var:functiona}): {var:functiona} = {var:functiona} + 1: GOTO {:drawacardloop}
	{var:shoe}({var:functiona}) = {var:shoe}({var:functiona}) - 1: {var:totalcards} = {var:totalcards} - 1: RETURN
Calling this function will return a card ID code in {var:functiona}. I'm setting any variable name starting with 'function' aside for subroutines to use. Similarly, any variable name starting with 'temp' will have a very limited lifespan.

When {var:totalcards} drops to or below {var:lastcard}, we'll call {:freshshoe} as soon as the current hand is done.

code:
	PRINT "PLEASE WAIT..."
	{var:tempstring} = "A23456789TJQK{$d3}{$da}{$d8}{$c1}"
	FOR {var:tempy} = 0 TO 3: FOR {var:tempx} = 0 TO 12
	{var:cardnames}({var:tempy} * 13 + {var:tempx}) = MID$({var:tempstring}, {var:tempx} + 1, 1) + MID$({var:tempstring}, {var:tempy} + 14, 1)
	NEXT: NEXT
This code will be called fairly early to initialize {var:cardnames}, an array containing the name of each card. The hex numbers at the end of {var:tempstring} are the Commodore 64's symbols for 'heart', 'diamond', 'club', and 'spade'; if you're using this program on a different system, you should know how to adapt them. We could generate a card name 'live', but doing this now saves time later, and we have the memory to spare.

I thought about adding color codes so that, for instance, spades would print in black and hearts in red - in case you don't know, 'turn the current text color red' is a symbol in the C64's character set and can be added to a string for printing - but the C64's red is rather dark, and the contrast between the two isn't great.

Let's put this together into a test program.

code:
{renumber}{number:10}{step:10}
' These commands tell C64List to start numbering the program at 10 and go up 10 for each line after that. (Renumber must be FIRST in a label file, which is why this comment is after it.)

' C64List has various ways to convert quoted text. In order to properly preserve the difference between uppercase and graphic glyphs in strings, we have to add this directive.
{alpha:off}

' We can save some memory by eliminating unnecessary spaces from the output.
{crunch:on}

' We have to supply the actual variable names that will be used for each long variable name. This is also where we set their type. If we use the same name twice, C64List will complain.
{var:cardnames=CN$}
{var:totalcards=TC}
{var:functiona=FA}
{var:functionz=FZ}
{var:shoe=S}
{var:decks=DN}
{var:lastcardrange=LR}
{var:lastcardmaximum=LM}
{var:lastcard=LC}
{var:temp=ZZ}
{var:tempstring=ZZ$}
{var:tempx=ZX}
{var:tempy=ZY}

' Dimensioning arrays, setting constants, and initializing the RNG.	
	DIM {var:cardnames}(51), {var:shoe}(51)
	{var:decks} = 8: {var:lastcardmaximum} = 104: {var:lastcardrange} = 52
	{var:temp} = RND(-TI)
	
' Initialize the {varcardnames}() array.
	PRINT "PLEASE WAIT..."
	{var:tempstring} = "A23456789TJQK{$d3}{$da}{$d8}{$c1}"
	FOR {var:tempy} = 0 TO 3: FOR {var:tempx} = 0 TO 12
	{var:cardnames}({var:tempy} * 13 + {var:tempx}) = MID$({var:tempstring}, {var:tempx} + 1, 1) + MID$({var:tempstring}, {var:tempy} + 14, 1)
	NEXT: NEXT

	GOSUB {:freshshoe}
	PRINT "DRAWING FIVE CARDS: ";
	FOR {var:tempx} = 1 TO 5: GOSUB {:drawacard}: PRINT {var:cardnames}({var:functiona}); " ";: NEXT
	PRINT
	
' Before the subroutines, we must...
	END
	
' Get a fresh shoe's worth of cards.
{:freshshoe}
	PRINT "GETTING A FRESH SHOE..."
	FOR {var:temp} = 0 TO 51: {var:shoe}({var:temp}) = {var:decks}: NEXT
	{var:lastcard} = INT(RND(.) * {var:lastcardrange}) + {var:lastcardmaximum}
	{var:totalcards} = 52 * {var:decks}: RETURN

' Draw a card. The card number is returned in {var:functiona}.
{:drawacard}
	{var:functionz} = INT(RND(.) * {var:totalcards}): {var:functiona} = 0
{:drawacardloop}
	IF {var:functionz} > {var:shoe}({var:functiona}) THEN {var:functionz} = {var:functionz} - {var:shoe}({var:functiona}): {var:functiona} = {var:functiona} + 1: GOTO {:drawacardloop}
	{var:shoe}({var:functiona}) = {var:shoe}({var:functiona}) - 1: {var:totalcards} = {var:totalcards} - 1: RETURN
Which, when we feed it into C64List, returns this text file:

code:
10 DIM CN$(51),S(51)
20 DN=8:LM=104:LR=52
30 ZZ=RND(-TI)
40 PRINT"PLEASE WAIT..."
50 ZZ$="A23456789TJQK{$d3}{$da}{$d8}{$c1}"
60 FOR ZY=0 TO 3:FOR ZX=0 TO 12
70 CN$(ZY*13+ZX)=MID$(ZZ$,ZX+1,1)+MID$(ZZ$,ZY+14,1)
80 NEXT:NEXT
90 GOSUB 140
100 PRINT"DRAWING FIVE CARDS: ";
110 FOR ZX=1 TO 5:GOSUB 180:PRINT CN$(FA);" ";:NEXT
120 PRINT
130 END
140 PRINT"GETTING A FRESH SHOE..."
150 FOR ZZ=0 TO 51:S(ZZ)=DN:NEXT
160 LC=INT(RND(.)*LR)+LM
170 TC=52*DN:RETURN
180 FZ=INT(RND(.)*TC):FA=0
190 IF FZ>S(FA)THEN FZ=FZ-S(FA):FA=FA+1:GOTO 190
200 S(FA)=S(FA)-1:TC=TC-1:RETURN
And a PRG that gives us this result when run a few times:



Whew! This post is getting pretty long, and we've barely done anything yet. Read this through, let me know if there's anything that isn't crystal clear, and we'll pick up from here next time!

namlosh
Feb 11, 2014

I name this haircut "The Sad Rhino".
cleaning up bookmarks and am bummed that this thread died

Adbot
ADBOT LOVES YOU

HisMajestyBOB
Oct 21, 2010


College Slice
Oh man I forgot about this thread :(
I got my Apple II GS up and running shortly after this thread died. I've been going through some of my old Softside Selections and typing up the code. So far I've done Kangarilla and Apple Maze.

Though, with the next program, I'm going to "cheat" and type it up on a modern computer in vim and then transfer it over.

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply