Jump to content
IGNORED

Schooner - a forth project


Retrospect

Recommended Posts

Hello everyone.

 

During my coding break, I decided to learn Forth.  That's right.  

 

So I am starting a little project called "Schooner" ... nothing ambitious ... it's really just going to be a program that displays a little Tombstone City Schooner character on a yellow screen and lets the player move it around, and eventually fire a missile.  Anything else more than that will depend upon whether my head has fried or not.  

 

So I started using Camel99 Forth and that's what my project runs on.  

 

So far, I've managed to make the screen do this .... it wasn't intentional though.  Don'cha think it looks nice!  :)

fudged.thumb.png.675962ceea4dc000ff64ef5b228de08e.png

  • Like 8
Link to comment
Share on other sites

5 minutes ago, TheBF said:

Remember DO needs the numbers reversed. 23 0 DO 

Thankyou .  So : B 23 0 DO 0 I 131 1 HCHAR LOOP ; should be okay?  I can feel my brain sizzling lol  
But it's still fun to do so long as I don't try to get above myself and start thinking "Why the heck can't I do Avaris yet"  :)

  • Like 1
Link to comment
Share on other sites

1 minute ago, Retrospect said:

Thankyou .  So : B 23 0 DO 0 I 131 1 HCHAR LOOP ; should be okay?  I can feel my brain sizzling lol  
But it's still fun to do so long as I don't try to get above myself and start thinking "Why the heck can't I do Avaris yet"  :)

Exactly. Slow and steady.

  • Like 1
Link to comment
Share on other sites

The characters after 127 don't have any colors assigned to them.

Maybe I should change that when GRAFIX loads up.

But in the mean time you need to assign a color to any character from 128 to 255

 

131 SET#  2 1 COLOR 

 

will get you started.

 

  • Thanks 1
Link to comment
Share on other sites

Here is something that you might never do in BASIC.

We make a partial call with the char and the length into a new word.

Then we pass the row,col to the new words and boom. You get walls.
 

\ PRINT BORDERS ON SCREEN
DECIMAL
: HWALL ( row col --) 131 32 HCHAR ; 
: VWALL ( row col --) 131 24 VCHAR ; 

: BORDER  
      0 0 HWALL
      0 0 VWALL 
     0 23 HWALL 
     31 0 VWALL 
;

 

This will jump start you. 

I know I know. It sucks without the safety net.

So write a word. Then try that word in the console, passing it parameters, until it's correct.

Then do that to the next word. The console is your friend. 

 

Don't try to make a pile of code and compile it all in one go. 

 

Spoiler
 
