Don't use #ifdef, use conditionals with global variables instead.
conditional code tends to get extremely confusing and hard to debug.
How so? The #ifdef's are only one level deep. It's nothing like the macro-recursion-nightmare of the ALLOC/FREE code for instance. Granted it's butt ugly, but C has never been the most elegant looking language, only C++'s template-meta-programming work manages to look worse.
In any event, assuming this was not quick-hack code (and thus the quickest and easiest solution is the best), should I go the `if(has_gui) { print_gui(...); } else { print_term(...); }` route, this would result in the print_gui symbols being exposed in all builds, not just the currently windows only gui build, and thus requiring a stub library of "do nothing" functions for all systems that don't support an actual gui.
Also for those picky about performance (say, building a DS or Iphone port) it also has an unnecessary set of runtime (rather then compile time) execution-stall/microcode-cache-flush, test-and-branch instructions every call, and has extra code-memory overhead (I'm regularly enough not just doing a print_gui() call, which could be optimised out if the body is empty, so any string fiddling code, or constants, will still be in there taking up space...). No idea if it'll have any effect though, bit out of practice since the last time I was doing anything of that level of optimisation was way back in the era of blazing-fast-100mhz sparcstations...
Conditional code is generally considered a bad thing.
Reasons include:
1. Prevents code rot. If both paths are always compiled, you can't introduce syntax errors in the other path.
2. Ensures that parenthesis matching will agree in the editor and in your code. (Tracking down improperly #-embedded braces is a huge pain.)
3. Means that you don't need two different conditional compiles to test your code. You can test the gui vs ascii versions without recompiling.
4. It's easier to read the indenting from {} than from #if code.
Software tools and techniques for global software development. Dr. Dobb's features articles, source code, blogs,forums,video tutorials, and audio podcasts, as well as articles from Dr. Dobb's Journal, BYTE.com, C/C++ Users Journal, and Software Development magazine.
Conditional code is generally considered a bad thing.
I'll be the first to agree with you on that, since I normally follow the exact advice in the last line of the article "Even in the case of header files, use of #ifdef is generally a poorer approach than a redesign."
The appropriate way to redesign would be to create an additional abstraction layer between the print_ functions and the term_ related functions (just like the term_ functions are an abstraction layer on top of the different hardware) so as to properly handle terminals, guis, or random other interfaces (ARS-33 teletypes? ).
Assuming I'm working on Un for real, rather then as a random hack, the issue is that I'm trying to work within the current framework, as a result if I use if() rather then #if, it exposes gui_ functions, which currently aren't supported on all compile targets. Which means I need to make even more changes to add "do nothing" functions, and test these changes somehow on platforms I can't compile on (I've already had this issue with breaking the Mac build to fix an odd problem on windows, that worked fine on both windows and linux).
For me it's a cost/time/correctness trade off. I can "solve" the problem with #ifdefs, by using if()'s, only at the cost of more complexity.
Reasons include:
1. Prevents code rot. If both paths are always compiled, you can't introduce syntax errors in the other path.
We already have this issue with multiple compile targets. There's already windows/linux/mac that are being maintained, and it's pretty rare to introduce a bug in one that's not in the others (I've only done it once so far in my massive batch of bug fixing), so having an extra windows-gui one would add negligable extra maintenance issues. Plus there's already a half dozen other platforms that are sitting there unmaintained (Amiga? OS/2? DOS/32? RISCOS?), so yet-another broken and unmaintained platform wouldn't hurt.
2. Ensures that parenthesis matching will agree in the editor and in your code. (Tracking down improperly #-embedded braces is a huge pain.)
Ya, that does suck. Which is why I try to avoid using them.
3. Means that you don't need two different conditional compiles to test your code. You can test the gui vs ascii versions without recompiling.
I just have multiple configurations in Visual Studio, one that builds a debug tree minus 3dgui, one that builds it with gui, and my changes are usually pretty local so it's only 5 seconds waiting to compile the other release with the one or two changed files. Also I'll often have both debug builds running simultaneously so I can replicate what I do in one, in the other, to make sure they are working the same. It would be much harder to do that, or at least much more fiddly to do that, if I were running two copies of the same program.
So for me this would actually be a disadvantage.
4. It's easier to read the indenting from {} than from #if code.
Yup. Multiple nested #ifdefs are a pain, but unfortunately pretty common in config.h's if you have to support multiple platforms.
Anyway, I think I've meandered quite off track from discussions over gui-ification of *bands.
Um... could you clarify? Perhaps with screen shots?
I'm not against moving to the the newer Angband display model, but its not had time to prove itself yet.
Andrew
Ah, this wasn't actually a remotely serious attempt at making a new gui, just me messing around with a 3d toolkit that I'd never really used for anything before.
I do have an older screen shot but I can't generate any for my current code since my compile system is currently trashed due it's windows install decided that it had lived a long life and so it committed suicide at an age of 6 months. (Currently in the process of reinstalling, well, everything. It'll probably take most of the week...)
It's not much to look at (no map, so black screen, but you can see the result of me wandering around killing hobbits blindly...), but it's attached. Also linked here since the attachment resizes things tiny: http://xs.to/xs.php?h=xs137&d=09132&...nshot_1597.png
The appropriate way to redesign would be to create an additional abstraction layer between the print_ functions and the term_ related functions (just like the term_ functions are an abstraction layer on top of the different hardware) so as to properly handle terminals, guis, or random other interfaces (ARS-33 teletypes? ).
Assuming I'm working on Un for real, rather then as a random hack, the issue is that I'm trying to work within the current framework, as a result if I use if() rather then #if, it exposes gui_ functions, which currently aren't supported on all compile targets. Which means I need to make even more changes to add "do nothing" functions, and test these changes somehow on platforms I can't compile on (I've already had this issue with breaking the Mac build to fix an odd problem on windows, that worked fine on both windows and linux).
For me it's a cost/time/correctness trade off. I can "solve" the problem with #ifdefs, by using if()'s, only at the cost of more complexity.
We already have this issue with multiple compile targets. There's already windows/linux/mac that are being maintained, and it's pretty rare to introduce a bug in one that's not in the others (I've only done it once so far in my massive batch of bug fixing), so having an extra windows-gui one would add negligable extra maintenance issues. Plus there's already a half dozen other platforms that are sitting there unmaintained (Amiga? OS/2? DOS/32? RISCOS?), so yet-another broken and unmaintained platform wouldn't hurt.
My general approach has been to try to maintain in sync with the general Angband platform based approaches. That's not always been the case, but I managed to get the most significant deviation (mouse input) accepted upstream - conveniently by adopting a patch originally intended for Angband and improving on it.
In order to modify the platform files, I'd prefer if you do something which maintains compatibility with those files. The only significant platform deviation on Windows is the isometric display code, which is fairly limited. The Unangband Mac OSX code has probably deviated the most due to the separate and parallel development by several contributors - but it's probably worth trying to merge any improvements back into Angband anyway.
Having said all that, a quick browse through of 3.1.0-beta suggests there hasn't been a significant change in the platform specific code (it's still term driven).
So feel free to come up with an alternative approach. I suspect the abstraction layer you're planning is the correct one, if only because having a glue layer would be the easiest way to maintain the existing term_ based compatibility with a more sophisticated gui_ mechanism.
I don't think the game needs to expose what an individual monster is: the abstraction of the map being a 2 dimensional grid with a stack of elements sitting in each grid square is fundamentally sound, as long as there is a call back mechanism which can allow more detailled queries on individual stack elements (such as 'give me a text string describing this element on the stack', or 'what commands can I do to this grid', or even 'what additional decorations does this item have that I can draw on top of it' - such as monster injury state).
Player state information will have to be exposed to the UI in more detail, which is why I've moved to the timed_effect model that Angband has. At the moment time effects are mostly a binary state: but you'll want to abstract this away from the interface as you highlight, so that you can display e.g. 'Mortal Wound' as a chunk of text on the screen somewhere, or an icon, or even a change to the player graphic displayed.
You'll notice I've started abstracting out object manipulation commands into the cmd_item_list table. This is done with the intention that I can right click on an object and choose a command to interact with the object in a context menu, or drag one object onto another and have them interact in some useful manner. Still early days on this, but designed with this in mind.
Feel free to drag Unangband in any direction you feel a need to. I'd like to steer clear of an unnecessarily whole sale reorganisation of which files code sits in - because finding where in the code base something happens is mostly a matter of rote learning anyway and having helpfully named files doesn't seem to improve the process by much.
Ah, this wasn't actually a remotely serious attempt at making a new gui, just me messing around with a 3d toolkit that I'd never really used for anything before.
I do have an older screen shot but I can't generate any for my current code since my compile system is currently trashed due it's windows install decided that it had lived a long life and so it committed suicide at an age of 6 months. (Currently in the process of reinstalling, well, everything. It'll probably take most of the week...)
It's not much to look at (no map, so black screen, but you can see the result of me wandering around killing hobbits blindly...), but it's attached. Also linked here since the attachment resizes things tiny: http://xs.to/xs.php?h=xs137&d=09132&...nshot_1597.png
Looks good.
I think you'll know if you've achieved the right layer of GUI abstraction when you can run Angband as a CGI application and with a 3d interface using the same model.
In order to modify the platform files, I'd prefer if you do something which maintains compatibility with those files. The only significant platform deviation on Windows is the isometric display code, which is fairly limited. The Unangband Mac OSX code has probably deviated the most due to the separate and parallel development by several contributors - but it's probably worth trying to merge any improvements back into Angband anyway.
I'm actually using very little of the main-win file, just the basic check for save files, and initialise the term data structures and the like, since the middle-ware I'm using handles most of the OS-layer stuff. In the end anything interacting with a higher layer gui (this 3d stuff, the tcl/tk stuff) it would have it's own file(s) to store it's specific code in rather then sharing anything more then minimal of the platform specific code. It would work the same way if someone integrated tck/tk or whatever, since we're essentially capturing and using the data in the layer above that the current main's handle.
So feel free to come up with an alternative approach. I suspect the abstraction layer you're planning is the correct one, if only because having a glue layer would be the easiest way to maintain the existing term_ based compatibility with a more sophisticated gui_ mechanism.
I originally tried to hook the data bring printed at the lower levels inside the term_ functions but it ended up being more voodoo then actual method, simply because a lot of the fields end up being programatically placed due to the length of their text.
The main problem I encountered with this higher level method was when to "clear" or rather show/hide the gui elements. I can trap a Term_clear event and wipe everything, but for example with things like the little status "command" messages "you bumped into a wall", etc at the top of the screen. I ended up having to dump them into a console because otherwise they'd sit there even after you'd stopped bumping into one because I was interpreting things at a too high level to understand a "wipe row 0" clear call. (... bleah, getting tired, english failing... it probably makes sense. Maybe.)
I don't think the game needs to expose what an individual monster is: the abstraction of the map being a 2 dimensional grid with a stack of elements sitting in each grid square is fundamentally sound, as long as there is a call back mechanism which can allow more detailled queries on individual stack elements (such as 'give me a text string describing this element on the stack', or 'what commands can I do to this grid', or even 'what additional decorations does this item have that I can draw on top of it' - such as monster injury state).
Technically any additions beyond "what you currently have in terminal mode", wouldn't need to go through a defined gui_ interface, since they're not necessary to share. In the same way that things like the menus in the windows version allow you to select your tiles, or load a save game via dropdown menus are value-added. It wouldn't necessarily be a bad thing, but it's not a necessary thing.
Player state information will have to be exposed to the UI in more detail, which is why I've moved to the timed_effect model that Angband has. At the moment time effects are mostly a binary state: but you'll want to abstract this away from the interface as you highlight, so that you can display e.g. 'Mortal Wound' as a chunk of text on the screen somewhere, or an icon, or even a change to the player graphic displayed.
Sort of doing that at the moment as the various functions already print out a wound-level and the like, but yes there's probably other stuff hiding around the place that would be convenient to expose, especially for a newbie player.
Feel free to drag Unangband in any direction you feel a need to. I'd like to steer clear of an unnecessarily whole sale reorganisation of which files code sits in - because finding where in the code base something happens is mostly a matter of rote learning anyway and having helpfully named files doesn't seem to improve the process by much.
No problem. I end up spending most of my day in grep anyway since Visual Studio (which despite my dislike for it, has somehow become my current programming environment) is really not designed for use with C, even though it begrudgingly is C89 compliant. Though the stuff I was hacking in has been all C++ based (since no one makes modern graphics middle-ware with C), I did try to preserve a C-compatible facing API.
I don't expect I'll be dragging anything, anywhere at least for the next few months though since I'm completely lacking in time. Part time university is fun enough, but it really does suck away all your free hours if you're working full time...
Originally posted by andrewdoull
Looks good.
I think you'll know if you've achieved the right layer of GUI abstraction when you can run Angband as a CGI application and with a 3d interface using the same model.
That's pretty much the way I was thinking, but that's probably because I spent the last 18 months hooking together SOAP/XML-RPC/sql/etc RPC queries at work. After a few months of that everything starts looking like a remote procedure call.
I think you'll know if you've achieved the right layer of GUI abstraction when you can run Angband as a CGI application and with a 3d interface using the same model.
Andrew
Wow! I wasn't expecting such a positive response, but if anyone wants to start coding a CGI *band, then I'll support in any way I can!
Seriously now, I'm swamped with work until the end of next week, but after, I'll start having a tinker with Unangband's internals, as it certainly seems like the most popular variant 'round here, and the one with the best support.
As a word of advice with the GUI, ZangbandTK may be dead, but it is very well documented, and goes way further than a basic GUI. There's menus, Store support, creation support, Knowledge database cataloguing, affliction reporting (like mortal wound etc.). In the words of Yoda "There is much code within, secrets there may be found."
@Darke,
would you say that your coding for that 3d engine could be turned into a usable abstraction layer? If we could have a simple method to say, hook a plugin into the code and serve that plugin with the simple facts of what's currently going on ingame, then we could blow open the pathway for user created GUI's. Plus, this could then be offered to the angband team as a standard. (Standardisation seems to be the bane of open source stuff these days, such a shame as without it nothing is ever easy to make cross-platform)
And depending on whether #ifdefs or conditionals are used, what is the chance of either forms of stat abstraction not having to be changed when its time for the next build of Unangband to come out?
@ takkaria and Andrew Doull
What's your policy on integrating user made content into the respective main codebases? Seeing as most builds aren't run on efficiency critical systems, would you be open to taking onboard some code that did a check for a plugin at run time and sent it data on current user actions, thus allowing easy implementation of a GUI?
Seriously now, I'm swamped with work until the end of next week, but after, I'll start having a tinker with Unangband's internals, as it certainly seems like the most popular variant 'round here, and the one with the best support.
Don't be so sure. The only reason it seems to get press is because the SVN is easily accessible and seems to attract programmers like flies... there are plenty of other variants that are popular and well-supported.
@ takkaria and Andrew Doull
What's your policy on integrating user made content into the respective main codebases? Seeing as most builds aren't run on efficiency critical systems, would you be open to taking onboard some code that did a check for a plugin at run time and sent it data on current user actions, thus allowing easy implementation of a GUI?
I don't think a plug-in approach is the way to go, necessarily, and certainly anything that accesses the internal game state is going to be incredibly limiting and non-portable. The advantage with the current term interface is that it has a useful level of abstraction that means you almost never have to touch the platform specific code unless you're doing something weird and wonderful (like implementing a 3d engine or isometric graphics).
Its a matter of finding another balanced level of abstraction - and that'll require a number of design and code iterations. Angband itself has (at least two IIRC) event models that attempt to abstract away some of the in-game specifics. I'm a strong advocate of a data driven approach, which means that you can tweak variables as opposed to writing code.
Take the current pref file approach, for instance. At first this looks really horrible, because you have the underlying game determining which graphic to display, and then telling the platform to display graphic z at screen coordinate x,y. But then, you realise that the abstraction of which graphic is used for which element is just another way of abstracting the concept of display some element at this location. Instead of abstracting elements to be (object, 300), we're abstracting them to be (tile, a, b). Which makes a huge amount of sense when you realise that the tiles are cross-platform and rarely change and therefore a tile-centric abstraction is appropriate.
To improve the current situation, we have to pick appropriate abstractions for (player-state, wounded, level 1) and (menu, open door, west) and (store, buy item, object 14) and so on which can be implemented in such a way that the game designer doesn't have to worry about editting a platform file when he, for instance, creates a new top tier game concept, such as the region code which I have just been working on.
I don't see a plug-in approach allowing this level of abstraction unfortunately.
Only because someone introduced a whole lot of C99 code which somehow ended up in Angband as C89 code...
I'm guilty of the former, but Takaria is guilty of the latter... (When I moved the code, I certainly didn't care about C89. And the OSX port in particular is gnu99--not even c99, because of some nonstandard (non-constant) array initializations.)
I'm guilty of the former, but Takaria is guilty of the latter... (When I moved the code, I certainly didn't care about C89. And the OSX port in particular is gnu99--not even c99, because of some nonstandard (non-constant) array initializations.)
Sorry if that came off sounding like I was blaming you. I don't particularly worry about C89 vs C99 standard as long as it doesn't affected portability (and gnu is ported to virtually everything these days).
What does keep me awake at night is issues affecting portability that I can't control or plan for, like the stack size limit on the Nintendo DS.
Comment