Why I write games in C
princeofspace

This may be controversial, but this is something I've turned over in my brain for a while.

I have no great aversion to C++, and if someone is willing to pay me to write code with, say, C++14, that is fine. I've considered switching to C++ as my primary tool lately because of Microsoft's unwillingness to support a proper C compiler.

However, there's a few significant reasons why I'm struggling with moving over to the other side -- primarily, because C makes me a better programmer.

Take, for example, mixing code with variable declarations. If you're thinking of putting variable declarations deep into a nested function, it should serve as a warning; it's time to consider breaking off a complicated function into a simpler one. It's cleaner organization to put the data first, followed by the operations on the data.

This practice encourages a high number of functions, which, in turn, promotes better readability, if said functions are unambiguously named and kept short. If you're worried about the overhead of a function call, don't be -- short, small "leaf" functions are easily inlined by any modern compiler.

Of course, any of this could be done in C++, but C FORCES me to write in a cleaner style. C enforces the VERB(NOUN) command structure, instead of the OOP paradigm of NOUN->VERB(). A lot has been said about OOP being tacked onto "C with Classes" but I actually think Stroustrup did the best he could here, considering C wasn't really designed with OOP in mind. I just don't like OOP very much. The more I program, the more I find it inhibits readability.

I could go on. But to summarize, writing in C, for me, isn't just about performance. It's about improved readability coupled with excellent library support.

SiegeLord

I don't really agree with the declarations up-front argument, but I do want to note that despite living in the age of C and C++ replacements, when doing recreational programming you should be using a language that's fun for you to program in. There's definitely a degree of fun that can only be had in C, and realizing that, I continue to contribute to Allegro to enable people such as yourself even if I use different languages myself.

Chris Katko

I'm moving toward D, myself. It's got way more constructs for the COMPILER to ENSURE that you're coding according to your own requirements.

For example, modern compilers CAN warn you if you don't check return values.

