I'm trying to convert a BASIC program which PEEKs the text screen, and I'm having a really hard time figuring out a similar formula for the Apple II. The original computer stores screen memory sequentially from top left to bottom right. But the Apple skips seven lines after every 40 characters (that's not entirely accurate, but you probably know what I'm describing.)
Beagle Bros has a short routine to solve this by positioning the cursor using HTAB and VTAB, then PEEK to find the memory location:
PEEK (40) + PEEK (41) * 256 + PEEK (36)
40 and 41 are the base memory address of the cursor position, and 36 is the horizontal position. However, adding this routine to the program, plus its overhead, slows it down considerably. I would imagine that there's a known mathematical formula to do this more directly, but I can't find it with Google and I can't figure it out on my own. Can anyone point me to a solution? Machine language isn't out of the question, but I'd rather keep it in BASIC. (Timing isn't critical, just inconvenient.)
I believe this should do it for you, although it won't be fast.
A =(V-INT(V/8)*8)*128+40*INT(V/8)+1024 +H
Where :
V is the vertical line (0 to 23 with 0 at the top)
H is the horizontal column (0-39 with 0 at the left)
A is the address to poke or peek as you see fit.
I'll admit that that line of code makes me cringe. In assembly it's not much more than a bit of bit manipulation, and could likely be done in 1/100th of the time that it takes applesoft.
0 GOSUB 9001 CALL -1184 : VTAB 32 PRINT PEEK(A(0) + 22)3 END900 DIM A(23)910 FOR V = 0 TO 23920 A(V) = (V-INT(V/8)*8)*128+40*INT(V/8)+1024930 NEXT940 RETURN
0 GOSUB 9001 CALL -1184 : VTAB 32 PRINT ASC(MID$(L$(0), 23, 1))3 END900 V = 0 : A = 0 : DIM L$(23) : L$(0)=L$(0) : POKE 252, PEEK (131) : POKE 253, PEEK (132) : A = PEEK(252) + PEEK(253) * 256910 FOR V = 0 TO 23920 VTAB V + 1930 POKE A + V * 5, 40940 POKE A + V * 5 + 1, PEEK(40)950 POKE A + V * 5 + 2, PEEK(41)960 NEXT970 RETURN
It's an interesting problem, and like a lot of things there are multiple solutions.
To derive the "mathematical formula" for mapping arbitrary 40-column text screen locations to memory addresses, think about the screen memory map by arranging the 24 lines of the screen into a 2-dimensional matrix/array like the following:
Where `I` is the "group of 8 lines" of which a line is a member: the 0-group starting at 0x400, the 1-group at 0x428, and the 2-group at 0x450.
Then `J` is the line number of the line within its group.
The algorithm to get from line number Y (0-indexed) to memory address is then:
or in Decimal
This works because each line starts on a 128-byte boundary and the line-groups start 40 bytes apart.
To do this in a program, you can obtain I and J by dividing the line number by 8: use the quotient for I and the remainder for J, as in `23/8 = 2r7`. In other words, the J value is `Y mod 8` and I is `INT(Y/8)`.
The first piece is easy enough, just sub in the formula for I:
Applesoft does not have a modulo operator like Integer BASIC did so to get a BASIC solution you have to build it from parts as already demonstrated. But there are ML approaches too.
It's worth noting that when dealing with powers of 2, `Y mod 2n == Y & (2n-1)` meaning that to get `Y mod 8` you can use the bitwise AND `Y & 7`, but Applesoft doesn't have bitwise operators either. Too bad, since it's such a quick operation in machine code.
One thing you can do is put a ML program in memory and attach it to the Applesoft USR function, which is an underrated feature of Applesoft. This is a two-part thing: you need a ML program in memory, and you have to tell Applesoft where to find it.
First, put a ML program at address $300 (768) that takes an 8-bit integer and ANDs it with 7. Second, add a `JMP $0300` statement to the addresses 0A through 0C, which is where the USR function goes to find out what it is supposed to do. This BASIC loader does both these things:
When you call the USR(x) function, that `x` value is put into memory and the subroutine referenced from location $0A is called. You can see this in action with the next program, which uses our USR function to calculate the value of each potential line number modulo 8.
Back to the algorithm, we can use that USR function to calculate our J index and complete the calculation:
That gets us the starting address for any screen line Y, then you just add the column position X (counting from 0) for the final address.
Let's try it out with another program:
For reference, the USR function at $0300 is
You can do this much more simply (and maybe faster?) than with the USR function, but I thought it was an interesting exercise. Jumping into ROM subroutines to manipulate the input and output values like this adds unknown overhead to the function because the USR function puts its argument in the Floating-point ACumulator (FAC) so our simple AND operation is bookended by ROM subroutines to turn that FAC value into an integer and back again.
The basic operation we want to accomplish -- `Y & 7` -- can be performed in just a few bytes of Machine Code:
Which translates to:
This requires the Line Number Y to be POKEd into address 7, and the return value will be found at the same location after a ML call, as in `POKE 7,Y : CALL 768 : PRINT PEEK(7)`.
Here's the same test program from before with the modified ML code:
Wow, talk about a wealth of information! These are all very interesting approaches, and I'll need to spend a day delving into them. Thank you all for your advice!
The algorithms described here are pretty legible to a layman compared to what actually happens in the IOU chip to produce a screen location memory address. It uses a lot of bit-banging to take an X,Y coordinate and turn it into a 16-bit memory location.
The process is described in Chapter 7 or Chapter 11 of the "Apple IIe Technical Reference" or "Apple //c Technical Reference Volume 1", respectively, and doesn't make sense the first few times you read it (well, your mileage may vary) but I think I got the hang of it.
Instead of spamming this thread with a bunch of stuff, I'll just link to my GitHub where I've been investigating this line of thought while teaching myself 6502 assembly language for no good reason.
The great thing about the "real" algorithm is that in principle you can use it on any screen for any mode. I don't know that there are a lot of applications for that information, but whatever, it's fun.
Thanks, I'll check this out later in the week! It 's an interesting problem with very clear parameters, but I just couldn't get all the parts into a working formula by myself. I really appreciate everyone's willingness to delve into this little excercise. I'll share my finished program translation if I ever get it working, although it won't be all that exciting: It's definitely more about the journey than the destination!