Jump to content
IGNORED

Forth Tutorials


matthew180

Recommended Posts

I think a useful tool for Forth would by my RXB routines.

CALL HGET(row,column, length,string-variable,[,...]) or CALL VGET(row,column,length,string-variable[,...])

also

CALL HPUT(row,column,string) or CALL VPUT(row,column,string)

or best of all

CALL MOVES("from-to",length,from-address,to-address) or CALL MOVES("from-to",length,from-address,to-VDPregisters)

"from-to" would be RV or VV or RV or GV or V#

as R=RAM, V=VDP,G=GROM/GRAM, #=VDP Register

I have strings in RXB and that one is $=strings so do not know it that would really apply to Forth.

My RXB commands

CALL ONKEY(string-list,key-unit,return-variable,status-variable)GOTO line-number-list

might be good for Forth as it is a CALL KEY ON GOTO and IF value THEN in one command.

Just a suggestion of ideas.

Rich

Link to comment
Share on other sites

Hi Rich,

 

Some nice ideas. Here's how I would tackle them in TurboForth: (I have no idea why the columns don't line up properly)

 

: HGET ( row column length buffer -- )
>R >R		   \ buffer address & length to return stack
SWAP XMAX * +   \ calculate screen address
R>			  \ get length from return stack
R>			  \ get buffer address from return stack
SWAP			\ stack is now: saddr buffer length
VMBR			\ read from screen into buffer
;

: HPUT ( row column length buffer -- )
>R >R		   \ buffer address & length to return stack
SWAP XMAX * +   \ calculate screen address
R>			  \ get length from return stack
R>			  \ get buffer address from return stack
SWAP			\ stack is now: saddr buffer length
VMBW			\ write to screen from buffer
;

The above just use the built in TurboForth words VMBR and VMBW (which work the same was as the editor assembler equivalents). They are very fast because they don't have to do any limit checks, since they are working on consequtive VDP addresses.

 

Here's the vertical equivalents:

0 VALUE EOS
: VGET ( row column length buffer -- )
XMAX 24 * TO EOS \ compute end of screen address
>R >R		   \ move buffer and length out of the way
SWAP XMAX * +   \ convert row and column to screen address
R>			  \ get length back
R>			  \ get buffer address back
SWAP 0 DO	   \ repeat length times
	SWAP		\ get screen address to top of stack
	DUP V@	  \ read a byte from VDP
	2 PICK	  \ get buffer address
	C!		  \ store byte in buffer
	XMAX +	  \ move down 1 row
	DUP		 \ copy new screen address
	EOS >= IF   \ moved off bottom of screen?
		EOS - 1+ \ move to top of next column
	THEN
	SWAP		\ get buffer address
	1+		  \ increase buffer address
LOOP			\ repeat
2DROP		   \ drop buffer address and screen address
;
: VPUT ( row column length buffer -- )
XMAX 24 * TO EOS \ compute end of screen address
>R >R		   \ move buffer and length out of the way	
SWAP XMAX * +   \ convert row and column to screen address  
R>			  \ get length back						  
R>			  \ get buffer address back				  
SWAP 0 DO	   \ repeat length times					  
	DUP C@	  \ get a byte from the buffer				
	2 PICK	  \ get screen address						
	V!		  \ write the byte from the buffer to screen  
	1+		  \ move to next buffer address
	SWAP		\ get screen address on top of stack
	XMAX +	  \ move down 1 row
	DUP		 \ copy new screen address
	EOS >= IF   \ moved off bottom of screen?
		EOS - 1+ \ move to top of next column
	THEN
	SWAP
LOOP
2DROP
;

 

Much more complex, as you can see. Because you are moving 'downwards' in VDP memory, you need to do a check to see if you have 'fallen off' the end of the screen, and correct the screen address if you have.

 

Regarding saving and restoring entire areas of the screen, I decided to leverage the above HGET and HPUT code. The code below will save rectangular areas of the screen to a buffer in memory, and allow you to restore it later. You can restore just a portion (relative to the top left of the saved rectangle) if you like. Neat for a windowing system in text mode.

 

0 VALUE buffer  0 VALUE repeat  0 VALUE length
0 VALUE column  0 VALUE row
: RGET ( row column length repeat buffer -- )
TO buffer
TO repeat
TO length
TO column
TO row
repeat 0 DO
	row column length buffer HGET
	1 +TO row
	length +TO buffer
LOOP
;

: RPUT ( row column length repeat buffer -- )
TO buffer
TO repeat
-ROT
TO column
TO row
repeat 0 DO
	row column 2 PICK buffer HPUT
	1 +TO row
	length +TO buffer
LOOP
DROP
;

 

To test, put something on the screen. Use the word WORDS as it writes lots of stuff to the screen.

post-24932-0-79128800-1318598833_thumb.png

 

Then, save the entire screen to memory:

 

0 0 40 24 HERE RGET

 

(HERE is a word that returns the next free address in memory, so we'll be saving our screen data in CPU ram in un-used RAM - it means we don't have to explicitly ALLOT memory to hold our screen data).

 

Now, clear the screen with PAGE.

 

Now, restore a portion of the saved screen, but at a different location:

 

5 11 12 10 HERE RPUT

 

post-24932-0-92428800-1318598841_thumb.png

 

Finally, here's the code again, but written in the classic horizontal Forth style, as you might see it in a disk block.

 

: HGET ( row column length buffer -- )
>R >R  SWAP XMAX * +  R> R>  SWAP VMBR ;
: HPUT ( row column length buffer -- )
>R >R  SWAP XMAX * +  R> R>  SWAP VMBW ;

0 VALUE EOS
: VGET ( row column length buffer -- )
XMAX 24 * TO EOS  >R >R  SWAP XMAX * +  R> R>  SWAP 0 DO
	SWAP DUP V@  2 PICK C!   XMAX +
	DUP EOS >= IF EOS - 1+ THEN  SWAP 1+
LOOP 2DROP ;
: VPUT ( row column length buffer -- )
XMAX 24 * TO EOS  >R >R  SWAP XMAX * +  R> R>  SWAP 0 DO
	DUP C@  2 PICK V!  1+  SWAP  XMAX +
	DUP EOS >= IF EOS - 1+ THEN SWAP
LOOP 2DROP ;

0 VALUE buffer  0 VALUE repeat  0 VALUE length
0 VALUE column  0 VALUE row

: RGET ( row column length repeat buffer -- )
TO buffer  TO repeat  TO length  TO column  TO row
repeat 0 DO
	row column length buffer HGET
	1 +TO row   length +TO buffer
LOOP ;

: RPUT ( row column length repeat buffer -- )
TO buffer  TO repeat  -ROT  TO column  TO row
repeat 0 DO
	row column 2 PICK buffer HPUT
	1 +TO row   length +TO buffer
LOOP DROP ;

 

Note that all of the above code uses the word XMAX which returns the number of columns on the screen (32, 40 or 80). Thus the above code works in 32, 40 or 80 column modes with no changes at all.

 

I'll tackle the ONKEY example when I get a chance, hopefully over the weekend. :)

 

Mark

Link to comment
Share on other sites

