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
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?