Jump to content
matthew180

Forth Tutorials

Recommended Posts

One final optimisation that is possible to with the above code:

 

Take a look at this fragment of code:

 

MXAX 1-

(please note: that is "1-" *NOT* "1 -" 1- is probably five times faster than 1 -)

 

That MAX 1- kind of troubles me. Why do we need it? We need it because XMAX returns 40 in 40 column mode, therefore the screen columns are from 0 to 39. Because we are looking for 39, we need to subtract 1 from XMAX for our range check (unless we hard code the max column, in which case, you can stop reading now!)

 

It seems a waste that we have to perform that calculation every time at run-time. It would be nice if we didn't have to do it at all. Is that possible? Not entirely, but nearly; clearly the calculation needs to be performed at least once, in order to know the answer! Is there a way we can do that? Yes, there is! Read on! Have a look at this:

 

DUP 68 = IF DROP PX [ XMAX 1- LITERAL ] < IF 1 +TO PX THEN ELSE

 

Huh? What the heck is going on here? Well, this, to use your (and my) parlence, is "some clever sh*t" - that's what it is!

 

To understand what is going on, you need to think 'in another dimension'. Yes. Forth, requires three dimensional thinking, whereas most other languages only require two dimensional thinking! What the hell am I talking about (I dunno! I only came in here for a cup of tea! ).

 

Okay, what I mean about 'another dimension' is this: (prepare for potential brain f**k).

 

When you write code in XB, or C, or PASCAL etc you really have only one 'time' to worry about - run-time. In Forth, you can do stuff with your code *at compile-time* before your code even runs! :-o

 

During the compilation of our word, the word[ forces TF to temporarily *stop* compiling, and just *execute* all the code it finds after the [ (which is just a word like any other word in Forth). So, when it sees [ it then executes (not compiles) XMAX and executes (not compiles) 1-. This results in 39 on the stack, just as if you had typed it in by itself on the command line. Then the word LITERAL is encountered. This causes the value currently on the stack, (in our case, 39) to be 'encoded' into our definition as a "literal" (which is just a posh way of saying "as a number"). Then, the word ] resumes normal compiling.

 

So, instead of our code being compiled as this:

 

DUP 68 = IF DROP PX XMAX 1- < IF 1 +TO PX THEN ELSE

 

It actually ends up (in memory) as this:

 

DUP 68 = IF DROP PX 39 < IF 1 +TO PX THEN ELSE

 

Ta dah! We just made our code more efficient, because we no longer (at run-time - every time a key is pressed) have to work out was XMAX-1 is - we already did it, *at compile time* and used that result, over and over again, at run-time! :-o

 

Don't you just love Forth? :grin:

  • Like 1

Share this post


Link to post
Share on other sites

Thanks alot Lee... that made perfect sense. =)

 

(it's good to have someone else here that can answer the Forth questions - takes some of the pressure off - Thanks Lee!)

 

You're both too kind. I'm happy to oblige when I think I know the answers or know enough to dig them out. Doing so is a sure-fire way for me to get a better lock on Forth. :)

 

...lee

Share this post


Link to post
Share on other sites

Ta dah! We just made our code more efficient, because we no longer (at run-time - every time a key is pressed) have to work out was XMAX-1 is - we already did it, *at compile time* and used that result, over and over again, at run-time! :-o

 

...just don't change GMODE after compiling TEST. :twisted:

 

...lee

Share this post


Link to post
Share on other sites

...just don't change GMODE after compiling TEST. icon_twisted.gif

Yep! We'll leave Owen to figure out why ;)

Share this post


Link to post
Share on other sites

Wow... that's GREAT stuff, Mark. =) Tomorrow should be a good day for me. I had to work this Saturday (tonight) but Sunday I'm taking a bit of time to work on a Forthing project or two.

 

XMAX is an interesting thing... And the optimization ideas are enlightening. And yes... I'm learning to love Forth. =)

 

 

Share this post


Link to post
Share on other sites

Hi Guys...

i founded some old disks of TI99 Forth programs... think you i could open that with turbo forth ? if yes, how i can try ?

 

Thankyou ;)

Share this post


Link to post
Share on other sites

Most of the code should be portable, however the conventions and built-in words different. Mark would have more information than I would about this, but my suggestion would be to try to load the programs or type in some of the smaller ones and see if they work. More than likely (as it seems with most Forths) it will be mostly compatible with some differences in root words. Others would be better suited to answer this than myself, but I thought I'd lend my minute understanding to the conversation.

 