WOW very cool Willsy! That was quick.

I had a command years ago in RXB called JOKE(joy-y1,joy-x1,joy-y2,joy-x2,key-return1,key-return2,status-return) scans Joystick 1 and 2 and key unit 1 and 2 in one command.

JOKE sounded cooler then JOYKEY, well it just sounded gay.

 

When I wrote version 1005 RXB I had one called JOKES(joy-y1,joy-x1,key-return1,key-return2,status-return,sprite#,sprite#) this would control from both joysticks 2 sprites in one command.

But you could also use CALL JOKES(joy-y1,joy-x1,key-return1,status-return,sprite#) would be great for games but XB was just to flipn slow to make much use of it.

 

Anyway do you think my CALL MOVES would be best for Forth as you might put a flag in it for character strings vs say pixels? (This would be impossible in XB)

Link to comment
Share on other sites

 

Hey Willsy... been trying to get some results in tf and making some progress. A few questions for you, if you don't mind...

 

1) Could you put a couple examples of simple sprite setting and moving? I tried to create a SPRITE and had an issue. Here's the word and I tried to attach a pic.

 

: ADSP PAGE 0 MAGNIFY

1 10 10 42 15

SPRITE

BEGIN

BREAK?

AGAIN

;

 

This would create a SPRITE #1 at location 10,10, ascii value of 42 (*) and white... Not exactly showing up as I'd have thought...

 

 

SPRITEISSUE.png

 

 

 

2) Here's an issue I'm having with getting a couple of words to execute... This is in BLOCK 6... I could combine this and make it smaller, but I wanted to make it as easy to read as possible. Here I create two variables, store them with values, fetch them from memory, and attempt to use them.

 

: MAKVAR

VARIABLE PX

VARIABLE PY

;

 

: SETVAR

10 PY !

5 PX ! ;

 

: PCSET

PY @ PX @

42 1 HCHAR

;

 

: TEST MAKVAR SETVAR PCSET ;

 

 

When I do this, I FLUSH, then COLD, then 6 LOAD... it then gives me the following error message:

 

Error:PY not found in SETVAR in block 6

 

Attempting to execute any of the words results in "Error:TEST not found"

 

I tried re-organizing it...

 

 

: MAKVAR VARIABLE PX VARIABLE PY ;

 

: SETVAR 10 PY ! 5 PX ! ;

 

: PCSET PY @ PX @ 42 1 HCHAR ;

 

: TEST MAKVAR SETVAR PCSET ;

 

Also I tried combining MAKVAR and SETVAR:

 

: SETVAR

VARIABLE PX

VARIABLE PY

10 PY !

5 PX !

;

 

and then changing TEST to: : TEST SETVAR PCSET ;

 

Same result. When I try to do MAKVAR and SETVAR in immediate mode, it SEEMS to work... I attached 3 pictures... The test in IMMEDIATE mode and 2 pictures of the EDITOR mode test.

 

EDITTEST1.png

 

EDITTEST2.png

 

 

Also tried using GOTOXY in both IMMEDIATE and EDITOR modes... In immediate mode, it worked perfectly...

 

VARIABLE PY

VARIABLE PX

5 PY ! 10 PX !

PY @ PX @ GOTOXY 42 EMIT

 

 

IMMEDTEST.png

 

The numbers were a bit different, but the same result... worked in IMMEDIATE, not inside a colon definition.

 

I noticed that figFORTH (specifically TI Forth) utilizes a number on the stack when initializing a variable... (n -- ) but there's nothing about that in your glossary. Since there was nothing about that in Starting FORTH by Brodie, I assumed TF got around that.

 

Then my next attempt... using stack only... no variables. In this case, my stack contains a y and an x value... 10 and 5. When "KEY" is called, the ascii code is placed on top of the stack.

 

: SETVAR 10 5 ;

 

: TEST SETVAR

BEGIN

BREAK?

2DUP PAGE 42 1 HCHAR (duplicate top two numbers, clear screen, place "*" onscreen)

KEY 68 -

0 = IF DROP 1 - ELSE (if "E" then drop ascii and subtract 1 from top stack item)

1 = IF DROP SWAP 1 + SWAP ELSE (if "D" then add 1 to second stack item)

15 = IF DROP SWAP 1 - SWAP ELSE (if "S" then subtract 1 from second stack item)

20 = IF DROP 1 + ELSE (if "X" then add 1 to top stack item)

DROP (if the key is none of these, drop it from the stack and complete loop)

THEN THEN THEN THEN

AGAIN

;

 

I am getting some crazy results... starts off okay, but then the screen starts changing color and a mess follows pretty quickly. I haven't really spent much time trying to modify this... I just started messing with this concept earlier this morning... keeping both values on the stack at all times. Perhaps I'm missing some very simple stuff, so I'm posing it here to learn. =)

 

 

Thanks in advance for your answers... I'm sure I'm missing simple stuff... but I'm new to Forth and I'm here to learn from the master. =) BTW, I spent some time in AceForth last night... Love to have an Ace someday. =)

 

Owen Brand

 

Link to comment
Share on other sites

sorry, it messed up my formatting... I didn't put code brackets in there... I hope you get the idea... the last one was:

 

: SETVAR 10 5 ;

: TEST SETVAR
      BEGIN 
      BREAK?
    2DUP PAGE 42 1 HCHAR (duplicate top two numbers, clear screen, place "*" onscreen)
         KEY 68 -
             0 = IF DROP 1 - ELSE  (if "E" then drop ascii and subtract 1 from top stack item)
             1 = IF DROP SWAP 1 + SWAP ELSE (if "D" then add 1 to second stack item)
             15 = IF DROP SWAP 1 - SWAP ELSE (if "S" then subtract 1 from second stack item)
             20 = IF DROP 1 + ELSE (if "X" then add 1 to top stack item)
                 DROP (if the key is none of these, drop it from the stack and complete loop)
         THEN THEN THEN THEN         
       AGAIN
;

Link to comment
Share on other sites

Okay, the sprite problem first. Let's have a look at your code:

 

: ADSP PAGE 0 MAGNIFY
1 10 10 42 15
SPRITE
BEGIN
BREAK?
AGAIN
;

 

Right. A few problems with this. Firstly, you have not selected 32 column mode - you're still in 40 column mode, so sprites won't work at all! The 'noise' you see on the screen in 40 column mode is the SPRITE routine writing into the Sprite Attribute List (SAL) - which is just normal visible screen area in 40 column mode.

 

So, you need a 1 GMODE at the beginning (which also clears the screen) to put it into 32 column mode.

 

Second problem: You are using sprite 1, but sprites begin with 0 in TF. Why is this a problem? Well, you might not be aware but the VDP chip is designed to disable a sprite *and all sprites after it* if its Y value is >D0 (208 decimal). TF, when it selects 32 column mode, disables ALL sprites by writing 208 to the Y value of sprite 0 (actually, it writes it to all 32 sprites). So, by selecting sprite #1, you'll never see it, because sprite 0 has a Y coordinate of 208, which has disabled ALL the sprites. Therefore, you need to use sprite #0 first, #1 next, #2 next etc (or, just set their coordinates to 0 0 and give them a transparent colour (thinking about it, I probably should have done it that way in the first place... Arse...)

 

