Designing a menu that wraps around? C++ / OOP - c++

I'm a new programmer trying to teach myself C++ and OOP. Right now I'm working on a project with multiple states, and it's my first attempt at a bigger programming task/challenge. Right now I'm trying to work on a simple menu state class, but I'm not sure how to go about designing it to do what I want. Specifically, I'm a beginner programmer, and I'm not sure what data types or templates to use.
Here's a brief description of the type of menu screen that I want to make:
I'm trying to make a simple menu screen that has multiple 'menu elements' listed. Each 'menu element' contains a string (which is used to display the name of the element or item) and a function (to change states, screens, do a task, etc.). -- The other 'feature' of the menu system that I want to have is the ability for selection to wrap around. For example, if the menu has 3 items, I want the user to be able to press up and down to switch the currently selected element, and if the user is on the last element (#3) and presses down again, they wrap back around to element #1. (and vice versa..)
Now, I ~think~ that this will require me to make a new class called 'MenuElement', and a class called 'MenuState' which contains and allows the user to switch between and select a specific element.
As a new, self-taught C++ programmer, I have limited experience with some of the more advanced containers and I'm much more familiar with simple c-style arrays. I have a little bit of experience with vectors, and I know a little bit about linked lists, maps, and trees, but I'm not sure which container type to use for this situation. I've seen examples of using pointers to create a circular linked list before, but the example was in C and it seemed a little bit clumsy. Is there a specific type of contain that bests fits my problem? Or am I over-thinking it, and I should just pick one and run with it?
I think I could eventually get this system up and running using an array of MenuElements with a series of conditional statements. However, I'm really interested in actually learning how to improve my programming and design, not only so that my code runs faster, but also so that my code is clean and logically constructed.

Wrap-around can be done with modulo arithmetic, i.e. given a private member index variable named idx_ of the active cursor into the array of menu elements, you could do have a member functions page_down() and page_up() like this
void CompositeMenu::page_down()
{
// idx_ will always remain in the interval [0, num_elements() )
// when idx_ equals num_elements() - 1, page_down() will yield 0 (i.e. wrap-around)
idx_ = (idx_ + 1) % num_elements();
}
void CompositeMenu::page_up()
{
// idx_ will always remain in the interval [0, num_elements() )
// when idx_ equals 0, page_up() will yield num_elements() - 1 (i.e. wrap-around)
idx_ = (idx_ - 1 + num_elements() ) % num_elements() ;
}
where num_elements() is the size of the current menu.
For your general design question, a sketch of your class hierarchy would use the Composite Design Pattern (Wikipedia). This defines an abstract menu interface called IMenu and derives both CompositeMenu and a LeafMenu as concrete classes. This allows arbitrary levels of nested submenus.
class IMenu
{
public:
virtual void SomeOperation() = 0;
};
class CompositeMenu
:
public IMenu
{
public:
virtual void SomeOperation()
{
// your implementation
}
void page_down(); // use implementation above
void page_up(); // use implementation above
int num_elements { return subMenus_.size(); }
private:
std::vector<IMenu*> subMenus_; // list of subMenus, which can also have nested menus themselves
int idx_; // active menu element
};
class LeafMenu
:
public IMenu
{
public:
virtual void SomeOperation()
{
// your implementation
}
};
Note that since num_elements() is a member function that allows for dynamic updates on the subMenus (i.e. it would allow drag-and-drop of menu items).

Related

Make C++ class partially constexpr and save RAM

