Help wanted optimizing text adventure

17 posts / 0 new
Last post
Offline
Last seen: 4 weeks 15 hours ago
Joined: Nov 12 2022 - 16:50
Posts: 201
Help wanted optimizing text adventure
AttachmentSize
Plain text icon Example TextAdv Code #001.txt3.54 KB

Hi!  I am working on a text adventure for the C64, but, if I succeed, I want to port it to the Apple2.  As it is written in cc65 C and mostly text, it should be easy to port.  Now, I believe myself to be a good optimizer, but I'm looking for more optimizations.  I attached some code snippets from the text adventure.  If you want more information, just ask.

mmphosis's picture
Offline
Last seen: 2 weeks 1 day ago
Joined: Aug 18 2005 - 16:26
Posts: 434
Too many errors

The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming.

../cc65/cc65/cc65-2.19.0.git.1670758151.d9ebfa7/bin/cc65 1.c
1.c:3: Error: Undefined symbol: 'Item'
1.c:3: Error: Undefined symbol: 'Itm1'
1.c:3: Error: Subscripted value is neither array nor pointer
1.c:3: Warning: Incompatible pointer conversion to 'struct item_ *' from 'char * const'
1.c:8: Error: Call to undeclared function 'printtok'
1.c:8: Error: Call to undeclared function 'hidereadw'
1.c:8: Error: No field named 'Name' found in 'struct item_ *'
1.c:8: Error: Illegal address
1.c:8: Error: Cannot get the address of a numeric constant
1.c:14: Error: Undefined symbol: 'Itm1'
1.c:18: Error: Call to undeclared function 'SearchInv'
1.c:18: Error: Undefined symbol: 'Player'
1.c:18: Error: Struct or union expected
1.c:18: Error: No field named 'Inv' found in 'int'
1.c:19: Error: Undefined symbol: 'CurRoomInv'
1.c:21: Error: No field named 'Info' found in 'struct item_ *'
1.c:25: Error: No field named 'Info' found in 'struct item_ *'
1.c:29: Error: Call to undeclared function 'printnoitem'
1.c:32: Error: Undefined symbol: 'StoreNum'
1.c:33: Error: Undefined symbol: 'CRm'
1.c:33: Error: Undefined symbol: 'Room'
1.c:33: Error: Undefined symbol: 'Player'
1.c:33: Fatal: Too many errors

Offline
Last seen: 4 weeks 15 hours ago
Joined: Nov 12 2022 - 16:50
Posts: 201
The code I gave you is code

The code I gave you is code snipppets, not the full code.  I have skeleton codes around which others can create text adventures at c65 additions - Manage /game at SourceForge.net.  They're called AdvSkelVic65 and AdvSkel65.  The latter has versions for the Apple2enh, and the former is for low-end systems.  However, they don't work on some systems.

mmphosis's picture
Offline
Last seen: 2 weeks 1 day ago
Joined: Aug 18 2005 - 16:26
Posts: 434
Make it obvious. Make it extremely obvious

I would optimize for readability and approachability.

Offline
Last seen: 4 weeks 1 day ago
Joined: Jul 5 2018 - 09:44
Posts: 2587
mmphosis wrote:I would
mmphosis wrote:

I would optimize for readability and approachability.

 

Speed isn't really a factor in a text adventure.  A lot of classic ones like the Scott Adams or Zork series aren't exactly fast in outputting their text.  But most people can only read so fast anyway, and usually if the puzzles of the adventure aren't trivial the player is going to spend at least some time thinking about it before making moves.  Optimizing for size maybe makes sense when it is necessary.  But there have been some pretty complex text adventure games built that fit into relatively small space of an Apple II, even back in the classic era when there wasn't that much sophisticated compression.  If the floppy is being used, some pretty impressive games shipped on a single side of a single floppy disk.  That's only 140k.  Today, you could target more than that since most people won't be playing off physical floppy disk media.

 

Offline
Last seen: 4 weeks 15 hours ago
Joined: Nov 12 2022 - 16:50
Posts: 201
I've been optimizing for size