Lastly (!) you STILL won't see anything (!), because sprites in TF use their own character set - they do not share characters with the the normal displayable 'tile' characters. You have 256 8x8 characters that can be used for sprites, completely seperately from the normal ASCII characters.

 

So, you won't see anything, because the sprite graphic definitions are all zeros! You need to put some data there! You can use DCHAR for this just as you do with normal ASCII characters; just add 256 to the ASCII code to define the equivalent sprite character.

 

So, let's put all of the above into code:

 

: TEST
 1 GMODE \ select 32 column mode

 \ define a cross hatch character for *sprite* char 42:
 DATA 4 $AA55 $AA55 $AA55 $AA55 42 256 + DCHAR

 \ now use sprite 0
 0 10 10 42 15 SPRITE
;

 

So, sprite character 42 is now a cross-hatch shape. It's important to get away from thinking that you have re-defined the asterisk - you haven't. The asterisk is ASCII character 42. We have defined sprite character 42. The asterisk is still there - try it with 42 EMIT :)

 

It's occurred to me as a result of writing this reply that the sprites being physically disabled might confuse the hell out of people - people would naturally expect to be able to use any sprite in any order. As a result, I have just changed the code that initialises the sprites in 32 column mode. It now just sets their colour to transparent, which obviously makes them invisible (so you don't get random sprites appearing instantly when you select 32 column mode), but it means you can use any sprite (e.g. sprite 1 as in your example and it will work). This change will be in version 1.1 :)

 

Hope this helps!

 

Mark

Link to comment
Share on other sites

Okay, your second issue, using variables. Here's your code:

 

: MAKVAR
VARIABLE PX
VARIABLE PY
;

: SETVAR
10 PY !
5 PX ! ;

: PCSET
PY @ PX @
42 1 HCHAR
;

: TEST MAKVAR SETVAR PCSET ;

 

The problem is that the declaration of variables does not go inside a colon definition. They can only go outside of a colon definition, by themselves:

 

VARIABLE PX
VARIABLE PY
: SETVAR
10 PY !
5 PX ! ;

: PCSET
PY @ PX @
42 1 HCHAR
;

: TEST SETVAR PCSET ;

 

Now, it works!

 

SETVAR can be made more flexible by removing the constants, and simply taking data from the stack.

 

So, if you change SETVAR to

: SETVAR ( row column -- ) PX !  PY ! ;

 

Note the stack comment which tells the reader what SETVAR expects on the stack before it is called, and shows that these two values are 'consumed' after the word is finished.

 

Now, we can enter rows and columns on the command line, and call TEST, like this:

 

post-24932-0-54148100-1318944039_thumb.png

 

Et voila!

 

Note: TF also has VALUES - these work like variables, but:

  • They take an initial value when created
  • They return their value when referenced, not their address
  • They are written to with TO not !
  • They are read by just giving their name

Example:

 

99 VALUE TI

TI .

99 ok:0

 

100 TO TI

TI .

100 ok:0

 

25 +TO TI

TI .

125 ok:0

 

I personally find them nicer to use, because you don't get the 'noise' of @ and ! in your source code:

 

0 VALUE PX
0 VALUE PY

: SETVAL
10 TO PY
5 TO PX ;

: PCSET
PY PX 42 1 HCHAR
;

: TEST SETVAL PCSET ;

 

Note also that VALUEs are initialised outside of colon definitions. They also need an initial value.

 

Also, you have CONSTANTs - they work exactly the same as VALUEs but you can't change their value once created (actually, that's not true, but we'll cover that another time!)

 

0 VALUE PX
0 VALUE PY
42 CONSTANT STAR

: SETVAL
10 TO PY
5 TO PX ;

: PCSET
PY PX STAR 1 HCHAR
;

: TEST SETVAL PCSET ;

Link to comment
Share on other sites

I noticed that figFORTH (specifically TI Forth) utilizes a number on the stack when initializing a variable... (n -- ) but there's nothing about that in your glossary. Since there was nothing about that in Starting FORTH by Brodie, I assumed TF got around that.

 

FIG Forth takes an initial value for variables, but TF, which follows the Forth-83 standard, does not - it initialises its variables to 0.

Link to comment
Share on other sites

Regarding your last program, it was crashing due to stack underflow. You were consuming more stack items than you had on the stack. Remember that = consumes two arguments, so you need to DUP the output of KEY for each test in

case the result of the test is false, then you still have the ascii code on the stack for the next test. Obviously, you don't need to do it for the last test.

 

I've re-written your program the way I would do it. I've used constants for the key codes. They do consume memory, but there's no measurable performance penalty in using them. The slowest thing here by far is the actual scanning of the keyboard, which is done via the console ROM.

 

CHAR E CONSTANT KEY_E  CHAR X CONSTANT KEY_X
CHAR S CONSTANT KEY_S  CHAR D CONSTANT KEY_D
: SETVAR 10 5 ;
: TEST SETVAR
BEGIN
PAGE  0 0 GOTOXY .S
2DUP 42 1 HCHAR
KEY  DUP KEY_E = IF DROP SWAP 1- SWAP
ELSE DUP KEY_S = IF DROP 1-
ELSE DUP KEY_D = IF DROP 1+
ELSE	 KEY_X = IF SWAP 1+ SWAP
THEN THEN THEN THEN
AGAIN ; 

 

Hopefully you'll find the above nice and clear. Almost self-describing. A better version might be this, which doesn't rely on PAGE to clear the screen each time:

 

: ERASE 2DUP 32 1 HCHAR ;
: TEST SETVAR PAGE
BEGIN
0 0 GOTOXY .S
2DUP 42 1 HCHAR
KEY  DUP KEY_E = IF DROP ERASE SWAP 1- SWAP
ELSE DUP KEY_S = IF DROP ERASE 1-
ELSE DUP KEY_D = IF DROP ERASE 1+
ELSE	 KEY_X = IF SWAP ERASE 1+ SWAP
THEN THEN THEN THEN
AGAIN ;

 

Now we can use KEY? instead, which doesn't wait for a key:

CHAR E CONSTANT KEY_E  CHAR X CONSTANT KEY_X
CHAR S CONSTANT KEY_S  CHAR D CONSTANT KEY_D
: SETVAR 10 5 ;
: ERASE 2DUP 32 1 HCHAR ;
: TEST SETVAR
BEGIN
0 0 GOTOXY .S
2DUP 42 1 HCHAR
KEY? DUP -1 <> IF
	DUP KEY_E = IF DROP ERASE SWAP 1- SWAP
	ELSE DUP KEY_S = IF DROP ERASE 1-
	ELSE DUP KEY_D = IF DROP ERASE 1+
	ELSE	 KEY_X = IF SWAP ERASE 1+ SWAP
	THEN THEN THEN THEN
ELSE DROP THEN
AGAIN ;

 

You might find it's a bit fast, and you'll also find you can fall off the top/bottom of the screen. We need to constrain the row and columns to the screen boundaries:

 

