Risk

by Michael Wardley: U6 Age 18

Introduction

Michael had the excellent idea of writing a console program based on the Risk board game. He uses meaningful identifiers and includes appropriate comments, so you can learn from the code after you have studied records. Michael shows you clear instructions at the start of the program and gives suggestions for development (in capitals) near the end. You may take a long time to finish a game. Note that the option to end means to end your turn and not the game! It can be a good strategy to give other countries the opportunity reduce their troops by fighting each other. Cash increases after each turn.

The first screenshot shows the game in progress. Program Risk in action

The second screenshot shows the game ending. Program Risk ending

The Program

program Risk;
{

use this file except in compliance with the License, as described at
}
uses
SysUtils, Math, Crt;

type
TCountry = record
CName : string;
CID : integer;
TroopCount : integer;
Cash : integer;
POwn : Boolean;
Team : String;
end;

var
PName : String;
Country : array[1 .. 10] of TCountry;
Count, Player, NumberOwned, PlayerCash, Target, From, Turns : integer;
CPUA, CPUB : integer;

const
STARTTROOP = 10;
STARTCASH = 100;

procedure Welcome;  // welcome and player details input
begin
writeln('Welcome to Risk, the classic Strategy Game');
sleep(50);
PName := 'General ' + PName;
sleep(50);
writeln(PName, ', what country would you like to start in?');
for Count := 1 to 10 do            //display list of countries to choose from
begin
with Country[Count] do
begin
writeln(CID, ': ', CName);
end;
end;
write('Enter the number of your chosen country ');
end;

procedure CountryDetails;         //pre-entered country details
begin
Country.CName := 'Britain';
Country.CID := 1;
Country.CName := 'France';
Country.CID := 2;
Country.CName := 'Germany';
Country.CID := 3;
Country.CName := 'Russia';
Country.CID := 4;
Country.CName := 'China';
Country.CID := 5;
Country.CName := 'Japan';
COuntry.CID := 6;
Country.CName := 'Korea';
Country.CID := 7;
Country.CName := 'India';
Country.CID := 8;
Country.CName := 'USA';
Country.CID := 9;
Country.CName := 'Middle East';
Country.CID := 10;
end;

procedure Setup;
var
RedCount, BlueCount, GreenCount : integer;
begin
for Count := 1 to 10 do  // sets all countries to CPU ownership and default cash and troop levels
begin
with Country[Count] do
begin
POwn := False;
TroopCount := STARTTROOP;
Cash := STARTCASH;
end;
end;
Country[Player].POwn := True;   // sets chosen country to player owned
NumberOwned := 1;
PlayerCash := Country[Player].Cash;
Turns := 1;
RedCount := 0;
BlueCount := 0;
GreenCount := 0;
for Count := 1 to 10 do         //setting countries to teams but not player owned
begin
if Country[Count].POwn = False then
begin
if RedCount < 3 then
begin
Country[Count].Team := 'Red';
RedCount := RedCount + 1
end
else if BlueCount < 3 then
begin
Country[Count].Team := 'Blue';
BlueCount := BlueCount + 1
end
else if GreenCount < 3 then
begin
Country[Count].Team := 'Green';
GreenCount := GreenCount + 1;
end;
end;
end;
writeln('Game is being Setup...');
sleep(100);
writeln('Game is ready. Press Enter to begin');
sleep(100);
end;

procedure FirstTurn;  // basic introduction to game and announcment of values
begin
clrscr;
writeln('Congratulations ', PName, ' you are now the ruler of ', Country[Player].CName);
sleep(100);
writeln('Your country starts off with ', Country[Player].TroopCount, ' loyal units of soldiers');
writeln('and \$', PlayerCash, ' cash to invest on your plans of world domination.');
writeln('Each turn you can perform a series of moves');
writeln('-You can attack another country');
writeln('-You can move your troops between countries you own');
writeln('-You can invest into your country');
writeln('-You can end your turn, and you will receive an extra \$100');
writeln('for each country you own');
writeln('All the commands require a character entered in upper case');
writeln('so toggle CAPS LOCK before you start!');
writeln('If in doubt, just press ENTER');
end;

