Sign in to follow this  

Creating a turn-based strategy game

This topic is 3199 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hello. First of all I'd like to say that I've been coding C++ for a year now and I know the basics. I'm also new to this board, so hi everyone :). My coding problem is this: I should make a turn-based strategy game, where units can move and throw projectiles. The units are controlled via a command-line taking turns between Team 1 and Team 2. The battlemap is given by a command-line argument, where "." indicates floor, "#" indicates wall and numbers 1-8 are the players. Player numbers divisible by 2 (2, 4, 6, 8) are on Team 2 and (1, 3, 5, 7) are on Team 1. I know how to parse the file and make some error checking for it. The teams don't have to be even, so Team 1 can be (2, 6) and Team 2 can be (1, 7). Then the turn-order of the units are 1, 2, 7, 6. If a projectile is shot (command: throw <x>,<y>), it moves one unit on the map on each turn, until it hits the wall or another player. If the coordinate is outside of the map, the trajectory is to that direction until it hits something. And then comes the tricky part. I should decide what data structure I'm going to use on the map, players, projectiles and at the same time implement the turn-based hierarchy. This is just something I can't figure out by myself. Here's what I've thought on the map:
class Map {
public:
    Map(); // ctor
    void readMap(); // reads the map to the vector
    void printMap(); // prints the map and the current situation
    void printPlayers(); //prints the positions (coordinates) of the players
private:
    struct Square {
        string mark; // holds the mark (can be ".", "#" or "1"-"8"
    }
    vector<vector<Square> > TheMap; // the map
    vector<Player> Players; // the players
}
Here Player is a class, something like
class Player {
public:
    Player(); // ctor
    void shoot(); // shoot
    void move();
private:
    int x_; /
    int y_;
}
...and now I'm confused :) The Player-class can't access to the TheMap-vector, which has the map. And projectiles are extremely hard to add, because the map needs to be printed out on each turn. So here are some questions: 1. What kind of data structure should I choose? 2. How do I implement the turn-based thing? (Team1 -> Team2 -> Team 1 -> Team 2) 3. How do I add projectiles, so it can be seen on the map on the beginning of each turn? 4. How can I move the player (for example "move south") Thanks in advance. If you have any questions I'm more than willing to answer them :-) ~ induction

Share this post


Link to post
Share on other sites
First, a few comments on your code so far: I would give the readMap() function a std::string as argument, the name of the file it has to read from. I would not let each Square hold a string, either: for the representation, a char suffices.

I'd probably also store some more properties per square: for example, passability (an enum with values PASSABLE and SOLID should do for now). Now, you can make passable tiles dots, or comma's, or some other character, without having to write additional checks to see if those characters are passable. Basically, their logic no longer depends on their visualization.

Oh, and a minor nitpick: TheMap is an odd name - that vector of Square vectors really represents the map tiles. Besides, the class that 'surrounds' it is already called Map. ;)


For the visualization, you'll need access to the map, the players and the projectiles. It makes sense to put this functionality not in one of these classes, but on a higher level. The level that owns the map, players and projectiles (sidenote: I would not let the map own the players - the map is, imho, just responsible for being a map, for providing map information).

Such a draw function could keep a character buffer, where it first draws the map on, and then the players and projectiles, after which it draws the buffer to screen.


As for moving the players, some higher-level logic could be put in charge of that as well. Let's say we write a Game class, that has a Map and a vector of Players and a vector of Projectiles. This Game class could contain the Draw function that I described above: it knows about the map, the players and the projectiles, so it has sufficient information. This class can also keep track of the current active team and player, and it could check the input for movement commands. When a command is issued, it can call a function on the Map class to check if the active player can move to that spot. If so, it calls a function on the active player to move itself to that spot. The same can be done for projectiles: the Game class checks if a player can fire and if so, it adds a new projectile to it's projectiles vector. Each turn the Game class also updates all projectiles and removes them (and players that are hit) when they hit something.



I hope that helps you organizing your thoughts somewhat. It's just one of several possible approaches, so use whatever of it that you think will work best for your situation.

Share this post


Link to post
Share on other sites
Hi induction. :)