\ 01 FIRE, 02 LEFT, 04 RIGHT, 08 DOWN, 10 UP
\ CODE JOYST( JOYSTICK# -- VALUE )

INCLUDE DSK1.GRAFIX

\ DEFINITIONS
\ 
\ 96 SCHOONER UP
\ 97 SCHOONER DOWN
\ 98 SCHOONER LEFT
\ 99 SCHOONER RIGHT
\ 100 SCHOONER MISSILE
\
\ 112-123 LARGE TITLE
\ 128-132 RED BORDER & MOUNTAINS

DECIMAL
: DEFINE-SHAPES 
	S" 001818183C7E7E42" 96 CALLCHAR 
	S" 427E7E3C18181800" 97 CALLCHAR 
	S" 00070E7E7E0E0700" 98 CALLCHAR 
	S" 00E0707E7E70E000" 99 CALLCHAR 
	S" 00003C3C3C3C0000" 100 CALLCHAR 
	S" FCFCC0C0C0C0FCFC" 112 CALLCHAR 
	S" 0C0C0C0C0C0CFCFC" 113 CALLCHAR 
	S" FCFCC0C0C0C0C0C0" 114 CALLCHAR 
	S" C0C0C0C0C0C0FCFC" 115 CALLCHAR 
	S" CCCCCCCCCCCCFCFC" 116 CALLCHAR 
	S" CCCCCCCCCCCCCCCC" 117 CALLCHAR 
	S" FCFCCCCCCCCCCCCC" 118 CALLCHAR 
	S" CCCCCCCCCCCCFCFC" 119 CALLCHAR 
	S" CCCCCCECFCFCDCCC" 120 CALLCHAR 
	S" FCFCC0C0C0C0F0F0" 121 CALLCHAR 
	S" F8FCCCCCCCCCFCF8" 122 CALLCHAR 
	S" F0F8DCCCCCCCCCCC" 123 CALLCHAR 
	S" 80C0E0F0F8FCFEFF" 128 CALLCHAR 
	S" C0F0FCFFFFFFFFFF" 129 CALLCHAR
	S" 00000000C0F0FCFF" 130 CALLCHAR  
	S" FFFFFFFFFFFFFFFF" 131 CALLCHAR 
	S" 00000000030F3FFF" 132 CALLCHAR 
	S" 030F3FFFFFFFFFFF" 133 CALLCHAR 
	S" 0103070F1F3F7FFF" 134 CALLCHAR 
;

\ GET DEFS, SCREEN COLOR 4 GREEN AND CLEAR IT
: CLS   PAGE 12 SCREEN ;

: HIGH-COLORS 
      128 SET# 2 1 COLOR
      136 SET# 2 1 COLOR 
;

\ PRINT BORDERS ON SCREEN
DECIMAL
: HWALL ( row col --) 131 32 HCHAR ; 
: VWALL ( row col --) 131 24 VCHAR ; 

: BORDER  
      0 0 HWALL
      0 0 VWALL 
     0 23 HWALL 
     31 0 VWALL 
;

\ WAIT FOR A KEYPRESS
: MYKEY    BEGIN   KEY?  ?DUP  UNTIL ; 

: RUN 
  CLS 
  DEFINE-SHAPES
  HIGH-COLORS 
  BORDER 
  MYKEY ;




 

 

Edited by TheBF
typo
  • Like 1
  • Thanks 1
Link to comment
Share on other sites

Thank you @TheBF you are a star.  I have borders now.  I colored them red for my game.  Next on my To-Do list is displaying a large title "schooner" ... I'll proudly show the screenshot of that when I get it working :)

I see a better style of programming Forth from your reworking of my code.  It's good that the words can be arranged like that, in my mind I was thinking this: (dunno why!)
: RUN word word word word word ;

But your example shows 

: RUN

 word

 word

 word

 word ; 

 

And that is so much better and sits better in my mind at least!  

Link to comment
Share on other sites

Vertical or horzizontal has been a discussion with Forth. The compiler doesn't care so do what makes sense to you.

Once you have your own set of words for the GAME a sentence format might look super.

But for hard stuff I go vertical and put comments beside each line sometimes to keep my head aligned.

It is more like Assembler that way. 

Link to comment
Share on other sites

Since we are doing a bit of teaching in this thread here is a thought for all those patterns.

Naming the strings is fair ball in BASIC or Forth but it's not as obvious in Forth. (Ok I know. Nothing is in Forth) 

 

In BASIC you might do:

BLOCK$="FFFFFFFFFFFFFFFF" 

 

Then use it later for different characters. 

CALL CHAR( 121,BLOCK$) 

 

In Forth the  S"  word parses a string up to a final ".

If we are interpreting,  S"  leaves the address of the string and the length on the data stack. 

 

If we are compiling in a colon definition,  S" compiles the string into the colon definition and when you run that new word it return the address and length. 

Like this

 

: BLOCK$  S" FFFFFFFFFFFFFFFF"  ;

 

Now in a similar manner we could say this in our program. 

 

BLOCK$ 121 CALLCHAR 


dirty little secret

CALLCHAR is part of the words I call "training wheels" that I added so people could move from TI BASIC to Forth in more comfort.

The truth is that keeping the data as a string uses 2x more space than if we stored the actually integers that need to go into VDP RAM. 

There are a couple of ways to do this differently that we can demonstrate once @Retrospect 's program is running.

 

 

Edited by TheBF
fixed typos
  • Like 2
  • Thanks 2
Link to comment
Share on other sites

The question came up about making a star field for this schooner project.

@retrospect PMed me with his BASIC code that uses a number of arrays.

 

A short list of 8 star characters, each one bit shifted

2 arrays of coordinates for the x,y locations of the the starts.

1 array of the character that is on the screen, so we can bump it to the next bit shifted star.

 

The code steps through the screen locations and bumps each star character but

the code must wrap around the list of 8 characters and wrap back to the start of the other 3 arrays. 

 

This is all "kosher" BASIC. Works fine. 

 

I explored making some words to duplicate all that but realized it seemed way more work than needed. 

 

In Forth (or C, or Assembler) we can touch memory directly.

We can even touch the VDP chip memory directly. 

So its faster to go direct to the VDP memory to update the screen.


 

VPEEK() in Camel99 is VC@  "video character fetch"

VPOKE() is VC!  "video character store" 

 

Here is what I came up with.  

It uses one weird concept that may take some explanation. 

In Forth we can use the interpreter to run some code while we are compiling. 

So in this case we made a word that compiles random screen addresses into memory.

That becomes an array of locations that was generated when the file compiles.

:) 

 

