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 <iostream>
#include <iomanip>
#include <cmath>
#include <vector>
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<Fraction> 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 << "Enter number of attackers: "; // find out how many men we're dealing with
cin >> attackers;
cout << "Enter number of defenders: ";
cin >> defenders;
attack_wins = vector<Fraction>(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<Fraction>(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 << endl << fixed;
for(int loop=attack_wins.size()-1; loop>=0; loop--){ // Output attacker wins
cout << "A:" << left << setw(2) << loop+1 << " | D:0 | Odds:" << right;
cout << setw(9) << 100.0 * attack_wins[loop].top / attack_wins[loop].bottom << "%\n";
}
for(int loop=0; loop<defend_wins.size(); loop++){ // Output defender wins
cout << "A:0 | D:" << left << setw(2) << loop+1 << " | Odds:" << right;
cout << setw(9) << 100.0 * defend_wins[loop].top / defend_wins[loop].bottom << "%\n";
}
cout << endl;
}
catch(FractionMathException){
cout << endl << "I'm sorry, but those numbers are too big for me to handle. Please try again." << endl;
}
}
void sorter(int attackers,int defenders,Fraction odds){ // The main part of the program, either adds the odds to attack/defend_wins
switch(attackers){ // or sends it's input to the correct subfunction depending on the number of dice that can be rolled
case 0:
defend_wins[defenders-1] = Add(defend_wins[defenders-1],odds); // If the attackers are dead, add the odds to the correct defenders slot
break;
case 1:
switch(defenders){
case 0:
attack_wins[attackers-1] = Add(attack_wins[attackers-1],odds); // If the defenders are dead, add the odds to the correct attackers slot
break;
case 1:
a1d1(attackers,defenders,odds);
break;
default:
a1d2(attackers,defenders,odds);
break;
}
break;
case 2:
switch(defenders){
case 0:
attack_wins[attackers-1] = Add(attack_wins[attackers-1],odds); // See above
break;
case 1:
a2d1(attackers,defenders,odds);
break;
default:
a2d2(attackers,defenders,odds);
break;
}
break;
default:
switch(defenders){
case 0:
attack_wins[attackers-1] = Add(attack_wins[attackers-1],odds); // See above
break;
case 1:
a3d1(attackers,defenders,odds);
break;
default:
a3d2(attackers,defenders,odds);
break;
}
break;
}
}
void a1d1(int attackers,int defenders,Fraction odds){ // If the attacker uses 1 die and the defender uses 1 die
sorter(attackers,defenders-1,Multiply(odds,Frac(5,12))); // Defender loses 1
sorter(attackers-1,defenders,Multiply(odds,Frac(7,12))); // Attacker loses 1
}
void a1d2(int attackers,int defenders,Fraction odds){ // If the attacker uses 1 die and the defender uses 2 dice
sorter(attackers,defenders-1,Multiply(odds,Frac(55,216))); // Defender loses 1
sorter(attackers-1,defenders,Multiply(odds,Frac(161,216))); // Attacker loses 1
}
void a2d1(int attackers,int defenders,Fraction odds){ // If the attacker uses 2 dice and the defender uses 1 die
sorter(attackers,defenders-1,Multiply(odds,Frac(125,216))); // Defender loses 1
sorter(attackers-1,defenders,Multiply(odds,Frac(91,216))); // Attacker loses 1
}
void a2d2(int attackers,int defenders,Fraction odds){ // If the attacker uses 2 dice and the defender uses 2 dice
sorter(attackers,defenders-2,Multiply(odds,Frac(295,1296))); // Defender loses 2
sorter(attackers-1,defenders-1,Multiply(odds,Frac(35,108))); // Both lose 1
sorter(attackers-2,defenders,Multiply(odds,Frac(581,1296))); // Attacker loses 2
}
void a3d1(int attackers,int defenders,Fraction odds){ // If the attacker uses 3 dice and the defender uses 1 die
sorter(attackers,defenders-1,Multiply(odds,Frac(95,144))); // Defender loses 1
sorter(attackers-1,defenders,Multiply(odds,Frac(49,144))); // Attacker loses 1
}
void a3d2(int attackers,int defenders,Fraction odds){ // If the attacker uses 3 dice and the defender uses 2 dice
sorter(attackers,defenders-2,Multiply(odds,Frac(1445,3888))); // Defender loses 2
sorter(attackers-1,defenders-1,Multiply(odds,Frac(2611,7776))); // Both lose 1
sorter(attackers-2,defenders,Multiply(odds,Frac(2275,7776))); // Attacker loses 2
}
unsigned long long GCD(unsigned long long a, unsigned long long b) { // Finds the Greatest Common Divisor (used in functions LCM, Add, and Multiply)
unsigned long long c; // Uses Euclid's algorithm (slightly modified version of stolen code)
if(a>b){
while (b > 0){
c = a % b;
a = b;
b = c;
}
return a;
} else {
while (a > 0) {
c = b % a;
b = a;
a = c;
}
return b;
}
}
unsigned long long LCM(unsigned long long a, unsigned long long b) { // Finds the Least Common Multiple (used in function Add)
a /= GCD(a,b);
return a*b;
}
Fraction Add(Fraction a, Fraction b){ // Adds two fractions (used to enter odds into attack_wins and defend_wins)
unsigned long long gcd = GCD(a.bottom,b.bottom);
unsigned long long top = ((a.top * b.bottom) + (b.top * a.bottom))/gcd;
unsigned long long bottom = a.bottom * b.bottom / gcd;
gcd = GCD(top,bottom);
top /= gcd;
bottom /= gcd;
if (top > bottom)
throw FractionMathException(); // If the numbers are too big for it to handle, send an error code.
return Frac(top, bottom);
}
Fraction Multiply(Fraction a, Fraction b){ // Multiply two fractions (used to find odds between repetitions of sorter)
if (a.top == a.bottom)
return b;
int gcd = GCD(a.top, b.bottom); // Simplifies fractions (so it can handle larger numbers)
a.top /= gcd;
b.bottom /= gcd;
gcd = GCD(b.top, a.bottom);
b.top /= gcd;
a.bottom /= gcd;
unsigned long long top = a.top * b.top;
unsigned long long bottom = a.bottom * b.bottom;
gcd = GCD(top, bottom); // Simplify again (since last time doesn't matter anymore)
top /= gcd;
bottom /= gcd;
if (top > bottom)
throw FractionMathException(); // If the numbers are too big for it to handle, send an error code.
Fraction out={top,bottom};
return out;
}
Fraction Frac(int a, int b){ // Just sends out a fraction (I hate making variables that I'll only use once unless they're in a loop)
Fraction out = {a, b};
return out;
}
Notes
- I'm sorry if it's hard to understand. I have a bad habit of not writing comments (which I've just added), so let me know if I need to explain something.
- I also have a habit of writing extremely long lines of code. I like to keep my editor at max size, and it's harder to tell when it's too long that way.
- The odds of an individual roll are correct and simplified (I found them using smaller, easier programs).
- The only reasons I feel comfortable asking are because
- I can't figure out how to fix this, and the concept of the fix can probably be used for other things
- I can't find any projects like this one anywhere on the web (I've tried, but I'll admit not as much as I could have)
EDIT: Thanks for all of the feedback. I've improved my program a lot, and have submitted it to be reviewed here.