HOWEVER, with a very simple D template / mixin, I can have functions and have them throw an assert(0, "You messed up on line 124") if you forget to check the return value, AND, have it compile away to zero cost with a single define in release mode. (Of course, this 99% a compile only check, so it shouldn't produce any slower code, but if you want other checks you can do the same thing.) But the cool thing is, I can actually selectively apply it to either certain return values, like an "error_return" type that casts down to an int. Or, I can have it apply to certain marked functions. The sky is the limit.

I'm a complete D newbie and I still managed, in a weekend, to make a template framework that allowed me to attach units of measurement to scalar values. So I could, at compile time, enforce that my physics equations all had the right units. (ala m^2 / j * t) I never worked further on it, but the hardest part was ensuring non-easy calculations worked out. On the otherhand, someone already WROTE a full units framework in D with probably better syntax, testing, and performance.

https://github.com/nordlow/units-d
(another one)

https://github.com/biozic/quantities

But the point is, I'm no genius and I managed to write 90% of "something really cool and useful" which can be used to increase my productivity by reducing common errors.

[edit] ALSO, D supports PURE FUNCTIONS. Which even John Carmack tried to make in Doom 3 because they're so useful. They're functions that can only call other pure functions. Pure functions cannot modify data. They can only input and output. There is no possibility for a side effect (like calling a global function that mutates global state). And no input data structures can change, so you have to create a new one if you want to modify the previous. That "sounds" slow, and in tight loops, sure. HOWEVER, it's also a GODSENT when you combine it with concurrency (you know, where ALL TECHNOLOGY is heading). You don't have to worry about something being mutated while you use it in another thread. It's ensured at the language level. So it forces you to think in a way that's more compatible with proper, concurrency safe, programming architectures and patterns. It also strongly reduces the possible areas for a error, especially in concurrent programs where errors happen randomly when certain threads line up.

princeofspace

So it forces you to think in a way that's more compatible with proper, concurrency safe, programming architectures and patterns. It also strongly reduces the possible areas for a error, especially in concurrent programs where errors happen randomly when certain threads line up.

This is an interesting argument, and one that's made me seriously examine languages like Rust. (Though for Rust, the toolchain isn't quite where I'd like it to be yet.)

One of the problems I've had in writing Allegro-based game engines which use concurrency is that I already have more performance than I can use out of plain old C. The major bottleneck is in drawing, but drawing to memory bitmaps is slow compared to video bitmaps, and video bitmaps MUST be in the same thread or performance penalties result. Game updates, even with sophisticated collision checks, are at least 50,000 times faster than rendering one frame, according to my numbers. Not that I have any complaints with Allegro's draw speed -- I'm very easily hitting 60 fps even on older hardware.

In an old language like C, concurrency introduces greater complexity, which I prefer to avoid in my personal projects. D might be an interesting alternative....

SiegeLord said:

There's definitely a degree of fun that can only be had in C, and realizing that, I continue to contribute to Allegro to enable people such as yourself even if I use different languages myself.

Thank you very much for all you do for the community!

Neil Roy

I only code in C. I have played with C++, but find it is convoluted bloatware that only makes your programs look like a mess and more prone to errors.

No, Microsoft doesn't support C properly. My solution is simple, I don't use Microsoft software. Problem solved! MinGW does support the latest version (the 2011 C standard) so I use it (with "-std=gnu11", so you get the GNU extensions as well).

I just don't see a need for C++, for me anyhow. C is faster and to me, the code looks cleaner and more understandable. The latest C++ standards are even worse and I hate even looking at all those template codes which are even more of a mess.

I use Code::Blocks + MinGW, which has the benefit of also being more cross platform friendly as well. I think it's a bad habit to rely on a Microsoft compiler made for Windows and that isn't totally free (free to use, but if you make something with it where you make money, you have to pay them). No thanks. My Deluxe Pacman 1 & 2 are both created in pure C, compiled with GCC, not a C++ compiler.

I simply see no reason to use anything else. And learning a whole new language because someone else thinks it's better is not a good reason. C works, and it works well. It is very low level, the closest thing to Assembly you will get.

Many may not know this, but there has been a new movement away from Object orientated programming. Newer C++ tutorials don't even use constructors and destructors, which I find hilarious.

There's a good talk by Jack Diederich called "Stop writing classes!" who doesn't speak out against them, but how they are overused, or misused. Personally, I am not a fan of them at all.

video

And this is also a good video to watch called "OOP is the Root of All Evil" by Jeff Ward. This is a REALLY good watch as he goes over some of the problems with object, memory problems like cache hits, big problems with virtual functions which make it impossible for the compiler to optimize etc... etc...

video

beoran

I personallu like to use C for games because I am guaranteed to have good performance, good portability, and relative simplicity. Furthermore, I agree with Neil Roy that C++ is a horribly boated language, so I thoroughly dislike C++. I also invesitaged many other languages, from Rust to Go, to OOC, etc, but I found none to be wholly to my liking. SO I'm just sticking with good plain old C.

But I admit that I do use mruby as my scripting language to help me ease the work somewhat in cases where C is a bit weak (string proceessing, easy scripting, ...)

So no, you're not weird. I think C is cool, and I am realy grateful to SiegeLord, Trent & others who keeps maintaining Allegro so we can keep on using C for our games.

Specter Phoenix

The old debate that silences questions. Fond memories of seeking advice for C++/Allegro||SDL1.2 and being told to change the language. In recent years you get told to change libraries as well as languages.

princeofspace

Fond memories of seeking advice for C++/Allegro||SDL1.2 and being told to change the language.

Even as a C die-hard, I don't think I'd tell someone they HAVE to write in C for a personal project. You can pretty near write games in almost any language. I remember scratching out games in QBasic based on text of a book I got from the library written for the Timex Sinclair.

They didn't work great...

Chris Katko

C really is great for forcing people to understand what's going on. I really can't think of a better "low level" high-level language. But, once you know what's going on, it's not some huge penalty to move to a higher-level language as long as the machine code equivalent is predictable.

I know what a hash function is. I know what a linked list is. I know how trees work. And I know how vtables work. I also know that syscalls are orders-of-a-magnitude slower than most code.

Back in college, I took two SEMESTERS worth of finite element analysis. We took an entire first semester (senior year) just of math behind finite-element analysis and calculated them by hand. It wasn't till grad school, the second class, that we even got to USE the software. Why? Because the software lies. It was basically a year dedicated to how the software lies, and in what ways, and why... down to the mathematical basis for the failure.

99% of people aren't going to be ripping out paper equations in their daily work. But the point was, you should be able to if you need it to verify why the computer model is wrong.

The same applies to programming. I WANT to care more about high-level issues--the work I'm trying to get done--than the implementation. BUT, I want to know--with confidence--what my high-level code is going to end up under-the-hood.

Personally, I think languages haven't gotten high-level enough yet. I have no idea why it's possible to write code, but 99.9% of people and languages don't have a proper means of specifying static and dynamic RULES. Oh yeah, we can turn on a compiler option for warning you if you missed a return value. We can run a static or dynamic checker program that checks for "best practices". By why are we still, in 2017, unable to easily specify our own rules and practices and instead, we have "coding standards" and "review" to ensure those standards are followed. You know what computers are good at? CHECKING CODE VALIDITY. So why don't we have easily available, powerful toolchains for specifying custom, per-project standards on an AST-level? And further, why isn't the entire industry already using it?

I should be able to specify a list of commands that need to be called in order, and flag at compile-time if anyone writes code that fails to follow that order. Created an object but forgot to call an initializer? Forgot to load the TTF addon? Compile error.

(The closest I've ever found were GCC MELT (dear god, look at that webpage gore), and manually forking DScanner to add custom rules. I shouldn't have to learn the entire LISP language just to say, 'make sure X method gets called before Y for any object Z')

Everyone I talk to always skirts around the real issue. They say, "You should design API's so it's impossible to call them out-of-order." and "if coders don't follow guidelines you've got bigger problems." Bullcrap. Programmers are human and make mistakes proportional to the codebase size. The less requirements they have to keep in their head at one time, the less likely they forget one of those things. The more balls you try to juggle at once, the more likely one falls to the ground. And as for the API being impossible to call incorrectly? It must be nice living in perfect-world, where people have unlimited time and resources and there are no other requirements except "programmer can't call incorrectly". Oh look out that window, it's raining exceptions!

Now, with a good language like D (and much lesser extend C++), you can use templates and static if's to enforce rules. But it's the wrong tool for the job. You shouldn't be abusing templates to make SEMANTIC rules from INSIDE the language itself. You wouldn't write a bash script that checks itself for correctness. You should be able to write RULES the same way language designers do. You should be able to write a new attribute called "enforce_return" and stick it in your function signature and the compiler checks the return values for your homemade criteria. (or whatever your rules are.)

(e.g. Don't allow goto EXCEPT in this circumstance? That should be an AUTOMATED rule.)

We're programmers. We're supposed to automate things. But somehow, everyone is so used to the status quo, they automate everything... their VIM/emacs macros... their makefiles... their code... their unit testing... we'll even include vim tab and line-width macros into our files to ensure consistent indentation... but code standards? NO. That's that ONE THING is silly to automate! We'd rather stare at other people's code for hours during code reviews like an accountant without a calculator.

I mean, if you really sit back and let your mind wander at the possibilities, there are tons of useful things we could all do for any medium-sized project or larger. And even with small ones, it would force us to "write properly." Static and dynamic code analyzers already exist with "Best practices." Why not add our own practices to ensure we follow our own rules?

Any FOSS project of size has a coding standard that contributors have to follow to submit a patch. Allegro does. GCC does. The Linux kernel does. Automate it.

princeofspace

I should be able to specify a list of commands that need to be called in order, and flag at compile-time if anyone writes code that fails to follow that order. Created an object but forgot to call an initializer? Forgot to load the TTF addon? Compile error.

I did a lot of thinking about this today at work and when I came home you had made this post! I completely agree.

Take global variables. The problem with global variables isn't that they're global scope -- global scope can be enormously useful in certain contexts -- but that access rules for globals are very poor, at least in any language I've encountered.

Why? I'm not sure. If we're completely honest, static classes (or singletons, depending on which form of OOP you subscribe to) are really just global scope variable(s) in disguise, so they're certainly in production use. In games, typically a class called the Registry is used to solve very specific problems at global scope.

In C, I get partway there by fancying up my header guards a bit. If an interface will be used in one place, and ONLY in one place, I use something like this:

#SelectExpand
1 2 3#ifndef INTERFACE_NAME_H 4#define INTERFACE_NAME_H 5 6[ some interface access functions go here ] 7 8#else 9#error "Interface subsystem should only be called once -- " __FILE__ 10#endif

Now, if I forget and use this header in more than one place, it trips a compiler error. I'll put in comments describing where in the overall game engine these function calls belong.

But it's far from a perfect solution.

Neil Roy

Even as a C die-hard, I don't think I'd tell someone they HAVE to write in C for a personal project.

I'm the same way. I'll tell you why I love C and prefer it. But I always generally tell people that the best language to program in is the one you enjoy using. Forget what others think honestly. If C++ is what you enjoy, than do it. If D or whatever... go for it. Popular opinion on this matter honestly doesn't count. I can, and will tell you why I like C, but... that doesn't really matter, in the end, you are the one who has to use it.

If you EVER plan to work with a team, you may wish to familiarize yourself with what ever language and possibly coding style they want to use though. I don't plan on it so, C it is for me.

I don't know Chris, I like the freedom the language gives me to do things the way I want. It's what makes it so powerful. If you start bogging it down with rules and stifle creativity, that makes things more difficult. In the end, if you can set your own rules, than you can also make mistakes setting those rules and leave yourself open to even more problems I would think. The simpler the better I think.

bamccaig

Object-Oriented Programming / Message Passing / Virtual Functions

There is a performance hit for this, but it's negligible and it allows very powerful things to occur. The same code can do different things depending on the type of data that it's operating on. That's huge. You can customize the way the code behaves by defining another type of data to operate on. And all other code that operates on a related class of data continues to work on that data, but might work differently depending on the type of data.

There's enormous value in OOP. The problem isn't OOP. The problem is that idiots with keyboards were set loose trying to figure out what it means to do OOP without really understanding the value in it. The Java world is an excellent example. Where you need 20 classes to achieve 1+1.

Even popular languages like C++ and Java and C# don't fully grasp the value in OOP/message passing. When I first learned an OOP style in a Lisp my eyes were opened to what it could be. In any case, not everything needs to be an object certainly, but at the same time the cost of an object is negligible and there's more flexibility in an object than a static function or POD structure.

Depending on the language sometimes OOP is the best you can do, but if the language supports functions as first-class citizens then it's overkill to make everything an object. A language like C lacks of these features and so it really limits what you can do without getting fancy with macros (yuck) or writing 100x more code (yuck).

For games programming it's probably not as useful since you hack the shit out of it for a few years and then it doesn't change much. Most software in the world doesn't work that way. Instead, you gradually build upon it for decades, and if you aren't doing something to reuse code you'll sooner end up with an unmaintainable mess.

Global Scope

The problem with global scope is absolutely global scope. The code can access it anywhere by definition. This makes it incredibly difficult to understand parts of the code that interact with this global state. If that global state isn't in an expected state then everything can go sour. This complicates testing and parallelism. It also limits the flexibility of code ALA polymorphism. If you're accessing data and code in global space then you cannot easily change the way a module of code works by injecting a differently behaving object or function (without unreliable, ugly hacks that might not even work in parallel). It really is best that you swear off this practice until you find one of those 0.001% of cases where it's actually better.

Kitty Cat
bamccaig said:

Object-Oriented Programming / Message Passing / Virtual Functions
There is a performance hit for this, but it's negligible and it allows very powerful things to occur.

That really depends on how you use it. If you're calling virtual functions hundreds or thousands of times per frame (e.g. a virtual update() method that all object types override, and you have thousands of active objects that have that called in a loop), it can be non-negligible. And the worst part is no profiler will tell you it's a problem because of what's wrong: it's throwing away a bunch of memory you've been given "for free" and causing excessive intermittent cache misses. There is no instruction or function that will be slow that it can point to, you're just causing spurious slowdowns in random places as it keeps stalling caches.

In regards to object-oriented programming, you just need to be careful on how you think about objects. The most important thing is to make sure you structure your objects efficiently (thinking in-game entity = in-code object, for instance, is a big folly). If you have float Health; and Vector3 Pos;, but you never access Health at the same time as Pos, they probably shouldn't be in the same structure, or otherwise near each other in memory. You should go through data sequentially (and remember too: code is data), so avoid having objects defined, allocated, or processed in a way that necessitate skipping around in memory.

On certain systems, inefficient memory use can be a real killer. Especially on modern systems where CPUs are orders of magnitude faster than memory. It's very easy to make the performance impact non-negligible because of these things and never know it.

princeofspace

I'm gonna invoke Steve McConnell from Code Complete here:

Quote:

Used with discipline, global variables are useful in several situations

He goes on to describe how most programmers get around use of global scope: by making a giant class that holds all the data and is passed around EVERYWHERE. This is global scope in disguise; as McConnell says, it holds to the letter of the law, but misses the benefits of encapsulation.

Actually, if you really need global scope, you should use global scope. Being global isn't of itself evil, but it can easily be overused. Using global scope where necessary avoids the overhead of passing very large structs.

True, there are repercussions where concurrency is concerned, but areas where concurrency is truly useful (IE -- Allegro's audio system) are unlikely to benefit from global scope anyway.

bamccaig

It matters more for software that is large and maintained for decades. A game that is written once and never touched again can have all of the worst practices, shortcuts, etc., you want. It just needs to work well enough to play the game. The behind the scenes bugs that the player doesn't notice or can workaround don't really matter much.

That said, I imagine that some of those AAA titles that are riddled with bugs are in that state because the code was written sloppy and it quickly became unmanageable. I can't remember the title anymore, but I think it was PS3 game. I probably downloaded it from the PlayStation Store. It was completely unplayable. I'd constantly just get stuck on nothing. Skyrim and other Bethesda games are also notorious for their countless bugs, but perhaps that's a necessary evil for such a large game to come to fruition and still be profitable.

A one-man or 5-man indie game probably won't suffer from that scale and can probably be hacked up with little regard for such practices. That doesn't make global state good, or OOP slow. It just means global state works as long as you wire it up without bugs, and OOP can be too slow under specific circumstances. Most programs in the world are nothing like games. Games have their own set of rules. There are times to use best practices and times to bend the rules to get the performance that you need from the machines (but the machines of tomorrow will probably be bigger and faster, so all of that hard work might be obsolete in a few years).

He goes on to describe how most programmers get around use of global scope: by making a giant class that holds all the data and is passed around EVERYWHERE. This is global scope in disguise; as McConnell says, it holds to the letter of the law, but misses the benefits of encapsulation.

Actually, if you really need global scope, you should use global scope. Being global isn't of itself evil, but it can easily be overused. Using global scope where necessary avoids the overhead of passing very large structs.

To avoid passing large structs you can pass a pointer (or a reference in C++) to one. If your program's state is in a large structure hierarchy on the heap with a root pointer in main then you can still pass pieces (e.g., state->resourceManager) of it into functions to manipulate that part of the program state without having access to other parts.

If all of the program state is available in global space then it becomes easy for parts of the code that have no business seeing or touching state doing so. Global state means you don't have to think anymore about how to organize the program because you can always just touch whatever you want from anywhere. This can lead to sloppy designs, and force even worse practices down the road to avoid spending countless days refactoring the resulting spaghetti code.

Global state isn't just bad for concurrency. It's lazy program design, and that can lead to sloppy code, and other problems.

princeofspace

There's a key difference between giving all parts of the program absolute power over global state and creating a few bits of data in global scope. I'd argue that passing a reference to program state in each and every function is equally lazy, and secretly poor design.

In truth, most software has some form of global state. Allegro programs, for example, must call al_init at or near startup. You've just set the global state of a program.

Is this bad design? No. Good design manages global scope parts of the code with other data. Blaming the bad design of games on global scope only begets more bad design, because we don't get to the root of the problem.

An example I might offer would be John Carmack's early designs shown in the open source releases of old id software games. Few would accuse those games of being poorly constructed, but they use limited global scope expertly to accomplish various goals.

bamccaig

It is scary to look in some old code that hasn’t been touched in years, and see a completely non-thread-safe static global variable used

I don't think he's much of a proponent for abusing globals. I've said again and again there are some rare uses for them, but if you're arguing with me about that then you must be arguing that globals are usually a good idea.

Edgar Reynaldo

Why I don't use C if I can't help it ;

Manual polymorphism sucks.

It doesn't have operator and function overloading.

Templates are handy in a pinch.

Namespaces.

I've been programming in Python lately, and it's a refreshing change from all the tedious manual crap you have to do in C and C++.

princeofspace
bamccaig said:

It really is best that you swear off this practice until you find one of those 0.001% of cases where it's actually better.

I'm not sure if global scope is usually a good idea, but I'm convinced now it's useful in more than 0.001% of all cases. I use hidden globals in about half my projects, because I've done it other ways and it's the most elegant and intuitive tool for that particular job, in that code. It's not the least bit more difficult to debug, if you use a debugger with watchpoints.

The old id software games are an example, and I'd also offer up Plan 9 from Bell Labs. Excellent code, and brilliantly designed, by some of the best engineers who ever wrote software. I learned a lot about design reading the Plan 9 code.

Later on in that tweet series you quoted, Carmack (apparently) says:

Quote:

@Jonathan_Blow yes, I would like to be able to require a #pragma on a per file basis to allow any static variables.

I think this is a reasonable practice and would like to see it used in modern C compilers. I would also like to see all global variables static by default -- there is usually no reason for the compiler to export the symbol.

bamccaig

Even a static (i.e., internal) "global" is better than an external global. At least you know that can only be accessed directly by that code file. It's a bit like a class variable at that point. I wouldn't even really call that a global variable. It's not global. It cannot be accessed by your entire program. It's a static variable, but that's a distinct concept.

princeofspace
bamccaig said:

It's a bit like a class variable at that point. I wouldn't even really call that a global variable. It's not global. It cannot be accessed by your entire program. It's a static variable, but that's a distinct concept.

That being the case, let me say I completely agree with your reasoning about true "extern" style globals -- genuine usage cases would be quite rare.

In one recent case I had an extern global array that could be replaced with a switch-case statement. Use of the "default" keyword made my code safer, sort of like using try-catch in python.

Neil Roy
bamccaig said:

This makes it incredibly difficult to understand parts of the code that interact with this global state. If that global state isn't in an expected state then everything can go sour.

Never had a problem with using globals. I won't use them if I don't have to, but I won't jump through hoops to avoid them either. I find I have more problems when I try avoiding them than just using them as intended. Mainly for variables that will exist through the life of the program and ones I wish to free up the memory when the program exits.

I always hear about the global boogyman and many other issues that never come up. Pointer horror stories, which also has never come up for me. I have more problems with a misplaced, or missing semicolon than I do with anything else.

Peter Hull
int* dest = malloc(item_count * sizeof(int));
int* src = prepare_source();
memcpy(dest, src, item_count * sizeof(int));

princeofspace
Neil Roy said:

Never had a problem with using globals. I won't use them if I don't have to, but I won't jump through hoops to avoid them either. I find I have more problems when I try avoiding them than just using them as intended. Mainly for variables that will exist through the life of the program and ones I wish to free up the memory when the program exits.

I always hear about the global boogyman and many other issues that never come up. Pointer horror stories, which also has never come up for me. I have more problems with a misplaced, or missing semicolon than I do with anything else.

Using globals, avoiding OOP, sticking strictly to C -- I love anti-establishment coding.

Specter Phoenix

I remember when I used to have the global variable const bool ANNOY_BAMS = true; just as a personal joke due to his stern view on global variables. He is fine with constant global variables though since they can't be changed after declaration. Just having it made me smile when I saw it, well back when I bothered coding.

AceBlkwell

I agree with Neil, You've got to go with what works for you. Familiarity is always a factor.

I remember writing in QBasic Interpreter years ago. I installed Quick C 2-3 times before making myself use it. It didn't make much sense at the time to go though all the setup with includes and main() and {} etc when I could just say PRINT and BAM there it was. I did learn over time though that with complexity can come options.

I guess the trick is to determine what you are looking to get accomplished and how much effort you are willing to put in to getting the desired results. How much do you want to know about the details, or how much you'd rather use a black box approach with language commands/ macros.

I also agree with Neil in that global scope taboos are often overstated. I have wasted a lot of time trying to "be a good programmer" just to realize there is nothing wrong with a global variable on occasion. ;D. It's much like I don't agree with never signing on as Root in Linux either. I ONLY sign on as Root. See I live on the edge like that..

;D
Ace

Neil Roy

I used to program in QBasic and QuickBasic 4.5 (compiled version). What helped me move to C was a great book I found called QBasic to C. It got bad reviews as they stated that it didn't teach proper C, which as usual, was nonsense. It done what it said, it showed you QBasic and then showed you how to do the same in C. Hardly useful these days as nobody starts out with BASIC anymore, but great at the time. From there I used DJGPP under DOS and started to program my own library for graphics by reading various resources I found that helped me learn how to access video RAM etc... when I found Allegro I abandoned writing it myself. I wish I had never found Allegro now as I would have continued programming my own library and probably gotten a lot further.

I still have some of my original library code and QuickBasic code. Good times, I miss those days honestly (in the'90s).

Some of my DOS/DJGPP code from my VGA library...

#SelectExpand
1void set_video_mode(unsigned char mode) 2{ 3 __dpmi_regs r; 4 r.h.ah = 0; 5 r.h.al = mode; 6 __dpmi_int (0x10, &r); 7} 8 9void plot_pixel(int x, int y, unsigned char color) 10{ 11 _farpokeb(_dos_ds, VIDEO_BUFFER+((y<<8) + (y<<6)) + x, color); 12} 13 14unsigned char get_pixel(int x, int y) 15{ 16 return _farpeekb(_dos_ds, VIDEO_BUFFER+((y<<8) + (y<<6)) + x); 17}

princeofspace

The first C I ever wrote was based on stuff Shawn had for the old Allegro 1 examples. Those WERE good days...

Neil Roy

I still have the DOS source for my first Allegro game I ever completed. I was just playing it today (an Artillery Duel game I called "Deluxe Artillery Duel") which I completed in 1999. I am thinking of resurrecting it and remaking it just for fun. It's funny to look at my code from back then. I still had many bad habits. While I don't mind globals, I had WAY too many of them back then!!! LMAO, my coding style wasn't set either, fascinating to look back at. Glad I'm a packrat and keep all these things. I still have Deluxe Paint 2e I used to make my graphics with.

Specter Phoenix

Deluxe Artillery Duel
Deluxe Pac-Man 2

Starting to see a pattern here.

I went from BASIC in middle school (7th & 8th) to C++ in high school. I dabbled in other languages, but can't say I've made anything of substance with other languages.

The oddest thing I've ever seen has to be when a guy declared all his functions at the start of the program and then defined them a few lines down.

Neil Roy
Quote:

Deluxe Pac-Man 2

Nope. It's Deluxe Pacman. "Pac-Man" is a trademark of Namco or whomever (I researched it) so I avoid "Pac-Man" like the plague. You may be surprised to learn that "Pacman" however is not trademarked to any game company (there is a "Pacman" hat maker in Korea though, but that isn't related to games, so no problem). :)

The oddest thing I've ever seen has to be when a guy declared all his functions at the start of the program and then defined them a few lines down.

WHen I first used C, the common teaching was to declare all your functions at the start, your prototypes. The idea was that main was always the first function you seen, and any others could easily access your functions once they have been prototyped first.

These days I just declare the functions in a logical order so there are no problems, but that is the way C was originally meant to be done. My Deluxe Artillery Duel game has them all prototyped like that. And there is a certain bit of tidiness to it all I guess.

I still have much of my old BASIC code too, I had it all backed up and was browsing it today. Found some code I wrote for an artillery duel style game in QuickBASIC 4.5. The reason why I moved to C was that BASIC just wasn't fast enough for what I wanted at the time, not even compiled BASIC. It is now. I ran the original BASIC version and it was really fast. Also found some of my first experiments in C with artillery duel and it made me laugh as I had a tank shooting randomly all over and some of the shots were aimed straight up so they hit the player's tank again. I should make a video of them all, showing the progression just for fun. The original C code is scary to look at, the globals I used at the time will give you a heart attack! ;D

Oh, if you want to relive the BASIC nostalgia, there is a modern version of QuickBASIC for windows. Runs old code and new, compiles etc. I should load up some of my old code on it and see how it runs now.

This site has lots of game and such for it... http://www.qb45.org/
You can download the latest version here... http://www.qb64.net/

Specter Phoenix
Neil Roy said:

It's Deluxe Pacman.

My mistake.

Quote:

WHen I first used C, the common teaching was to declare all your functions at the start, your prototypes. The idea was that main was always the first function you seen, and any others could easily access your functions once they have been prototyped first.

I understand having a file of code like this, in fact this is how I prefer to do it when learning a new library so everything is in one file so I can see the code just by scrolling instead of jumping through files:

#SelectExpand
1// includes 2 3void funct(); 4int funct2(); 5 6int main() 7{ 8 return 0; 9} 10 11void funct() 12{ 13 // code 14} 15 16int funct2() 17{ 18 // code 19 return num; 20}

The odd code I'm referring to is when I see C programmers do this:

#SelectExpand
1// includes 2void funct(); 3int funct2(); 4// global variables and constants 5void funct() 6{ 7} 8int funct2() 9{ 10 return num; 11} 12 13int main() 14{ 15 return 0; 16}

Neil Roy

Ah, I see what you mean now. Looks like the code from a new C programmer who thinks they need to declare prototypes no matter what.

Also, notice the comment at the top that says //includes.

Specter Phoenix

Well that was my code to stress my example. A series of active examples of my point is Lazyfoo.net's SDL2 tutorials. I have the code from lessons 12 and 17 and both have that similar format.

bamccaig

Always declaring takes the thinking out of it though. Especially as code changes and restructures. If you pre-declare all functions you never run into that "compile -> error -> predeclare -> compile" cycle. Modern languages take this guessing out of the equation for you. It can be a nuisance to run into it in C or C++.

Neil Roy

I never had that problem. If I know a function will call one I am writing, I make certain it comes before it. I don't know, I just always done it that way and it's never been a problem. But I do understand the concept and would recommend new programmers do it properly.

bamccaig

It makes me wonder why they can't just release a new C and C++ standard that makes forward declarations only necessary for backwards compatibility.

Chris Katko

Ever since I switched to D, I've basked in (and taken for granted) that there is no such thing as a forward declaration. Put any function, any class, anywhere you want. The order doesn't matter because compilers shouldn't be the dumbest thing in the toolchain.

Well, maybe forward declarations work for something else.

And the compile times. Good lord they're amazing once you hit a small-to-medium size project. No preprocessor pass. No includes. (Read: Modules. This thing they invented in what... the 80's?)

princeofspace

There's this school of thought (that I don't completely agree with) that putting all the functions into little table before they're defined it's like a table of contents in a book.

