HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppModerate

Risk battle simulator v1

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
simulatorbattlerisk

Problem

I've written a program to find the odds of either side winning a battle in Risk, and how many men they would have left. I've finished, but I feel that it can be improved since it doesn't function if the battle gets too big (with "too big" being ridiculously small, to the point of being near-useless). Can anybody find any ways to improve it?

```
#include
#include
#include
#include

using namespace std;

struct Fraction { //Fraction class used for adding/multiplying the odds of rolls together
unsigned long long top;
unsigned long long bottom;
};

struct FractionMathException : public exception { // Error code (to keep the system from creating bad numbers)
const char * what () const throw ()
{
return "C++ Exception";
}
};

void sorter(int attackers,int defenders,Fraction odds); //Declaring functions to use later
void a1d1(int attackers,int defenders,Fraction odds);
void a1d2(int attackers,int defenders,Fraction odds);
void a2d1(int attackers,int defenders,Fraction odds);
void a2d2(int attackers,int defenders,Fraction odds);
void a3d1(int attackers,int defenders,Fraction odds);
void a3d2(int attackers,int defenders,Fraction odds);
unsigned long long LCM(unsigned long long a, unsigned long long b);
Fraction Add(Fraction a, Fraction b);
Fraction Multiply(Fraction a, Fraction b);
Fraction Frac(int a,int b);

vector attack_wins,defend_wins; //Keeps track of how many times the attackers and defenders win. Which slot is determined by how many men are left standing.

int main(){
int attackers,defenders;

cout > attackers;
cout > defenders;

attack_wins = vector(attackers,Frac(0,1)); // Set attack_wins and defend_wins to the correct sizes, and set every slot to 0/1 (so 0)
defend_wins = vector(defenders,Frac(0,1));

try{
sorter(attackers,defenders,Frac(1,1)); // Run the main part of the program using the number of attackers, defenders, and a 100% chance of this happening.

cout =0; loop--){

Solution

using namespace std;


Don't do that. You're not really saving much typing, and Risk easily avoidable name collisions. If there's one thing I know of c++, it's that using namespace std is a bad habit to lose.

void a1d1(int attackers,int defenders,Fraction odds);
void a1d2(int attackers,int defenders,Fraction odds);
void a2d1(int attackers,int defenders,Fraction odds);
void a2d2(int attackers,int defenders,Fraction odds);
void a3d1(int attackers,int defenders,Fraction odds);
void a3d2(int attackers,int defenders,Fraction odds);


It's not very clear what these methods mean at first glance. ...at second glance either. You should use meaningful identifiers - always. One shouldn't need to read the comments you put up on the implementations to know what they stand for.

That said, I think it might have been better (and a bit harder, but better) to do something more like this:

void calculate(int attackerArmies, int attackerDice, int defenderArmies, int defenderDice, Fraction odds);


And then, perhaps regroup armies and dice together into an appropriate data structure - note, I don't do c++, so take this recommendation with a grain of salt - the point I'm making is that both the attacker and the defender have armies and dice, and this looks like a great [missed] opportunity for an abstraction; this could turn the signature into something like this:

void calculate(std::tuple attacker, std::tuple defender, Fraction odds);


Most likely there's a more appropriate data type for this, but you get the idea. Perhaps something like this:

class Player
{
    int armies;
    int dice;

public:
    Player(int armies, int dice):
        armies(armies),
        dice(dice)
    {}
};


Which turns the signature into this:

void calculate(Player attacker, Player defender, Fraction odds);


Now, you're calling this a function, but it's returning void, and there's no indication that you're altering the parameters (this might be a language thing though, ...but it doesn't feel right anyway). How about another abstraction?

class BattleSettings
{
    Player attacker;
    Player defender;

public:
    BattleSettings(Player attacker, Player defender):
        attacker(attacker),
        defender(defender)
    {}
};


Now you could have this:

BattleSettings calculate(BattleSettings players, Fraction odds);


..and the implementation wouldn't sneakily modify the parameter values, but return a new BattleSettings value instead, containing the state (armies and dice) of each player.

As for the odds themselves, you've essentially hard-coded the whole thing - and these magic numbers stick out like a sore thumb. Best would be to actually calculate them.

Code Snippets

using namespace std;
void a1d1(int attackers,int defenders,Fraction odds);
void a1d2(int attackers,int defenders,Fraction odds);
void a2d1(int attackers,int defenders,Fraction odds);
void a2d2(int attackers,int defenders,Fraction odds);
void a3d1(int attackers,int defenders,Fraction odds);
void a3d2(int attackers,int defenders,Fraction odds);
void calculate(int attackerArmies, int attackerDice, int defenderArmies, int defenderDice, Fraction odds);
void calculate(std::tuple<int,int> attacker, std::tuple<int,int> defender, Fraction odds);
class Player
{
    int armies;
    int dice;

public:
    Player(int armies, int dice):
        armies(armies),
        dice(dice)
    {}
};

Context

StackExchange Code Review Q#107005, answer score: 12

Revisions (0)

No revisions yet.