I'm not too experienced with this, but I'm working on a text-based RPG at the moment, so some of the problems you've mentioned, I've already run in to.

Quote:
Original post by induction
1. What kind of data structure should I choose?


Can't help with this one. I've got a similar problem with my game. I'm parsing input from the command-line using an InputManager and then creating commands based on that. That's all fine, except that some of the commands need access to methods in my Game class.

Quote:
Original post by induction
2. How do I implement the turn-based thing? (Team1 -> Team2 -> Team 1 -> Team 2)


What about keeping hold of this information in a Game class? You're going to need to store the turn orders for the players too, so an array of players would be fine. Add them to a team object, if you need to and simply have an integer representing the current team, in your Game class.

Quote:
Original post by induction
3. How do I add projectiles, so it can be seen on the map on the beginning of each turn?


Never done anything like this, but I'm guessing you'll need a Projectile class for it so you can store their current positions. I don't know how you're planning to use the projectiles, so I don't know how many you'd need to store, but again, you could keep a list of those in your Game class with methods to add/remove projectiles from the list.

Quote:
Original post by induction
4. How can I move the player (for example "move south")


I did this, as described above, by having an InputManager in my Game class and creating the appropriate Command objects after parsing the input. I derived Command objects from the same interface so I could use the same interface to create/parse the commands from InputManager. Here's how I create my commands:


ICommand* InputManager::GetCommand(const string& token)
{
if (token == "go") return new CommandGo;
if (token == "look") return new CommandLook;
if (token == "quit") return new CommandQuit;

return new CommandUnknown;
}



Then I use them like so:


auto_ptr<ICommand> command(this->GetCommand(token)); // using auto_ptr to delete the object afterwards.
command->Parse(args, game);



It's working great, but as I say, the only thing I'm a bit unhappy about is having to pass a Game pointer to Parse().

On the plus side, I'm able to add new commands really easily (I've already done it a few times) with very minimal changes to my codebase, so I'm still pretty happy with it.

Sorry I can't be of more help and good luck with it! :)

Share this post


Link to post
Share on other sites
Cool ideas guys :-) I like the idea of creating a class for the whole game, so in main() I can just say "get the command and do what you must". Printing the map is also easy that way.

class Game {
public:
Game();
void addRowToMap(string& row); // adds a row from the given file to the Map-vector
void printMap(ostream& cout); // prints the map from
void getCommand();
void updateProjectiles(); // updates the coordinates of the projectiles
private:
enum Team { TEAM1, TEAM2}; // the teams
Team activeTeam;
int activePlayer;
vector<string> Map;
vector<Player> Players;
vector<Projectile> Projectiles;
};

class Player {
public:
Player(int x, int y);
void moveTo(int x, int y);
void shoot(int x, int y);
int getXlocation();
int getYlocation();
int getNumber();
private:
int x_;
int y_;
int health;
int actionpoints; // every player has 10 actionpoints for each round.
};

class Projectile {
public:
Projectile();
void moveTo(int x, int y);
int getXlocation();
int getYlocation();
private:
int x_;
int y_;
bool destroyed;
};


So let's take an example. I have a map like this:

. . . # . . 2
. 1 . # . . .
. . . . . . .
. . . # . . .

On main() I read the map by getline(), and then send it to Map-vector by game.addRowToMap(row);. Then I go through the row character-by-character to see if there's a number. If there is, I add it to the Players-vector and replace it's location with a dot.

void Game::addRowToMap(string& row) {
for(int i = 0; i < row.size(); ++i) {
if(isdigit(row.at(i))) {
Player tmpPlayer(i, Map.size());
Players.push_back(tmpPlayer);
row.at(i) == ".";
}
}
Map.push_back(row)
}


After this the Map-vector will contain only "#" and ".". Then I thought the printMap(ostream& cout) would look something like this:

void Game::printMap(ostream& cout) {
for(int i=0; i < Players.size(); ++i) {
Map.at(Players.at(i).getYlocation, Players.at(i).getXlocation) = Players.at(i).getNumber();
}
for(int k=0; k < Projectiles.size(); ++k) {
Map.at(Projectiles.at(i).getYlocation, Projectiles.at(i).getXlocation) = "o";
}
for(int j=0; j < Map.size(); ++j) {
cout << Map.at(j) << endl;
}
}


