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

Program Risk in action

The second screenshot shows the game ending.

Program Risk ending

Program Risk ending

You can download here the code in risk.txt.

The Program

program Risk;
{
    Copyright (c) 2011 Michael Wardley

    Licensed under the Apache License, Version 2.0 (the "License"); you may not
    use this file except in compliance with the License, as described at
    http://www.apache.org/licenses/ and http://www.pp4s.co.uk/licenses/
}
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);
  write('Please enter your name ');
  readln(PName);
  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 ');
  readln(Player);
end;

procedure CountryDetails;         //pre-entered country details
begin
  Country[1].CName := 'Britain';
  Country[1].CID := 1;
  Country[2].CName := 'France';
  Country[2].CID := 2;
  Country[3].CName := 'Germany';
  Country[3].CID := 3;
  Country[4].CName := 'Russia';
  Country[4].CID := 4;
  Country[5].CName := 'China';
  Country[5].CID := 5;
  Country[6].CName := 'Japan';
  COuntry[6].CID := 6;
  Country[7].CName := 'Korea';
  Country[7].CID := 7;
  Country[8].CName := 'India';
  Country[8].CID := 8;
  Country[9].CName := 'USA';
  Country[9].CID := 9;
  Country[10].CName := 'Middle East';
  Country[10].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');
  readln;
  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');
  readln;
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.) ');
        readln(From);
        if Country[From].POwn = False then  //validation player owns country
          begin
            repeat
              write('You do not own that country. Select one you do own. ');
              readln(From);
            until Country[From].POwn = True;
          end;
        write('Now select the country you would like to transfer troops to. ');
        readln(Target);
        if Country[Target].POwn = False then   // validation player owns country
          begin
            repeat
              write('You do not own that country. Select one you do own. ');
              readln(Target);
            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
        readln(Transfer);
        //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. ');
              readln(Transfer);
            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');
        readln(Again);
      until Again = 'N';
    end
  else
    begin
      writeln('You only own one country.');
      readln;
    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;
  writeln('Your empire has $', PlayerCash);
  readln;
  Move;
  readln;
end;

procedure EndGameLose;            // end of game when no countries left
begin
  writeln('Endgame lose');
  writeln('Unlucky ', PName, ', your empire has been defeated.');
  readln;
  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);
  readln;
  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.) ');
  readln(From);
  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. ');
      readln(From);
    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) ');
  readln(TargetC);
  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. ');
          readln(Target);
        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.) ');
          readln(PTroopNo);
        //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;
            readln;
          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? ');
            readln(Again);
          end;
      until (Again = 'N') or (Again ='n') or (BattleOver = True);
      readln;
    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) ');
      readln(TargetC);
      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. ');
                readln(TargetC);
              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, ' ');
          readln(UnitNo);
          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. ');
                readln(UnitNo);
                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
    readln;
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 ');
    readln(Choice);
    case Choice of     //reads in player mover choice
      'A' : Attack;
      'M' : Move;
      'S' : Spend;
    end;
  until (Choice = 'E') or (NumberOwned = 0) or (NumberOwned = 10);
  readln;
  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? ');
    readln(Choice);
  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? ');
             readln(TroopNo);
           until (TroopNo <= 3) and (TroopNo <= Country[CPUB].TroopCount);  //Can only defend with max 3 troops or max troops owned
           ADice := random(6) + 1;
           BDice := random(6) + 1;
           ATroopScore := ADice * 3;
           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;
                           writeln('You have defended your Country');
                         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,'. ');
      ADice := random(6) + 1;
      BDice := random(6) + 1;
      ATroopScore := ADice * 3;
      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
  readln;
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
  Spend, UnitPrice, AddCash : Integer;
  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;
  AddCash := 100 * NumberOwned;
  PlayerCash := PlayerCash + AddCash;
  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
    PlayerTurn;                          //add ability to save and load games
    CPUTurn;
  until (NumberOwned = 0) or (NumberOwned = 10);
  if NumberOwned = 0 then
    EndGameLose;
  if NumberOwned = 10 then
    EndGameWin;
  readln;
end.

Remarks

Can you develop this game?

Programming - a skill for life!

Student programs to inspire you!