I spent part of Sunday working on GCHAR additions to the test code we had going above... got some neat stuff going... I should be able to show something in the next couple of days. =)

  • Like 1

Share this post


Link to post
Share on other sites

Hi Guys...

i founded some old disks of TI99 Forth programs... think you i could open that with turbo forth ? if yes, how i can try ?

 

Thankyou ;)

 

There are enough differences that porting between TI Forth and TurboForth can be frustrating. For example, the TI Forth word -DUP is the same as TurboForth's DUP?. That may be the least of the porting problems, however. TI Forth uses the disk from sector 0 to the end in blocks of four 256-byte sectors per screen. Though many TI Forth non-system disks have a single file (SCREENS) that takes up the entire free space on the disk and is the same format (DIS/FIX 128) as TurboForth blocks files, they don't have to be so formatted. If the disk of interest to you has this format, the nature of the TI file system knocks things out of order for trying to use the SCREENS file straight up as a TurboForth blocks file. Suffice it to say that screen 0 to half of screen 8 will show up at the end of SCREENS accessed as a file. If the screens of interest to you are after screen 8, you should actually be able to load them if the TI Forth words used are the same as TurboForth words. Let's say you have the disk with the SCREENS file in DSK2. In TurboForth, you should be able to type S" DSK2.SCREENS" USE to get to the SCREENS file. Even if this does not work for you, there are ways.

 

...lee

Share this post


Link to post
Share on other sites

TF is very different from TI Forth. Since TI Forth is still available, I don't see the point in porting TI Forth applications to it - just run them on TI Forth!

 

There is a chap in the USA that is porting an AMS memory overlay system from TI Forth to TF, which will be very useful indeed. In general though, you'd just run TI Forth code on TI Forth, wouldn't you?

 

Mark

Share this post


Link to post
Share on other sites

Hey Willsy... I'm working on something and I thought I'd post it here. I use two blocks, 9 and 10. Basically it tests out GCHAR very simply. One issue I'm having, however--- I think I know why it's doing this, but I'll post it here to hear more informed opinions than my own.

 

 

**sorry for the bad butchering of indentation and formatting

**Block 9**
: TEST															
BEGIN													  
	PY PX 42 1 HCHAR											  
    KEY? DUP											  
	-1 <> IF														
 PY PX 32 1 HCHAR									  
 RCHK DUP 68 = IF DROP PX XMAX 1- < IF 1 +TO PX THEN ELSE	  
    UCHK DUP 69 = IF DROP PY 0 > IF -1 +TO PY THEN ELSE	  
 LCHK DUP 83 = IF DROP PX 0 > IF -1 +TO PX THEN ELSE			
 DCHK DUP 88 = IF DROP PY 23 < IF 1 +TO PY THEN ELSE	  
	 32 = IF EXIT THEN											
  THEN THEN THEN THEN									
	ELSE DROP 2 + THEN AGAIN							  
;
**************************************
**Block 10**

10 VALUE PX 5 VALUE PY										
: RCHK DUP 68 = IF PY PX 1+ GCHAR							  
 32 <> IF 2 +													  
 THEN THEN ;											
: UCHK DUP 69 = IF PY 1- PX GCHAR								  
32 <> IF 2+											  
	THEN THEN ;											
: LCHK DUP 83 = IF PY PX 1- GCHAR							  
 32 <> IF 2 +													  
 THEN THEN ;											
: DCHK DUP 88 = IF PY 1+ PX GCHAR								  
32 <> IF 2 +											  
	THEN THEN ;																											
9 LOAD														
: TRY PAGE 11 6 35 10 HCHAR TEST ;							

 

When I run this, it works wonderfully. I'm kind of taking a roundabout way of doing this, I think, but my first goal was to "get something to work" and then find the "right" way to do it shortly thereafter.

 

The bar in the middle of the screen appears and you move around the screen very nicely. When you encounter the bar, it works as expected and doesn't allow movement. Fast fast fast.... nothing like GCHAR in XB... So I am very pleased thus far.

 

Now to the issue... When you're at the very top row of the screen and moving left, you can move out of the screen causing a crash. Now, the way I handled the "flag" for GCHAR is unconventional at best and probably inefficient, but it works for the most part... I basically skew the key input (if the checked screen location holds something other than a "space" character) so that it won't show up in the IF checks for the remainder of the loop, thereby skipping some steps. It's probably the absolute WRONG way to do it, but I figured it out and it seems to work (for the most part).

 