To be honest, I really like headers, though. It creates a well defined interface to a module. One of the things I don't like about, say, python, is that you can access everything once the module is imported.

Audric

There are a lots of things that I like in C in matters of mental discipline, but forcing the coder to sort his functions in the order "callee then caller" is really only for the compiler's benefit. An early processing of the C file would be enough for the compiler to determine which functions and variables the coder has ACTUALLY defined, and more accurately than guessing "Oh I don't know this function, it will probably return an integer".

Chris Katko

FYI D supports interfaces. (Especially because they intentionally DON'T support multiple inheritance.) So if you want more stuff, you use composition.

Every year I look around the D forums and I see some really smart people working toward cool stuff and making real progress.

princeofspace

I suppose it's time for me to check out D. I did back in like 2012, and the general consensus was that while the language was solid, the toolchain was a bit weak.

But that was 5 internet centuries ago.

Neil Roy

While D sounds intriguing, sorry, I finally got fairly good at C. Not starting from scratch again. I'll be dead of old age by the time I get decent at D and then they'll come out with E. ;)

Eric Johnson
Neil Roy said:

'll be dead of old age by the time I get decent at D and then they'll come out with E. ;)

You'd better get your will squared away, because E already exists. ;)

Chris Katko
Neil Roy

Okay then, E++. LMAO