procedure Move;
var
Transfer : Integer;
Again : Char;
begin
if NumberOwned > 1 then
begin
repeat
clrscr;
for Count := 1 to 10 do     //display all countries owned by player
begin
if Country[Count].POwn = True then
begin
writeln(Country[Count].CID,' ', Country[Count].CName, ': ', Country[Count].TroopCount, ' Troops. ');
end;
end;
write('Select a country to move troops from. (Choose a number.) ');
if Country[From].POwn = False then  //validation player owns country
begin
repeat
write('You do not own that country. Select one you do own. ');
until Country[From].POwn = True;
end;
write('Now select the country you would like to transfer troops to. ');
if Country[Target].POwn = False then   // validation player owns country
begin
repeat
write('You do not own that country. Select one you do own. ');
until Country[Target].POwn = True;
end;
write('How many would you like to transfer from ', Country[From].CName,
'? Which has ', Country[From].TroopCount, ' troops remaining. ');  //transfer of troops
//validation so the number of troops sent is less than the than all that exist
if Transfer > Country[From].TroopCount then
begin
repeat
write('You do not have that many troops in', Country[From].CName, '. Please enter a smaller amount. ');
until Transfer <= Country[From].TroopCount;
end;
Country[From].TroopCount := Country[From].TroopCount - Transfer;     //adjusting troops numbers
Country[Target].TroopCount := Country[Target].TroopCount + Transfer;
writeln('Sending troops ');
sleep(100);
writeln('Troops sent');
writeln('Would you like to send more troops? Y/N');
until Again = 'N';
end
else
begin
writeln('You only own one country.');
end;
end;

procedure Captured;     // action when a country is captured
begin
clrscr;
NumberOwned := NumberOwned + 1;
Country[Target].POwn := True;
writeln('You have captured', Country[Target].CName, ' and can spend the \$', Country[Target].Cash, ' left there.');
PlayerCash := PlayerCash + Country[Target].Cash;  //transferring country's cash to player
for Count := 1 to 10 do                           //display all countries owned by player
begin
if Country[Count].POwn = True then
begin
writeln(Country[Count].CID, ' ', Country[Count].CName,': ', Country[Count].TroopCount, ' Troops');
end;
end;
Move;
end;

procedure EndGameLose;            // end of game when no countries left
begin
writeln('Endgame lose');
writeln('Unlucky ', PName, ', your empire has been defeated.');
halt;
end;

procedure EndGameWin;            // end of game when player owns all countries
begin
writeln('Endgame win');
writeln('Congratulations ', PName, ' your empire rules the whole world.');
writeln('It took you only ', Turns, ' to conquer all 10 countries, and accumulate \$', PlayerCash);
halt;
end;

