Jump to content
IGNORED

Created English translation of the Extended Turbo Basic (Turex.com) documentation


markmiller

Recommended Posts

I typed in the program, but there were so many errors in the assembly, I haven't yet been able to debug it. The errors are all from bad OCR

 

That reminds me of a time a couple months ago, Stephen. I was trying to type in a program that I found in Hi-Res Magazine. I don't remember which issue it was. But it was a short-lived magazine that had four issues in total. You can find an HTML version of it at Atari Magazines. Archive.org has them in pdf format if (like me) you sometimes want to browse the old ads and stuff. (Here's the first issue.) I don't know if the Atari Magazines versions have every little bit of text in them. Sometimes just reading old letters from subscribers is interesting. And the pdf, of course, will have everything.

 

 

Atari Compendium has Hi-Res Magazine as well (scroll down a bit), as well as a selection of other stuff if you back up into the directory a bit. Atari Archives also has a nice selection of books, while we're on the subject.

 

 

So anyway, I was typing in a program from an issue of Hi-Res Magazine and it didn't work. I carefully proofread my listing to no avail. Sometimes you can figure out errors in the code just by studying it a bit. But this was too complex for me to understand. I'd just wasted a lot of time for nothing.

 

And then I remembered how it was very typical for subsequent issues of a magazine back-in-the-day to publish corrections. And, sure enough, the next issue had several typos corrected. Then it worked. So if bad OCR doesn't get you, sloppy proofreading (on the part of the publisher and/or the original programmer) might get you.

 

Of course, if you are using an emulator, you can (in the case of the one machine language-assisted circle drawing program in question) just download the file and run it. Or (as I learned only fairly recently), you can copy the text in the program listing in your web browser and paste it directly into Altirra. Even then, some errors can occur depending upon how "clean" the text is that you're copying.

Edited by Brad Nelson
Link to comment
Share on other sites

Here's a procedure for drawing a triangle in FastBasic. Granted, it's a bit klunky. And any procedure such as this would have to error-check (for not drawing off the edge of the screen) for whatever graphics mode you are in.

 

The error routines in this one are customized for Graphics 8 and simply restrain the vertex of the triangle from going off the edge of the screen, ignoring other parameters – sort of a "squish against the edge" effect. Obviously other possibilities exist such as "Draw nothing" or "If small enough to fit on screen, reposition center point of triangle so that it doesn't run off the screen." An even better technique might be to draw as close to the edge as you can and then stop. But that's more complicated and I'm not sure offhand how to implement that. But I like the "squishy" effect and it is simple to do.

 

I could find zero information on the web for drawing a triangle in BASIC. So I went to that great vomitorium of information, ChatGPT, and was able to cobble something together from its often erroneous suggestions. The first time I asked it to give me a formula for an equilateral triangle it gave me one for an isosceles. But even that was of some help.

 

The "angle" parameter is not the true angle, per se. But it does decide how acute or obtuse vertex A (the top one) is. And it is proportional in regards to the length of the base. I suppose it could have been called "base length" or "base". An angle parameter of 49 produces (near enough) a right triangle. Anything under 49 (48-2) is obtuse. Anything over 49 (50-1000) is acute.

 

If you want to produce a scaler triangle (no equal sides), that's not programmed as an option yet...other than squishing one vertex against the edge of the screen. I suppose the way to do that would be to have the lengths of the three sides as parameters. But I don't think that's doable with the routines in there now. Also, there is no parameter to rotate the triangle. I suspect for those with knowledge of trig that's probably not too difficult.

 

EXEC TRIANGLE 160,96,75,85,1

PROC TRIANGLE CX CY SIZE ANGLE FILL

'ANGLE = RIGHT: 0
'ANGLE = EQUILATERAL: 1 OR 85
'ANGLE = ISOSCELES: 2-1000

'CX = X-COORDINATE OF THE CENTER
'CY = Y-COORDINATE OF THE CENTER
'SIZE = RELATIVE LENGTH OF SIDES
'1 = FILL, 0 = NO FILL

IF ANGLE=1
  TYPE%=.85
ELSE
  TYPE%=(ANGLE/100)
ENDIF

' CALCULATE COORDINATES OF VERTICES
X1 = CX - INT(SIZE / TYPE%)
IF X1<0 THEN X1=0
Y1 = CY + INT((SQR(3) / 2) * SIZE)
IF Y1>191 THEN Y1=191
X2 = CX + INT(SIZE / TYPE%)
IF X2>319 THEN X2=319
Y2 = Y1
X3 = CX
Y3 = CY - INT((SQR(3) / 2) * SIZE)
IF Y3<0 THEN Y3=0

GRAPHICS 8+16
COLOR 1
PLOT X1, Y1
DRAWTO X2,Y2
IF ANGLE=0
   DRAWTO X1,Y3
ELSE
   DRAWTO X3,Y3
ENDIF
DRAWTO X1,Y1
FCOLOR3
IF FILL<>0 AND ANGLE<>0
   PLOT X3,Y3:FILLTO X1,Y1
ELIF FILL<>0 AND ANGLE=0
   PLOT X1,Y3:FILLTO X1,Y1
ENDIF
'COLOR 1:PLOT CX,CY 'DRAW CTR POINT IF DESIRED
  
ENDPROC

 

Edited by Brad Nelson
Link to comment
Share on other sites

Hi @Brad Nelson

 

Random comments on your quest.

 

There are a lot of algorithms for drawing circles, with different tradeoffs. In the Atari, floating point is slow, so it will be better to use an algorithm that uses integers only. Of those, the most common is the bresenham (also called "midpoint") circle drawing algorithm, this is what you had in your post at Tuesday, the one with only integer variables.

 

Instead of talking about "obliqueness", IMHO it is much clearer to talk about drawing ellipses, and I think that the best interface is to simply pass the horizontal and the vertical radius as two parameters - this way you can draw the circle any way you want.

 

There are other much faster algorithms if floating point calculations are fast, this is an example that works "fast enough" in FastBasic:

 

GRAPHICS 8

' Test many different radius
FOR R2=2 TO 90 STEP 10
  FOR R1=1 TO 91 STEP 3
    ' Clear screen
    MSET DPEEK(88),40*190,0
    ' Draw circle recording the time
    COLOR 1
    TIMER
    EXEC CIRCLE 150, 91, R1, R2
    ' Show TIME and both radius
    ? TIME; " "; R1; " "; R2
  NEXT