princeofspace

Neil had a pretty good point: it makes a lot of sense to work in a language you're most comfortable in. When I get an idea for one of my games, I can implement it in c without learning some new language feature.

Neil Roy

Funny thing is, I realized recently that I am addicted to learning. It's nice, but I tend to spend a lot of time learning and not as much as I should actually implementing what I have learned. ;)

Eric Johnson

I started off with C when I was 13 or 14. I quickly jumped ship to C++ though, because the book I was reading at the time told me C++ was newer and offered more features than C. I have stuck with C++ ever since.

A lot of people bash C++ for being bloated; for sporting features which result in hard-to-read code; and for tacking on classes and polymorphism, which can often lead to further difficulties in readability and scope of code.

Personally, I think that you can accomplish the same things in either language. C++ is only bad if you abuse it and write bad code. In other words, I do not think that C++ is inherently bad, but it does lend itself to sloppier code. So I would agree that C forces you to be a better programmer, but it is ultimately up to the programmer whether or not their code is difficult to follow.

I have not done much in C or C++ recently. Instead I have focused mostly on JavaScript. JavaScript is incredibly flexible and convenient. I absolutely love it. It is much more forgiving than C or C++, which certainly can lead to sloppy code, but again, a good programmer will structure their code to be well organized in any language. As far as making games goes, I think I will stick with JavaScript, because with it I can share games without having to make binaries, which is super convenient. JavaScript is not as quick as C or C++, and it has its limitations, but for my purposes, it gets the job done better than the alternatives.