Spoiler
INCLUDE DSK1.TOOLS 
INCLUDE DSK1.GRAFIX
INCLUDE DSK1.RANDOM 

\ define our stars 
DECIMAL 
S" 0000000100000000" 40 CALLCHAR
S" 0000000200000000" 41 CALLCHAR
S" 0000000400000000" 42 CALLCHAR
S" 0000000800000000" 43 CALLCHAR
S" 0000001000000000" 44 CALLCHAR
S" 0000002000000000" 45 CALLCHAR
S" 0000004000000000" 46 CALLCHAR
S" 0000000000000000" 47 CALLCHAR

: NEXT-STAR ( char -- char')  
  1+ 48 OVER = IF  DROP 40  THEN ; 

\ : TESTNEXT  40 BEGIN NEXT-STAR DUP .   ?TERMINAL UNTIL ;


: RND-STAR ( -- char)  8 RND 40 + ;
\ : RNDX     ( -- x) 32 RND ;
\ : RNDY     ( -- y) 24 RND ;

: RND-VADDR ( -- Vaddr) 
    C/SCR @ RND     \ get random address from zero to size of screen
    VPG @ + ;       \ get the video page, add to the random number 

32 CONSTANT #STARS   \ lock this down as a constant 

\ "compile" random video addresses into memory with comma 
\ comma in the name LOCATIONS, reminds us what this word does. 
: LOCATIONS, ( n --) 0 DO   RND-VADDR ,   LOOP ;


\ *******************************************************
\ This is a strange thing. We can use the interpreter to
\ make a table of data when the file is "INCLUDED"
\ ******************************************************
\ Now we can make oa table of data at compile time 
CREATE STAR-LOCATIONS      #STARS LOCATIONS,

\ make a way to access the table by number. (makes an array) :-) 
\ and use @ to fetch the value in cell 'n'
: ]LOCATION  ( n -- Vaddr) CELLS  STAR-LOCATIONS +  @ ;

\ Put the stars on the screen at the random STAR-LOCATIONS 
: .STARS 
    #STARS 0 
    DO   
      RND-STAR I ]LOCATION VC! \ write star to video memory 
    LOOP 
;

: SHIFT-STARS
    #STARS 0 
    DO  
       I ]LOCATION DUP  \ need to copies 
       VC@              \ read star character from screen location
       NEXT-STAR        \ bump it to next star character 
       SWAP VC!         \ write back new character to screen 
    LOOP 
;

