Jump to content

Gridslugger - Game in progress (compiled XB)


Recommended Posts

That's a shame. It looks beautiful. Forth *may* be faster - no guarantees - or, of course, there is assembly language. Assembly language is *exceptionally* easy (IMO) on the 4A.


Have you published the source code for Grid Slugger? Maybe the community can look through it and suggest areas for improvement.

Link to comment
Share on other sites

I ran into the same issue with my ZOMBI game. which is still unfinished because I haven't sat down to dump all the new ideas I have to keep the game fun, fast and reduce the hell out of the scope.

so I know what this feels like and my suggestion may or may not help.

Reduce the scope, drop all the bells and whistles. Make the game very simple with one caterpillar. Do something about player movement, maybe shooting. call it a tech demo, relase the source code and see of the community takes it from there.

Me? I'm not there yet. I have a lot of good ideas to flesh out and a lot of code to remove from my game. But it's sitting on the back burner simmering while I clear a bunch of other things off my plate.

Also, what parts of Harry's compiler package are you using, just compiling the code or are you taking advantage of his graphics compression tool also? It saves a lot of code lines but there are some limitations as far as manipulating the info in emulation etc... (can't copy/paste the data statements).

Edited by Sinphaltimus
Link to comment
Share on other sites

  • 2 weeks later...

Thanks for the feedback guys and apologies for the tardiness of this reply. Crazy time of year and all..... Below is my code for the caterpillar movement which seems to behind much of the slowdown. Everything was working rosy until the lines between 5800 - 6970 were added (these are the lines which update the caterpillar movement).


Some key explanations;


-Line 5500 begins loop (variable T) for processing caterpillar movement. Plan was for maximum of three caterpillars in later levels. M=1 is caterpillar 1 (5 segments). M=7 is caterpillar 2 (5 segments). M=13 is caterpillar 3 (10 segments). Values of M higher than 24 will be later used for other processing updates. Each "T loop" processes Caterpillar 1, Caterpillar 2, or Caterpillar 3. In the last Youtube clip only Caterpillar 1 is active.


-Lines 5570 - 5580 control caterpillar emerging from an egg once egg has hatched. No collision checking is ever required here so it is simply allowed to emerge onto the screen a single character at a time. All working fine here.


-Line 5800 identifies that caterpillar has fully emerged from egg (value is not 999 or 998), and passes control to caterpillar movement processing. This is where I feel things slow down too much....


-Lines 5810 - 5840 calculates next potential screen position of caterpillar segment.


-Lines 5850-5920 checks if game boundary reached, checks for in-game character collisions, checks if move is legal and updates all variables if move is to be allowed (in-game collision checking sent to 5930 is not yet written).


-Line 6900 ends T loop. By this stage the next screen position of all caterpillar segments is known and all variables have been written


-Line 6910 to 6950 draws the caterpillar to the new screen position as determined by lines 5810 - 6900.


