John B. Matthews, M.D.

Return home.


About war:

War is an Ada 95 program that repeatedly plays the children's card game of war. After playing a number of games, the program tallies up some statistics on wins and losses. It also prints out an ASCII plot of how long the games took.

The inspiration for many of the algorithms used in the program came from Dr. Michael B. Feldman's book Software Construction and Data Structures with Ada 95, Addison-Wesley, 1997, ISBN 0-201-88795-9.

Sample output:
Working...
Of 10000 games, 9896 ended before 1200 plays.
West won:       4971
East won:       4925
Average length: 317
Standard dev.:  221
Longest game:   1197
Shortest game:  33
W: K 4 T 9 A A 8 6 J J A 4 2 7 Q 6 Q Q Q 5 K 9 T J 8 3
E: 7 8 T 4 A 7 3 2 6 7 K K 9 4 3 5 8 9 J 3 T 5 5 2 6 2
Distribution of lengths:
0000-0100 |**********
0100-0200 |*****************************
0200-0300 |**********************
0300-0400 |***************
0400-0500 |**********
0500-0600 |*******
0600-0700 |****
0700-0800 |****
0800-0900 |**
0900-1000 |**
1000-1100 |*
1100-1200 |*

Source code:
-------------------------------------------------------
--
-- Simulate the card game of war.
--
-- John B. Matthews, M.D., Gem City Software, 5/14/1998
--
-- Distribution: per GPL
--
-------------------------------------------------------
with Ada.Text_IO;
with Ada.Strings.Fixed;
with Ada.Numerics.Elementary_Functions;
with Cards;
   use type Cards.Value;

procedure War is

package TIO renames Ada.Text_IO;
Max_Games  : constant Integer := 10_000;
Max_Count  : constant Integer := 1_200;
Bin_Size   : constant Integer := 100;
Plot_Size  : constant Integer := 100;

Deck, Copy : Cards.Pack;          -- the game deck and a copy
West, East : Cards.Pack;          -- the players' hands

West_Count : Integer := 0;        -- wins for West
East_Count : Integer := 0;        -- wins for East
Longest    : Integer := 0;        -- longest game
Shortest   : Integer := 9999;     -- shortest game
Game_Count : Integer := 0;        -- game count
Total      : Integer := 0;        -- total play count
Total2     : Float   := 0.0;      -- sum of squares
Sigma      : Float;

type Histogram is array (0 .. Max_Count / Bin_Size - 1) of Integer;
Graph : Histogram := (others => 0);

procedure Deal (Deck : in Cards.Pack; West, East: in out Cards.Pack) is
   Temp : Cards.Pack := Deck;     -- deal from a copy
begin
   Cards.Make_Empty(West);
   Cards.Make_Empty(East);
   while not Cards.Empty(Temp) loop
      Cards.Move(Temp, West);
      Cards.Move(Temp, East);
   end loop;
end Deal;

procedure Play is
   C1, C2 : Cards.Card;
   Table : Cards.Pack;
begin
   Cards.Draw(West, C1);          -- draw from each hand
   Cards.Draw(East, C2);
   if C1.V = C2.V then            -- war!
      while C1.V = C2.V loop      -- play until either wins
         Cards.Place(Table, C1);  -- lay 'em on the table
         Cards.Place(Table, C2);
         Cards.Draw(West, C1);    -- draw two more
         Cards.Draw(East, C2);
      end loop;
   end if;
   if C1.V > C2.V then            -- West wins
      Cards.Place(West, C1);      -- winning card first
      Cards.Place(West, C2);
      while not Cards.Empty(Table) loop
         Cards.Move(Table, West); -- clear the table
      end loop;
   else                           -- East wins
      Cards.Place(East, C2);      -- winning card first
      Cards.Place(East, C1);
      while not Cards.Empty(Table) loop
         Cards.Move(Table, East); -- clear the table
      end loop;
   end if;
end Play;

procedure Play_Game is
   Counter : Integer := 0;
   Bin     : Integer;
begin
   while Counter < Max_Count loop
      Play;
      Counter := Counter + 1;
   end loop;
exception
   when Cards.Pack_Empty =>       -- game over, collect stats
      Total := Total + Counter;
      Total2 := Total2 + Float(Counter * Counter);
      Game_Count := Game_Count + 1;
      Bin := Counter/Bin_Size;
      Graph(Bin) := Graph(Bin) + 1;
      if Cards.Empty(East) then
         West_Count := West_Count + 1;
      else
         East_Count := East_Count + 1;
      end if;
      if Counter < Shortest then
         Shortest := Counter;
         Copy := Deck; -- save this one
      end if;
      if Counter > Longest then
         Longest := Counter;
      end if;
      TIO.Put(ASCII.NUL);
end Play_Game;

function Label (J : Integer) return String is
   use Ada.Strings; use Ada.Strings.Fixed;
   Lower : String := Integer'Image(J * Bin_Size);
   Upper : String := Integer'Image((J + 1) * Bin_Size);
begin
   return Tail(Trim(Lower, Left), 4, '0') & "-" &
      Tail(Trim(Upper, Left), 4, '0') & " |*";
end Label;