I'll be back on tomorrow night with some optimizations... I started incorporating all the checks for GCHAR AND screen parameters into one word per direction but haven't finished that yet. UCHK will have GCHAR as well as check for screen parameters... It's about done but not quite. then I'm actually going to draw something to navigate through... a maze of sorts.

Edited by Opry99er

Share this post


Link to post
Share on other sites

I think the code can be better factored ('factoring' is organising your code into logical units that each do a section of job of at hand, and *only* that section of the job).

 

For example, you are checking the keys twice. Take the left key as an example: You check it in LCHK and you also check it in TEST. That is inefficient. If you moved the call to LCHK inside of the IF block in TEST, then LCHK wouldn't need to test is the S key was pressed at all, since LCHK would only ever be called if the key was pressed!

 

I mean from this (in TEST)

 

LCHK DUP 83 = IF DROP PX 0 > IF -1 +TO PX THEN ELSE

 

to this:

 

DUP 83 = LCHK IF DROP PX 0 > IF -1 +TO PX THEN ELSE

 

Now, LCHK doesn't need to know anything about which key was pressed, since it's principle job is to check if the screen position to it's left is vacant. You have 're-factored' LCHK. Consequently, the DUPs and IFs in LCJK related to detecting a left key-press can be removed - they're redundant.

 

Here is how I would approach your 'problem' in TurboForth. I think it is quite nicely factored and almost reads like English. Just paste it directly into Classic99 (if you're running Firefox, otherwise paste it into Notepad then from there copy and paste it into Classic99). Type MAIN_LOOP to run it. Press FCTN 4 to quit.

 

0 VALUE PX  \ player x coordinate
0 VALUE PY  \ player y coordinate

CHAR E CONSTANT KEY_UP
CHAR S CONSTANT KEY_LEFT
CHAR D CONSTANT KEY_RIGHT
CHAR X CONSTANT KEY_DOWN

32 CONSTANT BLANK  \ code for empty space
42 CONSTANT PLAYER \ player character code
2 CONSTANT FCTN4  \ key code for fctn & 4

: ERASE_PLAYER  PY PX BLANK  1 HCHAR ;
: DRAW_PLAYER   PY PX PLAYER 1 HCHAR ;

: DO_LEFT   ERASE_PLAYER -1 +TO PX DRAW_PLAYER ;
: DO_RIGHT  ERASE_PLAYER  1 +TO PX DRAW_PLAYER ;
: DO_UP	 ERASE_PLAYER -1 +TO PY DRAW_PLAYER ;
: DO_DOWN   ERASE_PLAYER  1 +TO PY DRAW_PLAYER ;

: LEFT?  PX 0>   IF PY PX 1- GCHAR BLANK = IF DO_LEFT  THEN THEN ;
: RIGHT? PX 39 < IF PY PX 1+ GCHAR BLANK = IF DO_RIGHT THEN THEN ;
: UP?	PY 0>   IF PY 1- PX GCHAR BLANK = IF DO_UP	THEN THEN ;
: DOWN?  PY 23 < IF PY 1+ PX GCHAR BLANK = IF DO_DOWN  THEN THEN ;

: MAIN_LOOP
 PAGE  12 1 45 38 HCHAR  DRAW_PLAYER
 BEGIN
KEY? DUP CASE
	KEY_UP	OF UP?	ENDOF
	KEY_DOWN  OF DOWN?  ENDOF
	KEY_LEFT  OF LEFT?  ENDOF
	KEY_RIGHT OF RIGHT? ENDOF
ENDCASE
 FCTN4 = UNTIL ;

 

It is possible to make this code smaller but I wanted to show something that was easy to understand and recognisable to most people :). In particular, DO_LEFT, DO_RIGHT, DO_UP, and DO_DOWN could be replaced very simply with one generic routine.

 

Hope the above helps.

Share this post


Link to post
Share on other sites

Owen...

 