procedure Attack;                //attack move on enemy
var
PTroopNo, CPUTroopNo : integer;
PDice, CPUDice, PTroopScore, CPUTroopScore, FinalTroopScore : integer;
Again : Char;
BattleOver : Boolean;
TargetC : String;
begin
randomize;
BattleOver := False ;
write('Which country would you like to attack from? (Choose a number.) ');
if Country[From].POwn = False then     //validation so players send troops from own country
repeat
write('You do not currently own ', Country[From].CName, '. Enter a country you do own. ');
until Country[From].POwn = True;
for Count := 1 to 10 do                    // display list of countries not owned by player
begin
if Country[Count].POwn = False then
begin
writeln(Country[Count].CID,' ', Country[Count].CName,': ', Country[Count].TroopCount,
' Troops, \$', Country[Count].Cash, ' Team:', Country[Count].Team );
end;
end;
write('Which country would you like to attack? (C to cancel) ');
if TargetC <> 'C' then
begin
Target := Strtoint(TargetC);
if Country[Target].POwn = True then    // validation so players cannot attack their own countries
repeat
write('You currently own ', Country[Target].CName, '. Enter a country you do not own. ');
until Country[Target].POwn = False;
repeat
clrscr;
writeln(Country[From].CName, ' prepare to attack ', Country[Target].CName);
repeat
write(Country[From].CName,' has ', Country[From].TroopCount,
' troops available for combat. (Select a maximum of 3.) ');
//validate player troop no is <= 3  //test for cheating
until (PTroopNo <= 3) and (PTroopNo > 0) and (PTroopNo <= Country[From].TroopCount);
writeln(Country[From].CName,' have sent ', PTroopNo, ' troops into battle.');
if Country[Target].TroopCount >= 3 then   //decides how many troops CPU will play
begin
CPUTroopNo := 3
end;
if Country[Target].TroopCount < 3 then
begin
CPUTroopNo :=  Country[Target].TroopCount;
end;
writeln(Country[Target].CName, ' sends forward ', CPUTroopNo, ' troops.');
PDice := random(6) + 1;           //generates troop multiplier
CPUDice := random(6) + 1;

PTroopScore := (PTroopNo * PDice) + 1;  //applies troop multiplier
CPUTroopScore := CPUTroopNo * CPUDice;

FinalTroopScore := PTroopScore - CPUTroopScore;     //difference between scores
writeln('After hours of intense battle...');
case FinalTroopScore of
-18 .. -1 : begin
writeln(Country[Target].CName, ' have defeated ', Country[From].CName);  //kills player troops
Country[From].TroopCount := Country[From].TroopCount - PTroopNo
end;
0 : begin
writeln('Both sides have encountered equal losses.');
Country[From].TroopCount := Country[From].TroopCount - PTroopNo;       //kills both troops
Country[Target].TroopCount := Country[Target].TroopCount - CPUTroopNo
end;
1 .. 19 : begin
writeln(Country[From].CName,' have defeated ', Country[Target].CName);  //kills CPU troops
Country[Target].TroopCount := Country[Target].TroopCount - CPUTroopNo
end;
end;
if Country[From].TroopCount <= 0 then   // stops there being negative troops
begin
Country[From].TroopCount := 0;
BattleOver := True;
end;
if Country[Target].TroopCount <= 0 then
begin
Country[Target].TroopCount := 0;
BattleOver := True;
end;
writeln(Country[From].CName,' have ', Country[From].TroopCount, ' troops remaining. ',
Country[Target].CName,' have ', Country[Target].TroopCount, ' troops remaining.');
if (Country[From].TroopCount = 0)  then    // reduces countries owned by one
begin
NumberOwned := NumberOwned - 1;
writeln('You have lost ', Country[From].CName);
Country[From].POwn := False;
if NumberOwned = 0 then
EndGameLose;
end;
if (Country[Target].TroopCount = 0) then   //runs captured procedure if player has conquered a country
begin
Captured;
end;
if BattleOver = False then                // asking to attack again, if neither side has currently won
begin
write('Would you like to attack this country again? Y/N? ');
end;
until (Again = 'N') or (Again ='n') or (BattleOver = True);
end;
end;

procedure Spend;
var
UnitNo, Cost, UnitPrice, Max : Integer;
TargetC : String;
begin
clrscr;
UnitPrice := 50;
writeln('Spend');
writeln('You currently have \$', PlayerCash, ' available to spend.');
if PlayerCash > 0 then
begin
writeln('You can purchase extra units of troops at \$', UnitPrice, ' each.') ;
for Count := 1 to 10 do     //display all countries owned by player
begin
if Country[Count].POwn = True then
begin
writeln(Country[Count].CID,' ', Country[Count].CName,': ', Country[Count].TroopCount,' Troops. ');
end;
end;
write('Where would you like to buy new troops for? (C to Cancel) ');
if TargetC <> 'C' then      // allows user to leave
begin
if Country[Strtoint(TargetC)].POwn = False then  //validates whether player owns country
begin
repeat
write('You do not own this country. Select another to send troops to. ');
until Country[Strtoint(TargetC)].POwn = True;
end;
Max := PlayerCash DIV UnitPrice;
write('How many extra units would you like to buy? Up to a max of ', Max, ' ');
Cost := UnitPrice * UnitNo;
if Cost > PlayerCash then     //validates whether player can afford desired troops
begin
repeat
write('You do not have \$', Cost, ' to spend. Select a fewer number of troops. ');
Cost := UnitPrice * UnitNo;
until Cost < Player;
end;
PlayerCash := PlayerCash - Cost;    //re-adjusts troops and cash
Country[Strtoint(TargetC)].TroopCount := Country[Strtoint(TargetC)].TroopCount + UnitNo;
end;
end
else
end;