: TEST  
    PAGE 
    40 SET# 16 1 COLOR 
    2 SCREEN 
    .STARS
    BEGIN 
       SHIFT-STARS 
      ?TERMINAL 
    UNTIL 
    8 SCREEN 
;

 

 

There are optimizations that could be made like using binary wrap for the NEXT-CHAR word.

I am not sure the look is correct yet. The video is running with no delays anywhere. 

 

 

 

  • Like 2
Link to comment
Share on other sites

2 hours ago, TheBF said:

The question came up about making a star field for this schooner project.

@retrospect PMed me with his BASIC code that uses a number of arrays.

 

A short list of 8 star characters, each one bit shifted

2 arrays of coordinates for the x,y locations of the the starts.

1 array of the character that is on the screen, so we can bump it to the next bit shifted star.

 

The code steps through the screen locations and bumps each star character but

the code must wrap around the list of 8 characters and wrap back to the start of the other 3 arrays. 

 

This is all "kosher" BASIC. Works fine. 

 

I explored making some words to duplicate all that but realized it seemed way more work than needed. 

 

In Forth (or C, or Assembler) we can touch memory directly.

We can even touch the VDP chip memory directly. 

So its faster to go direct to the VDP memory to update the screen.


 

VPEEK() in Camel99 is VC@  "video character fetch"

VPOKE() is VC!  "video character store" 

 

Here is what I came up with.  

It uses one weird concept that may take some explanation. 

In Forth we can use the interpreter to run some code while we are compiling. 

So in this case we made a word that compiles random screen addresses into memory.

That becomes an array of locations that was generated when the file compiles.

:) 

 

  Reveal hidden contents
INCLUDE DSK1.TOOLS 
INCLUDE DSK1.GRAFIX
INCLUDE DSK1.RANDOM 

\ define our stars 
DECIMAL 
S" 0000000100000000" 40 CALLCHAR
S" 0000000200000000" 41 CALLCHAR
S" 0000000400000000" 42 CALLCHAR
S" 0000000800000000" 43 CALLCHAR
S" 0000001000000000" 44 CALLCHAR
S" 0000002000000000" 45 CALLCHAR
S" 0000004000000000" 46 CALLCHAR
S" 0000000000000000" 47 CALLCHAR

: NEXT-STAR ( char -- char')  
  1+ 48 OVER = IF  DROP 40  THEN ; 

\ : TESTNEXT  40 BEGIN NEXT-STAR DUP .   ?TERMINAL UNTIL ;


: RND-STAR ( -- char)  8 RND 40 + ;
\ : RNDX     ( -- x) 32 RND ;
\ : RNDY     ( -- y) 24 RND ;

: RND-VADDR ( -- Vaddr) 
    C/SCR @ RND     \ get random address from zero to size of screen
    VPG @ + ;       \ get the video page, add to the random number 

32 CONSTANT #STARS   \ lock this down as a constant 

\ "compile" random video addresses into memory with comma 
\ comma in the name LOCATIONS, reminds us what this word does. 
: LOCATIONS, ( n --) 0 DO   RND-VADDR ,   LOOP ;


\ *******************************************************
\ This is a strange thing. We can use the interpreter to
\ make a table of data when the file is "INCLUDED"
\ ******************************************************
\ Now we can make oa table of data at compile time 
CREATE STAR-LOCATIONS      #STARS LOCATIONS,

\ make a way to access the table by number. (makes an array) :-) 
\ and use @ to fetch the value in cell 'n'
: ]LOCATION  ( n -- Vaddr) CELLS  STAR-LOCATIONS +  @ ;

\ Put the stars on the screen at the random STAR-LOCATIONS 
: .STARS 
    #STARS 0 
    DO   
      RND-STAR I ]LOCATION VC! \ write star to video memory 
    LOOP 
;

