Jump to content
Lee Stewart

fbForth—TI Forth with File-based Block I/O [Post #1 UPDATED: 12/12/2018]

Recommended Posts

LOL. That's a good analysis. So you have put your finger on a proper partition. I should begin thinking about overview and you can be the detail guy.

Where I have an idea on a detail you can evaluate if that's how you want to build it but we can leave those decisions to you.

 

I was feeling a little ill last night so I didn't get much accomplished but I noticed the use of ON GOSUB in the game. Here is a fast Vector CASE statement that I saw in HsForth and is also used by others. I tried to make it work with FBForth quickly bit it didn't work with <BUILDS DOES>.

 

Perhaps because DOES> is used outside the original colon definition. ?

 

This might useful later after everything is working. However I did play the game for a while last night and I think the speedups in Forth and the freedom to go beyond arrays will make it work well.

 

So my first overview request is let's: "make it work, then make it better" Steve Jobs.

 

Meaning nothing to fancy out of the gate , just use Forth, create some meta words to handle the complexity and on we go.

\ FAST vector table from HsForth, Jim Kalihan (RIP)
\ no speed difference versus  CASE OF ENDOF  etc.. for 2 items
\ improves greatly with long list of cases.

: CASE:  ( -- -7)   CREATE   ;
: |      ( <name> )  '  ,  ;
: ;CASE   ( n -- )  DOES>  OVER + + @ EXECUTE ;  \ !! no runtime error checking !!

\ example code:
\ : FOO   ." FOO" ;
\ : BAR   ." BAR" ;
\ : FIZZ  ." FIZZ" ;
\ : BUZZ  ." BUZZ" ;

\ CASE: CHOICE  ( n -- ) | FOO  | BAR | FIZZ | BUZZ  ;CASE
\ Usage:   3 CHOICE  ( executes BUZZ)

\ equivalent with Eaker CASE statement
\ : TEST
\         CASE
\           0 OF  FOO ENDOF
\           1 OF  BAR ENDOF
\           2 OF FIZZ ENDOF
\           3 OF BUZZ ENDOF
\         ENDCASE  ;

Share this post


Link to post
Share on other sites

"Also pondered how to handle restoring background when pieces are moved. Thought of keeping a screen copy in RAM to grab those background pieces—only need 640 bytes. There must be a way to simplify handling this 10x10 board with its 3x2 “squares”—even with its forbidden zones."

 

Yes you could even create a screen stack in VDP ram using a single variable pointer and the BUFFER word for the transfers.

It would let you save/restore previous screens quickly.

\ untested idea (correct word names for FBForth names)
HEX 
2000 VARIABLE SCRSTK
 300 CONSTANT $300

: PUSHSCR    SCRTOP @ 79 BUFFER $300 VSBR 
             79 BUFFER SCRSTK $300 VSBW
             $300 SCRSTK +1 ;

: POPSCR     SCRSTK 79 BUFFER $300 VSBR
             79 BUFFER SCRTOP $300 VSBW  
             $300 MINUS SCRSTK +! ;             

To manage the screen and piece movement it would be nice to have a set of words that lets you abstract all that detail away for moving things around.

: PIECE: ( arg arg arg arg -- )
         <BUILDS  ....   \ build a data structure of a given piece with all fields
         DOES>  ( return my address ;

\ From Wikipedia
\ RANK          NAME      COUNT    FEATURE
\ ---------------------------------------------
\ B	        Bomb	   6     Immovable; defeats any attacking piece except Miner
\ 10 or 1	Marshal	   1     Can be captured by the Spy
\ 9 or 2	General	   1     	
\ 8 or 3	Colonel	   2     	
\ 7 or 4	Major	   3     	
\ 6 or 5	Captain    4	
\ 5 or 6	Lieutenant 4	
\ 4 or 7	Sergeant   4	
\ 3 or 8	Miner	   5	Can defuse bombs
\ 2 or 9	Scout	   8	Can move any distance in a straight line, without leaping over pieces / lakes
\ 1 or S	Spy	   1	Can defeat the Marshal, but only if the Spy makes the attack
\ F	        Flag	   1

: SHAPE:  ( n n n n n n -- ) <BUILDS , , , , , ,  DOES>  ;

HEX
A0 A1 A2 A3 A4 A5 A6 SHAPE: ABOMB
A7 A8 A9 AA AB AC AD SHAPE: AMARSHAL

\ piece type just return the correct args to make a piece 

\ rank1 rank2  motion special shape
\ ----- ----- ------- ------- -----
   B     B        0     0     ABOMB    PIECE: BOMB
   10    1        1     S     AMARSHAL PIECE: Marshal
   8     3        1     0     AGENEAL  PIECE: General
   7     4        1     0     AMAJOR   PIECE: Major

\ simple access to piece fields
: _RANK1   ( piece -- addr)  ;
: _RANK2   ( piece -- addr) 2+ ; 
: _MOTION  ( piece -- addr) 4 + ;
: _special ( piece -- addr) 6 + ;
: _shape   ( piece -- addr) 8 + ;

\ usage
BOMB _RANK1 @ .
Marshal MOTION @ . 

: DRAW-PIECE ( x y piece -- ) _SHAPE @ PLACE-PIECE ... ;
: MOVE-PIECE ( X Y piece -- ) ERASE-PIECE  _SHAPE @ DRAW-PIECE ... ;

\ etc...

Not fully thought out, but this is a way to make it more readable than using array indices.

Edited by TheBF
  • Like 2

Share this post


Link to post
Share on other sites

LOL. That's a good analysis. So you have put your finger on a proper partition. I should begin thinking about overview and you can be the detail guy.

Where I have an idea on a detail you can evaluate if that's how you want to build it but we can leave those decisions to you.

 

I was feeling a little ill last night so I didn't get much accomplished but I noticed the use of ON GOSUB in the game. Here is a fast Vector CASE statement that I saw in HsForth and is also used by others. I tried to make it work with FBForth quickly bit it didn't work with <BUILDS DOES>.

 

Perhaps because DOES> is used outside the original colon definition. ?

 

This might useful later after everything is working. However I did play the game for a while last night and I think the speedups in Forth and the freedom to go beyond arrays will make it work well.

 

So my first overview request is let's: "make it work, then make it better" Steve Jobs.

 

Meaning nothing to fancy out of the gate , just use Forth, create some meta words to handle the complexity and on we go.

<snip>

 

Sounds good to me.

 

Re <BUILDS DOES> , what you say about the split definition is, indeed, likely, They “like” to be in the same word because DOES> compiles the address of an entry point into the inner interpreter that executes the cfa of the defining word. CREATE also may not work the way you think it does. However, one thing is definitely wrong: In fbForth 2.0 (mostly figForth-based), tick ( ' ) yields the pfa (parameter field address) of a word not its cfa (code field address). That said, tick looks for the word following it in the input stream, so you should probably use -FIND (works like BL WORD and is not immediate like tick) , the stack signature for which is ( -- false | [pfa len true] ). Here are definitions that work in fbForth 2.0:

: CASE:  ( -- )  <BUILDS 0 , DOES> OVER + + @ EXECUTE ;

: |  ( -- )  (IS:<name> )
   -FIND IF
      DROP
   ELSE
      ' NOP
   THEN
   CFA ,  ;

: ;CASE  ( -- ) ;  <--does nothing but look pretty 

The execution of a word defined by CASE: requires the option number on the stack. I could not figure out a useful function for ;CASE other than eye candy.

 

...lee

  • Like 2

Share this post


Link to post
Share on other sites

Here is the STRATEGO blocks file with the splash screen followed by the startup screen before placement of random features: STRATEGOfbf_03.zip

 

If you put STRATEGO on DSK2, the following will start it:

USEBFL DSK2.STRATEGO
1 LOAD

...lee

Share this post


Link to post
Share on other sites

One more pass at the splash and opening screens: STRATEGOfbf_04.zip

 

You can compare four different ways of handling the splash screen. The fastest is from files (be sure to point to the STRATEGO blocks file [see last post]):

1 LOAD

Compare this with loading from blocks:

6 LOAD 

Also from blocks, but not blanking the screen, with color table loaded first:

4 LOAD 

and with pattern table loaded first:

5 LOAD 

...lee

Share this post


Link to post
Share on other sites

I think we should use blocks throughout for V1.0

Make it work.... (which you have done)

 

I like the effect of blanking the screen until the image is ready to display.

 

Did any of my nonense about data structures resonate? (I know none of it works as is)

Share this post


Link to post
Share on other sites

I think we should use blocks throughout for V1.0

Make it work.... (which you have done)

 

I like the effect of blanking the screen until the image is ready to display.

 

Did any of my nonense about data structures resonate? (I know none of it works as is)

 

Indeed, it did. I think that will eventually make it look like working on the 10x10 board it is—dealing with illegal moves. of course.

 

...lee

  • Like 2

Share this post


Link to post
Share on other sites

 

Indeed, it did. I think that will eventually make it look like working on the 10x10 board it is—dealing with illegal moves. of course.

 

...lee

 

Cool. Yes we should be able to make it look very much like talking about the game in English. (Well maybe like Yoda speaks English) :-)

  • Like 1

Share this post


Link to post
Share on other sites

Lee,

 

Here is a working version of the faster case statement. It seems to be about 2X faster than the Eaker CASE when selecting the 5th option.

Might help down the road.

 

Of course if we did the execution with DOES>ASM it would another 50% faster at least.

\ Vectored CASE for FbForth

: [']     -FIND SWAP DROP 0= IF ." ['] can't find" ABORT
          THEN CFA  ;
: |    [']  ,  ;

: CASE:  ( -- )  99 <BUILDS 0 ,  DOES> OVER + + @ EXECUTE ;
: ;CASE  ( -- ) 99 ?PAIRS ;

\ example code:
 : FOO   ." FOO" ;
 : BAR   ." BAR" ;
 : FIZZ  ." FIZZ" ;
 : BUZZ  ." BUZZ" ;

 CASE: CHOICE  ( n -- ) | FOO  | BAR | FIZZ | BUZZ | NOP ;CASE

\ Usage:   4 CHOICE  ( executes BUZZ)

\ equivalent with Eaker CASE statement
 : CHOICE2
         CASE
           1 OF  FOO ENDOF
           2 OF  BAR ENDOF
           3 OF FIZZ ENDOF
           4 OF BUZZ ENDOF
           5 OF NOP  ENDOF
         ENDCASE  ;
DECIMAL
: TEST1  10000 0 DO  5 CHOICE LOOP ;

: TEST2  10000 0 DO  5 CHOICE2 LOOP ;

  • Like 2

Share this post


Link to post
Share on other sites

Brilliant! Maybe we could use that first cell in the created word to store a choice limit. I have not figured out yet how we would do that, but ....

 

Oh—and you can use ABORT" in ['] .

 

...lee

  • Like 2

Share this post


Link to post
Share on other sites

Brilliant! Maybe we could use that first cell in the created word to store a choice limit. I have not figured out yet how we would do that, but ....

 

Oh—and you can use ABORT" in ['] .

 

...lee

 

OK...the following code, though a bit busy, stores a CASE: -defined word’s option count in its pfa and does runtime range checking:

\ Vectored CASE for fbForth with runtime error checking

: [']   -FIND SWAP DROP 0= ABORT" ['] can't find"  CFA  ;

: |   1 LATEST PFA +! ['] ,  ;

: CASE:  ( -- )  99 <BUILDS 0 ,  
   DOES> OVER OVER @ OVER < OVER 1 < OR ABORT" range!" + + @ EXECUTE ;

: ;CASE  ( -- )  99 ?PAIRS ;

...lee

  • Like 2

Share this post


Link to post
Share on other sites

Not sure how far along you are with things but here is a way to do PIECE: SHAPE: that lets you build pieces and get the parts out them as well..

 

I also made CREATE: which works like ANS CREATE which can be handy for defining constant arrays at compile time.

The "shapes" are actually byte counted strings using BYTE,

So if there are other structures that work as counted string data you can 'CREATE:" those too.

\ PIECE array creation for Stratego in FbForth

: CREATE:   0 VARIABLE -2 ALLOT ;  \ works like ANS Create
: BOUNDS    OVER + SWAP ; \ handy for indexing through arrays in DO LOOPS

\ creates a byte counted string from stack data
: BYTES,  DUP C,  0 DO  C,  LOOP ;

: SHAPE:   CREATE:   6 BYTES, ;

\ 6 chars on stack
1 2 3 4 5 6 SHAPE: ABOMB

\ get ALL characters from a SHAPE
: _PARTS  ( piece -- c c c c c c) COUNT BOUNDS DO I [email protected] LOOP ; 

Edited by TheBF
  • Like 2

Share this post


Link to post
Share on other sites

A new word for FbForth.

 

Sometimes you want a loop to continue until a values gets to zero. Typically you would use "0= UNTIL" like this:

 HEX

: TEST  FFFF BEGIN   1- DUP    0= UNTIL ; 

Although the Forth interpreter is only 3 instructions it does take time and on the old 9900 everything takes more time than you want it to.

 

So how about we make a new compiler word that loops until zero is encountered? It's easily done by using two other words and extend the compiler.

: UNTIL0     [COMPILE] WHILE  [COMPILE] REPEAT ;  IMMEDIATE

: FASTERTEST  FFFF BEGIN  -1 DUP   UNTIL0  ;

FASTERTEST runs about 10% faster than TEST partially because the contents of the loop are small but it does remove the need to the 0= comparison as separate Forth instruction.

Edited by TheBF
  • Like 2

Share this post


Link to post
Share on other sites

A new word for FbForth.

 

Sometimes you want a loop to continue until a values gets to zero. Typically you would use "0= UNTIL" like this:

 HEX
: TEST  FFFF BEGIN   1- DUP    0= UNTIL ; 

Although the Forth interpreter is only 3 instructions it does take time and on the old 9900 everything takes more time than you want it to.

 

So how about we make a new compiler word that loops until zero is encountered? It's easily done by using two other words and extend the compiler.

: UNTIL0     [COMPILE] WHILE  [COMPILE] REPEAT ;  IMMEDIATE
: FASTERTEST  FFFF BEGIN  -1 DUP   UNTIL0  ;

FASTERTEST runs about 10% faster than TEST partially because the contents of the loop are small but it does remove the need to the 0= comparison as separate Forth instruction.

 

Very nice.

 

A few things, however:

  • Your 1-” and-1” should both be 1 - 1-
  • Instead of “1 -”, you should use “-1 +” because it is 30 % faster
  • TEST and FASTERTEST should probably DROP the value left on the stack after the loop [minor and not really your point]

But, then, why not just use

: ANOTHERTEST  FFFF BEGIN 1- DUP WHILE REPEAT  ;

I do realize the aesthetics are not the same and UNTIL0 does make the logic neater.

 

...lee

Edited by Lee Stewart

Share this post


Link to post
Share on other sites

OOPS! My last post is somewhat erroneous—now corrected! I forgot the word “1-”. I have had too long a hiatus!

 

...lee

Share this post


Link to post
Share on other sites

 

Very nice.

 

A few things, however:

  • Your 1-” and-1” should both be 1 - 1-
  • Instead of “1 -”, you should use “-1 +” because it is 30 % faster
  • TEST and FASTERTEST should probably DROP the value left on the stack after the loop [minor and not really your point]

But, then, why not just use

: ANOTHERTEST  FFFF BEGIN 1- DUP WHILE REPEAT  ;

I do realize the aesthetics are not the same and UNTIL0 does make the logic neater.

 

...lee

 

Yes they should both be 1-

 

I was free styling up here in the kitchen, not using real code to paste into the web.

I had been thinking I wanted to make word to this and then realized that WHILE REPEAT did it for me.

So yes as with all things Forth, you can decide to make a new word or not depending on how many times you need it or aesthetics as you see fit.

 

I tried it in FbForth just to be sure the speed up was not my implementation only.

 

B

Share this post


Link to post
Share on other sites

While trying to get back to porting Walid’s Stratego game, I reviewed a lot of the Forth code above to see what it was doing. So far, everything makes sense except that I got tripped up on

: [']   -FIND SWAP DROP 0= ABORT" ['] can't find"  CFA  ;

back in posts #1534 and #1536. I cannot figure out why it works because, as near as I can tell, it should fail at the SWAP if -FIND fails to locate a word and returns only 0 to the stack. I think it works because of the fortuitous 99 (check-number for ?PAIRS in ;CASE ) on the stack below the 0 (FALSE) result from -FIND  , which 99 is irrelevant after ABORT" executes ABORT . Anyway, to make it more robust, I think it should be coded as follows:

: [']   -FIND 0= ABORT" ['] can't find" DROP  CFA  ;

, unless, of course, I am missing something—entirely possible.

 

Unless the specialized error message is important, the following is shorter and will tell you which word failed:

: [']  [COMPILE] ' CFA ;

 

...lee

Share this post


Link to post
Share on other sites

Yes this was my attempt to provide some meaningful messages for what will be a very big program.

 

For reference here is my library code.  It bare bones and it works fine.

You could probably avoid the ['] definition and simply put it in the '|"   word.

: CASE:  ( -- <name> ) CREATE  ;
  
: |      ( <name> )  '  ,  ;

: ;CASE   ( n -- )  DOES> SWAP 2* +  @ EXECUTE ;  \ !! no runtime error checking !!

 

 

Share this post


Link to post
Share on other sites

With the above considerations, here are the latest CASE: | ;CASE definitions:

: CASE:  ( -- 99 ) ( IS:<name> )  \ defines choice words
   99             \ for error checking by ;CASE
   <BUILDS     \ ...compile-time action for CASE:-defined word
      0 ,         \ reserve space for choice count and initialize to 0
   DOES>    ( n -- )   \ ...runtime action for CASE:-defined word
      2DUP        \ dup n pfa..........stack:n pfa n pfa
      @           \ get choice count...stack:n pfa n cnt
      OVER <      \ cnt < n?...........stack:n pfa n f1
      OVER 1 <    \ n < 1?.............stack:n pfa n f1 f2
      OR ABORT" range!"    \ abort if either flag true..stack:n pfa n
      + +         \ pfa + n + n = addr of cfa of choice(n)
      @           \ get cfa(n)
      EXECUTE     \ execute cfa(n)
;
: |   ( IS:<name> ) 
   1 LATEST PFA +!      \ increment choice count of word defined by CASE:
   [COMPILE] ' CFA ,    \ compile CFA of this choice
;
: ;CASE  ( -- )  99 ?PAIRS ;  \ check for 99 from CASE:..else error

 

...lee

  • Like 3

Share this post


Link to post
Share on other sites

It just occurred to me that the above definition of | is a little dangerous. If the last word defined before executing | was not defined by CASE: , it will be trashed! Here is a definition for | that checks for that contingency:

\ Compile CFA of next choice <name> into just-defined CASE: word
: |   ( n -- 99 ) ( IS:<name> ) 
   99 ?PAIRS      \ check that latest word was defined by CASE:..else error
   99                   \ restore error-checking # for next | or ;CASE
   1 LATEST PFA +!      \ increment choice count of word defined by CASE:
   [COMPILE] ' CFA ,    \ compile CFA of <name> as this choice
;

 

...lee

  • Like 2

Share this post


Link to post
Share on other sites
5 hours ago, Lee Stewart said:

It just occurred to me that the above definition of | is a little dangerous. If the last word defined before executing | was not defined by CASE: , it will be trashed! Here is a definition for | that checks for that contingency:

\ Compile CFA of next choice <name> into just-defined CASE: word
: |   ( n -- 99 ) ( IS:<name> ) 
   99 ?PAIRS      \ check that latest word was defined by CASE:..else error
   99                   \ restore error-checking # for next | or ;CASE
   1 LATEST PFA +!      \ increment choice count of word defined by CASE:
   [COMPILE] ' CFA ,    \ compile CFA of <name> as this choice
;

 

...lee

Safety first!

It's very interesting to me to see how Forth has changed since '94.   

Many complain about the standard but there are some things that seem a little simpler to implement than FIG Forth.

  • Like 1
  • Thanks 1

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