CHAR E CONSTANT KEY_E  CHAR X CONSTANT KEY_X
CHAR S CONSTANT KEY_S  CHAR D CONSTANT KEY_D
: SETVAR 10 5 ; : ERASE 2DUP 32 1 HCHAR ;
: CLIP-ROW 24 MOD ;  : CLIP-COL XMAX MOD ;
: TEST SETVAR PAGE
BEGIN
2DUP 42 1 HCHAR
KEY? DUP -1 <> IF
		 DUP KEY_E = IF DROP ERASE SWAP 1- CLIP-ROW SWAP
	ELSE DUP KEY_S = IF DROP ERASE 1- CLIP-COL
	ELSE DUP KEY_D = IF DROP ERASE 1+ CLIP-COL
	ELSE	 KEY_X = IF ERASE SWAP 1+ CLIP-ROW SWAP
	THEN THEN THEN THEN
ELSE DROP THEN
AGAIN ;

 

There you go. Quite easy to understand. Admittedly not as intuitive as XB, but it's not too bad. Also, it fits on a single block, and occupies 294 bytes. It would be less if it didn't use constants. However, I think constants make the code MUCH easier to read, and, if you want to change something, you only have to change your code in one place - the constant itself. I use them for character codes, colours, everything!

 

E.g.

 

15 CONSTANT WHITE
42 CONSTANT FACE
20 VALUE ROW
40 VALUE COL
0 CONSTANT SPRITE#0
: AS-SPRITE 256 + DCHAR ;
: FACE_DATA DATA 4 $7E81 $A581 $A599 $817E ;
: TEST
 1 GMODE
 FACE_DATA FACE AS-SPRITE
 SPRITE#0 ROW COL FACE WHITE SPRITE
;

 

By the way, if you are using Firefox, you can just paste the above straight into Classic99, no need to use the editor!

 

Enjoy. May the Forth be with you!

Link to comment
Share on other sites

Figured out last night that the VARIABLE PX VARIABLE PY has to be INSIDE a block but OUTSIDE a colon definition. I don't know why I didn't try that yesterday... Just kind of came to me this morning. =) Still having some issues however... I'm alot closer

 

Block:006
0*VARIABLE PX VARIABLE PY
1*10 PX ! 5 PY !
2*: GETVAR PY@ PX @ ;
3*
4*: TEST
5*    BEGIN GETVAR
6*  2DUP PAGE 42 1 HCHAR
7*      KEY 68 -
8*  	 DUP 0 = IF DROP 1 + PX ! ELSE
9*  	 DUP 1 = IF DROP SWAP 1 - PY ! ELSE
10* 	 DUP 15 = IF DROP 1 - PX ! ELSE
11*		 DUP 20 = IF DROP SWAP 1 + PY ! ELSE
12*		     DROP
13*			 THEN THEN THEN THEN 
14*	  AGAIN
15*;

 

This is as close as I've gotten so far. It works the first several movements of the "*", but then freezes and an unending error tone fills the speakers. I'm thinking the stack might be going out of whack on me and I have no immediate way of telling... I tried to add a BREAK? in there to be able to bounce out and check the stack contents, but once the screen freezes up, I cannot even use BREAK? to get out. Also, it places the cursor at the top of the screen each time I move... I assume this is a product of "KEY"... I do not wish the cursor to appear each time the keyboard is waiting for input.

 

Right now, I don't have internet access at home, so I am doing all my work on my laptop (I'm writing this message on my laptop) then I'm transferring pictures, text and videos to my thumbdrive and taking them to work to my work computer to post here. I don't have regular access to that computer either, so it's kind of touch and go. I have no idea whether you've answered my previous questions or not as I write this message. Anyway, I'm moving in the right direction I believe... Soon it'll be moving more quickly but the toughest thing is learning to think in Forth terms. Once you get used to the format and design of the language, the cloud kind of lifts. =) Thanks again, Mark. This is becoming a very nice journey.

Link to comment
Share on other sites

Block:006
0*VARIABLE PX VARIABLE PY
1*10 PX ! 5 PY !
2*: GETVAR PY@ PX @ ;
3*
4*: TEST
5*	BEGIN GETVAR
6*  2DUP PAGE 42 1 HCHAR
7*	  KEY 68 -
8*	   DUP 0 = IF DROP 1 + PX ! ELSE
9*	   DUP 1 = IF DROP SWAP 1 - PY ! ELSE
10*	  DUP 15 = IF DROP 1 - PX ! ELSE
11*		 DUP 20 = IF DROP SWAP 1 + PY ! ELSE
12*			 DROP
13*			 THEN THEN THEN THEN
14*	  AGAIN
15*;

 

 

 

I think you have your thinking a bit mixed up in this version. I haven't tried your code. Just 'speed read' it, but it seems like you have two different ideas kind of mixed up.

 

You are placing the row and column on the stack with 2DUP and then you store the values back in the variables. However, if you're using the stack to carry your row and column, there's no need for variables in this particular example. I'm fairly sure you are overflowing the stack (the stack has room for ~20 values)

 

I would advocate a simpler, one or the other approach.

 

Using just variables, it becomes just like any other programming language: You read a variable, you modify, and you write it back again.

 

E.g. in BASIC:

 

CALL KEY(0,K,S)
IF K=68 THEN X=X+1

 

In Forth:

KEY 68 = IF X @ 1+ X !THEN 

 

Or:

KEY 68 = IF 1 X +!

 

There's a slight difference, in that in BASIC, your key code is in a variable, and in the Forth examples above, the key code is on the stack, meaning that if you want to test the key code more than once then you need to DUP ("dupe") it so that it's still on the stack for subsequent tests. I can see from your code that you do understand this concept, however.

 

However, if you wanted, you could simply put the key-code in a variable as well, and the Forth code suddenly becomes very BASIC like (and un-Forth like at the same time!)

 

VARIABLE ROW  10 ROW !
VARIABLE COL  10 COL !
VARIABLE KEYCODE

: TEST
 BEGIN
ROW @ COL @ 42 1 HCHAR
KEY? KEYCODE !
KEYCODE @ -1 <> IF
	ROW @ COL @ 32 1 HCHAR
	KEYCODE @ 68 = IF 1 COL +! THEN
	KEYCODE @ 83 = IF 1 COL -! THEN
	KEYCODE @ 69 = IF 1 ROW -! THEN
	KEYCODE @ 88 = IF 1 ROW +! THEN
	KEYCODE @ 32 = IF EXIT THEN
	ROW @ 24 MOD ROW !
	COL @ XMAX MOD COL !
THEN
 AGAIN
;

 

This is written in the style of a BASIC program. It works, and it's very fast (too fast, in fact). However, we can make it faster, and smaller (the above code is 248 bytes) by using the stack:

 

VARIABLE ROW  10 ROW !
VARIABLE COL  10 COL !

: TEST
 BEGIN