You may know this, but reading/writing screen positions is accomplished by reading/writing memory in VDP RAM at 0 to (XMAX * 24 - 1) (the screen image table (SIT)), which for 40 column text mode is 0 - 959 or 0 - 3BFh. GCHAR and HCHAR calculate the VDP RAM byte to access by multiplying XMAX by the passed row value and adding to the result the column value. I don't believe they check to see that your code is staying within the SIT. If your code accesses memory outside the first 960 bytes of VDP RAM for text mode, strange things wll happen as you discovered. Merely, reading a byte "below" the bottom of the SIT will change the foreground and background screen colors to a single color, rendering the screen unreadable. You can prove this by entering 0 -1 GCHAR <enter> and then (even though you cannot see what you are typing at this point) typing $1B SCREEN <enter>. I am not certain what byte GCHAR reads at that point. The calculated value is -1 or FFFFh for 1 position to the left of the the top left corner. It probably wraps to high VDP RAM, but I don't really know---Mark?

 

Of course, your program has likely read from and written to multiple high VDP RAM locations before you can react and may truly have crashed; but, it would be interesting to try $1B SCREEN after first tapping the space bar to end your program.

 

...lee

Edited by Lee Stewart

Share this post


Link to post
Share on other sites

Owen, Willy and Lee ... thankyou for your explanation about TI Forth vf TurboForth ;-)

sorry for my delay in answer but i coming back today at home ;)

Share this post


Link to post
Share on other sites

Good info, Lee and Mark. I'm very appreciative and I will be factoring my code out a bit more. I'm very busy these days at work, but glad to have TF to take my mind off monotony. Gracias...

Share this post


Link to post
Share on other sites

I finally started learning Forth using Brodie's book, but I'm doing it on a Cosmac Elf clone (ELF2K) connected to a Heathkit H19 text terminal. The included Forth in ROM is only a small subset of standard Forth, so it's forcing me to recreate many of the standard words, which is a great learning experience. Once I got over the initial shock of how Forth is structured, it started making a lot of sense. I figured I'll move to Turbo Forth once Willsy publishes the full manual (come on buddy! We're still waiting ;) ). My goal is to have a Turbo Forth program ready for the 2012 Chicago Faire.

Share this post


Link to post
Share on other sites

Well related to Forth (not TI99) but still interesting. This device uses Forth as a test language - http://thewikireader.com/

If you take the unit apart they even included a serial port and yes the unit will respond to a serial connection with a forth interpreter. Even more fun is that you can simply pull out the microsd card, add your forth programs as plain text files with a short menu file as well and run your forth apps on this device. Developer claims to have almost implemented a full ansi standard Forth with new words for various features of the unit. In addition the included sample apps contain a lot of example forth code such as screen drawing. One guy even wrote a forth simulator for it so you can develop code for it without having to pull the microsd card all the time. - http://createuniverses.googlecode.com/files/WikiReaderSim_v003.zip

 

Also...in case this is not enough...what about running forth on a computer built out of TTL chips? - http://www.holmea.demon.co.uk/Mk1/Architecture.htm

 

Hoping we will see more TurboForth apps over the next 8 months. We have a great tool but wow if it does not get used it will be such a waste. Hoping I will get to create Space Empires using TurboForth at least as a starting point.

Share this post


Link to post
Share on other sites

Hi Guys

 

Indeed. I hope people manage to get something from TurboForth. I'm having a blast with it. I wrote a program the other night using it to choose lottery numbers!

 

I'll get back to the book in a week or so and try and get it finished. I'm very very busy right now - real life - you know how it is, and there isn't enough hours in the day. However, it's at the top of my list.

 

I might as well mention, while I'm here, I've updated the TurboForth website with an introduction to what Forth "is" and I've also updated the download page with addition downloads. You can now download the source code, the 32-bit library (with manual), the Floating Point library (with manual) and the Assembler (with manual).

 

If you are not sure about installing this stuff into TurboForth then it's worth mentioning that Classic99 now comes pre-configured with TurboForth V1.1 and the utilities disk with all this stuff (and more) already on it.

 

Turning now to the book, I'm now at just about the most difficult part of the book. Describing what compiliation is, and the use of words like CREATE and DOES>

 

It's tricky stuff!

 

Mark

  • Like 1

Share this post


Link to post
Share on other sites

Willsy, at the risk of repeating what many others have said, THANK YOU for your work on Turbo Forth. It's a monumental achievement :thumbsup:

Share this post


Link to post
Share on other sites

Willsy, at the risk of repeating what many others have said, THANK YOU for your work on Turbo Forth. It's a monumental achievement icon_thumbsup.gif

 

Thanks Vorticon :)

Share this post


Link to post
Share on other sites

Hi.

 

I have some very basic questions:

 

I understand that we have to create a BLOCKS file before we can enter a program using MKDSK DSKx.BLOCKS nn

 

