Jump to content
IGNORED

Forth advice sought


Hans23

Recommended Posts

Hi,

 

I am struggling with doing things the Forth way, maybe someone here can help me out or point me to an material that'd help.  I'm attempting to write a game in TurboForth.  It will be a two person game where the objective of each player is to reach the base of the other player.  Consider it to be a programming exercise more than the attempt at a good game, as it is my first.  Anyway, here is what it looks like right now:

 

 

The vertical walls with the moving gates are updated every few frames, and the code that does it looks like this:

$A6 value gate-char
22 value wall-height
variable gates 3 cells allot
variable gate-width 5 gate-width !
variable gate-speed 10 gate-speed !
: update-gate ( n -- )
    >r
    r@ 1 and if
        r@ gate@
        dup r@ gate-x swap gotoxy gate-char emit
        dup gate-width @ + wall-height mod r@ gate-x swap gotoxy space
        1+
    else
        r@ gate@
        dup r@ gate-x swap gotoxy space
        dup gate-width @ + wall-height mod r@ gate-x swap gotoxy gate-char emit
        1-
    then
    wall-height mod r@ gate!
    r> drop ;

: update-gates ( -- )
    4 0 do i update-gate loop ;

 

The word that concerns me is obviously UPDATE-GATE, which moves the gate in one of the walls up or down, depending on whether its index is even or odd - I find it really hard to understand what the code does.  By using the return stack to hold the gate number, things get a little easier, yet there still is a lot of stack value juggling that one needs to follow.  Also, the code duplication between the up and down movement of the gate concerns me, but how could those two branches be combined into one parameterized word that, in itself, does not need to deal with many opaque stack parameters?

 

I'm seeing it as a reoccuring issue when I write words that need to deal with multiple associated values, like the X and Y coordinate of one or multiple objects.  Many of the operations involved are very simple, but the required mechanics to access values on the stack get complicated and confusing quickly.

 

Any help or pointers would be much appreciated!

Thank you,

Hans

Edited by Hans23
Typo
  • Like 2
Link to comment
Share on other sites

5 hours ago, Hans23 said:

Any help or pointers would be much appreciated!

 

Without screwing with your logic (yet!), the following changes save some code—the first 4 lines, by placement, and the last 2, by removing the return-stack value one step sooner:

$A6 value gate-char
22 value wall-height
variable gates 3 cells allot
variable gate-width 5 gate-width !
variable gate-speed 10 gate-speed !
: update-gate ( n -- )
   >r
   r@ 1 and if
      r@ gate@
\      dup r@ gate-x swap gotoxy gate-char emit
      r@ gate-x over gotoxy gate-char emit   
\      dup gate-width @ + wall-height mod r@ gate-x swap gotoxy space
      r@ gate-x over gate-width @ + wall-height mod gotoxy space            
      1+
   else
      r@ gate@
\      dup r@ gate-x swap gotoxy space
      r@ gate-x over gotoxy space
\      dup gate-width @ + wall-height mod r@ gate-x swap gotoxy gate-char emit
      r@ gate-x over gate-width @ + wall-height mod gotoxy gate-char emit
      1-
   then
\   wall-height mod r@ gate!
\   r> drop ;
   wall-height mod r> gate! ;

: update-gates ( -- )
    4 0 do i update-gate loop ;

...lee

  • Like 1
Link to comment
Share on other sites

Hi Lee,

 

thank you for looking at my code!  I had considered working with the data stack more, but the issue that I really have is that I then have one more value on the stack that I need to keep track of in my head in order to understand the code.  I don't know if this is difficult for me because I am not used to it or if it is and just stays difficult, being the price of the simplicity of Forth.

 

It also seems to me that my words are just too long, and making them generally smaller would make sense.  What keeps me from trying to go that way is that I also find it hard to invent meaningful names, and my intuition is to keep that namespace clean.  I guess that this is one of the things I need to overcome :)

 

Anyway, thanks again!  Any further hints, or maybe pointers to good source code to study, would be appreciated.


Cheers,

Hans

  • Like 1
Link to comment
Share on other sites

14 minutes ago, Hans23 said:

thank you for looking at my code!  I had considered working with the data stack more, but the issue that I really have is that I then have one more value on the stack that I need to keep track of in my head in order to understand the code.  I don't know if this is difficult for me because I am not used to it or if it is and just stays difficult, being the price of the simplicity of Forth.

 

When stack-robatics get the least bit complicated, I keep track of the stacks (“S:” for the parameter stack and “R:” for the return stack) in comments at the right of each code line, keeping the lines simple at the expense of space:

: update-gate ( n -- )
   >r                         \ S: R:n
   r@ gate@                   \ S:ny R:n
   r@ 1 and if                
      r@ gate-x over          \ S:ny nx ny R:n
      gotoxy gate-char emit   \ S:ny R:n
      r@ gate-x over          \ S:ny nx ny R:n
      gate-width @ +          \ S:ny nx ny+gw R:n
      wall-height mod         \ S:ny nx ny' R:n
      gotoxy space            \ S:ny R:n
      1+                      \ S:ny+1 R:n
   else
      r@ gate-x over          \ S:ny nx ny R:n
      gotoxy space            \ S:ny R:n
      r@ gate-x over          \ S:ny nx ny R:n
      gate-width @ +          \ S:ny nx ny+gw R:n
      wall-height mod         \ S:ny nx ny' R:n
      gotoxy gate-char emit   \ S:ny R:n
      1-                      \ S:ny-1 R:n
   then
   wall-height mod r>         \ S:ny" n R:
   gate! ;                    \ S: R:

[Note that I hoisted the redundant r@ gate@ out of the if construct.]

 

14 minutes ago, Hans23 said:

It also seems to me that my words are just too long, and making them generally smaller would make sense.  What keeps me from trying to go that way is that I also find it hard to invent meaningful names, and my intuition is to keep that namespace clean.  I guess that this is one of the things I need to overcome :)

 

You do want clarity, of course. Name length is not a big problem for the size of a program because a word’s cfa (code field address or execution token) is all that gets stored in the definition of another word. The name length does add 1 byte per character to a word’s header (padded to even cell boundary), but that should not be too onerous.  There are naming guidelines most Forthers try to follow in the spirit of Forth—ideas like reusing the classic, cryptic Forth names: ! @ . ' , etc., while composing new names (as you have done in this instance).

 

...lee

  • Like 2
Link to comment
Share on other sites

Nice comment style!  I'll try to use that.

 

One thing about names in TurboForth is that they seem to be restricted to 15 characters in length, and it seems to not take it well when one tries to define a word with a long name:

 

image.thumb.png.ec47f47f52a13e1c44a261d4d6b298f4.png

 

This caused me quite some head scratching before I realized that the limit exists.  Anyway, there are some habits from the modern world that don't carry over well when going back to a small system like the TI-99/4A.

 

Thank you again for your comment (style)!

  • Like 1
Link to comment
Share on other sites

3 hours ago, Hans23 said:

Nice comment style!  I'll try to use that.

 

OK, here is my take on your update-gate :

: update-gate ( n -- )
   >r r@ gate@                \ S:ny R:n
   r@ 1 and if                \ S:ny R:n  **CORRECTION: dup replaced with r@ **
      1+                      \ S:ny+1 R:n
      bl gate-char            \ S:ny+1 bl gc R:n
   else
      1-                      \ S:ny-1 R:n
      gate-char bl            \ S:ny-1 gc bl R:n
   then
   r@ gate-x                  \ S:ny+-1 gc|bl bl|gc nx R:n
   r@ gate@                   \ S:ny+-1 gc|bl bl|gc nx ny R:n
   2dup                       \ S:ny+-1 gc|bl bl|gc nx ny nx ny R:n
   gotoxy                     \ S:ny+-1 gc|bl bl|gc nx ny R:n
   rot emit                   \ S:ny+-1 gc|bl nx ny R:n
   gate-width @ +             \ S:ny+-1 gc|bl nx ny+gw R:n
   wall-height mod            \ S:ny+-1 gc|bl nx ny' R:n
   gotoxy emit                \ S:ny+-1 R:n
   wall-height mod r>         \ S:ny" n R:
   gate! ;                    \ S: R:

It could be a little more stack-robatics than you’d like, but there it is!  |:)

 

...lee

Edited by Lee Stewart
CORRECTION in code
  • Like 3
Link to comment
Share on other sites

Factor factor factor. When it's getting hard it's normally a sign that you need to factor into smaller definitions.

 

And don't be afraid to use global variables to store things in. It may ultimately be faster than wasting cycles just to manipulate the stack.

  • Like 4
Link to comment
Share on other sites

I have not tested this but to emphasize what Willsy said look how things simplify by just factoring out some of the common code from Lee's re-writes and naming them.

Any time you see the same 4 or more words in a row in your code consider factoring them out into a new word that describes that function. This really is the key to making Forth simpler to use.

I am sure there is more there to factor in such a long (for Forth) definition. 

$A6 value gate-char
22 value wall-height
variable gates 3 cells allot
variable gate-width  5 gate-width !
variable gate-speed 10 gate-speed !

: GATE-XY   ( n -- x y)  gate-x over gate-width @ + wall-height mod ;
: .GATECHAR ( x y -- ) GOTOXY gate-char emit ;

: update-gate ( n -- )
   >r
   r@ 1 and if
      r@ gate@
      r@ gate-x over .GATECHAR
      r@ gotoxy space
      1+
   else
      r@ gate@
      r@ gate-x over gotoxy space
      r@ gate-xy .GATE-CHAR
      1-
   then
   wall-height mod r> gate! ;

: update-gates ( -- )
    4 0 do i update-gate loop ;

 

Chuck Moore's Forth code is 1 line per definition typically.  Extreme perhaps but hey it's his language. :)

 

It's a very different way to think when used as designed. It's more like making a little language of words that solves your problem when you put them together in correct order. 

 

And yes it does take some time to get used to it. 

 

Some things to think about:

1. Never to try managing more than 3 parameters on the data stack.

    Factor out words until that is possible. If it's not possible re-think your solution or use variables.
 

2. Make word names that tell you what the word really does for you. It pays off since you use more function names than conventional languages.

 

3. Forth words can return more than one parameter. Use that as needed.

 

When you get some free time download a copy of Thinking Forth by Leo Brodie.  

 

You have running code. Congratulations!  Keep on it and it will get easier.

  • Like 4
Link to comment
Share on other sites

Chuck Moore's Forth code is 1 line per definition.. really? Wow, well (KISS, keeping it short ), you know coming from other languages, we tend to cram as much as we can quickly and brutefully, this just wants to slap your grandma.

I have a tough time with this, but i find myself coming back to that larger line of code, saying, but if, and adding on..so, your so right or else the WORD becomes a nightmare, like my, uh, you know the rest of the story.

 

Edited by GDMike
  • Like 2
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...