I have written code for a controller that has buttons and lamps. It is based on Arduino/ATmega2560. RAM is very limited. Changing to another device is no option.
Each button and lamp is modelled as a C++ class instance. They are derived from an abstract class Thing, which contains a pointer mpTarget that indicates connections between Things. The targets are initially assigned at runtime and do not change during runtime.
I need to save memory. As I have ~600 Things, it would help me alot if all those mpTargets were stored in ROM (and not in RAM), because the targets are known at compile time.
So I played with constexpr. However, it seems to be impossible to make a class only partially constexpr. I cannot make the whole class constexpr, because there are also member variables that change during runtime (mState in the example below).
What would be a good way to achieve this behavior? constexpr? Templates? Anything?
Clarifications
I know that mpTarget is currently initialized at runtime. But each target is known at compile time, that's why I'd like to find a good way to save RAM bytes of these pointers.
I don't really want to move away from object oriented design. I used an approach with simple, "plain old" datatypes before (which actually consumed less RAM). But as development of this code is still ongoing (classes and instances are added/deleted/modified), there is a high chance to miss necessary modifications, introducing hard-to-find bugs.
For this project, it would be more than enough to save the RAM storage only for mpTarget. Keeping the overhead of vtable pointers is fine. What I'm actually looking for is - as the title suggests - a way how we could implement a class whose members are partially constexpr. I also thought about using templates, but without success.
Sample code
#include <iostream>
class Thing
{
public:
// only called in setup()
void setTarget(Thing & pTarget)
{
mpTarget = & pTarget;
}
virtual void doSomething(int value) {};
protected:
// known at compile time - can we make this constexpr?
Thing * mpTarget;
};
class Button : public Thing
{
public:
void setState(int newState)
{
mState = mState + newState;
mpTarget->doSomething(mState);
}
private:
// changes during runtime
int mState;
};
class Lamp: public Thing
{
public:
virtual void doSomething(int value)
{
std::cout << value << std::endl;
};
};
Button myButton;
Lamp myLamp;
int main()
{
myButton.setTarget(myLamp);
while (1)
{
myButton.setState(123);
}
}
Redesign from ground up
ATmega2560 has just 8KB SRAM. This is extremely low compared to normal, desktop standards. 600 objects with 3 2-byte properties each would fill almost half of the available memory.
Programming on such a restrictive environment forces you to adapt your whole design from the start around the hardware limitations. Writing normal code and then trying to fit it to your hardware after the fact just doesn't cut it here. This is a good exception to the "first write readable code, then try it to optimize it".
One idea: group by properties, not by objects
First you need to abandon virtual methods. That would add at least a vtable pointer per instance. At 600 instances it is a very heavy cost.
One ideea I have for a design here is to ditch the OOP completely. Or at least partially. Instead of properties grouped per instance, group all properties together in vectors.
This has a few big advantages:
it saves space by abandoning the vtable
because how properties are grouped it makes it possible to store the properties known at compile time in ROM
it saves space by using the minimum number of bits necessary for each property
Example
For the sake of example let's consider this scenario:
we have 100 lamps, 200 buttons and 300 LEDs
buttons and LEDs have a target property. Only lamps can be targets
buttons have state property. There are 2 possible states (ON/OFF)
LEDs have color property. There are 16 possible predefined colors
The following examples use literals to be explicit, but in code you should use constants (e.g. NUM_BUTTONS etc.)
target property
We have 500 (200 buttons + 300 LEDS) objects with target property. So we need a vector of size 500. There are 100 targets. So the data type fits in a int8_t. So target looks like this:
constexpr int8t_t targets[500] = ...
In this vector the first 200 elements represent the targets of the buttons and the next 300 elements represent the targets of the LEDs. This vector will be stored in ROM.
To get the target of a thing we have:
int8_t button_target(int button) { return targets[button]; }
int8_t led_target(int led) { return targets[200 + led]; }
Alternatively use two vectors:
constexpr int8t_t button_targets[200] = ...
constexpr int8t_t led_targets[300] = ...
Populating this vector at compile time is a problem you need to solve. I don't know exactly how you are creating your objects now. You could hard code the values in the code or you could generate the code.
state property
We have 200 elements with state. Since there are 2 possible states we just need 1 bit per state. So we just need 200 / 8 = 25 bytes:
int8_t state[25] = {};
Getting and setting the state of a button is more complicated, it requires bitmask operations, but we have saved 175 bytes here (87.5% saved space on this data) and every byte matters.
bool button_get_state(int button)
{
int8_t byte = state[button / 8];
return byte & (1 << (button % 8));
}
color property
We have 300 elements with color. Since there are 16 colors we need 4 bits per color so we can encode 2 colors per byte:
int8_t color[150] = {};
Again getting and setting color requires bit fiddling.
Conclusion
As you can see this design is by far not as pretty as OOP. And is it requires more complex code. But it has the big advantage that is saves you a lot of space, which is paramount on a device with just 8,192 bytes of SRAM.
Use small state
Eliminate all virtual methods (and probably all base classes) to reduce class state.
Use Non-Type template parameters to move references from data (RAM) to code (ROM)
Use an enum based on a char for state when possible:
enum binary_enable_state : char {
BINARY_ENABLE_ON,
BINARY_ENABLE_OFF
};
Event listeners should just listen, and have no pointers or references or any real state.
class Lamp {
public:
void doSomething(binary_enable_state value)
{
std::cout << value << std::endl;
};
};
Event sources should uses non-type template parameter references to the listeners, thus avoiding pointers and state.
template<class Listener, Listener& mpTarget>
class Button {
public:
void setState(binary_enable_state newState)
{
mState = newState;
mpTarget.doSomething(mState);
}
private:
// changes during runtime
binary_enable_state mState; //Do you actually need to store this?
};
This basically puts the graph in the code itself (ROM), rather than as data in RAM.
Lamp myLamp;
Button<decltype(myLamp), myLamp> myButton;
int main()
{
myButton.setState(BINARY_ENABLE_ON);
}
http://coliru.stacked-crooked.com/a/7171e4b9547ccbe1