1- Why is USE needed (or is it)? Can the string parameter in USE be any filename I want and not necessarily DSKx.BLOCKS?

2- If I EDIT block 1, and I reach the end of it's 1K, what happens then if I still have more program commands to enter? Does it automatically flow into block 2 or do I have to manually perform a 2 EDIT etc...?

 

Sorry if this seems very elemental...

Share this post


Link to post
Share on other sites

Forth has its virtues, but to be honest, using the compiler's capability to do computations during compile-time, thus not having to do everything at run-time, isn't unique to Forth.

The pre-processor in C is a typical example of such a capability outside Forth.

Share this post


Link to post
Share on other sites

Hi.

 

I have some very basic questions:

 

I understand that we have to create a BLOCKS file before we can enter a program using MKDSK DSKx.BLOCKS nn

 

1- Why is USE needed (or is it)? Can the string parameter in USE be any filename I want and not necessarily DSKx.BLOCKS?

2- If I EDIT block 1, and I reach the end of it's 1K, what happens then if I still have more program commands to enter? Does it automatically flow into block 2 or do I have to manually perform a 2 EDIT etc...?

 

Sorry if this seems very elemental...

 

Well, you can have more than one blocks file (for example) on the same disk. So, you can tell TF which blocks file to USE.

 

When TF boots, by default it looks for a file called BLOCKS on DSK1 and if it finds it, it will load block 1 and obey whatever it finds in there.

You can also hold down any key as TF starts (just after you select TF from the cart menu, try holding a key down) and TF will look for DSKn.BLOCKS where n is the key you are holding. So, if you hold down Z it will look for DSKZ.BLOCKS (ram disks etc permit drive letters as well as numbers).

You can bypass auto-loading by holding down ENTER as TF starts, and you'll just go straight to the command line.

 

I tend to have a blocks file for each application I am writing. So, if I were going to write a space invaders, I would probably create a blocks file called INVADERS and then just do a

 

S" DSK1.INVADERS" USE

 

to use it.

 

In classic99, I have my standard blocks file, which loads automatically, and if I am always working in another application I will add a line like the following to the DSK1.BLOCKS file:

 

S" DSK1.INVADERS" USE
.( Using DSK1.INVADERS)

 

Now, when I start TF, the normal DSK1.BLOCKS file loads, and it switches to INVADERS. Now, all block related operations (EDIT, BLOCK, LOAD etc) are directed towards my invaders file.

 

Does this help, or does it create more questions? :)

Share this post


Link to post
Share on other sites

Hi.

 

I have some very basic questions:

 

I understand that we have to create a BLOCKS file before we can enter a program using MKDSK DSKx.BLOCKS nn

 

1- Why is USE needed (or is it)? Can the string parameter in USE be any filename I want and not necessarily DSKx.BLOCKS?

2- If I EDIT block 1, and I reach the end of it's 1K, what happens then if I still have more program commands to enter? Does it automatically flow into block 2 or do I have to manually perform a 2 EDIT etc...?

 

Sorry if this seems very elemental...

 

Well, you can have more than one blocks file (for example) on the same disk. So, you can tell TF which blocks file to USE.

 

When TF boots, by default it looks for a file called BLOCKS on DSK1 and if it finds it, it will load block 1 and obey whatever it finds in there.

You can also hold down any key as TF starts (just after you select TF from the cart menu, try holding a key down) and TF will look for DSKn.BLOCKS where n is the key you are holding. So, if you hold down Z it will look for DSKZ.BLOCKS (ram disks etc permit drive letters as well as numbers).

You can bypass auto-loading by holding down ENTER as TF starts, and you'll just go straight to the command line.

 

I tend to have a blocks file for each application I am writing. So, if I were going to write a space invaders, I would probably create a blocks file called INVADERS and then just do a

 

S" DSK1.INVADERS" USE

 

to use it.

 

In classic99, I have my standard blocks file, which loads automatically, and if I am always working in another application I will add a line like the following to the DSK1.BLOCKS file:

 

S" DSK1.INVADERS" USE
.( Using DSK1.INVADERS)

 

Now, when I start TF, the normal DSK1.BLOCKS file loads, and it switches to INVADERS. Now, all block related operations (EDIT, BLOCK, LOAD etc) are directed towards my invaders file.

 

Does this help, or does it create more questions? :)

 

It definitely does. Thanks :) What about my question about editing?

Share this post


Link to post
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.

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...