ROW @ COL @ 42 1 HCHAR
KEY? DUP
-1 <> IF
	ROW @ COL @ 32 1 HCHAR
	DUP 68 = IF DROP 1 COL +! ELSE
	DUP 83 = IF DROP 1 COL -! ELSE
	DUP 69 = IF DROP 1 ROW -! ELSE
	DUP 88 = IF DROP 1 ROW +! ELSE
	32 = IF EXIT THEN
	THEN THEN THEN THEN
	ROW @ 24 MOD ROW !
	COL @ XMAX MOD COL !
ELSE
	DROP
THEN
 AGAIN
;

 

Here, the keycode variable has been eliminated, the keycode is 'carried' on the stack. The reason it is faster is because a those DUPs and DROPs are much simpler machine code routines than the variable fetch routines. It's slightly smaller, at 242 bytes.

 

Of course, it is possible to code the above with no variables at all - the 'pure' Forth way:

 

: TEST
 10 10 \ row and column
 BEGIN
2DUP 42 1 HCHAR
KEY? DUP
-1 <> IF
	-ROT 2DUP 32 1 HCHAR ROT
	DUP 68 = IF DROP 1+ ELSE
	DUP 83 = IF DROP 1- ELSE
	DUP 69 = IF DROP SWAP 1- SWAP ELSE
	DUP 88 = IF DROP SWAP 1+ SWAP ELSE
	32 = IF 2DROP EXIT THEN
	THEN THEN THEN THEN
	XMAX MOD SWAP  24 MOD SWAP
ELSE
	DROP
THEN
 AGAIN
;

 

This is, not to put too fine a point on it, is f***ing fast! The 'slow' part is the actual scanning of the keyboard. It's also far smaller, at 188 bytes.

 

We keep the row and column on the stack with the 2DUP just after the BEGIN.

 

Stack: row column

 

Then we scan the keyboard, and DUP the keycode returned:

 

Stack: row column keycode keycode

 

Then we check to see if the keycode was -1 (no key pressed). The <> test consumes the top keycode (that's why we DUPd it).

 

Stack: row column keycode

 

If NO key was pressed, we enter the ELSE clause at the end of the code, and drop the keycode:

 

Stack: row column

 

And then we loop 'round again.

 

If a key was pressed, then our stack looks like this:

 

row column keycode

 

We need to remove the star from the screen (by replacing it with a space). We need to get to the row and column on the stack, but the keycode is in the way. So, we use -ROT which rotates the top 3 stack items to the right. The items that WAS on the top now becomes the 3rd stack item:

 

Stack: keycode row column

 

Hey presto. We can get to the row and column.

 

We now DUP the row and column, because HCHAR would consume them otherwise:

 

Stack: keycode row column row column

 

We now execute HCHAR to write a space.

 

Stack: keycode row column

 

Now we use ROT to move the top three items the other way:

 

Stack: row column keycode

 

Now we DUP the keycode, because we are going to perform a test on it, and the = word will consume it:

 

Stack: row column keycode keycode

 

We compare it to 68:

 

Stack: row column keycode

 

If it IS 68, we drop the keycode:

 

Stack: row column

 

And add 1 to the column with 1+

 

Stack: row column+1

 

This is repeated for the other keys.

 

We don't need to DUP the keycode for the last test (our exit condition - the space bar) because we want it off the stack for the next time 'round, so we let = get rid of it for us.

 

Then we just limit the row and column with MOD.

 

We're done.

 

Very fast, very Forthy, and very cool.

 

Not intuitive though. There's no doubt, its harder than Basic. It's more like machine code. You have to follow the stack contents in your head, much like you would registers and condition bits in machine code. It's the code equivalent of Suduko. But it gets easier.

 

For what it's worth: The code I wrote above didn't work first time, and I had to have a couple of hacks at it! I write my code in Notepad++ and just paste it into the command line of TF to test it. When it works, I save it in blocks!

 

Hope this helps.

 

Mark

Link to comment
Share on other sites

Just had the time to run through your examples... Very VERY good stuff, Mark! Using constants DOES make code alot more legible... and using VALUEs instead of variables is quite intuitive as well. I had already figured out the "outside-inside" colon definition issue you mentioned in your earlier response, but I have a better understanding of it now from reading your posts.

 

I WAS using both concepts in that example and it was a mistake. =) Thanks for pointing it out for me and showing the alternatives. I am reminded of the line in Starting Forth quoting Charles Moore... something about factoring...

 

Get Can of Tomatoes

Open Can of Tomatoes

Pour Can of Tomatoes

Get Can of Mushrooms

Open Can of Mushrooms

Pour Can of Mushrooms

 

In Forth, you factor out Get, Open, Pour, and Can

 

Which factors DOWN to:

 

ADD TOMATOES

ADD MUSHROOMS

 

Figuring out the things you use multiple times and creating a word so that your highest level definitions are human-readable... The washing machine example in that book... It comes together quite nicely when you start working with Forth at an actual terminal with an editor. And I very much enjoy the feel of TF. It feels much more natural than Wycove and is much easier to program than AceForth.

 

I don't have much time today to program, but this weekend I plan to make a good go of writing the start of my Tidmouth Station game. (Willsy, you ought to know what that's about =)) I will need to figure out how to do screen-draws using VMBW while referencing a pre-made 768 byte list (probably produced from Magellan)... I have your glossary but will probably be needing a bit of a short tutorial on how to use file IO in TF. The glossary is a skeleton of the system, but a bit of fleshing out may be required for me. Also will need to learn to use SMLIST to send my engine and freight cars around the tracks. =)

 

 

tidmouth.png

 

 

The glossary has the following stack effect:

vdp_address cpu_address count --

 

I'm assuming it would be something like

 

some address here CONSTANT MAPDAT1

 

: DRAW 0 MAPDAT1 768 VMBW ;

 

the VMBW will hopefully read something like the following:

 

 

 

      DATA >2020,>4245,>5259,>4C20    ;
      DATA >2048,>5020,>3335,>2020    ;
      DATA >2020,>2020,>4C49,>5645    ;
      DATA >5320,>3220,>2020,>2020    ;
* -- Map Row 1 --
      DATA >2020,>2020,>2020,>2020    ;
      DATA >2A20,>202A,>2020,>2020    ;
      DATA >2020,>2020,>2020,>2A20    ;
      DATA >2020,>202A,>2020,>202A    ;
* -- Map Row 2 --
      DATA >2020,>2020,>2020,>2020    ;
      DATA >2A20,>202A,>2020,>2020    ;
      DATA >2020,>2020,>2020,>2A20    ;
      DATA >2020,>202A,>2020,>202A    ;
* -- Map Row 3 --
      DATA >2020,>2020,>2020,>2020    ;
      DATA >2A20,>202A,>2020,>2020    ;
      DATA >2020,>2020,>2020,>2A20    ;
      DATA >2020,>202A,>2020,>202A    ;
* -- Map Row 4 --
      DATA >2020,>2020,>2020,>2020    ;
      DATA >2A20,>202A,>2A2A,>2A2A    ;
      DATA >2A2A,>2020,>2020,>2A2A    ;
      DATA >2A2A,>2A2A,>2020,>202A    ;
* -- Map Row 5 --
      DATA >2020,>2020,>2020,>2020    ;
      DATA >2A20,>2020,>2020,>2020    ;
      DATA >202A,>2A2A,>2A2A,>2A20    ;
      DATA >2A20,>2A2A,>2020,>202A    ;