NEXT

GET K ' Wait for key before exit


' Draws an ellipse centered at (CX, CY), with horizontal radius RX and
' vertical radius RY.
PROC CIRCLE CX CY RX RY

IF RX <= 0   ' Special case RX=0
  PLOT CX, CY-RY : DRAWTO CX, CY+RY
ELIF RY <= 0 ' Special case RY=0
  PLOT CX-RX, CY : DRAWTO CX+RX, CY
ELSE
  XI=0:YI=RY ' Starting integer coordinates
  X%=0:Y%=RX ' Starting float coordinates
  FC%=RY/Y%  ' The scaling factor RY/RX
  REPEAT
    ' Calculate new X,Y coordinates by rotation
    TMP%=X% - 0.02 * X% + 0.2 * Y%
    Y%=Y% - 0.2 * X%  - 0.02 * Y%
    X%=TMP%
    ' And convert to integer
    XD=INT(X%) : YD=INT(FC% * Y%)
    ' Plot from old to new coordinates
    PLOT CX+XI,CY+YI:DRAWTO CX+XD,CY+YD
    PLOT CX-XI,CY+YI:DRAWTO CX-XD,CY+YD
    PLOT CX+XI,CY-YI:DRAWTO CX+XD,CY-YD
    PLOT CX-XI,CY-YI:DRAWTO CX-XD,CY-YD
    ' Copy new to old coordinates
    XI=XD:YI=YD
    ' Until we cross the X axis
  UNTIL YD<=0
ENDIF

ENDPROC

 

The algorithm works based on the fact that if you rotate a point in a circle (X,Y) by an angle A, the next point is at coordinates ( X*COS(A)+Y+SIN(A) , Y*COS(A)-X*SIN(A) ).

In the example above, this is approximately A = 0.2 rad, so SIN(A) = 0.2 and COS(a) = 0.98 = 1-0.02. I selected those values because in the Atari mathpack multiplying by a number with many zeroes is faster, and 0.2rad is 11.5 degrees, about 1/32 of a circle, a good enough approximation.

 

It is possible to implement the above in integer math, but with the lower resolution the circles will be deformed - it will mostly work for big circles but not small ones.

 

Have Fun!

 

  • Like 2
Link to comment
Share on other sites

Hi @Brad Nelson!

 

Another random comment:

 

About passing string parameters. You can't pass string parameters currently, but you can pass a pointer to a string.... like so:

' Sample of "INSTR" procedure that searchs inside a string
' --------------------------------------------------------
A$ = "a big text with many words to search into"
B$ = "any"
i=0

EXEC Instr &A$, &B$, &i

if i
  ? "Found at "; i
  ? "->"; A$[i]
else
  ? "Not found"
endif

' Parameters:
'  StrBase: address of string to search into
'  StrSearch: address of string to find
'  PtrReturn: address of integer variable with the result
PROC Instr StrBase StrSearch PtrReturn
  for StrPos=LEN($(StrBase))-LEN($(StrSearch)) to 1 step -1
    if $(StrSearch) = $(StrBase)[StrPos,LEN($(StrSearch))] then Exit
  next
  dpoke PtrReturn, StrPos
ENDPROC

 

The above example also illustrates passing the address of a variable (the third parameter) to the procedure, that then stores the result inside the variable, and also uses the fact the the FOR variable is 0 at the normal ending of the loop, signaling that the string was not found.

 

Have Fun!

 

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

Thanks, dmsc. That works fine. It takes a little bit of mental gymnastics to do it. But that's what we're here for. :D

 

If one really needs to pass a string in the parameter of a procedure, yours would be the method. Here's the revised code for the "INSTRING" function. I simply sort of one-to-one altered what I already had.

 

'REVISED TO ACCEPT STRING PARAMETERS PER PROFESSOR DMSC
'POS IS THE POSITION IN THE SOURCE STRING TO BEGIN THE SEARCH
'SRC$ IS THE STRING TO BE SEARCHED (ADD AN "&" TO THE FRONT OF WHATEVER STRING NAME YOU USE)
'FND$ IS THE SEARCH TERM (ADD AN "&" TO THE FRONT OF WHATEVER STRING NAME YOU USE)
LOC=0 ' TEMPORARILY NEEDED FOR TESTING (IN ORDER TO PRINT OUT LOC)

EXEC INSTR POS, &SRC$, &FND$
? LOC

PROC INSTR POS WHOLE PART
C=LEN($(WHOLE)):D=LEN($(PART))
FOR A = POS TO C
  IF $(WHOLE)[A,D]=$(PART)
    LOC=A
    EXIT
  ENDIF
NEXT A
ENDPROC

'PREVIOUS VERSION

EXEC INSTRPREV POS
? LOC

PROC INSTRPREV POS
C=LEN(SRC$):D=LEN(FND$)
FOR A = POS TO C
  IF SRC$[A,D]=FND$
    LOC=A
    EXIT
  ENDIF
NEXT A
ENDPROC

 

Edited by Brad Nelson
Link to comment
Share on other sites

There are other much faster algorithms if floating point calculations are fast, this is an example that works "fast enough" in FastBasic:

 

That's an understatement, dmsc. In the procedure I had, it took 2.2 seconds to draw a large Graphics 8 ellipse. Your example did it in 0.3 seconds (using faster floating-point ROMS in Altirra). I'll revise my procedure, for sure. Thanks very much.

Edited by Brad Nelson
Link to comment
Share on other sites

4 hours ago, Brad Nelson said:

In the procedure I had, it took 2.2 seconds to draw a large Graphics 8 ellipse. Your example did it in 0.3 seconds

For once, this ran faster than in TB. I tried it in FB, and was getting times around 0.4 seconds for the largest circles. In TB, I was getting 0.6 seconds.

 

I attribute this to the integer capability in FB. TB only works in floating-point, which is more taxing.

  • Like 1
Link to comment
Share on other sites

For once, this ran faster than in TB.

 

Way to go, TB (and Mark)! Either way is good because that really is a faster method. In all my Googling, I had never run across the Bresenham or "Midpoint Circle Algorithm." And if I had, it wouldn't have done me much good because nowhere did I see it rendered in BASIC, of whatever dialect. It was all Greek to me. I suspect that dmsc graciously took the time to do a BASIC version.

 