I've been optimizing for size, and sorry about my programs' illegibility.  :(  Do you have any tips on optimizing about which I don't already know?  My printtok-002.c code helped my text adventure's text by a little more than 25%.  I want 30% before adding automatic compression and compression of literals.

mmphosis's picture
Offline
Last seen: 2 weeks 1 day ago
Joined: Aug 18 2005 - 16:26
Posts: 434
optimizing for size
Harry Potter wrote:

I've been optimizing for size, and sorry about my programs' illegibility.  :(  Do you have any tips on optimizing about which I don't already know?  My printtok-002.c code helped my text adventure's text by a little more than 25%.  I want 30% before adding automatic compression and compression of literals.

I appreciate you reading our feedback. :)

As for optimizing for size, cc65 adds a lot of code and stack space, but cc65 appears to be the best we've got for a cross development Apple II C compiler. I haven't used cc65 enough to give a good answer. Keep It Simple Someone once said.

Just get rid of the crappy stuff and focus on the good stuff.

-2000 Lines Of Code (folklore.org)

 

Offline
Last seen: 4 weeks 15 hours ago
Joined: Nov 12 2022 - 16:50
Posts: 201
I am using my CBMSimpleIO,

I am using my CBMSimpleIO, A2SimpleIO and AtaSimpleIO libraries instead of the standard I/O library.  The Apple2 versions use ProDOSi, as it's much more efficient yhan the standard file I/O library.  I also killed some unnecessary features in the crt0.s files, such as replacing jsr callmain with jsr _main, killling the library init code and, for the CBM versions, bypassing the zp-saving code and resetting upon program exit.  If you want more optimizations, I can tell you where I have my optimizations.  I'm looking for something I didn't know.

Offline
Last seen: 11 hours 47 min ago
Joined: May 31 2022 - 18:18
Posts: 225
Harry Potter wrote:I'm
Harry Potter wrote:

I'm looking for something I didn't know.

 

 

It's clear there's a lot more here that what's on the surface... so let me ask this.What's the problem you're trying to solve?

I don't know the cc65 compiler but still, there's a few things I could suggest...

First, understanding what problem you're trying to fix is important. Optimize for speed, space or both? You say this is text adventure, so that's an interesting point because really get any faster than text so, what problems are you facing?This was one thing I notice in the following... and ah crap this won't display code well...

CRm=&Room[Player.CurRoom];

CurRoomInv=Player.RoomInv[Player.CurRoom];

printtok ((char*)hidereadw((void*)&CRm->Desc));

func=hidereadw((void*)&CRm->RoomHandler);

(*func) ();

printcr();

for (i=0; i<8 && (Itm1=CurRoomInv[i])!=0xFF;++i) {

    prints ("There is a ");

    printitmname(); 

    printscr (" here.");

}

 

 

First, I understand what you're you're thinking with the pointer, calc once and then access the elements. In general this is a good idea eliminate extra math on the memory addressing. But here... I'm not so sure because good compilers will often do a better job than you. Not always, but often enough you should check what it does first. In addition, you only use the value a couple of times so you're allocating space for something that's not used much. If you have concerns about the code, first step is check the assembly code. Then test a change and see if it makes a difference. If it doesn't benefit the code, then stick with the standard coding methods because this type of coding will confuse the average C programmer. Hell, it may even confuse you after 6 months... never over estimate your ability to understand something you wrote months ago.  

I think I understand what you're trying to do with your (*func)() , but have you looked at what the compiler does with this? Does this really do what you think? Is it helpful or is setting up for calling a function from a variable more work than just calling the function? I'm guessing there's a better way. Why didn't you include source to hidereadw?

 

Your GetItemPtr and printitmname would likely be better as an inline macros, your wasting space and execution cycles.

 

Another thing that is questionable is your for loop used to consruct strings. Sub-routines are always slower than inline. In this case I would also look at figuring out how to make this more efficient. If there isn't a sprintf function, then write something similar using tokens or pointers to construct a single string and call print function once.I think it's super important to take a look at the compiled code... you may be surprised by what the compier does. Sometimes the simpliest things take way more work than one would expect. This is totally NOT 6502, but as an example with the bit field addressable modern systems one could assume that needing to set/clear 4 bits in a configuration register would be best accomplished with just assigning the correct value to each bit one at a time. But in reality if you check the compiler output... that code is 20x larger than ANDing off the 0 values and ORing in the 1's with two instructions because even tho there are bit-addressable functions the compiler has to do work to get the desired results not just CLR/SET  bitX. Look at the compiler output, then identify and address the specific problems you find. 

Offline
Last seen: 4 weeks 15 hours ago
Joined: Nov 12 2022 - 16:50
Posts: 201
I really don't have much of a

I really don't have much of a problem here; I just want to buy a few more bytes optimality.  I am not checking the assembler but am checking the program size, and the GetItemPtr and printitmname functions produce tighter code.  I think the for loop you mentioned is better as is than constructing the string once as you mentioned, as it requires less overhead. I thank you for responding, though.

Offline
Last seen: 4 weeks 15 hours ago
Joined: Nov 12 2022 - 16:50
Posts: 201
You mentioned calling a

You mentioned calling a function from a pointer.  Cc65 is bad at handling pointers, but I need this, as some rooms require extra code to handle case-specific conditions.  For example, in the first room, my text adventure requires you to answer the phone, so the extra code will only state that the telephone is ringing if it wasn't already answered.  

Offline
Last seen: 11 hours 47 min ago
Joined: May 31 2022 - 18:18
Posts: 225
I understand that this is

I understand that this is both a project and a learning process so I wasn't trying to directly critique your code other than to say, and this was my big point... you never should make any assumptions about what works best until you actually look at the code generated. And here's why what you see looking at something like total code size doesn't give good clarity... as I said, compliers are generally pretty good at getting the job done in an efficient way.

 

Unless the cc65 compiler is optimizing the first two functions and promoting them to inline, there's really no reason why the function versions you have could be better. The reason I say this is because at a minimum the compiler will have to insert a jsr and rts for the function, then do the same work inside the function as would be done inline, but... if the compiler needs to trash a regiser (A, X, Y) in the function, you'll also have PH? and PL? in the functions. That's at best 4 more bytes of code space for every time the function is called and those instructions come with a time hit too. The code inside the function likely won't be any smaller and may even be more efficent when left inline. If you give me your all your soures, I'm happy to grab the cc65 and run some tests... If you're interested... I'm curious! I'll also admit I've been surprised before, and that's why you always need to check the generated output.

 

In modern times, wth RISC processors I let the compiler do the work in almost 99% of the time becuase those compilers understand things better than most programmers do (espeically true of those who only learn object programming). Those of us that grew up writting assembly and later moved into the higher-level languages have a diffrent way of looking at things. As an example... If I just have to run a loop a fixed number of times, I start at the end number and decrement down to zero because I know the compiler won't have to do a compare to check the end, it just branches while the zero flag is clear, one less instruction two less cycles. Do others do ths?  Not really, but I've seen Woz do the same in the II firmware!

Offline
Last seen: 4 weeks 15 hours ago
Joined: Nov 12 2022 - 16:50
Posts: 201
Jeff D, I thank you for your

Jeff D, I thank you for your advice.  I admit to not analyzing the object code, but I am looking at the executable size.  That's how I know my code produces good results.  I don't know whether I should post all my code or not, but I have empty text adventure codes at c65 additions - Manage /game at SourceForge.net.  The files are AdvSkelVic65 and AdvSkel65.  If you want, I can post more verb codes and the code to process user input.

Offline
Last seen: 4 weeks 15 hours ago
Joined: Nov 12 2022 - 16:50
Posts: 201
I just started to look at the

I just started to look at the assembler output of the Words.c file, which contains the vocabulary functions, and found a few inefficiencies with cc65's output.  I also now realize what you were saying about my pointers not being used often enough to save space, but they are used elsewhere in the code.  I know that cc65 is poor about handling pointers--even from zeropage--which I learned from experience.  I'd better convert some pointer calculations to inline assembler now.

Offline
Last seen: 4 weeks 15 hours ago
Joined: Nov 12 2022 - 16:50
Posts: 201
I attached some more example

I attached some more example code from my text adventure and some examples of my optimizations.Package iconWORDS.zip

Offline
Last seen: 4 weeks 15 hours ago
Joined: Nov 12 2022 - 16:50
Posts: 201
Jeff D, I thank you for

Jeff D, I thank you for telling me to analyze the object code.  The printmsg() function seems to be producing inefficient code.  The C code is:

 

void __fastcall__ printmsg(unsigned char m){printtok ((void*)Message[m]); printcr();} 

 

It produces the following output:

.segment    "ZP2CODE"

.proc    _printmsg: near

.segment    "ZP2CODE"

;; {printtok ((void*)Message[m]); printcr();};    jsr     pusha    ldx     #$00    lda     (sp,x)    asl     a    bcc     L0003    inx    clcL0003:    adc     #<(_Message)    tay    txa    adc     #>(_Message)    tax    tya    jsr     ldaxi    jsr     _printtok    jsr     _printcr    jmp     incsp1

.endproc 

I can do better.

Offline
Last seen: 4 weeks 15 hours ago
Joined: Nov 12 2022 - 16:50
Posts: 201
I replaced it with: _printmsg

I replaced it with:

 

_printmsg:    asl    tax    lda    _Message,x    pha    lda    _Message+1,x    tax    pla    jsr    _printtok    jmp    _printcr 

and saved 15 bytes.  Thank you.  :)

Log in or register to post comments