-
Content Count
3,144 -
Joined
-
Last visited
Content Type
Profiles
Member Map
Forums
Blogs
Gallery
Calendar
Store
Everything posted by Willsy
-
I wonder if Tursi would consider adding the following feature to the next release (or add it to the todo list!) in Classic99: Software controlled CPU overdrive; whereby CLR @>0000 sets CPU to normal SETO @>0000 sets CPU mode to overdrive Obviously, the above would have no effect (which is a good thing!) on real TI equipment, but it would allow Classic99 users to switch on Overdrive for slow stuff, then turn it back off (I personally would not be interested in, for example, developing games that rely on the overdrive to function). For me, it would be useful to switch on CPU overdrive while TF is loading blocks from disks (to give the compiler a kick) and then turn it off again.
-
Here's a nice sprite designer program that I wrote back in about 2006 I think. It uses sub-programs heavily, as a result I think the code itself is quite easy to read. 40 DATA 120,124,128,132,136,140 50 CALL CLEAR :: CALL MAGNIFY(4):: CALL SCREEN(2):: FOR I=1 TO 14 :: CALL COLOR(I,15,1):: NEXT I :: CALL COLOR(4,5,1) 60 CALL CHAR(92,"F8F0F0F89C0E0703000000000000000000000000000000000000000000000000") 70 A$="AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55" 80 CALL CHAR(88,"08080006C6191B061D336142444A3400003C6282848A54E05CFC60DED8561400") 90 RESTORE 40 :: FOR I=1 TO 6 :: READ A :: CALL CHAR(A,A$):: NEXT I 100 CALL CHARPAT(ASC(":"),A$):: CALL CHAR(ASC("@"),A$) 110 CALL SPRITE(#2,120,15,16,156):: CALL SPRITE(#3,124,15,16,204) 120 CALL SPRITE(#4,128,15,60,156):: CALL SPRITE(#5,132,15,60,204):: CALL SPRITE(#6,136,15,104,156):: CALL SPRITE(#7,140,15,104,204) 130 CALL CHAR(60,"0000001818000000"):: CALL CHAR(62,"FEFEFEFEFEFEFE00") 140 CX=1 :: CY=2 :: OPTION BASE 1 :: DIM C$(16,4),C(16,4):: SZ=1 150 CALL INIT(C$(,),C(,)) 160 CALL HELP 170 CALL SPRITE(#1,92,4,(CY*-4,(CX*+14) 180 CALL KEY(0,K,S):: IF K=-1 THEN 180 190 IF K=101 THEN CY=CY-1 :: IF CY<2 THEN CY=17 ELSE GOTO 170 200 IF K=120 THEN CY=CY+1 :: IF CY>17 THEN CY=2 ELSE GOTO 170 210 IF K=115 THEN CX=CX-1 :: IF CX<1 THEN CX=16 ELSE GOTO 170 220 IF K=100 THEN CX=CX+1 :: IF CX>16 THEN CX=1 ELSE GOTO 170 230 IF K=48 THEN CALL POINT(CY-1,CX,C$(,),C(,)) 240 IF K=73 THEN CALL INVERT(C$(,),C(,)) 250 IF K=68 THEN CALL DOWN(C$(,),C(,)) 260 IF K=85 THEN CALL UP(C$(,),C(,)) 270 IF K=76 THEN CALL LEFT(C$(,),C(,)) 280 IF K=50 THEN SZ=SZ XOR 1 :: CALL MAGNIFY(3+SZ) 290 IF K=82 THEN CALL RIGHT(C$(,),C(,)) 300 IF K=72 THEN CALL HFLIP(C$(,),C(,)) 310 IF K=86 THEN CALL VFLIP(C$(,),C(,)) 320 IF K=78 THEN CALL SCRATCH(C$(,),C(,)) 330 IF K=65 THEN CALL ASSIGN(C$(,),C(,)) 340 IF K=71 THEN CALL LOADGRID(C$(,),C(,)) 350 IF K=70 THEN CALL FILEMENU(C$(,),C(,)) 360 IF K=74 THEN CALL LFSPRITE(C$(,),C(,)) 370 IF K=87 THEN CALL ANIMATE(C$(,),C(,)) 380 GOTO 170 390 ! ############# 400 SUB INIT(C$(,),C(,)) 410 CALL BUSY :: DISPLAY AT(1,1):"Sprite designer v1.2 M.Wills" 420 CALL NEW(C$(,),C(,)):: CALL DONE 430 SUBEND 440 ! ############# 450 SUB NEW(C$(,),C(,)):: CALL BUSY 460 FOR Y=1 TO 16 :: FOR X=1 TO 4 :: C$(Y,X)="<<<<" :: C(Y,X)=0 :: NEXT X :: NEXT Y 470 CALL DRAWGRID(C$(,),C(,)):: CALL DONE 480 SUBEND 490 ! ############# 500 SUB DRAWGRID(C$(,),C(,)):: CALL BUSY 510 FOR Y=1 TO 16 :: XX=1 :: FOR X=1 TO 4 :: DISPLAY AT(Y+1,XX)SIZE(4):C$(Y,X):: XX=XX+4 :: NEXT X :: NEXT Y :: CALL DONE 520 SUBEND 530 ! ############# 540 SUB POINT(Y,X,C$(,),C(,)) 550 S=INT((X-1)/4)+1 :: P=3-(X-1)AND 3 :: C(Y,S)=C(Y,S)XOR 2^P 560 CALL GETNYBBLE(C(Y,S),N$):: DISPLAY AT(Y+1,((S-1)*4)+1)SIZE(4):N$:: C$(Y,S)=N$ 570 SUBEND 580 ! ############# 590 SUB GETNYBBLE(V,N$) 600 IF V=0 THEN GOSUB 620 ELSE ON V GOSUB 630,640,650,660,670,680,690,700,710,720,730,740,750,760,770 610 SUBEXIT 620 N$="<<<<" :: RETURN 630 N$="<<<>" :: RETURN 640 N$="<<><" :: RETURN 650 N$="<<>>" :: RETURN 660 N$="<><<" :: RETURN 670 N$="<><>" :: RETURN 680 N$="<>><" :: RETURN 690 N$="<>>>" :: RETURN 700 N$="><<<" :: RETURN 710 N$="><<>" :: RETURN 720 N$="><><" :: RETURN 730 N$="><>>" :: RETURN 740 N$=">><<" :: RETURN 750 N$=">><>" :: RETURN 760 N$=">>><" :: RETURN 770 N$=">>>>" :: RETURN 780 SUBEND 790 ! ################ 800 SUB INVERT(C$(,),C(,)):: CALL BUSY 810 FOR Y=1 TO 16 :: FOR X=1 TO 4 :: C(Y,X)=15-C(Y,X):: CALL GETNYBBLE(C(Y,X),N$):: C$(Y,X)=N$ :: NEXT X :: NEXT Y :: CALL DRAWGRID(C$(,),C(,)):: CALL DONE 820 SUBEND 830 ! ############## 840 SUB DOWN(C$(,),C(,)) 850 DIM T$(1,4),T(1,4) 860 CALL BUSY :: FOR X=1 TO 4 :: T$(1,X)=C$(16,X):: T(1,X)=C(16,X):: NEXT X 870 FOR Y=16 TO 2 STEP -1 :: FOR X=1 TO 4 :: C$(Y,X)=C$(Y-1,X):: C(Y,X)=C(Y-1,X):: NEXT X :: NEXT Y 880 FOR X=1 TO 4 :: C$(1,X)=T$(1,X):: C(1,X)=T(1,X):: NEXT X :: CALL DRAWGRID(C$(,),C(,)):: CALL DONE 890 SUBEND 900 ! ############# 910 SUB UP(C$(,),C(,)) 920 DIM T$(1,4),T(1,4) 930 CALL BUSY :: FOR X=1 TO 4 :: T$(1,X)=C$(1,X):: T(1,X)=C(1,X):: NEXT X 940 FOR Y=2 TO 16 :: FOR X=1 TO 4 :: C$(Y-1,X)=C$(Y,X):: C(Y-1,X)=C(Y,X):: NEXT X :: NEXT Y 950 FOR X=1 TO 4 :: C$(16,X)=T$(1,X):: C(16,X)=T(1,X):: NEXT X :: CALL DRAWGRID(C$(,),C(,)):: CALL DONE 960 SUBEND 970 ! ############## 980 SUB LEFT(C$(,),C(,)) 990 CALL BUSY 1000 FOR Y=1 TO 16 :: T4=C(Y,4)*2 :: IF T4>15 THEN C4=1 ELSE C4=0 1010 T3=C(Y,3)*2 :: IF T3>15 THEN C3=1 ELSE C3=0 1020 T2=C(Y,2)*2 :: IF T2>15 THEN C2=1 ELSE C2=0 1030 T1=C(Y,1)*2 :: IF T1>15 THEN C1=1 ELSE C1=0 1040 T4=T4 AND 15 :: T3=T3 AND 15 :: T2=T2 AND 15 :: T1=T1 AND 15 1050 C(Y,1)=T1 OR C2 :: C(Y,2)=T2 OR C3 :: C(Y,3)=T3 OR C4 :: C(Y,4)=T4 OR C1 1060 CALL GETNYBBLE(C(Y,1),N$):: C$(Y,1)=N$ :: CALL GETNYBBLE(C(Y,2),N$):: C$(Y,2)=N$ :: CALL GETNYBBLE(C(Y,3),N$):: C$(Y,3)=N$ 1070 CALL GETNYBBLE(C(Y,4),N$):: C$(Y,4)=N$ :: NEXT Y 1080 CALL DRAWGRID(C$(,),C(,)):: CALL DONE 1090 SUBEND 1100 ! ############ 1110 SUB RIGHT(C$(,),C(,)):: CALL BUSY 1120 FOR Y=1 TO 16 :: C1=(C(Y,1)AND 1)*8 :: C2=(C(Y,2)AND 1)*8 :: C3=(C(Y,3)AND 1)*8 :: C4=(C(Y,4)AND 1)*8 1130 T1=INT(C(Y,1)/2):: T2=INT(C(Y,2)/2):: T3=INT(C(Y,3)/2):: T4=INT(C(Y,4)/2) 1140 C(Y,1)=T1 OR C4 :: C(Y,2)=T2 OR C1 :: C(Y,3)=T3 OR C2 :: C(Y,4)=T4 OR C3 1150 CALL GETNYBBLE(C(Y,1),N$):: C$(Y,1)=N$ :: CALL GETNYBBLE(C(Y,2),N$):: C$(Y,2)=N$ :: CALL GETNYBBLE(C(Y,3),N$):: C$(Y,3)=N$ 1160 CALL GETNYBBLE(C(Y,4),N$):: C$(Y,4)=N$ :: NEXT Y 1170 CALL DRAWGRID(C$(,),C(,)):: CALL DONE 1180 SUBEND 1190 ! ############## 1200 SUB HFLIP(C$(,),C(,)):: CALL BUSY 1210 DIM T$(16,4),T(16,4):: FOR Y=1 TO 16 :: XX=4 :: FOR X=1 TO 4 1220 IF C(Y,X)=0 THEN T(Y,XX)=0 ELSE ON C(Y,X)GOSUB 1260,1270,1280,1290,1300,1310,1320,1330,1340,1350,1360,1370,1380,1390,1400 1230 XX=XX-1 :: NEXT X :: NEXT Y 1240 FOR Y=1 TO 16 :: FOR X=1 TO 4 :: C(Y,X)=T(Y,X):: CALL GETNYBBLE(C(Y,X),N$):: C$(Y,X)=N$ :: NEXT X :: NEXT Y :: CALL DRAWGRID(C$(,),C(,)) 1250 SUBEXIT 1260 T(Y,XX)=8 :: RETURN 1270 T(Y,XX)=4 :: RETURN 1280 T(Y,XX)=12 :: RETURN 1290 T(Y,XX)=2 :: RETURN 1300 T(Y,XX)=10 :: RETURN 1310 T(Y,XX)=6 :: RETURN 1320 T(Y,XX)=14 :: RETURN 1330 T(Y,XX)=1 :: RETURN 1340 T(Y,XX)=9 :: RETURN 1350 T(Y,XX)=5 :: RETURN 1360 T(Y,XX)=13 :: RETURN 1370 T(Y,XX)=3 :: RETURN 1380 T(Y,XX)=11 :: RETURN 1390 T(Y,XX)=7 :: RETURN 1400 T(Y,XX)=15 :: RETURN 1410 CALL DONE :: SUBEND 1420 ! ############## 1430 SUB VFLIP(C$(,),C(,)):: CALL BUSY :: DIM T(16,4) 1440 YY=16 :: FOR Y=1 TO 16 :: FOR X=1 TO 4 :: T(YY,X)=C(Y,X):: NEXT X :: YY=YY-1 :: NEXT Y 1450 FOR Y=1 TO 16 :: FOR X=1 TO 4 :: CALL GETNYBBLE(T(Y,X),N$):: C$(Y,X)=N$ :: C(Y,X)=T(Y,X):: NEXT X :: NEXT Y :: CALL DRAWGRID(C$(,),C(,)) 1460 SUBEND 1470 SUB BUSY :: CALL PATTERN(#1,88):: SUBEND 1480 SUB DONE :: CALL PATTERN(#1,140):: SUBEND 1490 SUB HELP 1500 DISPLAY AT(18,1):"[email protected] [email protected] [email protected] [email protected] down [email protected] up [email protected] [email protected] [email protected] l/r [email protected] u/d [email protected] grid" 1510 DISPLAY AT(22,1):"[email protected] [email protected] [email protected] [email protected] [email protected] sprite on grid" 1520 SUBEND 1530 ! ########## 1540 SUB SCRATCH(C$(,),C(,)):: CALL HCHAR(18,1,32,6*32) 1550 DISPLAY AT(20,1):"Erase grid? y/n" :: ACCEPT AT(20,16)SIZE(1)VALIDATE("YNyn"):A$ 1560 IF A$="y" OR A$="Y" THEN CALL NEW(C$(,),C(,)) 1570 CALL HELP 1580 SUBEND 1590 ! ######## 1600 SUB ASSIGN(C$(,),C(,)) 1610 CALL HCHAR(18,1,32,6*32) 1620 DISPLAY AT(19,1):"Assign design to 1-6:" :: ACCEPT AT(19,22)SIZE(1)VALIDATE("123456"):V$ :: IF V$="" THEN CALL HELP :: SUBEXIT 1630 H$="0123456789ABCDEF" :: CALL BUSY 1640 A$="" :: FOR Y=1 TO 16 :: FOR X=1 TO 2 :: A$=A$&SEG$(H$,C(Y,X)+1,1):: NEXT X :: NEXT Y 1650 FOR Y=1 TO 16 :: FOR X=3 TO 4 :: A$=A$&SEG$(H$,C(Y,X)+1,1):: NEXT X :: NEXT Y 1660 V=116+(VAL(V$)*4) 1670 CALL CHAR(V,A$):: CALL HELP :: CALL DONE 1680 SUBEND 1690 ! ########## 1700 SUB LOADGRID(C$(,),C(,)):: CALL HCHAR(18,1,32,6*32) 1710 H$="0123456789ABCDEF" :: FOR I=1 TO 4 1720 DISPLAY AT(18,1):"Enter data for character";I 1730 ACCEPT AT(19,1)SIZE(16)VALIDATE("0123456789ABCDEF"):A$ :: CALL BUSY 1740 IF I=1 THEN Y=1 ELSE IF I=2 THEN Y=9 ELSE IF I=3 THEN Y=1 ELSE Y=9 1750 IF I<3 THEN S=1 ELSE S=3 1760 FOR M=1 TO LEN(A$) 1770 V=POS(H$,SEG$(A$,M,1),1)-1 1780 C(Y,S)=V :: CALL GETNYBBLE(C(Y,S),N$):: C$(Y,S)=N$ :: IF M/2<>INT(M/2)THEN S=S+1 ELSE S=S-1 1790 IF M/2=INT(M/2)THEN Y=Y+1 1800 NEXT M :: CALL DRAWGRID(C$(,),C(,)):: NEXT I 1810 CALL HELP :: CALL DONE 1820 SUBEND 1830 ! ########### 1840 SUB SAVEDATA 1850 CALL HCHAR(18,1,32,6*32) 1860 DISPLAY AT(18,1):"Enter device and file name, eg DSK1.SPRITES etc Press enter to abort" 1870 ACCEPT AT(21,1)SIZE(15)VALIDATE("_-#@.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"):FN$ 1880 IF FN$="" THEN CALL HELP :: SUBEXIT 1890 CALL BUSY :: OPEN #1:FN$,DISPLAY ,VARIABLE 80,OUTPUT 1900 RESTORE 40 1910 FOR I=1 TO 6 :: B$=" DATA " :: READ V :: FOR II=V TO V+3 :: CALL CHARPAT(II,T$):: CALL SPLIT(T$,B$):: NEXT II 1920 PRINT #1:SEG$(B$,1,LEN(B$)-1):: NEXT I 1930 CLOSE #1 :: CALL HELP :: CALL DONE 1940 SUBEND 1950 ! ############## 1960 SUB SPLIT(A$,B$) 1970 FOR I=1 TO 16 STEP 4 :: B$=B$&">"&SEG$(A$,I,4)&"," :: NEXT I 1980 SUBEND 1990 ! ########## 2000 SUB FILEMENU(C$(,),C(,)) 2010 CALL HCHAR(18,1,32,6*32):: DISPLAY AT(19,1):"S=SAVE L=LOAD Press enter to abort" 2020 ACCEPT AT(21,1)SIZE(1)VALIDATE("sSlL"):A$ 2030 IF A$="" THEN CALL HELP :: SUBEXIT 2040 IF A$="s" OR A$="S" THEN CALL SAVEDATA 2050 IF A$="l" OR A$="L" THEN CALL LOADDATA(C$(,),C(,)) 2060 CALL HELP 2070 SUBEND 2080 ! ########## 2090 SUB LOADDATA(C$(,),C(,)) 2100 CALL HCHAR(18,1,32,6*32):: DISPLAY AT(20,1):"Enter device and filename Eg DSK1.SPRITES Press Enter to abort" 2110 ACCEPT AT(23,1)SIZE(15)VALIDATE("_-#@.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"):FN$ 2120 IF FN$="" THEN CALL HELP :: SUBEXIT 2130 CALL BUSY :: OPEN #1:FN$,INPUT ,DISPLAY ,VARIABLE 80 2140 IF EOF(1)<>0 THEN 2170 2150 RESTORE :: FOR I=1 TO 6 :: LINPUT #1:A$ :: LINPUT #1:B$ :: A$=A$&B$ 2160 A$=SEG$(A$,8,LEN(A$)):: B$="" :: XX=1 :: FOR X=1 TO 16 :: B$=B$&SEG$(A$,XX,4):: XX=XX+6 :: NEXT X :: READ V :: CALL CHAR(V,B$):: NEXT I 2170 CLOSE #1 2180 CALL DONE :: SUBEND 2190 ! ############ 2200 SUB LFSPRITE(C$(,),C(,)) 2210 CALL HCHAR(18,1,32,6*32):: DISPLAY AT(19,1):"Load grid from which sprite?" :: ACCEPT AT(20,1)SIZE(1)VALIDATE("123456"):S$ :: CALL HELP 2220 IF S$="" THEN SUBEXIT ELSE S=VAL(S$) 2230 CALL BUSY :: SP=116+(S*4) 2240 CALL CHARPAT(SP,P1$):: CALL CHARPAT(SP+1,P2$):: CALL CHARPAT(SP+2,P3$):: CALL CHARPAT(SP+3,P4$):: H$="0123456789ABCDEF" 2250 P$=P1$&P2$&P3$&P4$ 2260 Y=1 :: FOR I=1 TO 31 STEP 2 2270 V$=SEG$(P$,I,1):: V1=POS(H$,V$,1)-1 :: V$=SEG$(P$,I+1,1):: V2=POS(H$,V$,1)-1 2280 CALL GETNYBBLE(V1,N$):: C$(Y,1)=N$ :: CALL GETNYBBLE(V2,N$):: C$(Y,2)=N$ :: C(Y,1)=V1 :: C(Y,2)=V2 :: Y=Y+1 :: NEXT I 2290 Y=1 :: FOR I=33 TO 63 STEP 2 2300 V$=SEG$(P$,I,1):: V1=POS(H$,V$,1)-1 :: V$=SEG$(P$,I+1,1):: V2=POS(H$,V$,1)-1 2310 CALL GETNYBBLE(V1,N$):: C$(Y,3)=N$ :: CALL GETNYBBLE(V2,N$):: C$(Y,4)=N$ :: C(Y,3)=V1 :: C(Y,4)=V2 :: Y=Y+1 :: NEXT I 2320 CALL DRAWGRID(C$(,),C(,)):: CALL DONE 2330 SUBEND 2340 ! ################# 2350 SUB ANIMATE(C$(,),C(,)):: CALL COLOR(#1,1) 2360 FOR I=2 TO 17 :: CALL HCHAR(I,3,32,16):: NEXT I 2370 DISPLAY AT(3,1):"Frame [email protected]" :: DISPLAY AT(4,2):"1234565432" 2380 ACCEPT AT(4,2)VALIDATE(DIGIT)SIZE(-10)$ 2390 CALL DRAWGRID(C$(,),C(,)):: CALL DONE :: SUBEND
-
That may be true, but your code can be MUCH clearer and easier to read with them. Mark
-
I'll be there until around 5pm ish Sunday I think, then off to the airport... Mark
-
a room will be fine by me!
-
Yep! We'll leave Owen to figure out why
-
One final optimisation that is possible to with the above code: Take a look at this fragment of code: MXAX 1- (please note: that is "1-" *NOT* "1 -" 1- is probably five times faster than 1 -) That MAX 1- kind of troubles me. Why do we need it? We need it because XMAX returns 40 in 40 column mode, therefore the screen columns are from 0 to 39. Because we are looking for 39, we need to subtract 1 from XMAX for our range check (unless we hard code the max column, in which case, you can stop reading now!) It seems a waste that we have to perform that calculation every time at run-time. It would be nice if we didn't have to do it at all. Is that possible? Not entirely, but nearly; clearly the calculation needs to be performed at least once, in order to know the answer! Is there a way we can do that? Yes, there is! Read on! Have a look at this: DUP 68 = IF DROP PX [ XMAX 1- LITERAL ] < IF 1 +TO PX THEN ELSE Huh? What the heck is going on here? Well, this, to use your (and my) parlence, is "some clever sh*t" - that's what it is! To understand what is going on, you need to think 'in another dimension'. Yes. Forth, requires three dimensional thinking, whereas most other languages only require two dimensional thinking! What the hell am I talking about (I dunno! I only came in here for a cup of tea! ). Okay, what I mean about 'another dimension' is this: (prepare for potential brain f**k). When you write code in XB, or C, or PASCAL etc you really have only one 'time' to worry about - run-time. In Forth, you can do stuff with your code *at compile-time* before your code even runs! During the compilation of our word, the word[ forces TF to temporarily *stop* compiling, and just *execute* all the code it finds after the [ (which is just a word like any other word in Forth). So, when it sees [ it then executes (not compiles) XMAX and executes (not compiles) 1-. This results in 39 on the stack, just as if you had typed it in by itself on the command line. Then the word LITERAL is encountered. This causes the value currently on the stack, (in our case, 39) to be 'encoded' into our definition as a "literal" (which is just a posh way of saying "as a number"). Then, the word ] resumes normal compiling. So, instead of our code being compiled as this: DUP 68 = IF DROP PX XMAX 1- < IF 1 +TO PX THEN ELSE It actually ends up (in memory) as this: DUP 68 = IF DROP PX 39 < IF 1 +TO PX THEN ELSE Ta dah! We just made our code more efficient, because we no longer (at run-time - every time a key is pressed) have to work out was XMAX-1 is - we already did it, *at compile time* and used that result, over and over again, at run-time! Don't you just love Forth?
-
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
-
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.
-
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.
-
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
-
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
-
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
-
Woohoo! Great! See you at the Faire! I arrive around 3pm ish IIRC at O'Haire, and I don't leave until Sunday evening.
-
Thats how I feel. I was shocked to see some of the photo's of the recent Italian meeting - just photo's of people playing Parsec. Nothing going on at all. I don't need to fly to Italy (or the USA) to play Parsec. But that's why I skipped the Italian meeting and am going to Chicago - the USA is where it's at, pretty much. I see Tursi, Matthew, Marc Hull et al as the 'hub' of the TI community these days. These are the people that are driving innovation forward for the TI, keeping it fresh. I think the TI scene has been very stale for the last 10 or more years, and although the scene has never been smaller than it is now, it is very much alive I think, and has been awesome for the last two years or so. Mark
-
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!
-
FIG Forth takes an initial value for variables, but TF, which follows the Forth-83 standard, does not - it initialises its variables to 0.
-
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: 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 ;
-
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
-
I'm trying to twist Tursi's arm too and drag him to Chicago! I suggest a multi-pronged pincer movement - if we all gang up on him, he'll have no choice but to come!
-
Okay, this kicks so much ass it's just not funny!
-
I'd be completely up for that. I don't think I'll be able to make the 2012 Faire, so it would be nice to meet you in person if you can make it...! A hack fest is right up my street. I *might* nip out for a drink in the evening - I'm not a drinker at all, but this will be first time in the USA so it would be kind of dumb to go Chicago and only see the inside of a library conference room! However, I stress I'm not really a drinker. I'd be happy to sit in a corner somewhere with a beer, the ED/AS quick reference card and a notebook and just hack that shit right up! I reckon Bob C would be up for it too! I personally would be very interested to talk to you about programmable logic - in particular implementing simple processors in programmable logic... You can probably guess where I'm going with that...!
-
HA HA!!! I LOVE IT LOVE IT LOVE IT!!!! :lust:
-
SSGC Submission - Inaccurate Invaders (now with less inaccuracy)
Willsy replied to unhuman's topic in TI-99/4A Development
Yeah - just reviewing this after sometimes' neat little demo. This is the real deal, right here! I'm still gobsmacked by this! Utterly utterly amazing!