I was curious as to the speed of the "pass pointer to a string" method suggested by dmsc for the INSTRING (INSTR) procedure for FastBasic. Via a loop, I searched 100 times for "SUB" in "THE QUICK BROWN PROCEDURE JUMPED OVER THE LAZY GOSUB". Both times were 5.1 seconds. I really didn't expect much of a difference but you never know until you test.

 

I have used the INSTR command over the years to good effect. When doing extensive text manipulation, it's very handy. I'm sure its version in Python and other languages is handy as well.

Edited by Brad Nelson
Link to comment
Share on other sites

This is sort of a proof-of-concept or practical demonstration for FastBasic. I took the fast ellipse procedure that was provided by dmsc (pretty much as-is) and added an error-handling procedure. The entire program below will draw 10 random ellipses in Graphics 8+16. We might even get a true circle if we're lucky. Daddy needs a new pair of shoes.

 

There is bounds checking for the ellipses. It won't try to draw them off the edge of the screen. This particular error procedure is customized for Graphics 8+16. But I suppose it could be universalized (to some extent?) if there is a way for the BOUNDS_CHECK procedure to know which graphics mode is in use. Offhand, I don't know where to PEEK to find that.

 

 

GRAPHICS 8+16:COLOR 1:ERROR=0:GOOD=0
TIMER 'FOR TESTING
'CX=160:CY=96:RX=80:RY=40 ' SAMPLE TYPICAL PARAMETERS

REPEAT
  CX=RAND(320):CY=RAND(192)
  RX=RAND(160):RY=RAND(96)
  EXEC BOUNDS_CHECK
  IF ERROR<>1
     GOOD=GOOD+1
     EXEC CIRCLE CX, CY, RX, RY
  ENDIF
UNTIL GOOD=10


'EXEC CIRCLE CX, CY, RX, RY
ELAPSED%=TIME 'FOR TESTING
GET K 'FOR TESTING
? "TIME: ";ELAPSED%/60.0 'FOR TESTING


PROC BOUNDS_CHECK
'ERROR HANDLER GRAPHICS 8+16 (320X192)
ERROR=0
IF RX>160 OR RY>96
   ERROR=1 'RADIUS TOO LARGE
ENDIF
IF RX>CX OR RX>(319-CX)
   ERROR=1 'CTR POINT ERROR
ENDIF
IF RY>CY OR RY>(191-CY)
   ERROR=1 'CTR POINT ERROR
ENDIF
ENDPROC


PROC CIRCLE CX CY RX RY

' DRAWS AN ELLIPSE CENTERED AT (CX, CY)
' HORIZONTAL RADIUS RX, VERTICAL RADIUS RY

IF RX <= 0   ' SPECIAL CASE RX=0
  PLOT CX, CY-RY : DRAWTO CX, CY+RY
ELIF RY <= 0 ' SPECIAL CASE RY=0
  PLOT CX-RX, CY : DRAWTO CX+RX, CY
ELSE
  XI=0:YI=RY ' STARTING INTEGER COORDINATES
  X%=0:Y%=RX ' STARTING FLOAT COORDINATES
  FC%=RY/Y%  ' THE SCALING FACTOR RY/RX
  REPEAT
    ' CALCULATE NEW X,Y COORDINATES BY ROTATION
    TMP%=X% - 0.02 * X% + 0.2 * Y%
    Y%=Y% - 0.2 * X%  - 0.02 * Y%
    X%=TMP%
    ' AND CONVERT TO INTEGER
    XD=INT(X%) : YD=INT(FC% * Y%)
    ' PLOT FROM OLD TO NEW COORDINATES
    PLOT CX+XI,CY+YI:DRAWTO CX+XD,CY+YD
    PLOT CX-XI,CY+YI:DRAWTO CX-XD,CY+YD
    PLOT CX+XI,CY-YI:DRAWTO CX+XD,CY-YD
    PLOT CX-XI,CY-YI:DRAWTO CX-XD,CY-YD
    ' COPY NEW TO OLD COORDINATES
    XI=XD:YI=YD
    ' UNTIL WE CROSS THE X AXIS
  UNTIL YD<=0
ENDIF

ENDPROC 

 

 

 

Edited by Brad Nelson
Link to comment
Share on other sites

I've expanded and revised the FastCircle drawing program for FastBasic. This is more proof-of-concept than anything. I don't know how transportable it is. The only means I found to determine the graphics mode you are in is by peeking into where low screen memory starts. And because some graphics modes use the same amount of screen memory, this method is not definitive, to say the least. But I did export it as an XEX and tried it on the old Atari800Win Plus 4.1 emulator and it worked.

 

But for quick-and-dirty it will do in a pinch. It gives you the absolute and amazing freedom to draw ellipses in not one, not two...not even three. Act now and you get FOUR graphic modes for your ellipses.

 

Granted, there isn't all that much point to drawing circles and ellipses in Graphic 3. It's a little chunky for that. But you have modes 8, 7, and 5 to choose from as well. For now, the colors will be randomized between Color 1, Color 2, and Color 3 for Graphics 3, 5, and 7 (with the background color, Color 0, being left to the default). Graphics 8 is restricted to Color 1 (and is automatically set for that).

 

It still runs reasonable fast, although obviously the cavalcade of circle-drawing does bog down a bit with all the error checking and such. But for me it's an exercise in learning about the Atari and just having a bit of elliptical fun.

 

TIMER

EXEC MAIN
ELAPSED%=TIME
GET K
? "TIME: ";ELAPSED%/60.0
END

PROC MAIN
MPL=0:ERROR=0
GRAPHICS 8+16
EXEC GET_MODE
REPEAT
  CX=RAND(320/MPL):CY=RAND(192/MPL)
  RX=RAND(160/MPL):RY=RAND(96/MPL)
  EXEC BOUNDS_CHECK
  IF ERROR<>1 AND RX>1 AND RY>1
     GOOD=GOOD+1
     IF MPL>1
       COLOR (RAND(3)+1)
     ELSE
       COLOR 1
     ENDIF
     EXEC CIRCLE CX, CY, RX, RY
  ENDIF
UNTIL GOOD=10
ENDPROC


PROC GET_MODE
' 41296 = GR.8 : 45152 = GR.7
' 48032 = GR.5 : 48752 = GR.3
MODE%=PEEK(88)+(PEEK(89)*256)
IF MODE%=41296 THEN MPL=1
IF MODE%=45152 THEN MPL=2
IF MODE%=48032 THEN MPL=4
IF MODE%=48752 THEN MPL=8
ENDPROC