Should I add an optional class member using std::optional or inheritance?

I have class Item, one of no interest itself. However, I need some Items to be gear like clothes or weapons which need to additionally store an enum class Limb object which represents the body part where they can be equipped. Moreover, ordinary Items must be placed together with "gear" Items in a single STL container and possibly be taken out later, maybe as a "gear" Item. I see two options here: inheritance
class Item { /* ... */ };
class Gear : public Item {
Limb limb;
// ...
};
std::vector<std::unique_ptr<Item>> items;
std::unique_ptr<Gear> gear = // dynamic cast items[...] and check for success
or std::optional
class Item {
std::optional<Limb> limb; // if equippable, limb.has_value() == true
// ...
};
std::vector<Item> items;
Item gear = // get items[...] and check limb.has_value()
I like the first one for extensibility and compile-time type checking when a function expects Gear (not just an Item) instead of a runtime assert(item.is_gear()) inside it.
The second one seems better in terms of speed (I like to care about it even when there are no performance issues expected) and design naturalness: I don't need to know about any other classes and special use cases when engineering Item (for example, making its destructor virtual like in the first variant would destroy this) which seems like clean and innocent design.
Which decision would be better and why? Or is there an even better solution?

Choose algorithm in accordance with chosen item in combobox

Good day,
I'm writing a program 'geocalculator' as an assignment at our university. I faced up with a design problem. An interface contains two groups: A and B. Each group consists of two comboboxes and a table. The first combobox allows user to choose between a couple of reference systems (WGS-84, SK-42, SK-95, etc.). The second - between geodetic, spatial and planimetric coordinate systems. The table is used to input a bunch of points (label, X (or B), Y (or L), Z (or H)). Conversion functions involve a lot of constants and massive formulas.
My question is: what is the best way to organize code, connect comboboxes and functions in order to call appropriate conversion function?
Let's come to the point. I subclassed an abstract model (PointsModel) where all my points are being stored. It has no idea about comboboxes and algorithms. The comboboxes are declared inside of a MainWindow class. My model do have custom delegate: it is used to convert representation of the input coordinates from sexadecimal (degrees, minutes and seconds) to decimal (decimal degrees). I have two buttons (actions on the toolBar): convert points from A to B and vice versa. I imagine the process in the next way:
The user presses 'convert' button and the appropriate function is called.
The general conversion function reads value of the comboboxes and chooses required conversion function.
...
I don't want to do it via "if else" statements! Because there would be a huge number of condition statements (if the one combobox is WGS-84 and the other is SK-42 then do this, if the one combobox is SK-42 and the other is WGS-84 then do this and so on and so forth for a bunch of reference systems).
I have some ideas about declaring functions and corresponding functors. I would like to attach this functors to the comboboxes in some way and then the time comes just call a general method, which would automatically redirect the call to the required function.
I'll describe two popular choices: Look up table and event handler (a.k.a. button press). The selection of the algorithm depends on your calculator implementation.
Look up table
The fundamentals here is that you want to associate a function with a symbol. For example, your parser wants to evaluate the '+' for the expressions on the stack. Rather than using a switch or an if-elseif ladder, you could look up a function that is associated with '+' and execute the function with the two expressions.
// Synonym for a pointer that accepts two parameters,
// performs an operation, and returns the result as a string.
typedef std::string (*Function_Pointer(const std::string& param1, const std::string& param2))
struct Table_Entry
{
const char * operator_text;
Function_Pointer p_operaton_function;
};
const Table_Entry Operation_Table[] =
{
{"+", Perform_Addition},
{"-", Perform_Subtraction},
{"*", Perform_Multiplication},
};
// Or
typedef std::map<std::string, Function_Pointer> Operation_Container;
//...
Operation_Container operations_map;
operations_map["+"] = Perform_Addition;
operations_map["-"] = Perform_Subtraction;
Event Handler
Another idea is to put the computation inside the handler for the button.
void On_Button_Dash(Event& e)
{
result = operand_1 - operand2;
}
Edit 1: OOP
Another alternative is to create a base class for the operations. The base class would have an abstract method to evaluate or perform the operation. Define child classes for each operation. Create the operation classes during lexing phase and store in a vector of pointers to base classes.
You main loop could be:
std::vector<Base_Operation *> operations;
for (unsigned int i = 0; i < operations.size(); ++i)
{
operations[i].Evaluate(parameter_1, parameter_2);
}
There are a number of ways that you can handle this, but I think the best is by setting a functor on each element in your QComboBox.
When you do (QComboBox::addData)[http://qt-project.org/doc/qt-5/qcombobox.html#addItem] you'll notice the defaulted userData parameter. You can assign a functor here.
When you're ready to use it just call (QBomboBox::currentData)[http://qt-project.org/doc/qt-5/qcombobox.html#currentData-prop] to get it back out and use it.
As far as storing your functor in a QVariant, you can either store your functors in your own array and just store an index into that array in the QVariant of you can directly store the functor as a void* using QVariant(int typeId, const void * copy).
EDIT:
From your comment it sounds like something like this might be in order:
Store your functions in a std::map<std::string, std::map<std::string, std::function<X>>> Where the key to the outer map is the from-type and the key to the inner map is the to-type.
Then store the keys in your QComboBox's userData.

How to create method which will know that its instance is in matrix of another class

I'm an absolute beginner in OOP (and C++). Trying to teach myself using resources my university offers for students of higher years, and a bunch of internet stuff I can find to clear things up.
I know basic things about OOP - I get the whole point of abstracting stuff into classes and using them to create objects, I know how inheritance works (at least, probably the basics), I know how to create operator functions (although as far as I can see that only helps in code readability in a sense that it becomes more standard, more language like), templates, and stuff like that.
So I've tried my first "project": to code Minesweeper (in command line, I never created a GUI before). Took me a few hours to create the program, and it works as desired, but I feel like I'm missing a huge point of OOP in there.
I've got a class "Field" with two attributes, a Boolean mine and a character forShow. I've defined the default constructor for it to initialize an instance as an empty field (mine is false), and forShowis . (indicating a not yet opened filed). I've got some simple inline functions such as isMine, addMine, removeMine, setForShow, getForShow, etc.
Then I've got the class Minesweeper. Its attributes are numberOfColumns, ~ofRows, numberOfMines, a pointer ptrGrid of type Mine*, and numberOfOpenedFields. I've got some obvious methods such as generateGrid, printGrid, printMines (for testing purposes).
The main thingy about it is a function openFiled which writes the number of mines surrounding the opened field, and another function clickField which recursively calls itself for surrounding fields if the field which is currently being opened has 0 neighbor mines. However, those two functions take an argument -- the index of the field in question. That kinda misses the point of OOP, if I understand it correctly.
For example, to call the function for the field right to the current one, I have to call it with argument i+1. The moment I noticed this, I wanted to make a function in my Field class which would return a pointer to the number right to it... but for the class Field itself, there is no matrix, so I can't do it!
Is it even possible to do it, is it too hard for my current knowledge? Or is there another more OOP-ish way to implement it?
TLDR version:
It's a noob's implemetation of Minesweeper game using C++. I got a class Minesweeper and Field. Minesweeper has a pointer to matrix of Fields, but the navigation through fields (going one up, down, wherever) doesn't seem OOP-ishly.
I want to do something like the following:
game->(ptrMatrix + i)->field.down().open(); // this
game->(ptrMatrix + i + game.numberOfColumns).open(); // instead of this
game->(ptrMatrix + i)->field.up().right().open(); // this
game->(ptrMatrix + i + 1 - game.numberOfColumns).open(); // instead of this
There are a couple of ways that you could do this in an OOP-ish manner. #Peter Schneider has provided one such way: have each cell know about its neighbours.
The real root of the problem is that you're using a dictionary (mapping exact coordinates to objects), when you want both dictionary-style lookups as well as neighbouring lookups. I personally wouldn't use "plain" OOP in this situation, I'd use templates.
/* Wrapper class. Instead of passing around (x,y) pairs everywhere as two
separate arguments, make this into a single index. */
class Position {
private:
int m_x, m_y;
public:
Position(int x, int y) : m_x(x), m_y(y) {}
// Getters and setters -- what could possibly be more OOPy?
int x() const { return m_x; }
int y() const { return m_y; }
};
// Stubbed, but these are the objects that we're querying for.
class Field {
public:
// don't have to use an operator here, in fact you probably shouldn't . . .
// ... I just did it because I felt like it. No justification here, move along.
operator Position() const {
// ... however you want to get the position
// Probably want the Fields to "know" their own location.
return Position(-1,-1);
}
};
// This is another kind of query. For obvious reasons, we want to be able to query for
// fields by Position (the user clicked on some grid), but we also would like to look
// things up by relative position (is the cell to the lower left revealed/a mine?)
// This represents a Position with respect to a new origin (a Field).
class RelativePosition {
private:
Field *m_to;
int m_xd, m_yd;
public:
RelativePosition(Field *to, int xd, int yd) : m_to(to), m_xd(xd),
m_yd(yd) {}
Field *to() const { return m_to; }
int xd() const { return m_xd; }
int yd() const { return m_yd; }
};
// The ultimate storage/owner of all Fields, that will be manipulated externally by
// querying its contents.
class Minefield {
private:
Field **m_field;
public:
Minefield(int w, int h) {
m_field = new Field*[w];
for(int x = 0; x < w; x ++) {
m_field[w] = new Field[h];
}
}
~Minefield() {
// cleanup
}
Field *get(int x, int y) const {
// TODO: check bounds etc.
// NOTE: equivalent to &m_field[x][y], but cleaner IMO.
return m_field[x] + y;
}
};
// The Query class! This is where the interesting stuff happens.
class Query {
public:
// Generic function that will be instantiated in a bit.
template<typename Param>
static Field *lookup(const Minefield &field, const Param &param);
};
// This one's straightforwards . . .
template<>
Field *Query::lookup<Position>(const Minefield &field, const Position &pos) {
return field.get(pos.x(), pos.y());
}
// This one, on the other hand, needs some precomputation.
template<>
Field *Query::lookup<RelativePosition>(const Minefield &field,
const RelativePosition &pos) {
Position base = *pos.to();
return field.get(
base.x() + pos.xd(),
base.y() + pos.yd());
}
int main() {
Minefield field(5,5);
Field *f1 = Query::lookup(field, Position(1,1));
Field *f0 = Query::lookup(field, RelativePosition(f1, -1, -1));
return 0;
}
There are a couple of reasons why you might want to do it this way, even if it is complicated.
Decoupling the whole "get by position" idea from the "get neighbour" idea. As mentioned, these are fundamentally different, so expose a different interface.
Doing it in this manner gives you the opportunity to expand later with more Query types in a straightforwards fashion.
You get the advantage of being able to "store" a Query for later use. Perhaps to be executed in a different thread if it's a really expensive query, or in an event loop to be processed after other events, or . . . lots of reasons why you might want to do this.
You end up with something like this: (C++11 ahead, be warned!)
std::function<Field *()> f = std::bind(Query::lookup<RelativePosition>,
field, RelativePosition(f1, -1, -1));
. . . wait, what?
Well, what we essentially want to do here is "delay" an execution of Query::lookup(field, RelativePosition(f1, -1, -1)) for later. Or, rather, we want to "set up" such a call, but not actually execute it.
Let's start with f. What is f? Well, by staring at the type signature, it appears to be a function of some sort, with signature Field *(). How can a variable be a function? Well, it's actually more like a function pointer. (There are good reasons why not to call it a function pointer, but that's getting ahead of ourselves here.)
In fact, f can be assigned to anything that, when called, produces a Field * -- not just a function. If you overload the operator () on a class, that's a perfectly valid thing for it to accept as well.
Why do we want to produce a Field * with no arguments? Well, that's an execution of the query, isn't it? But the function Query::lookup<RelativePosition> takes two arguments, right?
That's where std::bind comes in. std::bind essentially takes an n-argument function and turns it into an m-argument function, with m <= n. So the std::bind call takes in a two-place function (in this case), and then fixes its first two arguments, leaving us with . . .
. . . a zero-argument function, that returns a Field *.
And so we can pass around this "function pointer" to a different thread to be executed there, store it for later use, or even just repeatedly call it for kicks, and if the Position of Fields was to magically change for some reason (not applicable in this situation), the result of calling f() will dynamically update.
So now that I've turned a 2D array lookup into a mess of templates . . . we have to ask a question: is it worth it? I know this is a learning exercise and all, but my response: sometimes, an array is really just an array.
You can link the four neighbours to the cell via pointers or references. That would likely happen after the playing field has been created. Whether that's good or bad design I'm not sure (I see the same charme though that you see). For large fields it would increase the memory footprint substantially, because a cell probably doesn't hold that much data besides these pointers:
class Cell
{
// "real" data
Cell *left, *right, *upper, *lower;
// and diagonals? Perhaps name them N, NE, E, SE, S...
};
void init()
{
// allocate etc...
// pseudo code
foreach r: row
{
foreach c: column
{
// bounds check ok
cells[r][c].upper = &cells[r-1][c];
cells[r][c].left = &cells[r][c-1];
// etc.
}
}
// other stuff
}

How to write an elegant collision handling mechanism?

I'm in a bit of a pickle: say I'm making a simple, 2D, Zelda-like game.
When two Objects collide, each should have a resulting action. However, when the main character collides with something, his reaction depends solely on the type of the object with which he collided. If it's a monster, he should bounce back, if it's a wall, nothing should happen, if it's a magical blue box with ribbons, he should heal, etc. (these are just examples).
I should also note that BOTH things are part of the collision, that is, collision events should happen for both the character AND the monster, not just one or the other.
How would you write code like this? I can think of a number of incredibly inelegant ways, for instance, having virtual functions in the global WorldObject class, to identify attributes - for instance, a GetObjectType() function (returns ints, char*s, anything that identifies the object as Monster, Box, or Wall), then in classes with more attributes, say Monster, there could be more virtual functions, say GetSpecies().
However, this becomes annoying to maintain, and leads to a large cascading switch (or If) statement in the collision handler
MainCharacter::Handler(Object& obj)
{
switch(obj.GetType())
{
case MONSTER:
switch((*(Monster*)&obj)->GetSpecies())
{
case EVILSCARYDOG:
...
...
}
...
}
}
There's also the option of using files, and the files would have things like:
Object=Monster
Species=EvilScaryDog
Subspecies=Boss
And then the code can retrieve the attributes without the need for virtual functions cluttering everything up. This doesn't solve the cascading If problem, however.
And THEN there's the option of having a function for each case, say CollideWall(), CollideMonster(), CollideHealingThingy(). This is personally my least favourite (although they're all far from likeable), because it seems the most cumbersome to maintain.
Could somebody please give some insight into more elegant solutions to this problem?
Thanks for any and all help!
I would do it vice versa - because if the character collides with an object, an object collides with the character as well. Thus you can have a base class Object, like this:
class Object {
virtual void collideWithCharacter(MainCharacter&) = 0;
};
class Monster : public Object {
virtual void collideWithCharacter(MainCharacter&) { /* Monster collision handler */ }
};
// etc. for each object
Generally in OOP design virtual functions are the only "correct" solution for cases like this:
switch (obj.getType()) {
case A: /* ... */ break;
case B: /* ... */ break;
}
EDIT:
After your clarification, you will need to adjust the above a bit. The MainCharacter should have overloaded methods for each of the objects it can collide with:
class MainCharacter {
void collideWith(Monster&) { /* ... */ }
void collideWith(EvilScaryDog&) { /* ... */ }
void collideWith(Boss&) { /* ... */ }
/* etc. for each object */
};
class Object {
virtual void collideWithCharacter(MainCharacter&) = 0;
};
class Monster : public Object {
virtual void collideWithCharacter(MainCharacter& c)
{
c.collideWith(*this); // Tell the main character it collided with us
/* ... */
}
};
/* So on for each object */
This way you notify the main character about the collision and it can take appropriate actions. Also if you need an object that should not notify the main character about the collision, you can just remove the notification call in that particular class.
This approach is called a double dispatch.
I would also consider making the MainCharacter itself an Object, move the overloads to Object and use collideWith instead of collideWithCharacter.
How about deriving all collidable objects from one common abstract class (let's call it Collidable). That class could contain all properties that can be changed by a collission and one HandleCollision function. When two objects collide, you just call HandleCollision on each object with the other object as the argument. Each object manipulates the other to handle the collision. Neither object needs to know what other object type it just bounced into and you have no big switch statements.
Make all colidable entities implement an interface (lets say "Collidable") with a collideWith(Collidable) method.
Then, on you collision detection algorithm, if you detect that A collides with B, you would call:
A->collideWith((Collidable)B);
B->collideWith((Collidable)A);
Assume that A is the MainCharacter and B a monster and both implement the Collidable interface.
A->collideWith(B);
Would call the following:
MainCharacter::collideWith(Collidable& obj)
{
//switch(obj.GetType()){
// case MONSTER:
// ...
//instead of this switch you were doing, dispatch it to another function
obj->collideWith(this); //Note that "this", in this context is evaluated to the
//something of type MainCharacter.
}
This would in turn call the Monster::collideWith(MainCharacter) method and you can implement all monster-character behaviour there:
Monster::CollideWith(MainCharacter mc){
//take the life of character and make it bounce back
mc->takeDamage(this.attackPower);
mc->bounceBack(20/*e.g.*/);
}
More info: Single Dispatch
Hope it helps.
What you call "an annoying switch statement" i would call "a great game" so you are on the right track.
Having a function for every interaction/game rule is exactly what I would suggest. It makes it easy to find, debug, change and add new functionality:
void PlayerCollidesWithWall(player, wall) {
player.velocity = 0;
}
void PlayerCollidesWithHPPotion(player, hpPoition) {
player.hp = player.maxHp;
Destroy(hpPoition);
}
...
So the question is really how to detect each of these cases. Assuming you have some sort of collision detection that results in X and Y collide (as simple as N^2 overlap tests (hey, it works for plants vs zombies, and that's got a lot going on!) or as complicated as sweep and prune + gjk)
void DoCollision(x, y) {
if (x.IsPlayer() && y.IsWall()) { // need reverse too, y.IsPlayer, x.IsWall
PlayerCollidesWithWall(x, y); // unless you have somehow sorted them...
return;
}
if (x.IsPlayer() && y.IsPotion() { ... }
...
This style, while verbose is
easy to debug
easy to add cases
shows you when you have
logical/design inconsistencies or
omissions "oh what if a X is both a
player and a wall due to the
"PosessWall" ability, what then!?!"
(and then lets you simply add cases
to handle those)
Spore's cell stage uses exactly this style and has approximately 100 checks resulting in about 70 different outcomes (not counting the param reversals). It's only a ten minute game, that's 1 new interaction every 6 seconds for the whole stage - now that's gameplay value!
If I am getting your problem correctly, I would sth like
Class EventManager {
// some members/methods
handleCollisionEvent(ObjectType1 o1, ObjectType2 o2);
// and do overloading for every type of unique behavior with different type of objects.
// can have default behavior as well for unhandled object types
}