Any thoughts on this?

Share this post


Link to post
Share on other sites
Seems fine to me. Really though, the only way you're going to see how well it holds up (if you've not done anything like this before, that is) is to try a skeleton of your design and see how it fits together.

Looks alright from where I am, but of course, we don't know the intricacies of your game mechanics, which you most likely will develop partly as you progress. There's always going to be some things you don't think of, but I'd imagine the more practice you get, the more your experience will steer you away from those unknowns in future projects.

Give it a try and see how happy you are with the way things are set out.

I actually took an idea from Open Learn with the intent of keep a "learning file" which you add to as you're working through your project (or anything you're learning, for that matter). Their belief is that "reflection" on what you've learnt, is what helps to drive your core understanding of what you've been reading/implementing. I gave it a try and I do think it's pretty useful. They also suggest mind-mapping but I've not tried that one yet. :)

If you're interested, I took that from this free introduction - one of the many free course introductions they offer.

Share this post


Link to post
Share on other sites
Why did you remove the Map class altogether? I think it's fine to have a separate class for that. That class could offer functions like LoadFromFile(filename), IsTilePassable(x, y), GetTileChar(x, y), and so on, which means that the Game class has a little less things to worry about.

This also means that you can simply destroy the current Map object, create a new one and call it's LoadFromFile function with a different filename, to load a new map while the game is playing.


Your current printMap implementation is a bad idea though. You're actually modifying the map itself simply because you need to visualize it. That's gonna get messy real soon. Try to come up with something that uses the map data, but does not modify it.


@SeymourClearly: good points. As for learning files, I'm keeping logs of what I'm doing at work and at home. Useful for reflection and sometimes also just for organizing your thoughts. :)

Share this post


Link to post
Share on other sites
Quote:
Original post by Captain P
@SeymourClearly: good points. As for learning files, I'm keeping logs of what I'm doing at work and at home. Useful for reflection and sometimes also just for organizing your thoughts. :)


Yeah, I must admit, I wasn't expecting the results to be so good. It's amazing just how disorganised your thoughts can be whilst they're in your mind, yet so lucid when they're in physical form - whether on a piece of paper or a Word document. :)

The great thing is you don't even have to have a "structure" to it. In fact, from what I've read so far about it, it's better if you do it just how you feel as your thoughts will more accurately flow towards finding the root of your current problem.

I'll try the mind-mapping on my next project and see how it goes. :)

Share this post


Link to post
Share on other sites
3 kinds of things:
1> Static Objects, like walls.
2> Mobile Objects, like bullets.
3> Player Objects, like players.

Static Objects only 'act' when acted upon.

Mobile Objects are 'team-less'.

Player Objects are on a team.

The Map is a mapping from Coordinate to Object.

Each Object knows what Coordinate it is at, and has a reference to the Map. It can move itself on the Map.

Each Object has access to a class that can spawn a Mobile Object for them (ie, it deals with hooking it into the Framework and placing it on the Map, and any other details).

The Framework has one list of static objects, one list of mobile objects, and one list of player objects per team.

The Framework requests tells each player on a team to do their turn. Then it tells the mobile objects to do (1/n where n is the number of teams) of their turn. Then it tells next team, then the mobile objects, etc.

Include interfaces in your Object for common ways for objects to interact, like "I hit you" and "I exploded onto you" and "I tried to move into you, how do you resist?"

...

Your solution can generate this effect without changing.

When you ask what object(s) is(are) at a coordinate, you check the player and bullet lists, and report back what object(s) are there. You don't have to explicitly store the objects in a map from A to B to be a map from A to B. :)

The main idea is that you tell the objects to evaluate their turn, which lets different kinds of objects have different behavior. And then the objects tell the map where they want to move, which then tells the objects what is in the way, which the objects then decide what happens when the things collide.

The virtual behavior -- the behavior that changes based on object type -- lives in the objects. The non-virtual behavior -- each type of object gets to go once per turn -- is encoded in the framework of the game.