: SHIFT-STARS
    #STARS 0 
    DO  
       I ]LOCATION DUP  \ need to copies 
       VC@              \ read star character from screen location
       NEXT-STAR        \ bump it to next star character 
       SWAP VC!         \ write back new character to screen 
    LOOP 
;

: TEST  
    PAGE 
    40 SET# 16 1 COLOR 
    2 SCREEN 
    .STARS
    BEGIN 
       SHIFT-STARS 
      ?TERMINAL 
    UNTIL 
    8 SCREEN 
;

 

 

There are optimizations that could be made like using binary wrap for the NEXT-CHAR word.

I am not sure the look is correct yet. The video is running with no delays anywhere. 

 

 

 

 

So the stars patterns are moving from 40 to 47.  If it's possible to do, when any individual star reaches pattern 47, and it goes back to 40, then that star should be deleted and displayed again 1 char to the left.  If left <1 then display it at char position 32.  That way, you get them moving across the screen.  I'm all good saying this, but I can only do this in XB (or ti basic) ... I wouldn't know how to visit each star and do that.  

Link to comment
Share on other sites

Also to note,  I may have mislead somewhat ... the starfield thing, is not for the Schooner game.  The Schooner game will be my first attempt at a game in Forth  ... once I've mastered that, and maybe some more little games ...  the starfield is for another Forth game.  That game will be called "Avaris - Anubis Arises" and it will be exclusive to Forth.   That's my ultimate target.   

Link to comment
Share on other sites

1 hour ago, Lee Stewart said:

 

Here is one:

 

: NEXT-STAR ( char -- char')  
   1+          \ increment character
   7 AND       \ force right nybble to range 0..7
   40 +        \ add back stripped-off 40
;

 

LOL.

Ya I tried that and it didn't work quite right so I will have work on why. 

 

Link to comment
Share on other sites

1 hour ago, Retrospect said:

So the stars patterns are moving from 40 to 47.  If it's possible to do, when any individual star reaches pattern 47, and it goes back to 40, then that star should be deleted and displayed again 1 char to the left.  If left <1 then display it at char position 32.  That way, you get them moving across the screen.  I'm all good saying this, but I can only do this in XB (or ti basic) ... I wouldn't know how to visit each star and do that.  

Ah ok I get it now. 

 

"if it's possible to do..." 

BFs two rules of software:

1. Anything is possible.

2. Nothing is easy. 

 

They have never let me down. :) 

 

That may take a bit more head scratching for a "Forth way to do it".

At some point Joe, I might have to have private tutor session to explain what I did there. 

As you can see it looks very different than BASIC, because I choose not to do it the way you must do it in BASIC. 

 

  • Like 2
Link to comment
Share on other sites

Ok this game stuff is way harder than I thought. (subtext I suck as a programmer) :) 

 

But here is what I finally came up with.  

I changed the original code to use a library array, mostly because it is more understandable.

 

NEW-STARS  fills that array up with random VDP addresses that fit on the screen. 

I run  NEW-STARS at compile time to initialize the array. 

 

The biggest difference between this and the BASIC code (see post on the RXB thread by @Retrospect)

is in BASIC you tend to think in x,y coordinates for the screen.

 

When you get close to the hardware you can think of addresses in VDP RAM. 

So we are handling less numbers here because each star is in a single VDP address.

 

We we do the bit migration to the left with different chars and when we get to the last character,

we just

  • erase the old character 
  • subtract 1 from the VDP address, protecting the address from going negative
  • test if the address is on the left edge of the screen
    • if it is add 31 to the address (go back to the right side) 
  • Store the new VDP address in the LOCATION array. 

I think it's pretty close to what is going on in BASIC now.

 

Spoiler
INCLUDE DSK1.TOOLS 
INCLUDE DSK1.GRAFIX
INCLUDE DSK1.RANDOM 
INCLUDE DSK1.ARRAYS 