* -- Map Row 6 --
      DATA >2A2A,>2A2A,>2020,>2020    ;
      DATA >2A20,>2020,>2020,>2020    ;
      DATA >2020,>2020,>2020,>2A2A    ;
      DATA >202A,>202A,>2020,>202A    ;
* -- Map Row 7 --
      DATA >2020,>202A,>2020,>2020    ;
      DATA >2A20,>2020,>2020,>2020    ;
      DATA >2020,>2020,>2020,>2A20    ;
      DATA >2A20,>2A2A,>2020,>202A    ;
* -- Map Row 8 --
      DATA >2020,>202A,>2020,>2020    ;
      DATA >2A20,>202A,>2A2A,>2A2A    ;
      DATA >2020,>2020,>2020,>2A2A    ;
      DATA >202A,>202A,>2020,>202A    ;
* -- Map Row 9 --
      DATA >2020,>202A,>2020,>2020    ;
      DATA >2A20,>202A,>2020,>202A    ;
      DATA >2A2A,>2A2A,>2020,>2A20    ;
      DATA >2A20,>2A2A,>2020,>202A    ;
* -- Map Row 10 --
      DATA >2023,>202A,>2020,>2020    ;
      DATA >2A20,>202A,>2020,>2020    ;
      DATA >2020,>202A,>2020,>2A2A    ;
      DATA >202A,>202A,>2020,>202A    ;
* -- Map Row 11 --
      DATA >2020,>202A,>2A2A,>2A2A    ;
      DATA >2A20,>202A,>2020,>2020    ;
      DATA >2020,>202A,>2020,>2A20    ;
      DATA >2A20,>2A2A,>2020,>202A    ;
* -- Map Row 12 --
      DATA >2020,>2020,>2020,>2020    ;
      DATA >2020,>202A,>2020,>2020    ;
      DATA >2020,>202A,>2020,>2A2A    ;
      DATA >202A,>202A,>2020,>202A    ;
* -- Map Row 13 --
      DATA >2023,>2020,>2020,>2020    ;
      DATA >2020,>202A,>2020,>2020    ;
      DATA >2020,>202A,>2020,>2A20    ;
      DATA >2A20,>2A2A,>2020,>202A    ;
* -- Map Row 14 --
      DATA >2020,>202A,>2A2A,>2A2A    ;
      DATA >2A23,>202A,>2020,>2020    ;
      DATA >2020,>202A,>2020,>2A2A    ;
      DATA >2A2A,>2A2A,>2020,>202A    ;
* -- Map Row 15 --
      DATA >2020,>202A,>2020,>2020    ;
      DATA >2A20,>202A,>2020,>2020    ;
      DATA >2020,>202A,>2020,>2020    ;
      DATA >2020,>2020,>2020,>202A    ;
* -- Map Row 16 --
      DATA >2023,>202A,>2020,>2020    ;
      DATA >2A20,>202A,>2A2A,>2A20    ;
      DATA >2020,>202A,>2020,>2020    ;
      DATA >2020,>2020,>2020,>202A    ;
* -- Map Row 17 --
      DATA >2020,>202A,>2020,>2020    ;
      DATA >2A20,>2020,>2020,>2A20    ;
      DATA >2020,>202A,>2A2A,>2A2A    ;
      DATA >2A2A,>2A2A,>2020,>202A    ;
* -- Map Row 18 --
      DATA >2020,>202A,>2020,>2020    ;
      DATA >2A20,>2020,>2020,>2A20    ;
      DATA >2020,>2020,>2A2A,>2A2A    ;
      DATA >2A2A,>2A2A,>2020,>202A    ;
* -- Map Row 19 --
      DATA >2A2A,>2A2A,>2020,>2020    ;
      DATA >2A2A,>2A2A,>2A20,>2A20    ;
      DATA >2020,>2020,>2A20,>2020    ;
      DATA >2020,>2020,>2020,>2A2A    ;
* -- Map Row 20 --
      DATA >2020,>2020,>2020,>2020    ;
      DATA >2020,>2020,>2A20,>2A20    ;
      DATA >2020,>2020,>2A20,>2020    ;
      DATA >2020,>2020,>2020,>2A20    ;
* -- Map Row 21 --
      DATA >2020,>2020,>2020,>2020    ;
      DATA >2020,>2020,>2A20,>2A20    ;
      DATA >2020,>2020,>2A20,>2023    ;
      DATA >2020,>2020,>2020,>2A20    ;
* -- Map Row 22 --
      DATA >2020,>2020,>2020,>2020    ;
      DATA >2020,>2020,>2A20,>2A20    ;
      DATA >2020,>2020,>2A20,>2020    ;
      DATA >2020,>2020,>2020,>2A20    ;
* -- Map Row 23 --
      DATA >2020,>2020,>2020,>2020    ;
      DATA >2020,>2020,>2A20,>2A20    ;
      DATA >2020,>2020,>2A2A,>2A2A    ;
      DATA >2A2A,>2A2A,>2A2A,>2A20    ;
      END	   

 

 

 

As a newb, I'm quite dependent on examples to fill out my shaky understanding of certain aspects of Forth as a language. I have the TI Forth manual which gives solid examples on how to use file IO as it pertains to the TI-99 architecture--- but I'm pretty sure the syntax and operations are at least slightly different with TF. I assume that's where your book comes in. =)

 

Again, I'm endebted to you. =) Byebye XB.

 

 

Link to comment
Share on other sites

Very impressive, Mark!

 

I realize I'm standing on the shoulders of giants, here; but, what about this:

 

: TEST
 10 10 \ row and column
 BEGIN
 2DUP 42 1 HCHAR
 KEY? DUP
 -1 <> IF
-ROT 2DUP 32 1 HCHAR ROT
CASE
 68 OF 1+ ENDOF
 83 OF 1- ENDOF
 69 OF SWAP 1- SWAP ENDOF
 88 OF SWAP 1+ SWAP ENDOF
 32 OF 2DROP EXIT ENDOF
ENDCASE
XMAX MOD SWAP 24 MOD SWAP
 ELSE
DROP
 THEN
 AGAIN
;

 

It's only 10 bytes longer at 198 bytes, but, it may be clearer---at least, it's different.

 

...lee

Link to comment
Share on other sites

Aha! Here it is at 188 bytes. Now, it's just a matter of taste:

 

: TEST
 10 10 \ row and column
 BEGIN
2DUP 42 1 HCHAR
KEY? 1+  \ add 1 to make 0 if no key pressed
?DUP	 \ DUP if not 0
IF
  -ROT 2DUP 32 1 HCHAR ROT
  CASE  \ check incremented ASCII values
	69 OF 1+ ENDOF
	84 OF 1- ENDOF
	70 OF SWAP 1- SWAP ENDOF
	89 OF SWAP 1+ SWAP ENDOF
	33 OF 2DROP EXIT ENDOF
  ENDCASE
  XMAX MOD SWAP 24 MOD SWAP
THEN
 AGAIN
;

 

...lee

Link to comment
Share on other sites