PROC BOUNDS_CHECK
ERROR=0
IF RX>160/MPL OR RY>96/MPL
   ERROR=1 'RADIUS TOO LARGE
ENDIF
IF RX>CX OR RX>((318/MPL)-CX)
   ERROR=1 'CTR POINT ERROR
ENDIF
IF RY>CY OR RY>((190/MPL)-CY)
   ERROR=1 'CTR POINT ERROR
ENDIF
ENDPROC


PROC CIRCLE CX CY RX RY

' DRAWS AN ELLIPSE CENTERED AT (CX, CY)
' HORIZONTAL RADIUS RX, VERTICAL RADIUS RY

IF RX <= 0   ' SPECIAL CASE RX=0
  PLOT CX, CY-RY : DRAWTO CX, CY+RY
ELIF RY <= 0 ' SPECIAL CASE RY=0
  PLOT CX-RX, CY : DRAWTO CX+RX, CY
ELSE
  XI=0:YI=RY ' STARTING INTEGER COORDINATES
  X%=0:Y%=RX ' STARTING FLOAT COORDINATES
  FC%=RY/Y%  ' THE SCALING FACTOR RY/RX
  REPEAT
    ' CALCULATE NEW X,Y COORDINATES BY ROTATION
    TMP%=X% - 0.02 * X% + 0.2 * Y%
    Y%=Y% - 0.2 * X%  - 0.02 * Y%
    X%=TMP%
    ' AND CONVERT TO INTEGER
    XD=INT(X%) : YD=INT(FC% * Y%)
    ' PLOT FROM OLD TO NEW COORDINATES
    PLOT CX+XI,CY+YI:DRAWTO CX+XD,CY+YD
    PLOT CX-XI,CY+YI:DRAWTO CX-XD,CY+YD
    PLOT CX+XI,CY-YI:DRAWTO CX+XD,CY-YD
    PLOT CX-XI,CY-YI:DRAWTO CX-XD,CY-YD
    ' COPY NEW TO OLD COORDINATES
    XI=XD:YI=YD
    ' UNTIL WE CROSS THE X AXIS
  UNTIL YD<=0
ENDIF

ENDPROC 

 

Edited by Brad Nelson
Link to comment
Share on other sites

Hi!

40 minutes ago, Brad Nelson said:

 

 

The only means I found to determine the graphics mode you are in is by peeking into where low screen memory starts. And because some graphics modes use the same amount of screen memory, this method is not definitive, to say the least. But I did export it as an XEX and tried it on the old Atari800Win Plus 4.1 emulator and it worked.

 

To know the graphics mode, use PEEK(87) , this is the DINDEX variable from the OS.

 

Have Fun!

 

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

Re: PEEK(87): That's the ticket, dmsc. Thank you very much. And now I see why you said it's best to draw ellipses in terms of horizontal and vertical radii.

 

This was brought home by how relatively easy it was to error-check and confine the bounds of the ellipses to the screen. I think it would take some major kind of math to try to do that with some of the other methods of drawing circles. And when I first thought about doing so with those other algorithms, my eyes glazed over and I quickly perceived that it was over my head.

 

The name of my "MPL" variable in the BOUNDS_CHECK procedure is funny because it originally meant "multiplier." But it soon become apparent that, at least the way I was doing it, that it would be a divider instead. And so it now means "Mode Parameter Liaison."

 

The basics of your ellipses algorithm stays the same throughout this, so anyone who wants a good circle procedure can use that. I've provided some wrappers for other uses or to give some ideas of how to expand on it. At this point, turning this into a bare-bones LOGO-type program wouldn't be all that difficult.

 

And this now accommodates graphics modes 3 to 11. Although it's arguable this program isn't good for much, I can tell you that it has given me a much greater familiarity with the graphics modes just by doing this. And it might similarly aid anyone perusing or using the program. Whatever the case may be, it's fun doing it. Oh, and of all the charts I've used to understand the graphics modes, I've found this to be the best.

 

TIMER

EXEC MAIN
ELAPSED%=TIME
GET K
? "TIME: ";ELAPSED%/60.0
'GET K
END

PROC MAIN
MPL=0:ERROR=0:MODE=0:NI=1
GRAPHICS 11+16
EXEC GET_MODE
REPEAT
  CX=RAND(320/MPL):CY=RAND(192/MPL)
  RX=RAND(160/MPL):RY=RAND(96/MPL)
  EXEC BOUNDS_CHECK
  IF ERROR<>1 AND RX>1 AND RY>1
     GOOD=GOOD+1
     IF MODE=3 OR MODE=5 OR MODE=7
       COLOR (RAND(3)+1)
     ELIF MODE=9 OR MODE=11
       COLOR (RAND(15)+1)
     ELIF MODE=10
       COLOR (RAND(4)+4)
     ELSE
       COLOR 1
     ENDIF
     EXEC CIRCLE CX, CY, RX, RY
  ENDIF
UNTIL GOOD=10
ENDPROC


PROC GET_MODE
MODE=PEEK(87)
IF MODE=9 OR MODE=10 OR MODE=11
   MPL=1:NI=4
ENDIF
IF MODE=8 THEN MPL=1
IF MODE=6 OR MODE=7 THEN MPL=2
IF MODE=4 OR MODE=5 THEN MPL=4
IF MODE=3 THEN MPL=8
ENDPROC


PROC BOUNDS_CHECK
ERROR=0
IF RX>160/(MPL*NI) OR RY>96/MPL
   ERROR=1 'RADIUS TOO LARGE
ENDIF
IF RX>CX OR RX>((318/(MPL*NI))-CX)
   ERROR=1 'CTR POINT ERROR
ENDIF
IF RY>CY OR RY>((190/MPL)-CY)
   ERROR=1 'CTR POINT ERROR
ENDIF
ENDPROC


PROC CIRCLE CX CY RX RY

' DRAWS AN ELLIPSE CENTERED AT (CX, CY)
' HORIZONTAL RADIUS RX, VERTICAL RADIUS RY

IF RX <= 0   ' SPECIAL CASE RX=0
  PLOT CX, CY-RY : DRAWTO CX, CY+RY
ELIF RY <= 0 ' SPECIAL CASE RY=0
  PLOT CX-RX, CY : DRAWTO CX+RX, CY