procedure PlayerTurn;
var
Choice : Char;
begin
repeat
clrscr;
for Count := 1 to 10 do  //display all countries owned by player
begin
if Country[Count].POwn = True then
begin
writeln(Country[Count].CID, ' ', Country[Count].CName, ': ', Country[Count].TroopCount, ' Troops.');
end;
end;
writeln('Your empire has \$', PlayerCash, '. Turns:', Turns);
writeln('What would you like to do now?');
write('A:Attack, M:Move, S:Spend, E:End ');
case Choice of     //reads in player mover choice
'A' : Attack;
'M' : Move;
'S' : Spend;
end;
until (Choice = 'E') or (NumberOwned = 0) or (NumberOwned = 10);
Turns := Turns + 1;
end;

procedure Defeat;
var
Trans : integer;
begin
writeln(Country[CPUB].Team, Country[CPUA].Team);
Country[CPUA].POwn := False;
Country[CPUB].Team := Country[CPUA].Team;
writeln(Country[CPUA].CName,' have defeated ', Country[CPUB].CName, ' and it becomes part of the ',
Country[CPUB].Team, ' empire.');
Country[CPUB].TroopCount := 0;
Trans := Country[CPUA].TroopCount DIV 2;       //ADD : OPTIMISE BEST COUNTRY TO SEND TROOPS FROM
Country[CPUA].TroopCount := Country[CPUA].TroopCount - Trans;      //NOT JUST ATTACKING COUNTRY
Country[CPUB].TroopCount := Trans;
end;

procedure Defend;
var
Choice : Char;
TroopNo, Bribe : integer;
ADice, BDice, ATroopScore, BTroopScore, FinalTroopScore : integer;
begin
randomize;
writeln('You are under attack from ', Country[CPUA].CName);
repeat
write('Do you want to D: Defend the attack, or B: Bribe your attackers to retreat? ');
until (Choice = 'D') or (Choice = 'B');
case Choice of
'D': begin
repeat
write(Country[CPUA].CName,' are attacking with 3 troops. How many would you like to defend with? ');
until (TroopNo <= 3) and (TroopNo <= Country[CPUB].TroopCount);  //Can only defend with max 3 troops or max troops owned
BDice := random(6) + 1;
BTroopScore := BDice * TroopNo;
FinalTroopScore := ATroopScore - BTroopScore;
case FinalTroopScore of
0 : begin
Country[CPUA].TroopCount := Country[CPUA].TroopCount - 1;
Country[CPUB].TroopCount := Country[CPUB].TroopCount - 1;
writeln('Equal losses');
end;
1 .. 18 : begin
Country[CPUB].TroopCount := Country[CPUB].TroopCount - 3;
writeln('You have been successfully attacked');
end;
-18 .. -1 : begin
Country[CPUA].TroopCount := Country[CPUA].TroopCount - 3;
end;
end;
if Country[CPUB].TroopCount <= 0 then
Defeat;
end;
'B' : begin
bribe := random(3) + 1; //Pay a random amount of cash to repel enemy troops
bribe := bribe * 50;
writeln('It will cost you \$', bribe, ' to protect your Country from attack');
PlayerCash := PlayerCash - bribe;
Country[CPUA].Cash := Country[CPUA].Cash + bribe;
end;
end;
end;