begin
   TIO.Put_Line("Working...");
   for I in 1 .. Max_Games loop
      Cards.Shuffle(Deck);
      Deal(Deck, West, East);
      Play_Game;
   end loop;
   Sigma := Ada.Numerics.Elementary_Functions.Sqrt(
      (Total2 - Float(Total) ** 2 / Float(Game_Count)) /
      (Float(Game_Count) - 1.0) );
   TIO.Put("Of" & Integer'Image(Max_Games) & " games,");
   TIO.Put(Integer'Image(Game_Count) & " ended before");
   TIO.Put(Integer'Image(Max_Count) & " plays.");
   TIO.New_Line;
   TIO.Put("West won:      " & Integer'Image(West_Count));
   TIO.New_Line;
   TIO.Put("East won:      " & Integer'Image(East_Count));
   TIO.New_Line;
   TIO.Put("Average length:" & Integer'Image(Total / Game_Count));
   TIO.New_Line;
   TIO.Put("Standard dev.: " & Integer'Image(Integer(Sigma)));
   TIO.New_Line;
   TIO.Put("Longest game:  " & Integer'Image(Longest));
   TIO.New_Line;
   TIO.Put("Shortest game: " & Integer'Image(Shortest));
   TIO.New_Line;
   Deal(Copy, West, East);        -- reconstruct the shortest
   TIO.Put("W: "); Cards.Show(West); TIO.New_Line;
   TIO.Put("E: "); Cards.Show(East); TIO.New_Line;
   TIO.Put_Line("Distribution of lengths:");
   for J in Graph'Range loop
      TIO.Put(Label(J));
      for K in 1 .. (Graph(J) * Plot_Size) / Game_Count loop
         TIO.Put("*");
      end loop;
      TIO.New_Line;
   end loop;
end War;

------------------------------------------------------
--
-- Simulate a pack of playing cards, suitable for war.
--
------------------------------------------------------

package Cards is

type Pack is private;

Pack_Empty : exception; -- attempt to Draw from an empty Pack
Pack_Full  : exception; -- attempt to Place on a full Pack

type Value is (Deuce, Trey, Four, Five,
   Six, Seven, Eight, Nine, Ten, Jack, Queen, King, Ace);

type Suit is (Hearts, Clubs, Diamonds, Spades); -- not required

type Card is record
   V : Value;
   S : Suit;
end record;

procedure Make_Empty (P : in out Pack);
-- Make P empty

procedure Shuffle (P : in out Pack);
-- Shuffle a fresh pack

procedure Draw (P : in out Pack; C : out Card);
-- Draw one card C from the top of pack P

procedure Place (P : in out Pack; C : in Card);
-- Place the card C on the bottom of pack P

procedure Move (P1, P2 : in out Pack);
-- Draw from P1 and Place on P2;

function Empty (P : in Pack) return Boolean;
-- Returns true if P is empty

function Full (P : in Pack) return Boolean;
-- Returns true if P is full
procedure Show (P : in Pack);

private

subtype Card_Range is Positive range 1 .. 52;
type Card_Store is array (Card_Range) of Card;
-- a ring buffer holding up to 52 cards
type Pack is record
   Size : Natural := 0;
   Head : Natural := 1;
   Tail : Natural := 0;
   Card : Card_Store;
end record;

end Cards;

with Ada.Text_IO;
with Ada.Numerics.Discrete_Random;

package body Cards is

package TIO renames Ada.Text_IO;
package Any_Card is new Ada.Numerics.Discrete_Random(Card_Range);
G : Any_Card.Generator;

procedure Make_Empty (P : in out Pack) is
begin
   P.Size := 0;
   P.Head := 1;
   P.Tail := 0;
end Make_Empty;

procedure Shuffle (P : in out Pack) is
N : Positive;
Temporary : Card;
begin
   Make_Empty(P);
   for S in Suit'Range loop
      for V in Value'Range loop
         Place (P, (V, S));
      end loop;
   end loop;
   for J in Card_Range loop
      N := Any_Card.Random(G);
      Temporary := P.Card(N);
      P.Card(N) := P.Card(J);
      P.Card(J) := Temporary;
   end loop;
end Shuffle;

procedure Draw (P : in out Pack; C : out Card) is
begin
   if Empty(P) then
      raise Pack_Empty;
   else
      C := P.Card(P.Head);
      P.Size := P.Size - 1;
      P.Head := P.Head + 1;
      if P.Head > Card_Range'Last then
         P.Head := Card_Range'First;
      end if;
   end if;
end Draw;

procedure Place (P : in out Pack; C : in Card) is
begin
   if Full(P) then
      raise Pack_Full;
   else
      P.Size := P.Size + 1;
      P.Tail := P.Tail + 1;
      if P.Tail > Card_Range'Last then
         P.Tail := Card_Range'First;
      end if;
      P.Card(P.Tail) := C;
   end if;
end Place;

procedure Move (P1, P2 : in out Pack) is
   C : Card;
begin
   Draw (P1, C);
   Place(P2, C);
end Move;

function Empty (P : in Pack) return Boolean is
begin
   return P.Size = 0;
end Empty;

function Full (P : in Pack) return Boolean is
begin
   return P.Size = Card_Range'Last;
end Full;

procedure Show (P : in Pack) is
   Temp : Pack := P;
   C : Card;
begin
   while not Empty(Temp) loop
      Draw(Temp, C);
      case C.V is
         when Deuce => TIO.Put("2 ");
         when Trey  => TIO.Put("3 ");
         when Four  => TIO.Put("4 ");
         when Five  => TIO.Put("5 ");
         when Six   => TIO.Put("6 ");
         when Seven => TIO.Put("7 ");
         when Eight => TIO.Put("8 ");
         when Nine  => TIO.Put("9 ");
         when Ten   => TIO.Put("T ");
         when Jack  => TIO.Put("J ");
         when Queen => TIO.Put("Q ");
         when King  => TIO.Put("K ");
         when Ace   => TIO.Put("A ");
      end case;
   end loop;
end Show;

begin
   Any_Card.Reset(G);
end Cards;
Disclaimer:
Copyright 1998 by John B. Matthews. This program is distributed under the terms of the GNU General Public License (GPL), incorporated herein by reference. The author disclaims any liability arising out of use of this program, including (but not limited to) the time you waste playing with it. Happy programming!

Last updated 24-Oct-2009

Return home.