ELSE
  XI=0:YI=RY ' STARTING INTEGER COORDINATES
  X%=0:Y%=RX ' STARTING FLOAT COORDINATES
  FC%=RY/Y%  ' THE SCALING FACTOR RY/RX
  REPEAT
    ' CALCULATE NEW X,Y COORDINATES BY ROTATION
    TMP%=X% - 0.02 * X% + 0.2 * Y%
    Y%=Y% - 0.2 * X%  - 0.02 * Y%
    X%=TMP%
    ' AND CONVERT TO INTEGER
    XD=INT(X%) : YD=INT(FC% * Y%)
    ' PLOT FROM OLD TO NEW COORDINATES
    PLOT CX+XI,CY+YI:DRAWTO CX+XD,CY+YD
    PLOT CX-XI,CY+YI:DRAWTO CX-XD,CY+YD
    PLOT CX+XI,CY-YI:DRAWTO CX+XD,CY-YD
    PLOT CX-XI,CY-YI:DRAWTO CX-XD,CY-YD
    ' COPY NEW TO OLD COORDINATES
    XI=XD:YI=YD
    ' UNTIL WE CROSS THE X AXIS
  UNTIL YD<=0
ENDIF

ENDPROC

 

Edited by Brad Nelson
Link to comment
Share on other sites

One of the interesting aspects of using both FastBasic and Turbo-BASIC XL is that I find myself often going to TB to test an idea. The integer/floating-point division (as it works in FastBasic) will have me testing complicated formulas in Turbo-BASIC XL which is much more forgiving.

 

Or you can just set up TB (or even a second instance of FastBasic) to quickly test an idea. And given that the editors of both are different in how they handle insert/delete, I find myself often making mistakes...especially when just taking a moment to compose something in TB. But I wouldn't say that one is better than the other, although I find myself far less accidentally typing over or erasing stuff in FastBasic.

 

As for the line-number-based method compared to basically the free-form text editor in FastBasic, I've adapted quickly to the free-form. I now feel extremely constrained in TB with the inability to scroll UP the screen to access some line of code. And it's just way easier to edit or even to just quickly try something new.

 

And the procedure-based programming (vs. gosub, goto) does obviously have its benefit, although I'll admit that if I was dubious about the prospect because I had seen (to my eye) so many badly-done and all-but-unreadable procedures here and there around the web. It's no use if a programming technique is technically better if you can't make heads or tails of it. And in my humble opinion, I would say that the better "form" of procedural programming has often hid a lot of sloppy "function" as conceit often trumped just plain clarity. "I'm using a big-boy programming language, therefore whatever I type in it must be gold." Well...maybe.

Link to comment
Share on other sites

If you want to play the Draw the Ellipse party game at home, attached are the FastBasic and Turbo-BASIC XL versions. Now added to this Graphic Mode Demo/Ellipse Procedure program (the ellipse procedure portion provided by dmsc) are graphic modes 14 and 15. That's all the graphic modes now. A couple of these modes I was barely aware of. But I suppose they all have their uses.

 

Run it and it will loop through all the modes, starting with Graphics 3, and draw 10 random ellipses in each mode...in random colors, if available.

 

Converting to TB from FastBasic wasn't too difficult. Obviously I had to add line numbers which Notepad++ made very easy. I had to restructure FastBasic's "IF/ELIF/ELIF/ELSE/ENDIF" (an extremely useful structure) to the "IF/ELSE/ENDIF" Turbo-BASIC XL method (a less useful structure). I knew my choice of "ERROR" as a variable name was problematic. But FastBasic was just fine with it. But TB was not so pleased so I changed them all to "EARL." It was the first word I thought of.

 

All "%" floating-point references had to be removed because TB doesn't understand the "%" after a number or variable. And, of course, the remarks had to be searched-and-replaced from an apostrophe to the normal "REM". The explicit parameter listed after the procedure names had to be removed. But because all those variable were being defined globally elsewhere, it caused no problems.

 

Once I worked through the error messages that popped up in the line listing when opening the exported file in Turbo-BASIC XL, I finally got no errors. And I ran it and was surprised it work, although I had a logic error in the conversion of the "IF/ENDIF" constructs that didn't allow it to choose Color correctly. Fixed.

 

But it was an exercise that showed just how much these two programs have in common and that they can both be used in concert. Both, I would say, are extraordinary dialects of BASIC.

fastcr10.tbs fastcr10.fba

Link to comment
Share on other sites

@Brad Nelson - Since you expressed interest in my project (assembler for VM), I thought I'd mention I've posted some related code. I still don't have the assembler in a condition I can put out there, but I've posted the inspiration for it on my Github account. I've called it "Parr VM," after Terence Parr, who demonstrated it years ago:

 

https://github.com/marktmiller/atari-8-bit-projects/tree/main/parr-vm

 

He originally implemented it in Java. The logic looked simple enough that I thought maybe Basic could handle it. My first shot at it was in Atari Basic, which...really drove home why I would've found this a challenge if I'd tried to do this as a kid. The skill required wouldn't have been the difficulty. Not having an if-then-else construct was really noticeable. I had to emulate it (Instead of testing for the positive case, you test for the negative case, branch to an "else" location if that's true, and otherwise drop through for the "then" logic. Clunky). Once I understood TBXL, I converted these VM programs over to it, which is what I have listed on Github.

 

These listings (VM1.TBS - VM5.TBS) follow along with Terrence Parr's examples in a video I linked to in the README. I wrote them the way he demonstrated them, which was writing up the logic for the operators in the VM, and then initializing an array with the bytecodes for the program he wanted to run, and then running it. In other words, the program the VM runs is hardcoded into it, in these examples. So, you can just load up each program, and run it, to see what the result of running the bytecodes does.

 

The first example (VM1.TBS) is just running 4 bytes of code. All he does is push a value on the stack, and then print it (which pops the value off). The last examples (VM4.TBS and VM5.TBS) run a factorial algorithm, running a recursive function (and setting it up to run the factorial of 2, which is 2).

  • Like 1
Link to comment
Share on other sites

I thought the first thing to do was to watch a little bit of the video to get a generally idea of what's going on.

 

Mark, that project must rate at least an 8.5 on the Geek-O-Meter. Very interesting, although it's a few tiers above my geek grade.

 