procedure CPUAttack;
var
ADice, BDice, ATroopScore, BTroopScore, FinalTroopScore : integer;
begin
randomize;
if Country[CPUB].POwn = False then  //Battle between two CPU countries
begin
write(Country[CPUA].CName,' attack ', Country[CPUB].CName,'. ');
BDice := random(6) + 1;
BTroopScore := BDice * 3;
FinalTroopScore := ATroopScore - BTroopScore;
case FinalTroopScore of
0 : begin
Country[CPUA].TroopCount := Country[CPUA].TroopCount - 1;
Country[CPUB].TroopCount := Country[CPUB].TroopCount - 1;
writeln('Equal losses');
end;
1 .. 18 : begin
Country[CPUB].TroopCount := Country[CPUB].TroopCount - 3;
writeln('And succeed');
end;
-18 .. -1 : begin
Country[CPUA].TroopCount := Country[CPUA].TroopCount - 3;
writeln('And fail');
end;
end;
if Country[CPUB].TroopCount <= 0 then
Defeat;  //If a country is captured
end
else
Defend;      //If a player owned country is attacked
end;

procedure CPUAttackCheck;
var
Countx, Diff : Integer;
Rand : Real;
Attacked : array [1 .. 10] of integer;
begin
randomize;
for Countx := 1 to 10 do
Attacked[Countx] := 0;          //iniatialising attack counts to blanks
for Count := 1 to 10 do
if Country[Count].POwn = False then                    //all countries not owned by player
begin
for Countx := 1 to 10 do
begin
if (Countx <> Count) and
(Country[Count].Team <> Country[Countx].Team) then
begin
Diff := Country[Count].TroopCount -  Country[Countx].TroopCount;
if (Diff > 3) and (Country[Count].TroopCount > 3) then
begin
CPUA := Count;
CPUB := Countx;
//Store the number of times a country is attacked each turn.
Attacked[Countx] := Attacked[Countx] + 1;
Rand := random();
//Prevent countries being attacked more than 3 times. Anti-bullying
if (Rand > 0.6) and (Attacked[Countx] <= 3) then
begin
CPUAttack;
end;
end;
end;

end;
end;
end;

procedure CPUTurn;
var
Rand : Real;
begin
randomize;
clrscr;
UnitPrice := 50;
for Count := 1 to 10 do   //display all countries not owned by player
begin
if Country[Count].POwn = False then
with Country[Count] do
begin
Cash := Cash + 100;    //adds cash at end of turn
Rand := random();
if rand > 0.5 then     //so countries don't spend every turn
begin
Spend := Cash DIV 100;  // divides the cash by 100
Spend := Trunc(Spend);
if Spend > 1  then      //spending any excess they have of \$100
begin
TroopCount := TroopCount + Spend;  //makes countries troops expand
Cash := Cash - (Spend * UnitPrice);
end;
end;
end;
end;
CPUAttackCheck;
end;

//ORDER IN WHICH COUNTRIES ATTACK CHECK SHOULD BE RANDOM
//CREATE A WEAPONS UPGRADE DEVELOPMENT SYSTEM : ARMOURY / BLACKSMITHS / WALLS
//CREATE SAVE FUNCTION
//WRITE HELPFILE IN FIRST TURN
//FIX BUG WHERE COUNTRY OBTAINS 4million ODD TROOPS [PPS: if bug still exists]
//REWRITE USING PARAMETERS

//add cancel function on attack           DONE
//validate defend bribe                   DONE
//MAKE CPU ATTACK EACH OTHER AND PLAYER   DONE

begin
CountryDetails;
Welcome;
Setup;
FirstTurn;
repeat
CPUTurn;
until (NumberOwned = 0) or (NumberOwned = 10);
if NumberOwned = 0 then
EndGameLose;
if NumberOwned = 10 then
EndGameWin;