Hi, If you guys are considering exporting data from Magellan, then please take a look at some of my last comments in the Magellan thread.

There are some errors in the assembler data export. Values for color table don't match up and there was some other issue I can't recall right now.

Anyway, I've fixed these bugs and there is an "unofficial" Magellan version attached in one of the last posts of that thread.

Link to comment
Share on other sites

Aha! Here it is at 188 bytes. Now, it's just a matter of taste:

 

: TEST
 10 10 \ row and column
 BEGIN
2DUP 42 1 HCHAR
KEY? 1+  \ add 1 to make 0 if no key pressed
?DUP	 \ DUP if not 0
IF
  -ROT 2DUP 32 1 HCHAR ROT
  CASE  \ check incremented ASCII values
	69 OF 1+ ENDOF
	84 OF 1- ENDOF
	70 OF SWAP 1- SWAP ENDOF
	89 OF SWAP 1+ SWAP ENDOF
	33 OF 2DROP EXIT ENDOF
  ENDCASE
  XMAX MOD SWAP 24 MOD SWAP
THEN
 AGAIN
;

 

...lee

 

He he! Very nice sir! Indeed, CASE is your friend. The only reason I didn't mention it (though I think I mentioned earlier up in the thread) is that I figure that Owen isn't ready for it yet - I think he's still thinking first in BASIC terms, and how that translates to Forth. As we know, it helps if you try not to think in terms of your previous experience with other languages, as Forth is so different.

 

The trick with adding 1 to the keycode is nice! I think I used that somewhere inside the TF Kernal if I remember correctly. Probably with the word WORDS.

 

But yep, for sure, CASE is the way to go for me.

 

I think the rule is "When you have a CASE of checking the same value against a list, it just a CASE of using CASE!" :)

 

Man, I'm good ;)

Link to comment
Share on other sites

Hi, If you guys are considering exporting data from Magellan, then please take a look at some of my last comments in the Magellan thread.

There are some errors in the assembler data export. Values for color table don't match up and there was some other issue I can't recall right now.

Anyway, I've fixed these bugs and there is an "unofficial" Magellan version attached in one of the last posts of that thread.

Thanks, Retroclouds, good call.

 

Owen, with regard to your screen data for your levels, there are (as there always are in forth) many ways to skin the cat (poor old cat).

 

I think the way we will go, is to simply use the block editor to, quite literally, draw your screen! Your screens will sit inside blocks.

 

If your train tracks are no deeper than 16 screen lines, it's really really simple: Just draw them using the ASCII characters that define them (or, use a substitute, and we'll translate the data on the fly as we draw it).

 

If your level data is deeper than 16 lines, then you can still fit it on one block: A block is 64 characters wide, so it will hold exactly two lines of 32 column screen data. In that case, characters 32 to 64 on line 0 of the block would be line 17 of your track. Characters 32 to 64 on line 1 would be line 18 of your track etc.

 

Hope that makes sense.

 

By doing it that way, you don't need pages and pages of data (that take up space in CPU ram, as they are loaded with your code and sit in memory). Your track data, being in a block, will effectively take up no RAM at all. Nothing.

 

BLOCK IO in TF is also pathetically easy. There are a suite of words associated with Block IO (far more than are mandated in the Forth 83 standard) so it's quite powerful. Also, remember that in TF, block data lives in VDP memory - you can have up to six blocks in memory. When you ask TF to go get a block, it first checks its '6 block cache' and if it is already in memory then it won't even go to the disk drive - it will be instant. This means you could, if you wanted to, load 6 levels at a time, with effectively no memory penalty (they are in VDP ram) and no run-time penalty, once they are loaded.

 

Hope this all makes sense; when you are ready let me know. Hang in there - you're about to go on a great journey!

 

And Lee (and anyone else), please feel free to chip in as you like!

 

Mark

Link to comment
Share on other sites

Hi, If you guys are considering exporting data from Magellan, then please take a look at some of my last comments in the Magellan thread.

There are some errors in the assembler data export. Values for color table don't match up and there was some other issue I can't recall right now.

Anyway, I've fixed these bugs and there is an "unofficial" Magellan version attached in one of the last posts of that thread.

 

For the benefit of everyone, the magellan thread is here: http://www.atariage.com/forums/topic/161356-magellan/page__st__250

 

It should be perfectly possible to develop the screens in magellan, too, and write a 'pre-processor' in TF to process and import the data into blocks.

Link to comment
Share on other sites

Yes Mark... I VERY much like the "Forthy" one (of your examples from yesterday)... But, as you said, not very intuitive. It is the type of code that shows seasoned skill with the language. I would, of course, like to eventually be able to code in that fashion--- It is what makes Forth so "nifty." It's almost like a waltz... You "dance" with the stack as you manipulate it--- Good dancers won't step on toes... =)

For me... a square dance is probably the best way to start. Blocky VALUEs and straight-forward, BASIC-styled code segments will give me the start I need before I learn more intricate steps. Thank you for the step-by-step of the stack contents as well... That actually helped quite a bit. I have a feeling that AceForth and FIGnition are primarily designed to work the "pure-Forth way".

Block:006
0*VARIABLE PX VARIABLE PY
1*10 PX ! 5 PY !
2*: GETVAR PY@ PX @ ;
3*
4*: TEST
5*    BEGIN GETVAR
6*  2DUP PAGE 42 1 HCHAR
7*	  KEY 68 -
8*    DUP 0 = IF DROP 1 + PX ! ELSE
9*    DUP 1 = IF DROP SWAP 1 - PY ! ELSE
10*   DUP 15 = IF DROP 1 - PX ! ELSE
11*   DUP 20 = IF DROP SWAP 1 + PY ! ELSE
12*	   DROP
13*    THEN THEN THEN THEN
14*   AGAIN
15*;

Looking back at the above code, what I did was just combine my straight Forth example

from a previous post with the VARIABLE concept without really modifying it. I see clearly

that the stack was overflowed--- the 2DUP was used for the example without variables and

wasn't removed when the variables were added. I think I'll go with VALUEs now, though...

Since I know they are available and how they're used... much cleaner. =)

 

(again, apologies for the messed up formatting in the below test code)

10 VALUE PX 5 VALUE PY
: TEST
   BEGIN PY PX
   2DUP 42 1 HCHAR
   KEY? DUP
-1 <> IF
 -ROT 2DUP 32 1 HCHAR ROT
 	 DUP 68 = IF DROP 1 +TO PX ELSE
 	 DUP 69 = IF DROP -1 +TO PY ELSE
  DUP 83 = IF DROP -1 +TO PX ELSE
 DUP 88 = IF DROP 1 +TO PY ELSE
		  THEN THEN THEN THEN
	  ELSE DROP 2DROP THEN     
  AGAIN
;

1) defined values for PX and PY (-)

2) begin loop and add PY and PX to stack (PY PX)

3) 2DUP the values, add 42 and 1, execute HCHAR (PY PX)

4) Check for KEY, if -1 (no key pressed) skip to ELSE (PY PX KEY)

5) If no key pressed, DROP KEY, 2DROP PX PY (-)