I had to do some jiggering to get your TBS files to load and run them. (I used the "download raw file" option.) They didn't run directly in TB. I opened them in Notepad++, copied the listing (because the files seemed to be protected), pasted into a new file (making sure the coding was set to "ANSI" first), replaced the line endings (search for"\r\n", replace with "›"), saved, and then opened in TB with the "ENTER" command. If there's a better way to do this or something I'm missing, let me know.

 

After running VM1 I got:

0: ICONST 99

2: PRINT

99

 

After running VM2 I got:

0: ICONST 99 STACK=[ 99 ]

2: PRINT 99 STACK=[ ]

 

VM3 gave me more extensive results.

VM4 game me quite an extensive list of results...over 22 lines or so.

VM5 seemed to give about the same results as VM4. If there were differences, I didn't offhand notice them.

I hope those are good results. I didn't see any triangles or ellipses being drawn, but I'm pretty sure something was working right. :)

 

I hope you are able to complete this to the point where you can share a demo and explain the workings. The programming looks quite complex. Good luck.

  • Like 1
Link to comment
Share on other sites

@Brad Nelson - In order to get the source code to display in Github, it needs to use "traditional" encoding, not ATASCII. With some of the other stuff I've done, which is also on my Github account, I've resorted to uploading listings in PDF, because the program used control characters. In that case, I accompanied it with a tokenized Basic file, so people wouldn't have to type it in to run it.

 

I don't know what other emulators are doing. I use Atari800MacX. It has some hard drive spec's you can use that translate between ASCII and ATASCII automatically. I LISTed out the code into ASCII. So, the listings need to be converted back into ATASCII, and then ENTERed into Turbo Basic, to run. It looks like you got them running alright. Hopefully, it wasn't too much work. :)

 

Yes, the first couple VM programs are super simple, just pushing a value on the stack, and then printing (and popping) it. The later ones run a factorial algorithm. (They're set up to get the factorial of 2,...which is 2. As I recall, that was Parr's first example for factorial.)

 

After I put up my note, I uploaded another, more fleshed-out program to run bytecode, called INTVM.TBS, for "'interactive' VM." It sets up a minimalist environment, where you can type a few commands, to load programs, list them, and run them. It works with some binary files I uploaded (so, no need to convert them between ASCII and ATASCII) that contain serialized bytecode, with a .BCD extension. I have a few files up there that run the factorial algorithm (this time for the factorial of 5, which is 120), and a couple algorithms I wrote up to compute Fibonacci numbers (getting the 8th Fibonacci number, which is 21).

 

This is all pretty low-level right now. Most of what I did was translate Parr's VM from Java to Turbo Basic. He didn't get into graphics, just math stuff. So, I didn't go much beyond that. Though, as you know, this is the sort of thing that's necessary for the neater stuff. :) It's a work in progress, and really, the reason I'm doing it is to learn how to do it. I'm not trying to create anything a developer would use, just yet, to write software. I'm just trying to get an idea about what a stack machine is, and what it can do.

 

What's funny is after I'd been working on my assembler for a while, I found out that someone had ported an old 8-bit project called Chip-8 to the Atari back in the 2010's or 2000's. I hadn't heard of it until this year.

 

It's a similar idea, a stack machine, as I remember, but it has graphics, of a certain resolution, and I think simple beeps for sound. It was developed for 8-bit computers back in the 1970s or 80s, and had been ported to a bunch of platforms. It has an assembler you use to write programs for it.

 

The point is the bytecode is portable between machines (It's funny now, how many times I've seen this, of projects that were done years before Java came along with its slogan of "Write Once, Run Anywhere"). It's gone through a bunch of versions, too, with feature additions, over the years.

Edited by markmiller
  • Like 1
Link to comment
Share on other sites

@Brad Nelson - Speaking of other instances before Java, I was surprised to learn some months ago that Pascal, a language I used in high school and college, was a very early one. It may have been the first. I think the first version of the language by Nicklaus Wirth compiled to machine code, in the early 1970s, but he also had the idea that he'd like Pascal programmers to be able to share their programs, without having to compile them from source code. My guess about why he wanted that was that computing resources were expensive. Computer time was limited, as was memory and storage space. He came up with a version that compiled to what he called p-code, or "portable code." It was bytecode that ran on a stack machine. He released a package he called the "p-kit" with a Pascal compiler, and a VM.

 

Some people at the University of California San Diego (UCSD) extended this idea in the mid-70s, writing an operating system in Pascal p-code, and running it on a VM. This is what became known as UCSD Pascal, or the P-System.

 

Little did I know, I actually used this when I was in high school. My school had a copy of Apple Pascal that ran on the Apple IIe, and it was a version of the P-System. I remember it came on a bootable disk, and had this odd thing where it used its own disk format that was different from DOS 3.3, and ProDOS. You had to format a disk in it just to save your programs. Supposedly, this disk format was portable with P-Systems on other platforms (the others I know about were available for the TI-99/4A, and the IBM PC). I recently watched a video of someone using a P-System card on his TI computer (he also used a boot disk, so it seems part of it was in ROM, and part was loaded into RAM), and it operated just like on the Apple IIe. The OS was menu-based. It looked like it had similar options.

 

This all sounds great, but the catch was Wirth, and the people at UCSD, didn't think about things like graphics and sound. So, what ended up happening was different P-Systems added their own extensions that did this stuff. If your program used those extensions, they were immediately locked in to those platforms. They couldn't run anywhere else. However, if you stuck to just taking text input, and printing text, they would port fine.

 

The platform never took off as a bastion of portable software. So, we didn't have "write once, run anywhere" in the 1970s or 80s, as we've had (for the most part) since the 2000s with Java. There have been portability problems with Java. I saw them when I worked with it in the mid- and late '90s. I don't know if those problems have been resolved since. Most Java developers I've talked to haven't run into them.

  • Like 1
Link to comment
Share on other sites

I don't know what other emulators are doing. I use Atari800MacX. It has some hard drive spec's you can use that translate between ASCII and ATASCII automatically. I LISTed out the code into ASCII. So, the listings need to be converted back into ATASCII, and then ENTERed into Turbo Basic, to run. It looks like you got them running alright. Hopefully, it wasn't too much work.

 

I just assumed that most of the Free World was using Altirra. To the best of my knowledge, Alitrra is still under development and AtariWin800 Plus (PC version) has been stuck at 4.1 for ages. But it works well and that's what matters. I thought Altirra had the majority of the bells-and-whistle. But from what you're saying, Atari800MacX (which indeed is continuing to be developed) has a few tricks up its sleeve.

 