bamccaig

C doesn't force you to be a better programmer... C just forces you to be more verbose with your code (which is arguably worse). Of course, C++ has a lot of tricky features that are difficult to do right. Code that compiles will not work or won't even run for reasons that are completely oblivious to beginners and even some intermediates. C++ is a very difficult language to master. That makes it a poor language. C has an edge there because there's less to remember, however, C offers so few features that you tend to have to be very careful to write proper code. C++ can automate some of that. There's no real winner between them.

C's major advantage over C++ is probably unmangled symbols. This makes it much easier to invoke C code from almost any other language in the world. Whereas interoperability between C++ and other languages is more difficult (if not impossible) to achieve.

C++'s major advantage over C is probably RAII.

Dynamic, high-level languages are typically way ahead of both in terms of functionality and tersity. However, they typically are slower to some degree (which often doesn't matter unless you're writing a game that pushes the limits of today's hardware).

Neil Roy
bamccaig said:

There's no real winner between them.

Oh yeah, C is absolutely the winner. It is faster as it is closer to machine code as you can get without writing assembly. It is less complex which makes it easier for the compiler to optimize. C++ is a higher level language and is harder for the compiler to optimize, especially with volatile functions and the like, it is impossible for a compiler to optimize those. The extra layers in C++ only add to the problems, they don't help any. And in recent years I am seeing a movement away from the very basics of C++ classes like the constructor and destructor, and instead I am seeing init() functions etc... in classes which is stupidity, may as well use C if you're going to do that. There is much about C++ classes that was touted as a great thing like inheritance, polymorphism etc... that are starting to be seen as bad things. Instead we are seeing more things like vectors used (which is one thing I like about C++, but that I have replicated in C easily, what I needed from them anyhow) and other stuff that leads to long, convoluted horrible code that is difficult to follow.

I am seeing videos out now where newer developers are moving away from OOP and only using C++ for the vectors and such, but otherwise their code looks like C. And they could move to just C if Microsoft supported newer C versions better. Thankfully we have GNU compilers (MinGW) which does support the 2011 standard of C.

I simply do not see high level languages being better. The only exceptions are ones like JavaScript perhaps that lend themselves well to online development.

I have completed several games in C, they all work, they run smoothly, not a problem with them. I tried to do it in C++ and the headaches using it were enormous and I didn't like the mess my code had become. I can make game objects similar to C++ classes in C. I can put my code in separate files, with their own headers and use structs for everything I need. I will never be convinced that C++ is the way to go. I think it probably turns more people off of programming than any other language. C is really easy to learn in comparison. I moved from BASIC to C and they were very similar in a lot of respects. C++? Forget it.

Chris Katko
Neil Roy said:

I simply do not see high level languages being better.

Try D. :P

You can write native assembler in D the same way you can in C, C++. But performance is useless for most hobbyists now. Even if there was a 10x slowdown (or 100x in some cases!) those are still COEFFICENTS as opposed to order (quadratic, cubic, exponential) algorithm complexity. So by time you FINISH your program, the CPU's will still exist 2x-4x faster than you started with.

What's really more important these days... is simply coding efficiency. Getting a program that does what you want it to do ("almost" regardless of speed) is the most important problem.

I mean, how many games would we all have on our belts already if we could simply "imagine" a game in our mind and have the code written for us?

Now clearly, speed matters. Except it only matters... when it matters. In games where we're actually limited by huge numbers of objects, complex physics, AI, and graphics, and so on. Networking is still very much a limiting factor.

Modern high-level languages make it easier to CORRECTLY and reliably employ multithreading which is where ALL computing systems are heading (and have been for over a decade). I remember one professional (Tim Sweeney?) talking about functional languages and how most multi-threading these days is pathetic. Coders will say "the code works" but they can't prove it, and they can't prove it beyond a shadow of a doubt. That proof comes in DAMN important when you're doing things like security proofing, distributed computing, and more. But even in games, crashing to desktop is a complete failure of the programming ecosystem. Programs shouldn't crash. There should never be a case in your program where something happens that you didn't expect and plan for. We're supposed to be engineers, not technicians welding random pieces of scrap together. We're supposed to KNOW and be able to prove that when things run, they will always run.

Things like pure functions and immutability drastically increase the ability for a compiler to prove your code works as intended, and ALWAYS works as intended. The definition of a pure function is one that can't mutate data... and has NO SIDE EFFECTS. (No mutations of global state.) And simply restricting yourself to that "as much as possible" (obviously not in tight loops in say, a particle system) means >70/80/90% of the codebase can be proven to not break random stuff, or break in random ways.

Eric Johnson

Somewhat related to C versus C++... Having spent so much time in C++, there are a few things that I'm not sure how to accomplish in C. For example, how would you go about spawning multiple enemies of the same type in C if you don't have classes?

If I am writing a game in C++, for example, and I wanted to spawn a multitude of bats to attack the player, I would do so by creating a bat class, and then by instantiating said class as many times as I need for each bat. How would I accomplish this in C though? With an array of structs for each bat?

bamccaig

There's no such thing as a software "engineer". There is never "proof" that the program is correct. As a matter of fact, if it's non-trivial you can almost guarantee that it's incorrect (i.e., not perfect), but hopefully it's close enough to do its job. Hell, even code that hasn't changed much in 20 years still has bugs in it.

Append:

If I am writing a game in C++, for example, and I wanted to spawn a multitude of bats to attack the player, I would do so by creating a bat class, and then by instantiating said class as many times as I need for each bat. How would I accomplish this in C though? With an array of structs for each bat?

A class is basically just syntactic sugar over a struct and objects. The same could be accomplished using a struct and various functions that operate on the struct.

struct t_bat_object {
    // Whatever fields.
};

typedef struct t_bat_object t_bat_object;

void bat_method_x(t_bat_object * self, type1 arg1, type2 arg2) {
    // Manipulate "object" pointed to by self.
}

To achieve something fancy like "polymorphism" where "bat" is also an "animal" you'd have to use composition to define the data for the t_bat_object (which is partially composed of the data for a t_animal_object) and then define basically a "vtable" (i.e., virtual function table) for your methods so that they could be "overridden" by derived types. The implementation of which is ugly and messy since structs must be typed to dereference fields, and function pointers must be typed to invoke them, and their parameters need to be abstracted to allow different shapes of functions to be defined... I spent about 40 minutes trying to demonstrate a solution before I realized it was far too simple to achieve the goal and gave up. :P Google is your friend. Once you read that you'll probably swear off C, at least where true polymorphism is necessary. :D

Chris Katko
princeofspace

Typically, I write game entities as systems, not objects. That means that instead of

auto& m = new_game_monster(x, y, etc...);
game_state->add(m);

I can just call

const unsigned int i = make_monster(x, y);

Now, the monster system does the plumbing instead of a generic game "state" class.

My preference is to use indexers instead of pointers. Instead of making a generic system for updating all objects, I get the control of updating individual systems, and doing it all at once. I like doing this because there's times when I want a specific monster or sprite to execute code in a certain order -- which can be hard to do with a big vector full of generic "sprite" objects.

move_monsters();
ai_monsters();
draw_monsters();

or, more typically,

#SelectExpand
1 2/* control system */ 3ai_monsters(); 4ai_powerups(); 5control_interface_players(keyboard_struct); 6 7/* movement system */ 8move_monsters(); 9move_powerups(); 10move_players(); 11 12/* draw system */ 13draw_monsters(render_context_struct, camera_struct); 14draw_powerups(render_context_struct, camera_struct); 15draw_players(render_context_struct, camera_struct);

Obviously, this is just a quick mockup, but you get the idea.

Audric

Just to bounce on bamccaig's answer :
The following pattern of C coding is used everywhere. Even Allegro uses it.

#SelectExpand
1typedef struct { 2 int x; 3 int y; 4 char *name; 5} BAT; 6 7BAT* create_bat(int x, int y, const char *name) 8{ 9 BAT* bat = (BAT *)malloc(sizeof(BAT)); 10 bat->x = x; 11 bat->y = y; 12 bat->name = strdup(name); 13 14 return bat; 15} 16 17void destroy_bat(BAT *bat) 18{ 19 free(bat->name); 20 free(bat); 21} 22 23// Usage : to allocate 24BAT* new_bat = create_bat(3, 4, "Bob"); 25 26// To delete : 27destroy_bat(new_bat);

Peter Hull

...and to follow on from Audric, Allegro is also an example of how to do polymorphism in C, e.g. a uniform 'bitmap' interface even if the implementation is different.
https://github.com/liballeg/allegro5/blob/master/src/bitmap.c#L127
(it's the call to a function stored in vt)

There is something good about C, I find it encourages just getting down to it. With C++ I find myself agonising about how to organising classes, what should be private, where to put const, references vs. pointers vs. pass-by-value and so on.

Lately I've had some success with using 'C++ in a C way'; I stick to functions and plain-ish structs, BUT I make use of the standard container and string libraries. This takes care of a load of commonly used functionality and I haven't noticed any drop in performance, not for the stuff I'm doing, anyway.

bamccaig

Polymorphism is actually relatively easy to achieve in C. You just need a structure with function pointers that can point to different functions for different objects.

The more difficult thing is probably inheritance. You'd have a different vtable for each type, with a different shape. So if your t_animal_object * is actually a t_bat_object *, how do you find the animal vtable in your bat object not knowing it's a bat object? You'd have to have a generic way. I'm thinking that a dynamic structure like a dictionary or something might be the key, but I haven't given it much thought.

#SelectExpand
1typedef struct t_vtable_dictonary { 2 // Magic. 3} t_vtable_dictonary; 4 5void * vtable_dictionary_get(t_vtable_dictionary * dict, char * key) { 6 // Magic. 7} 8 9void vtable_dictionary_set(t_vtable_dictionary * dict, char * key, void * vtable) { 10 // Magic. 11} 12 13const char * const ANIMAL_OBJECT_VTABLE_KEY = "animal"; 14 15typedef struct t_animal_vtable { 16 void (*eat)(t_animal_object *, char *); 17}; 18 19typdef struct t_animal_object { 20 // Always the first element.
21 t_vtable_dictionary * vtable_dict;
22} t_animal_object; 23 24typedef struct t_bat_object {
25 t_vtable_dictionary * vtable_dict;
26 t_animal_object animal; 27} t_bat_object; 28 29void bat_animal_eat(t_animal_object * self, char * food) { 30 // Eat it! 31} 32 33void foo(t_animal_object * animal) {
34 t_vtable_dictionary * dict = (t_vtable_dictionary *)animal;
35 t_animal_vtable * vtable = (t_animal_vtable *) 36 vtable_dictionary_get(dict, ANIMAL_OBJECT_VTABLE_KEY); 37 38 assert(vtable); 39 40 vtable->eat(animal, "blood"); 41} 42 43int main(int argc, char * argv[]){ 44 // Normally you'd put most of this stuff in a "constructor" or "initialization" 45 // function, but for brevity it's here... 46 t_bat_object * bat = malloc(sizeof(t_bat_object)); 47 t_animal_vtable * vtable = malloc(sizeof(t_vanimal_vtable)); 48 t_vtable_dictionary * dict = malloc(sizeof(t_vtable_dictionary)); 49 50 vtable->eat = bat_animal_eat; 51 bat->vtable_dict = dict; 52 53 vtable_dictionary_set(dict, ANIMAL_OBJECT_VTABLE_KEY, vtable); 54 55 foo((t_animal_object *)bat); 56 57 return 0; 58}

Of course, you can imagine how easily this will fail since you're forcing the cast and if the programmer passes in a wrong object it'll crash hard. There's basically no type safety. It would fail in most languages, but in C++ it could determine the type was wrong at run-time (if not compile-time!), and in most dynamic languages it would also detect the type mismatch at runtime and tell you. Your C program is just going to get killed by the kernel at best, and overwrite the wrong memory at worst, and let you poke around in a debugger or with printf() until you figure out your mistake.

Audric

I'm a bit wary of trying to get close to C++ polymorphism in C (and certainly don't advise it to new C coders :) ). It may be a clever use of the language, but if you mess up, I fear it's very difficult to locate where. I'd rather have to maintain code like the following - You can say it's verbose, maybe repetitive, but I think I can more easily spot errors by reading the code.

#SelectExpand
1typedef struct { 2int sub_type_id; // 0 = cat, 1 = dog; // FIXME: implement enum 3void * sub_type; 4} ANIMAL; 5 6void speak(ANIMAL *animal) 7{ 8 switch (animal->sub_type_id) 9 { 10 case 0: // cat 11 cat * cat = (CAT)animal->sub_type; 12 cat_mew(cat); 13 break; 14 15 case 1: // dog 16 dog * dog = (DOG)animal->sub_type; 17 dog_bark(dog); 18 break; 19 ... 20 } 21}

Lately I've had some success with using 'C++ in a C way'; I stick to functions and plain-ish structs, BUT I make use of the standard container and string libraries. This takes care of a load of commonly used functionality and I haven't noticed any drop in performance, not for the stuff I'm doing, anyway.

I want to say, it makes your code easy to understand and modify by the biggest possible number of people.

Oscar Giner
bamccaig said:

There's no such thing as a software "engineer". There is never "proof" that the program is correct. As a matter of fact, if it's non-trivial you can almost guarantee that it's incorrect (i.e., not perfect), but hopefully it's close enough to do its job. Hell, even code that hasn't changed much in 20 years still has bugs in it.

That you haven't studied software engineering doesn't mean it doesn't exist ;). And yes, there're mathematical methods to prove a piece of code is correct. It's costly, so it's not very viable to do for a full big project, but you can at least proof some key parts.

princeofspace

I'm wary of polymorphism as a whole, because lots of programmers use it in a way that hinders readability.

For example: most game objects will have an Update method called once (or more) per frame. This is a sort of nebulous way to handle game object maitenance, because all these Update calls do different things; you never know EXACTLY what Update does.

If an Update method calls func1, func2, func3, it improves readability to simply call func1, func2, func3, and skip Update. I can't remember where I read this -- maybe somebody else does -- but if ALL objects call Update, then NO objects need to call Update.

Neil Roy

Lately I've had some success with using 'C++ in a C way'

When I have used C++, this is how I end up using it. I have seen a new movement of people doing exactly this, one series I have been following of an indy developer who does just this. He's not doing OOP anymore, but using procedural programming instead. Episode 4 he talks about that more...

video

I honestly have considered this myself, but I really like C, and I love doing it myself. I recently wrote functions that do something similar to vector stacks. It felt better to do it myself in C and I understand what is going on because... I wrote it. And it didn't take long to implement.

If I was writing code to spawn many monsters, I would use my personal vector functions to push new monsters onto the stack that I spawn, then just... handle them in order, pulling them off as they die. It's a no brainer. Use a linked list which are simple to do. And you could have a monster creation function which handles all this for you. You tell it to spawn a new monster, it creates one, allocates the memory for it, adds it to your linked list or stack and when their life runs out, they are removed from the stack and memory is cleared for it. It's no more complicated than C++ classes to be honest.

Polymorphism = bad. It is really difficult, if not impossible in many cases for the compiler to optimize, so it leads to slower code. You can do similar things with structs and unions to be honest.

bamccaig

I'm wary of polymorphism as a whole, because lots of programmers use it in a way that hinders readability.

For example: most game objects will have an Update method called once (or more) per frame. This is a sort of nebulous way to handle game object maitenance, because all these Update calls do different things; you never know EXACTLY what Update does.

If an Update method calls func1, func2, func3, it improves readability to simply call func1, func2, func3, and skip Update. I can't remember where I read this -- maybe somebody else does -- but if ALL objects call Update, then NO objects need to call Update.

This isn't polymorphism's fault. This is OOP's fault, or rather a misunderstanding in what it means. People that jump on the OO bandwagon immediately want their objects to have behaviors to satisfy their fantasy that the objects simulate something real world. They fail to think deeply enough and arrive at the conclusion that "Mario" "walks". In reality, Mario's brain would send electrical impulses to his leg muscles causing them to contract, causing his legs to move, and the friction with his shoes and the ground would cause his body to come into motion. He only controls the electrical impulse. The rest of the action is carried about by biology and physics, things he does not control.

Instead of saying: mario.update(); princess.update(); bowser.update();, you should just be saying engine.update();. The engine is a process that understands how all things in the world interact. It understands that when Mario's brain sends that signal to his legs the resulting action is movement. It also understands that when there's a wall in front of Mario the signal doesn't work because Mario collides with the wall. You need an object with a view of the world and an understanding about how objects interact to manipulate the world. A proper game world doesn't let Mario decide if he collides or not. If he's trying to save the princess it would be a lot easier if he could just skip past all this running and jumping and set Bowser's dead bit instead and then warp the princess and himself to a romantic bedroom or something. He can dream. Mario doesn't control the world. He is bound by the rules of the world, and these rules should be imposed on him from a higher-level object.

This kind of thinking is the result of not taking "OO" far enough. Ultimately, most game objects are pretty stupid, and the code should reflect that. They're really mostly just state. What brings that state to life is the engine. I've found that's generally a better way to structure code. Don't mix "service" i.e., behavior objects with "state" i.e., data objects. Your state objects should be pretty stupid. Your service objects should have little to no real state. State objects and other service objects are passed into a service object and the service object transforms the state objects using the service objects and its own behaviors. The result is that the state changes.

When you structure code like this the program structure makes a lot more sense, and it's a lot more flexible, and a lot more powerful.

Chris Katko

If an Update method calls func1, func2, func3, it improves readability to simply call func1, func2, func3, and skip Update.

I don't agree with that at all. You're still calling the same functions. What's the problem with grouping them in easily memorizable hierarchies like update() and draw()?

You want to know what functions monster_type calls verses a player_type? Go to the class.

I mean. func1, func2, and func3 are all going to call things too. How many layers are you going to strip off before you end up with:

#SelectExpand
1int main() 2{ 3obj1.x(); 4obj1.y(); 5obj1.z(); 6obj1.w(); 7obj1.w2(); 8obj1.w3(); 9 10obj2.x(); 11obj2.y(); 12obj2.z(); 13obj2.w(); 14obj2.w2(); 15obj2.w3(); 16}

A program can have call stacks that are too wide but it can also have call stacks that are too tall. There's a balancing act. But the same decisions need to be solved regardless of the hierarchy. Game logic needs to be done, and it doesn't matter where it gets called--except for the human trying to understand it.

[edit]

Neil Roy said:

If I was writing code to spawn many monsters, I would use my personal vector functions to push new monsters onto the stack that I spawn, then just... handle them in order, pulling them off as they die.

So... you're using RAII but without the simplicity of C++ RAII?

[edit]

bamccaig said:

This is OOP's fault, or rather a misunderstanding in what it means. People that jump on the OO bandwagon immediately want their objects to have behaviors to satisfy their fantasy that the objects simulate something real world.

That's exactly what Mike Acton talks about. Programming schools use EXAMPLES for classes because it's easier to learn. But then people spend a lifetime trying to UNDO the damage of school on their way of thinking. It's the same thing with math and physics. 99% of math we're taught in school is "wrong" but it's easier to teach the approximations. Then college (and definitely grad school) is all about unraveling those approximations and understanding when and where they fall apart.

princeofspace

Adele Goldberg famously said of smalltalk that "everything happens somewhere else." This has widely been taken as a criticism of OOP in general; sooner or later, you have to quit stringing through virtuals and WRITE THE CODE that's going to make your program work.

Polymorphism itself may not be the culprit, or even OOP, but it's certainly the way most programmers seem to approach software design.

Neil Roy

Thankfully, I never jumped on the OOP bandwagon. :)

Also, if each object is calling the same function, than you have more problems as calling the same function many times (especially if you have a lot of objects) means you are thrashing the stack like crazy for all those function calls rather than just one. And who knows what that is doing to the cache.

princeofspace

A program can have call stacks that are too wide but it can also have call stacks that are too tall. There's a balancing act. But the same decisions need to be solved regardless of the hierarchy. Game logic needs to be done, and it doesn't matter where it gets called--except for the human trying to understand it.

Separate out stage update (where the business logic happens) from stage draw, then stage update will look like this:

#SelectExpand
1stage_func1(void) 2{ 3 /* call all sprites that need func1 */ 4} 5 6 7stage_func2(void) 8{ 9 /* call all sprites that need func2 */ 10} 11 12 13stage_func3(void) 14{ 15 /* call all sprites that need func3 */ 16} 17 18 19/* 20 * Update stage once per frame. 21 */ 22stage_update(void) 23{ 24 stage_func1(); 25 stage_func2(); 26 stage_func3(); 27}

This method avoids branching, which as we all know, we should ALWAYS avoid when possible. It also ensures leaf functions will never go too deep. If you want to know what sort of functionality a sprite has, it's here, presented as a sort of list.

Thread #616982. Printed from Allegro.cc