6) Loop back to BEGIN, adding PY and PX again (PX PY)

7) If KEY is <>-1 (meaning key pressed) perform -ROT (KEY PY PX) **This particular function I VERY much like

8) 2DUP PX and PY and add 32 and 1, execute HCHAR, erasing the * (KEY PY PX)

9) ROT KEY to top (PX PY KEY)

10) Begin checks by DUPing the key each check

11) Instead of using + and ! (store) (variable), a +TO will modify my VALUE

I think I have this right, Willsy... I still don't have screen parameters

in this example, but it works nicely AND I'm avoiding using variables.

 

This sh** makes me salivate, man. =) I don't really know how to describe the effect it has on my brain, but it's pretty consuming. I'm sure you understand what I mean. I have no desire to use line numbers anymore. This crazy reverse-Polish, stack-based language has me pretty much wound up. The inventor of FIGnition is sending me a machine with some experimental NTSC firmware to try out... I'm buying the system from him--- but it's only $35 USD shipped. =) It's quirky and interesting to me, so I thought I'd buy one.

 

 

Sunday will be a Forth day for me... I've "block"ed out a few hours to work on my Forthing. If I get started on my game idea, great... but I'll probably be exploring and exercising my itchy fingers. =)

Wish me luck buddy. =)

Link to comment
Share on other sites

Sorry if this is an extremely elementary question, but....

 

XMAX MOD SWAP 24 MOD SWAP

 

I understand that this checks (using MOD) whether "something" is above XMAX (which is the maximum column value for the current GMODE) I also understand that 24 MOD checks for the row... but how is the ROW indicated in this line? In BASIC, It would be something like

 

IF PY>24 THEN PY=24

 

This would hold the character at 24 as long as "down" is pressed... How does

 

XMAX MOD SWAP 24 MOD SWAP

 

check for WHERE the star is in this case... I guess I understand in principle and it would be easy to put this into my above code (where I used VALUEs instead of VARIABLEs) but I'd like to know exactly how/why it works before I add it into my code... Thanks in advance. =)

Link to comment
Share on other sites

Owen...

 

I hope you don't mind my jumping in here. Regarding

 

XMAX MOD SWAP 24 MOD SWAP

 

Some of this I am sure you know, but tracking through it, before this line is executed in Willsy's example, the stack contains the row and column: ( 5 10 ). XMAX adds the screen width (say 40) to the top of the stack ( 5 10 40 ). MOD divides the top number on the stack into the next one down and leaves the remainder on the stack ( 5 10 ). SWAP gets the row to the top of the stack ( 10 5 ). 24 is added ( 10 5 24 ) MOD executes again, taking the top two numbers and leaving the remainder ( 10 5 ). SWAP retores the row-column order ( 5 10 ), which is now ready for the next go round.

 

What the above code does is to wrap around from one edge of the screen to the other, such that, if you hold one key down, the '*' will continue moving in that direction (with wrapping) until you release the key, rather than banging into the edge and staying there.

 

Also, since you wish to use VALUEs instead of the faster and more compact stack manipulations, you don't need much of the DUP, ROT, SWAP and -ROT code. Here is your last example with those removed and making full use of PX and PY:

 

10 VALUE PX 5 VALUE PY
: TEST
  BEGIN
PY PX 42 1 HCHAR
KEY? DUP
-1 <> IF
PY PX 32 1 HCHAR
DUP 68 = IF DROP 1 +TO PX ELSE
DUP 69 = IF DROP -1 +TO PY ELSE
DUP 83 = IF DROP -1 +TO PX ELSE
DUP 88 = IF DROP 1 +TO PY ELSE
32 = IF EXIT THEN
   THEN THEN THEN THEN
PY 24 MOD TO PY
PX XMAX MOD TO PX
 ELSE
DROP
 THEN
  AGAIN
;

 

FYI, this takes 238 bytes of dictionary space, which is 25 % more space than Willsy's or my stack-based code; but, it is still very fast, indeed!

 

...lee

Edited by Lee Stewart
Link to comment
Share on other sites

Yeah! What Lee said ;-)

 

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

 

Owen, to illustrate the use of MOD, try this:

 

: TEST 1000 0 DO I XMAX MOD . LOOP ;

 

See? The value displayed never goes above 39, because we are taking a REMAINDER of a division.

 

This is very neat for wrap around effects and stuff and like that.

Link to comment
Share on other sites

IF PY>24 THEN PY=24

 

The equivalent in Forth would be:

 SWAP DUP 23 > IF DROP 23 THEN SWAP ;

 

You need the swaps because the row is underneath the column on the stack.

 

For the column, it's simpler:

DUP 39 > IF DROP 39 THEN

 

For example, if I wanted, say, Lee's routine, and didn't want wrap around, then I could go for this:

 

10 VALUE PX 5 VALUE PY
: TEST
  BEGIN
PY PX 42 1 HCHAR
KEY? DUP
-1 <> IF
 PY PX 32 1 HCHAR
 DUP 68 = IF DROP 1 +TO PX ELSE
 DUP 69 = IF DROP -1 +TO PY ELSE
 DUP 83 = IF DROP -1 +TO PX ELSE
 DUP 88 = IF DROP 1 +TO PY ELSE
 32 = IF EXIT THEN
 THEN THEN THEN THEN

 \ do limit checks
 PY 23 > IF 23 TO PY THEN
 PY 0 < IF 0 TO PY THEN
 PX XMAX = IF XMAX 1- TO PX THEN
 PX 0 < IF 0 TO PX THEN

ELSE
 DROP
THEN
  AGAIN
;

 

That should work fine. We're quite used to seeing code like this in XB. However, it annoys me that the code is checking both the row AND the column each time through the loop, when we can only move up/down OR left/right each time through the loop (we can't do diagnals with the keyboard). Even worse, if the star is, say, on the far right of the screen, then it cant possibly be on the far left of the screen, so why check both? (Same for the row, if it's at the top then it can't be at the bottom, so why check?)

 

It would be faster to put the appropriate checks with the code that does the movement:

 

10 VALUE PX 5 VALUE PY
: TEST
  BEGIN
PY PX 42 1 HCHAR
KEY? DUP
-1 <> IF
 PY PX 32 1 HCHAR
 DUP 68 = IF DROP PX XMAX 1- < IF 1 +TO PX THEN ELSE
 DUP 69 = IF DROP PY 0 > IF -1 +TO PY THEN ELSE
 DUP 83 = IF DROP PX 0 > IF -1 +TO PX THEN ELSE
 DUP 88 = IF DROP PY 23 < IF 1 +TO PY THEN ELSE
 32 = IF EXIT THEN
 THEN THEN THEN THEN
ELSE
 DROP
THEN
  AGAIN
;

 

As you can see, when we want to go, say, downwards, we check the row first, and if we're not on the bottom line already we allow it, otherwise we leave the row unchanged. The point is, *only* one check is done per direction, instead of four, making it potentially 4 times faster, and measurably (if you have some way of measuring it!) faster.

 

You're probably already familiar with tese types of optimizations, but I thought I'd point it out for the benefit of the lurkers!

 

Mark

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