-Line 6960 updates EXO(##) & EYO(##) to reflect the current value of EX(##) & EY(##).


-Line 8000 increments value of M which will then process next caterpillar / or enemy if value over 24 (yet to be written).



I guess some variable explanations are required;


E(##) represents if enemy is active. If not active (such as might have already been destroyed), the value is zero. If active, value is equal to screen grid position calculated by (Y-1)*32+X-192 and is used for collision checking.


EX(##) is enemy X HCHAR coordinate.


EXO(##) is previous enemy X coordinate before any screen position processing. Once enemy new screen position is updated, this historic position is blanked with the background character, EXO(##) is then updated to EX(##) value.


EY(##) is enemy Y HCHAR coordinate. .


EYO(##) is previous enemy Y coordinate before any screen position processing. Once enemy new screen position is updated, this historic position is blanked with the background character. EYO(##) is then updated to EY(##) value.


EC(##) is the ASCII code of enemy.


ECO(##) is added to displayed ASCII value in HCHAR statement. It offsets the value of the displayed CHAR to set the direction the caterpillar is looking ie-left, right, down (if the char in question is a caterpillar head).


EDX(##) represents the direction of travel for each char. Value of 2 = travelling left. Value of 1=travelling right. Potentially any caterpillar segment can be separated from rest of body and travel its own course.


G(###) holds the integer value of E(##) or the ascii code if part of the game border (ie- The T and/or M value in the program). This is so it can be quickly determined which enemy (if any) is located in any given screen position.


* Note that by "Enemy", I refer to each component (segment) of caterpillar.


* It appears as though some double processing is going on when looking at lines 6910 - 6970. I found it more practical to first calculate all the moves of the caterpillar segments, and then write them to the screen in a second pass. Previous attempts to update each caterpillar to the screen as the location was first defined caused issues with the caterpillar following the leading character. I feel time absorbed by the second loop is negligible as no double processing is happening here.


* I needed to allow control of individual caterpillar segments in the event that any part of the caterpillar is shot and separated from the rest of the body. At the same time if intact, I need all parts to "follow the leader" or be snakelike. Hence the requirements for each caterpillar component to have its own range of complete variable definitions.


* There are a few leftovers such as line 5930 which were used for error checking along the way, 6920 & 6930 which are used for some animation which I have temporarily removed in an attempt to add speed, plus some other odd looking things that I have placed in limbo but left in the original program listing. Just ignore these, I haven't yet committed to completely removing.


Not asking for anyone to rewrite anything, but it was requested that I offer the code for examination so here it is. :)

5000 FOR M=1 TO 24
5010 !
5110 IF M<>1 THEN 5120 :: MN=1 :: MX=5 :: D=13 :: CX=46 :: GOTO 5200 !PROCESS CAT1
5120 IF M<>7 THEN 5130 :: MN=7 :: MX=11 :: D=13 :: CX=46 :: GOTO 5250 !PROCESS CAT2
5130 IF M<>13 THEN 5140 :: MN=13 :: MX=22 :: D=8 :: CX=41 :: GOTO 5300 !PROCESS CAT3
5140 GOTO 7000
5200 IF E(1)+E(2)+(3)+E(4)+E(5)<>0 THEN 5500 ! PROCESS CAT1
5210 M=7 :: GOTO 5120 ! CAT1 NOT ACTIVE
5250 IF E(7)+E(+E(9)+E(10)+E(11)<>0 THEN 5500 ! PROCESS CAT2
5260 M=13 :: GOTO 5130 ! CAT2 NOT ACTIVE
5300 IF E(13)+E(14)+E(15)+E(16)+E(17)+E(18)+E(19)+E(20)+E(21)+E(22)<>0 THEN 5500 ! PROCESS CAT3
5310 M=23 :: GOTO 8000 ! CAT 3 NOT ACTIVE
5510 IF E(MN)=998 THEN 5560
5520 IF E(MN)=999 THEN 5600
5560 A=MX-MN+1 :: C=EAV(MN)
5570 FOR I=1 TO A :: B=T+I-1 :: EY(B)=8 :: EX(B)=D+I :: C=(EY(B)-1)*32+EX(B)-192 :: G(C)=B :: E(B)=C :: EYO(B)=8 :: EXO(B)=EX(B)
5575 R=EAS(B):: CAT$=CAT$&CHR$(182+AS(R,1)):: NEXT I :: S=EAS(MN):: CAT$=CHR$(178+ECO(MN)+(AS(S,1)))&SEG$(CAT$,1,A-1):: E(MN)=999 :: CAT=0
5580 M=MX+1 :: GOTO 8000
5600 A=MX-MN+1 :: CALL SOUND(1,1000,1+I)
5710 M=MX+1 :: IF CAT<>MX-MN THEN 8000 :: CALL LINK("DISPLY",8,D+1,CAT$):: E(MN)=CX :: CAT$="" :: GOTO 8000
5720 LN=5720 :: GOTO 5720 !ERROR
5730 M=MX+1
5800 ECO(T)=0 :: IF E(T)=0 THEN 6900
5810 IF EDX(T)=2 THEN 5830 :: XT=EX(T)-1 :: IF EC(T)<>178 THEN 5840 :: ECO(T)=2 :: GOTO 5840
5830 XT=EX(T)+1 :: IF EC(T)<>178 THEN 5840 :: ECO(T)=1
5840 GT=(EY(T)-1)*32+XT-192
5850 IF G(GT)<16 THEN 5890 :: A=E(T):: G(A)=37 :: EX(T)=XT :: G(GT)=EC(T):: E(T)=GT :: GOTO 6900
5890 IF G(GT)>15 THEN 5930 :: YT=EY(T)+1 :: IF EC(T)<>178 THEN 5900 :: ECO(T)=3
5900 GT=(YT-1)*32+EX(T)-192 :: A=E(T):: G(A)=37 :: EY(T)=YT :: G(GT)=EC(T):: E(T)=GT
5910 IF EDX(T)=1 THEN 5920 :: EDX(T)=1 :: GOTO 6900
5920 EDX(T)=2 :: GOTO 6900
5930 GOTO 5930 
6900 NEXT T
6910 FOR T=MN TO MX :: IF EC(T)=0 THEN 6970
6920 !EAV(T)=EAV(T)+1 :: IF EAV(T)<>13 THEN 6930 :: EAV(T)=1
6930 ! A=EAS(T):: B=EAV(T)
6960 EYO(T)=EY(T):: EXO(T)=EX(T)
6970 NEXT T
6990 M=MX+1 :: GOTO 8000
8000 NEXT M
8010 !GOSUB 25000
10000 GOTO 5000
Edited by Bones-69
  • Like 2
Link to comment
Share on other sites

It saves a lot of code lines but there are some limitations as far as manipulating the info in emulation etc... (can't copy/paste the data statements).

Once I nailed down character defs, sounds, sprites and screens, I just added them all to the same merge file, just keep track of the line numbers and make sure your base code doesn't overwrite those line numbers, as long as the filename doesn't change it is as simple copy and pasting the merge command. I use a batch file to copy the merge command to clipboard and paste it in after my base program is done pasting

Link to comment
Share on other sites

A little more going on there than I expected, I've had trouble finding a large enough time block to try to absorb it. But some small observations that hopefully will be helpful...


5110 IF M<>1 THEN 5120 :: MN=1 :: MX=5 :: D=13 :: CX=46 :: GOTO 5200

I think structures like this are an ELSE case without the word ELSE for the compiler? Otherwise I've not seen it. ;)


The interaction between M and T is a little strange. I see what you're going for, and I see you're skipping large blocks of M. But I think it's adding complexity. Consider if it's possible to have a single loop. I wrote a (really, really simple) Centipede way back when I was first learning assembly, and I did each body segment as a distinct, separate enemy. It wasn't concerned with the rest of the centipede - they all stuck together only because they followed the same movement rules. To that end, each separate piece simply needed to move left or right, and if that move was illegal, drop down a row and reverse direction. (I didn't have to worry about heads, but that would just be a different enemy type so it draws differently ;) ). Just as a suggestion anyway.


This would also allow you to eliminate all the code working on the caterpillar as a single object in some places and distinct pieces in others. (ie: the 'alive' checks in 5200-5300 would simply check if the current segment was alive). The code jumps around a lot when caterpilars are dead and kind of defeats the outer loop anyway.


5560 A=MX-MN+1 :: C=EAV(MN)

Assignment of C seems to be unnecessary - EAV is not (currently) assigned and C is reused in the next line.


Loop in 5570 is another example of code that could be simplified with independent enemies. Rather than calculating which segment to release next, perhaps give each one a staggered countdown timer. When their timer hits zero, they go without worrying about the other segments. Also means you wouldn't need to worry about CAT and it wouldn't interfere if multiple caterpilars were appearing at the same time.


Why the "-192" in the E() array, out of curiousity? Is it to save room for G()?


I sort of get the G and E are paired up, each a different type of lookup, but I don't follow it perfectly so I might not have much to offer there.


EAS() and CAT$ are for drawing the caterpillar, yes?


Line 5730 appears to be unreachable code?


Not that it's a performance enhancement, but I think ECO and EDX could be combined (would require a different character set layout in the case of ECO and is not a big deal). EDX might be more useful if it stored -1,+1 instead of 1 and 2, though, because then you could use it directly without an if.


In 5810 and 5830 you set ECO() every frame - it's a tiny optimization to set it only when initializing or when direction changes. (But only if the character is not 178...? Is that a body segment? I don't see where it's initialized)


5850 to 5890 - the test in 5890 seems unnecessary. 5850 tests IF G(GT) < 16 THEN 5890. 5890 then tests IF G(GT)>15 THEN 5930. Is there another way to get to 5890? Otherwise we already know the test is false because 5850 proves the value is 15 or lower. (I suppose this is part of the as-yet unwritten tests at 5930).


5910-5920 could simply invert EDX if it was -1,+1 (EDX=-EDX), although I've been known to force it like this myself in some cases. ;)


That's all I see. Don't know if that's helpful or not... I think the main performance boost can come from not worrying about the caterpillar itself as an object, but treating each body segment as a separate one.

Link to comment
Share on other sites

I think you could save jumps by changing:

5110 IF M<>1 THEN 5120 :: MN=1 :: MX=5 :: D=13 :: CX=46 :: GOTO 5200 !PROCESS CAT1
5120 IF M<>7 THEN 5130 :: MN=7 :: MX=11 :: D=13 :: CX=46 :: GOTO 5250 !PROCESS CAT2
5130 IF M<>13 THEN 5140 :: MN=13 :: MX=22 :: D=8 :: CX=41 :: GOTO 5300 !PROCESS CAT3
5140 GOTO 7000
5200 IF E(1)+E(2)+(3)+E(4)+E(5)<>0 THEN 5500 ! PROCESS CAT1
5210 M=7 :: GOTO 5120 ! CAT1 NOT ACTIVE
5250 IF E(7)+E(+E(9)+E(10)+E(11)<>0 THEN 5500 ! PROCESS CAT2
5260 M=13 :: GOTO 5130 ! CAT2 NOT ACTIVE
5300 IF E(13)+E(14)+E(15)+E(16)+E(17)+E(18)+E(19)+E(20)+E(21)+E(22)<>0 THEN 5500 ! PROCESS CAT3
5310 M=23 :: GOTO 8000 ! CAT 3 NOT ACTIVE


5110 IF M<>1 THEN 5120 :: MN=1 :: MX=5 :: D=13 :: CX=46 !PROCESS CAT1
5111 IF E(1)+E(2)+(3)+E(4)+E(5)<>0 THEN 5500
5112 M=7 ! CAT1 NOT ACTIVE
5120 IF M<>7 THEN 5130 :: MN=7 :: MX=11 :: D=13 :: CX=46 !PROCESS CAT2
5121 IF E(7)+E(+E(9)+E(10)+E(11)<>0 THEN 5500
5122 M=13 ! CAT2 NOT ACTIVE
5130 IF M<>13 THEN 5140 :: MN=13 :: MX=22 :: D=8 :: CX=41 !PROCESS CAT3
5131 IF E(13)+E(14)+E(15)+E(16)+E(17)+E(18)+E(19)+E(20)+E(21)+E(22)<>0 THEN 5500
5132 M=23 :: GOTO 8000 ! CAT 3 NOT ACTIVE
5140 GOTO 7000

So, then you skip a bunch of gotos. Probably not resolutionary in time savings, but should work slightly more efficiently.


Don't forget to drop all the lines I've moved...


Additionally, you might be able to collapse things a little bit more:

5110 IF M<>1 THEN 5120 :: MN=1 :: MX=5 :: D=13 :: CX=46 :: IF E(1)+E(2)+(3)+E(4)+E(5)<>0 THEN 5500 !PROCESS CAT1
5111 M=7 ! CAT1 NOT ACTIVE
5120 IF M<>7 THEN 5130 :: MN=7 :: MX=11 :: D=13 :: CX=46 :: IF E(7)+E(+E(9)+E(10)+E(11)<>0 THEN 5500 !PROCESS CAT2
5121 M=13 ! CAT2 NOT ACTIVE
5130 IF M<>13 THEN 5140 :: MN=13 :: MX=22 :: D=8 :: CX=41 :: IF E(13)+E(14)+E(15)+E(16)+E(17)+E(18)+E(19)+E(20)+E(21)+E(22)<>0 THEN 5500 !PROCESS CAT3
5131 M=23 :: GOTO 8000 ! CAT 3 NOT ACTIVE

Finally, you're doing a lot of math to determine if any bits are sent... I think you could do something like:

IF E(1) OR E(2) OR E(3) OR E(4) OR E(5) THEN 5500

If this works, line length might get longer, so you might need to go back to split lines. Not only is this faster, by avoiding all the math, it should also short-circuit and be more efficient as well.

Edited by unhuman
Link to comment
Share on other sites

Tusi / unhuman, thanks for the time invested. I have started a few projects (games) in the past and always either lost interest or got side tracked with life. I am keen to get this one done and have already invested a lot into it, so again – Thanks for the input.

5110 IF M<>1 THEN 5120 :: MN=1 :: MX=5 :: D=13 :: CX=46 :: GOTO 5200

Tursi, yes the alternative ELSE format is to remain compatible with the XB compiler which only likes THEN statements which GOTO line numbers. I found it a bit awkward at the beginning but now I am actually starting to prefer this way of ‘elsing’. J



I agree the interaction between M & T is strange, if not a somewhat cumbersome. My plan was to have additional enemies which would enter the game from the right side of the screen. The processing of these would simply continue on within the same FOR/NEXT loop (not yet written). As I wanted to process each complete caterpillar a chunk at a time this seemed like a good way to achieve both. As things progress further I will re-consider if anything is gained by changing this. I can think of several ways to improve this. Not sure if speed will be impacted, but I agree it could be a lot prettier.

5560 …….. C=EAV(MN)

.That is a leftover from some previous fooling around – good catch!


Yes, the -192 is simply to offset the first 6 rows of the screen which are not part of the grid, and save the memory that G(1-192) would otherwise reserve and never use.



The G & E pairing is supposed to work like this;

-Players bullet encounters a G() value that indicates that something has been hit. I could have used GCHAR to identify a screen character, but would then have to search all enemies and check coordinates to see which enemy has actually been hit. With this approach I can identify instantly which E(enemy) needs to be destroyed/processed.

-Now I know why E having a value <>0 is important, but I can’t for the life of me remember why I decided that it should otherwise hold the grid position. It now seems unnecessary. Having taken some time out I have lost the thought process….. Agree that I can give the added effort to define this value the flick. Woohoo – That’s a saving! J


In regards to some of your other points Tursi, when things started to slow down I removed some code for testing which I always intended to add back in. I had come up with some ideas which might explain some of the strange ways of defining the head direction and the way the variables are treated and displayed.


Currently my character map looks like the photo in the attachment.


I have defined a series of arrays represented by AS(#, counter 1-12). When a caterpillar/enemy is defined it is given a # value. Basically the counter pulls out values such as “0,0,0,0,0,0,0,0,0,0,0,0,” or “8,8,8,8,8,8,8,8,8,8,8,8” or “0,8,16,0,8,16,0,8,16,0,8,16” as the counter moves from 1 to 12. The value retrieved is the amount that the displayed ASCII code will be offset


This approach allows me to individually animate each different “Enemy” and each different segment. Ie, each enemy can have its own colour, can flash all colours, can flash some colours, can be a head facing in any direction, or a body part that might or might not change colour… and yet I only ever have to deal with two characters. 178 is always a head, and 182 is always a body - regardless what it is displayed on the screen.


The same counter was to be used for all non-sprite graphics. For example;. When an enemy was destroyed, EC(##) would change to a value of 160, and then using the same counter I could increase the displayed char value each cycle by increasing the counter. This would have the effect of changing the displayed value from 160, 168 , 176…. Etc moving it through character definitions and character sets.


My plan was to use these counters for displaying/animating everything and my character set was designed to use the values returned with as the counter incremented. By determining the value of AS(#,..) when each enemy was first created, I could determine exactly how every character behaved. I felt it worked extremely well and the time it took to control the counter would be offset when other special things had to happen. When I removed the counters the caterpillar march speed increased by around 10%. I was a little disappointed to find it took this much resources to maintain. I still want to go with this concept but I think I am forced to rethink just how liberally I use this function.


Line 5730 is a leftover. It currently should never be reached and will be deleted.


The whole 5850 / 5890 thing is to fall into line with my Character Set. <16 is always going to be a boundary character. If over >15 it will be something else that requires more processing. It must be confusing trying to make sense of stuff like this when I submitted unfinished code.


Thanks Tursi. Great stuff and good suggestions which I will take fully onboard!





Appreciate the input. I agree that some of the areas you highlighted could be improved. As per earlier comments, I was very pleased with the speed until it came to redrawing/calculating the caterpillar position- This seemed to be the source of the significant slowdown as this is when things took a huge speed hit. However, by milking even minute amounts it will certainly help. I especially like avoiding the math as you put it and didn’t realise there was potential for saving in this regard. This being the case there is possibly several other areas that I could do similar. I need to test it with the compiler and see just how the performance is affected. Thank you!


Again – Thanks guys! JTusi / unhuman, thanks for the time invested.


Edited by Bones-69
  • Like 3
Link to comment
Share on other sites

It is a lovely looking game screen... really nice.


I hope you can find a way to optimize and get it running the way you want. Seems like some issues I was having using the compiler to try and simulate a multi-threaded arcade style vertical shooter. Sometimes the extra logic to manually walk a piece through a series of steps can choke your speed. I found that by randomizing many of the elements that I wanted to have as static sped things up a bit, I just had to be willing to let go and let it happen.


That may not be what you are experiencing here, but I only mention it because sometimes you just need to re-think what is important and what is bells and whistles and re-prioritize your goals to fit the limitations.


Again, very lovely implementation... those graphics are sweet!!

Link to comment
Share on other sites

  • 5 weeks later...

Made some decisions and cut some bells and whistles. Also re-wrote several of the little routines. I have chewed up more stack and more program space, but I think the significant boost in speed was worth the sacrifice. I am still making little tweaks here and there trying to reclaim some more programming space - but I think things are heading in a better direction. At the very least I am now feeling more encouraged to continue.


  • Like 12
Link to comment
Share on other sites

  • 3 weeks later...

Another update for Gridslugger;


-Rewrote the "caterpillar march" soooo very many times and don't think I can squeeze any more speed out of it.

-Got the player controls working roughly the way I want. Still some room for improvement here.

-Player shooting is working.

-Added player bullet "bounce-back" when an empty shield is hit. Further down the track these shields will occupy bonuses that can be activated by shooting and then collecting. When empty they provide something else for the player to dodge.

-Explosions are working.

-HCHAR and sprite collisions are coincing perfectly.

-Scores are updating.

-Still no sounds. Have read about the XB256 sound lists but haven't started fooling around with this yet to appreciate exactly what is possible. I think sounds will really bring it to life.


Down to 4K stack and 4K available program space so not too nervous at this stage (guess I am 70% done). Having so much fun with XB256 & the Compiler! Don't think I could ever go back now....


  • Like 10
Link to comment
Share on other sites

  • 2 weeks later...

Small update but I expect it will be the last one for a few weeks....


-The bottom gunner now fires a laser. This adds a whole new level of difficulty.

-Made some changes to the "caterpillar march". When split, the forward facing part always becomes a head character.

-Changed some timing. Still tinkering a little here.

-Clawed back 2K in programming space by changing the way some processes were handled. There is still some further potential to save maybe another K if things get tight, but this will require I re-write the data file which will be a horrible job. I won't do this unless I absolutely have to.


Pretty happy with the way it is coming out. One thing I notice straight up is that it is quite difficult game. I remember writing other stuff and it always seemed like the computer was easy to beat, like losing was always hard. Getting out on this one is not going to be hard....


  • Like 10
Link to comment
Share on other sites

that's looking really nice! I'm impressed that you can get it that fast in BASIC (and it's going to be a right bugger with all those side lasers, mine was bad enough with just the two) :D.


I know Giles would hate it though, he has an absolute phobia about slugs. I mean run away screaming at the merest sight of one phobia. Which to be fair is pretty funny at times must be said.


Anyway keep it up, it's looking nice, I'll be looking forward to see how it progresses and will dig out a TI emulator to give it a go.


Been working on a Gridrunner port myself lately, ps4 and VR version, what the hell am I daft this is a Vic 20 game what the hell am I doing



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

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.

  • Recently Browsing   0 members

    • No registered users viewing this page.
  • Create New...