Jump to content

Photo

Forth Tutorials


331 replies to this topic

#26 RXB OFFLINE  

RXB

    Stargunner

  • 1,569 posts
  • Location:Vancouver, Washington, USA

Posted Thu Oct 13, 2011 6:48 PM

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

#27 Willsy OFFLINE  

Willsy

    River Patroller

  • 2,014 posts
  • Location:Uzbekistan (no, really!)

Posted Fri Oct 14, 2011 7:34 AM

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.
Attached File  RGET1.PNG   72.03KB   9 downloads

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

Attached File  RGET2.PNG   22.93KB   8 downloads

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

#28 RXB OFFLINE  

RXB

    Stargunner

  • 1,569 posts
  • Location:Vancouver, Washington, USA

Posted Fri Oct 14, 2011 2:46 PM

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)

#29 Opry99er OFFLINE  

Opry99er

    River Patroller

  • 3,616 posts
  • Location:Denver, CO

Posted Mon Oct 17, 2011 7:26 PM


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


Posted Image



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.

Posted Image

Posted Image


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


Posted Image

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


#30 Opry99er OFFLINE  

Opry99er

    River Patroller

  • 3,616 posts
  • Location:Denver, CO

Posted Mon Oct 17, 2011 7:30 PM

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
;



#31 Willsy OFFLINE  

Willsy

    River Patroller

  • 2,014 posts
  • Location:Uzbekistan (no, really!)

Posted Tue Oct 18, 2011 6:24 AM

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

#32 Willsy OFFLINE  

Willsy

    River Patroller

  • 2,014 posts
  • Location:Uzbekistan (no, really!)

Posted Tue Oct 18, 2011 7:29 AM

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:

Attached File  owen1.PNG   35.38KB   8 downloads

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 ;


#33 Willsy OFFLINE  

Willsy

    River Patroller

  • 2,014 posts
  • Location:Uzbekistan (no, really!)

Posted Tue Oct 18, 2011 7:33 AM

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.

#34 Willsy OFFLINE  

Willsy

    River Patroller

  • 2,014 posts
  • Location:Uzbekistan (no, really!)

Posted Tue Oct 18, 2011 9:15 AM

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!

#35 Opry99er OFFLINE  

Opry99er

    River Patroller

  • 3,616 posts
  • Location:Denver, CO

Posted Tue Oct 18, 2011 7:48 PM

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.

#36 Opry99er OFFLINE  

Opry99er

    River Patroller

  • 3,616 posts
  • Location:Denver, CO

Posted Tue Oct 18, 2011 7:50 PM

BTW thanks for your detailed responses above... I've copied them to my thumbdrive and I'll be reviewing and working through them tonight. I had a video of my issues but cannot seem to post it from my antiquated dinosaur of a computer here at work. =) You're the man Willsy!

#37 Willsy OFFLINE  

Willsy

    River Patroller

  • 2,014 posts
  • Location:Uzbekistan (no, really!)

Posted Wed Oct 19, 2011 3:23 AM

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

#38 Opry99er OFFLINE  

Opry99er

    River Patroller

  • 3,616 posts
  • Location:Denver, CO

Posted Wed Oct 19, 2011 7:21 PM

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


Posted Image


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:

Spoiler


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.



#39 Lee Stewart OFFLINE  

Lee Stewart

    Stargunner

  • 1,562 posts
  • Location:Maryland

Posted Wed Oct 19, 2011 8:36 PM

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

#40 Lee Stewart OFFLINE  

Lee Stewart

    Stargunner

  • 1,562 posts
  • Location:Maryland

Posted Wed Oct 19, 2011 9:39 PM

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

#41 retroclouds OFFLINE  

retroclouds

    Stargunner

  • 1,370 posts
  • Location:Germany

Posted Wed Oct 19, 2011 11:56 PM

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.

#42 Willsy OFFLINE  

Willsy

    River Patroller

  • 2,014 posts
  • Location:Uzbekistan (no, really!)

Posted Thu Oct 20, 2011 2:27 AM

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 ;)

#43 Willsy OFFLINE  

Willsy

    River Patroller

  • 2,014 posts
  • Location:Uzbekistan (no, really!)

Posted Thu Oct 20, 2011 2:40 AM

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

#44 Willsy OFFLINE  

Willsy

    River Patroller

  • 2,014 posts
  • Location:Uzbekistan (no, really!)

Posted Thu Oct 20, 2011 2:51 AM

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

#45 Opry99er OFFLINE  

Opry99er

    River Patroller

  • 3,616 posts
  • Location:Denver, CO

Posted Thu Oct 20, 2011 1:45 PM

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

#46 Opry99er OFFLINE  

Opry99er

    River Patroller

  • 3,616 posts
  • Location:Denver, CO

Posted Thu Oct 20, 2011 1:53 PM

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

#47 Lee Stewart OFFLINE  

Lee Stewart

    Stargunner

  • 1,562 posts
  • Location:Maryland

Posted Thu Oct 20, 2011 4:48 PM

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, Thu Oct 20, 2011 7:39 PM.


#48 Opry99er OFFLINE  

Opry99er

    River Patroller

  • 3,616 posts
  • Location:Denver, CO

Posted Thu Oct 20, 2011 7:37 PM

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

#49 Willsy OFFLINE  

Willsy

    River Patroller

  • 2,014 posts
  • Location:Uzbekistan (no, really!)

Posted Fri Oct 21, 2011 2:04 AM

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.

#50 Willsy OFFLINE  

Willsy

    River Patroller

  • 2,014 posts
  • Location:Uzbekistan (no, really!)

Posted Fri Oct 21, 2011 2:29 AM

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




0 user(s) are browsing this forum

0 members, 0 guests, 0 anonymous users