\ define our stars 
DECIMAL 
S" 0000000100000000" 40 CALLCHAR
S" 0000000200000000" 41 CALLCHAR
S" 0000000400000000" 42 CALLCHAR
S" 0000000800000000" 43 CALLCHAR
S" 0000001000000000" 44 CALLCHAR
S" 0000002000000000" 45 CALLCHAR
S" 0000004000000000" 46 CALLCHAR
S" 0000000000000000" 47 CALLCHAR

: NEXT-STAR ( char -- char') 1+  7 AND 40 +  ; 

 \ : TESTNEXT  40 BEGIN NEXT-STAR SWAP .  UNTIL ;

: RND-STAR ( -- char)  8 RND 40 + ;

: RND-VADDR ( -- Vaddr) 
    C/SCR @ RND     \ get random address from zero to size of screen
    VPG @ + ;       \ get the video page, add to the random number 

32 CONSTANT #STARS   \ lock this down as a constant 

\ array that holds the VDP address of every star 
#STARS ARRAY ]LOCATION 

: NEW-STARS  ( n --) #STARS 0 DO   RND-VADDR I ]LOCATION !  LOOP ;

NEW-STARS   \ initialize the array now 

\ This array FETCHES the star's VDP screen address from ]location
: ]VDPLOC  ( n -- Vaddr) ]LOCATION @ ;

\ Put the stars on the screen at the random LOCATION in our LOCATIONS array 
: .STARS 
    #STARS 0 
    DO   
      RND-STAR I ]VDPLOC VC! \ write star to video memory 
    LOOP 
;

\ Given a screen address, test if we hit the left edge of the screen 
: LEFT-EDGE?  ( n -- ?) C/L @ MOD 0= ; \ modulo divide by "chars per line" 

\ Take the address of a cell in ]LOCATION array. 
\ get the screen location of a star 
\ move it 1 one left, but address can't go below zero so stop that. 
\ If we hit the left edge add 31 to the Vaddr and store it back in Address
: MOVELEFT ( addr -- ) \ decrement a variable to 0 but not below 
    DUP @   ( -- addr Vaddr)  
    BL OVER VC!         \ erase char in old screen location  
    1- 0 MAX  ( -- addr Va)   \ subtract 1 but don't go below 0 
    DUP LEFT-EDGE?      \ test if we are at the edge of the screen  
    IF   31 +           \ yes. Add 31 to go back to right side of screen 
    THEN SWAP ! ;       \ store this new location in the addr of ]LOCATION array 
;

: SHIFT-STARS
    #STARS 0 
    DO  
       I ]VDPLOC VC@        \ read star character from screen location
       NEXT-STAR            \ advance to the next bit character 
       DUP 47 =             ( -- Va c ?)
       IF  
          I ]LOCATION MOVELEFT   
       THEN 
       I ]VDPLOC VC!         \ store the new star at the VDP location 
    LOOP 
;

: TEST  
    PAGE 
    40 SET# 16 1 COLOR 
    2 SCREEN 
    .STARS
    BEGIN 
       SHIFT-STARS
      ?TERMINAL 
    UNTIL 
    8 SCREEN 
;

 

 

 

 

 

Edited by TheBF
edited .STARS
  • Like 1
Link to comment
Share on other sites

5 minutes ago, TheBF said:

Does the idea of using Video addresses instead X,Y coordinates make sense?

If not ask away.

I am going to make a guess here that a video address being only a single number means the top left of the screen is zero and bottom right is 767.  So the middle of the screen is somewhere around 320?

Link to comment
Share on other sites

2 minutes ago, Retrospect said:

I am going to make a guess here that a video address being only a single number means the top left of the screen is zero and bottom right is 767.  So the middle of the screen is somewhere around 320?

Yes sir.  And by going with addresses we are handling 1/2 the numbers and so less math.

I think it worked out pretty well.

You could do the same thing I did in BASIC with VPEEK() and VPOKE() and single array that holds the VDP addresses. 

 

  • Thanks 1
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...