Collision is highly virtual, so needs to be encoded in the object type. If you are careful, you can avoid double-dispatch problems (where the result of a collision or even is dependent on the virtual behavior of two different instances). One approach to dealing with this is to abstract out the properties of a collision -- damage, shock, push, etc, then pass that abstract 'impulse' to the receiving object, which then responds abstractly with how it dealt with the impulse.

Ie, a paper wall might respond to damage at a given 'penetration' level by soaking only a small amount of the damage and becoming a clear area. The bullet, having been told that the target only soaked a small amount of the damage, would continue (with maybe a tiny random factor added to it's direction of travel).

The same bullet that hits a wall would have all of the damage soaked.

An armor piercing bullet might pass in a penetration factor. Less damage would be applied to the wall, but less would be soaked, and the bullet might continue through.

A rocket might have a very low penetration, and high damage, from it's kinetic impact. So the paper wall might soak some of the rocket. The rocket responds to enough damage being soaked by exploding -- which it does in concentric circles dealing damage to nearby objects.

Etc.

Share this post


Link to post
Share on other sites
Thanks for the comments once again.

Quote:

Original post by SeymourClearly
If you're interested, I took that from this free introduction - one of the many free course introductions they offer.


Thanks for the hint. I'll try that. :)

Quote:

Original post by Captain P
Why did you remove the Map class altogether? I think it's fine to have a separate class for that. That class could offer functions like LoadFromFile(filename), IsTilePassable(x, y), GetTileChar(x, y), and so on, which means that the Game class has a little less things to worry about.


Yup, I thought I won't need it but apparently I will. I added Map class, which looks like this:


class Map {
public:
Map();
bool readFromFile(string& file, vector<Player>& Players); // reads the file to world
char getChar(int x, int y); // gets a character from the map
bool checkTile(int x, int y); // checks for a tile if it's passable
void printMap(ostream& flow, vector<Player> Players); // prints the map
private:
vector<string> world;
bool addRow(string& row, vector<Player>& Players); // adds a row to the map
};


At the moment I'm doing the command-line interpreter.

Quote:

Original post by Captain P
Your current printMap implementation is a bad idea though. You're actually modifying the map itself simply because you need to visualize it. That's gonna get messy real soon. Try to come up with something that uses the map data, but does not modify it.


I fixed this too. Now the printMap is on a for-loop, where I check if there's a player on (i, k), and if there is then print it. I haven't implemented the projectiles yet, but it can be implemented almost identically. The only "problem" is that I have to pass the Players and Projectiles vector in the printMap-function. Are there any easier (or smarter) implementations on that? Also my Game.printMap() is pretty awful. It's all because the Map is on the private section, so I can't call it from main() directly...


void Game::printMap(ostream& flow) {
map.printMap(flow, Players);
}


Quote:

Original post by NotAYakk
The main idea is that you tell the objects to evaluate their turn, which lets different kinds of objects have different behavior. And then the objects tell the map where they want to move, which then tells the objects what is in the way, which the objects then decide what happens when the things collide.


Yep. That's what I thought.

Share this post


Link to post
Share on other sites
Quote:
Original post by induction
I fixed this too. Now the printMap is on a for-loop, where I check if there's a player on (i, k), and if there is then print it. I haven't implemented the projectiles yet, but it can be implemented almost identically. The only "problem" is that I have to pass the Players and Projectiles vector in the printMap-function. Are there any easier (or smarter) implementations on that? Also my Game.printMap() is pretty awful. It's all because the Map is on the private section, so I can't call it from main() directly...


void Game::printMap(ostream& flow) {
map.printMap(flow, Players);
}

Since the map doesn't know enough, I wouldn't have it put in charge of drawing. I'd say the Game should do that - it knows both the map (it can request the visualization char for each tile from the Map class) and the players and projectiles.

Now, the reason why I'm suggesting a drawing buffer is that otherwise, you'd have to loop through all the players and projectiles for each tile. With a buffer, you just fill it with the map characters, then overwrite those locations where players and projectiles are, and then print the buffer to screen.


For a more complex game, I'd split up the drawing and the logic, but for this project and with your skill level, that's probably a bit too much for now.

Share this post


Link to post
Share on other sites

This topic is 3199 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this