I have Atari800MacX on my iMac. But that's my business machine so, generally speaking, I do very little game playing on it because I can't afford to mess anything up so nearly all my emulating is on a Windows 10 machine. But, yeah, these emulators aren't going to do anything to it. But, still, I just shy away from messing with the iMac workhorse. Atari800MacX works well on the iMac although I find the interface a bit flaky. Or maybe it's just me.

 

I'm certainly not a "power user." But I do like the ability to "paste" text into Altirra. That usually works pretty good. Maybe you can do that in Atari800MacX. I don't think I've every tried. It's very nice for short routines that you run across and just want to see what it does.

 

And it wasn't very much work converting your program. I'm pretty sure I should be able to streamline that. I've only scratched the surface of Notepad++. I do see it has a Macro menu item. So I really should get off my butt and automate. I'll report back on any success I have with this.

 

I tried out INTVM.TBS. It took me a while to figure out how to run the BDC files. I read your documentation but couldn't get anything to load. But then it seemed apparent that the program was expecting the "D:" drive. So I changed line 850 to an "H:" and then, after figuring out the syntax of the BYTELD command, I got it to work. I got these results:

 

FIB1: 21 2231

FIB2: 22 399

FACT: 120

 

This is all pretty low-level right now.

 

I believe you!

Link to comment
Share on other sites

This all sounds great, but the catch was Wirth, and the people at UCSD, didn't think about things like graphics and sound. So, what ended up happening was different P-Systems added their own extensions that did this stuff. If your program used those extensions, they were immediately locked in to those platforms.

 

Maybe there's something to be said for just the sheer simplicity of a mostly text-only computer. Regarding Pascal, I'm pretty sure it was the language of choice for creating much of the software (internal or third-party) for the early Macs. I've dabbled in it a bit and it seems a most sensible language.

 

There are obviously a variety or interests that draw people to retro computing. But I think one of them is the relative simplicity. I would suppose that even advanced programmers such as yourself must find some charm in it. I do find it fun to actually accomplish something on a system that is reasonably comprehensible (and the same could be said of most other 8-bit systems.)

 

We live in an era where the graphics have reached the level (or nearly so) of real-life. I remember walking by a TV in Best Buy, and this was several years ago. I thought there was a football game on. But a second (and closer) glance show that it was a football video game program.

 

Personally, I think most people know that 95% of the culture out there is artificial and fake. I guess when you're playing a video game, that's exactly what you want, and it can be a lot of fun. But there's something real about a single "POKE" changing your screen color. There's something real (even in an emulator) about the comprehensibility of single pixels.

 

And then you see what modern and quite talented programmers and hardware specialists are doing with these 8-bit machines, and you know there is a lot of capability that remains in them.

 

I have ten retro systems in view from where I sit, including two XL's (one needs a little fixing) and one 800. But there are just so many advantages to an emulator. But the hardware is there. And it does still indeed get used. And it's certainly just fun to look at. And I think that no gaming or small computer systems have surpassed the design of the early Atari 8-bits. But certainly the Apple IIs were an extraordinary design as well.

Link to comment
Share on other sites

3 hours ago, Brad Nelson said:

FIB1: 21 2231

FIB2: 22 399

FACT: 120

Hmm. A typo in your comment, perhaps? When I run FIB2, I get 21 399. It should get the same Fibonacci number (21) as FIB1.

 

Re. pasting into the emulator

 

I've also used that with Atari800MacX. I've sometimes written up some code in TextEdit, and copied and pasted it into the emulator. A bit slow doing it that way, though, if you're inserting a lot of code, because, at least in Atari800MacX, it emulates someone literally typing what's being pasted. It can't "type" too fast, or else the Atari will miss keystrokes. Even so, I've seen that happen sometimes.

 

The ability to copy text out of Atari800MacX is pretty nice. You can draw a rectangle around whatever text fits on the Atari screen, do a copy, and paste into something like TextEdit. The formatting is a bit funky on the paste. I always need to correct it, since it inserts lots of needless spaces at the ends of lines. It seems like it fills in the selection rectangle with spaces, wherever there are no characters inside the selection window.

 

Re. INTVM, and D: specification

 

Good for you, for figuring out how to change the code to what you needed. :)

 

Yes, I thought about that after I posted it. I did have it look for "D:" or "D<number>:" I think I should change that, to make that more flexible.

 

I used a Windows computer many years ago. I've been using a MacBook Pro since 2008. I was able to run whatever Windows software I wanted by using either a VM (nowadays, I use VirtualBox), or Wine. I used Altirra in Wine for a bit, which ran acceptably. Since about 2019, I haven't been able to use Wine, because Apple removed all support for 32-bit software, and as I remember, the Mac version of Wine fell out of maintenance after 2014. So, there was no prospect of it being updated to run in a 64-bit environment, while still running 32-bit software. I heard about a fork of it that would run in 64-bit, but it could only run 64-bit Windows software. Maybe Altirra would run in that? For the other Windows software I wanted to run, that wouldn't work, though.

 

I use VirtualBox occasionally. It runs 32-bit Windows stuff fine, but it's a memory hog, and runs slower than Wine did. Wine has a much lighter footprint. For whatever reason, Altirra locks up in VirtualBox. So, I've been staying with Atari800MacX. I like it, for the most part.

 

I really came to appreciate its machine monitor when I was working in assembly language, since I was working on a vertical blank interrupt routine, and for some reason, any assembler I use doesn't seem to like that. I'd set the interrupt vector, and expect to see my routine run, and...nothing. I put some test code in my routine, so I could see if it even ran, and it didn't. It was like the assembler was blocking any attempt to change the interrupt vector, which is what you have to do to run a VBI routine. I had an initialization routine that would set the vector to my routine, and then exit. The OS takes over running the routine every vertical blank cycle of the display (every 60th of a second). The initialization would run, but the vector wouldn't change. The only way I could run it was from DOS, but then I had nothing to use if I wanted to debug it, except for Atari800MacX's machine monitor. I kind of dreaded it, since I'd never used a machine monitor before, but it worked out pretty well.

 

A monitor allows you to examine memory in an organized fashion, and change memory locations in real time. When I knew some expert Apple II hackers in high school, I saw them go into its machine monitor to look at memory locations, and enter machine code by hand, to try stuff out, all in hexadecimal. Back then, I didn't want to get anywhere near that.

 

In Atari800MacX's monitor, I was able to set breakpoints, and step through code, one instruction at a time, or just let 'er rip, and let it run. A really nice feature is it disassembled my routine, so I could see 6502 mnemonics, and it even inserted virtual comments, labeling memory map locations, so I could differentiate between the memory locations I'd set up, and the locations the OS uses. The emulator was doing all of it. I'd go back to the assembler, running inside the emulator to edit code, and re-assemble, and use the monitor to debug.

 

I reflected on how I'd have a harder time doing this on a real Atari, since I'd have to add some hardware to get a machine monitor, probably using the XE's expansion bus. I remember when I was in college, I had a friend with an Atari 800, and he had an Omnimon card in it, which was a machine monitor.

 

From what I hear, Altirra is the most actively developed emulator, and the reason Atarians like it so much is it's the most accurate. It emulates the Atari down to the clock timing, so its responses are exact, compared to the real hardware. I've heard complaints from some people re. how emulators run Atari software differently from the way it appears, or sounds, on real hardware.

Link to comment
Share on other sites

4 hours ago, Brad Nelson said:

Regarding Pascal, I'm pretty sure it was the language of choice for creating much of the software (internal or third-party) for the early Macs.

That's my understanding. I've even heard some version of Pascal was used to write the first version of System, the Mac's first OS. This would not be the p-code version of the language, though.

 

I don't really know, but I think the version of it I used in college compiled to machine code. That seemed to be the mode that people preferred. Turbo Pascal from Borland was a popular language in the 1980s for software developers on the PC, and it optimized code, and compiled to machine code. That was the reason people liked it. It was fast. I think this gave way to C, though, by the time I graduated.

 

I remember Pascal felt a bit too strict for me. I rolled my eyes when I learned that if you wanted to pass an array to a procedure, you could only do it by using a user-defined type, and then using that type name for a certain type of array, of a specific length, and the procedure could only accept an array of that type, and that length! I can understand array type strictness, but not length strictness! If I define the type to accept 13-character arrays, but I have a 20-character array I want to use with it, shouldn't I be able to pass that in? It felt like overkill.

 

C felt nice while I was in school, but I saw its shortcomings once I got out into the work world, because if you wanted some protection for some things, just to keep your sanity, you had to use it very carefully.

 

What I tell people who want to learn C is learn assembly first, because that will give you a good idea of what the C compiler is actually formulating from your code. What I tell people is it's like a high-level assembler. Others have used the term "macro assembler," as an analogy.

 

Re. going retro

 

I like it, because it's not that intimidating. It feels familiar, because I was an Atari user as a teenager. I'm trying to learn some stuff in the realm of computer science at the system level, and I feel much more comfortable learning that with a simple system, at this point. I figure at some point, I'll grow beyond the Atari's limitations, and so I'll move on to something more advanced, maybe something more modern, or maybe the ST.

Link to comment
Share on other sites

When I run FIB2, I get 21 399

 

Yeah, you're right Mark. That's what I got too and that's what I wrote down on a little piece of paper I have sitting right here. LOL. But it's not what I typed in the post.

 

The ability to copy text out of Atari800MacX is pretty nice.

 

I just noticed that you can do that with Altirra. I hadn't thought of trying it till you mentioned it. It could save some typos as well. It seems to do a pretty good job. It's certainly faster than having to copy it by hand.

 

Yes, I thought about that after I posted it. I did have it look for "D:" or "D<number>:" I think I should change that, to make that more flexible.

 

Here: I just copied from the Altirra screen listing and changed line 850 (now that I know that I can do so). I would assume this would work: IF COM$(8,8)="D" OR IF COM$(8,8)="H". I'm not sure if that would find "H2:" which I have on one of my emulators. Yes, it can get confusing. :) (Yes, it did work for "H2:".)

 

I've been using a MacBook Pro since 2008

 

In the retro lab here, I've got a nearly pristine PowerBook G4 Titanium that a friend gave me a couple years ago. I really love it. It's the main jukebox for running music in the rear retro room. And I've got an old Tangerine iBook that is actually working as a print server for an old HP 5000. (Newer versions of OS X will not talk to it directly.) And one of my favorite retro computers is the PowerBook G3 Wallstreet (Machine ID 314). It runs OS 9.1 for just general gaming purposes. Some guy walked in to work several years ago with it. He had the system software so messed up, it wouldn't boot. I gave him $50.00 for it hoping that I could fix it. It works like a charm, is in very good condition, and has since been upgraded with a daughter card to 266 MHz and 384 MB of memory  with a nice HP monitor hanging off of it.

 

But I'd never owned a MacBook until a couple months ago when I picked up a 2012 MacBook Pro (13") on eBay (Model 9,2). It will natively run Catalina, so Safari works just fine and you get a lot of relatively modern features. I tried the OpenCore Legacy Patcher to try to get an even newer version on it but that was a no-go no matter what I tried. Others got it to work but darned if I ever could. I gave up on it, although I might try again. I suppose they keep improving it.

 

Anyway, the reason I bought it was that I was going to visit a friend in Texas and this would be able to run my suite of software that I had Carbon Copy Cloned from my working drive to an external Thunderbolt drive. I could do a little work on the road, answer emails, etc., so that it all didn't just pile up. But I caught the Wuhan Flu a week before the scheduled trip. But at least I have this neat MacBook Pro. Maybe I can put it to some good retro use. I can't remember which YouTuber said this. I think it was Luke Miana. But the style of these old MacBooks looks as good and as new as anything on the market, including Apple's latest offerings. In some regards (lack of a "butterfly" keyboard), they are better.

 

A monitor allows you to examine memory in an organized fashion, and change memory locations in real time.

 

Yes, I understand the concept having watched Robin at 8-bit Show and Tell do that on the Commodore 64 (or 128). In my own programs (in BASIC), I'm constantly putting little statements to print variables and such so that I can see what is going on.

 

This is where BASIC fills a void. Kudos to those who get under the good and program in assembly, use monitors, deal with displays lists and interrupts, player-missile graphics, etc. And obviously there are hooks to some of that via BASIC. And I tried to wrap my head round display lists a while back and almost got there. But I think I just need a better tutorial. But BASIC allows you to just get in there and go. If you want to learn more, your programs can become more complex and you can thus do more. But the point is, BASIC makes it relatively easy to do something whereas the learning curve for some other languages can be all but impenetrable to even the motivated user.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...