Thanks to Julio Marchi for this space in MSX All
 

Games Course



Game 21
Updated


  Portugues

  Twenty-one is a famous card game where two players take cards from the deck, aiming at scoring more points than the opponent without exceeding 21 points. In that case, the player looses.


1. Game rules
  Initially, both player and computer receives 2 cards each. The player starts the game, choosing between stand or hit. The player stands when he/she believes that his/her hand is great enough to beat the computer. In that case, he stops the game and the turn is passed to the computer. The player hits when he/she thinks his hand is weak, taking more cards from the deck. When the computer plays, it does the same as the player did. The maximum cards stored at hand is the 5 and whenever someone exceeds 21 points, he/she busts and the oppenent is the winner.

  Card values:
  • A - scores 1 or 11 ponits, according to the player decision.
  • 2 a 10 - scores the same card value.
  • J, Q e K - scores 10 ponits each.


2. Download
  Pascal version:

  Source code and binary: 21.zip

  The file 21.zip has inside:
  • 21.PAS - Source code for PC Free Pascal.
  • 21PT.PAS - Source code in portugues for MSX.
  • 21EN.PAS - Source code in english for MSX.
  • 21PT.COM - Binary file in portuguese. Run under MSX-DOS.
  • 21EN.COM - Binary file in english. Run under MSX-DOS.

  C version:

  Source code: 21_c.zip

  Use Hitech-C to compile, where the patch "time.obj" was added in order to the rand() function works fine.


3. Game structure
  The game is divided into functions and procedures which are responsible for many tasks. The following table describe such functions and procedures.

Function / Procedure Description
clean  Clear all variables.
take_card  Pick up randomly one card for any player.
player_score  Calculate the player or the computer score.
print_hand  Print player or computer hand.
check_winner  Routine to check the winner.
computer  Routine for the computer.
player  Routine for the player.
game  Game main routine.
main body  Starts the game.


4. Code review
  The code was written in Pascal and must be compiled using Turbo Pascal.


  Global variables

  Let's take a look to the game global variables.
var card : array[1..13,1..4] of boolean;
    hand : array[1..5,1..2] of char;
    card_hand : array[1..2] of integer;
  The matrix "card" represents the card selected from the deck. The values from 1 to 13 are directly related to the cards A, 2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K whereas the values from 1 to 4 are related to the four suits -- heart, diamond, spades and clubs. The matrix type is boolean, once this table is designed to mark the cards that has already been selected.
  The matrix "hand" holds the player and the computer hands. The card is not relevant here, but only the score.
  The vector "card_hand" saves the next position inside the "hand" matrix for both player and computer. The player or computer total cards is calculated as follows:
total := card_hand[x] - 1;


 Clean

  This function resets the global variables, or, the game tables. Once the work here is only set initial values to the tables, no further comments are necessary.
{ Clean }
procedure clean;
var f,g : integer;
begin
  { Clear cards }
  for f := 1 to 13 do
  begin
    for g := 1 to 4 do
      card[f,g] := false;
  end;

  { Clear hand }
  for f := 1 to 5 do
  begin
    hand[f,1] := ' ';
    hand[f,2] := ' ';
  end;

  { Clear cards from hand }
  hand_pointer[1] := 1;
  hand_pointer[2] := 1;
end;
  After this routine runs, the tables are valued as follows:

card
  1 2 3 4
1 false false false false
2 false false false false
3 false false false false
4 false false false false
5 false false false false
6 false false false false
7 false false false false
8 false false false false
9 false false false false
10 false false false false
11 false false false false
12 false false false false
13 false false false false


hand
  1 2 3 4 5
1 ' ' ' ' ' ' ' ' ' '
2 ' ' ' ' ' ' ' ' ' '


card_hand
1 2
1 1


  Take_card

  This function removes one card from the deck for one player. The "player" parameter chooses between player (player :=1 ) or computer (player :=2).
{ Take_card }
procedure take_card(player : integer);
var card_value, card_suit : integer;
    flag : boolean;
    card_char : char;
begin
  Randomize;
  flag := false;

  while (not flag) do
  begin
    card_value := Random(13)+1;
    card_suit := Random(4)+1;

    if (card[card_value, card_suit] = false) then
    begin
      card[card_value, card_suit] := true;
      flag := true;
    end;
  end;

  case (card_value) of
    1 : card_char := 'A';
    2..9 : card_char := char(card_value + 48);
    10 : card_char := 'D';
    11 : card_char := 'J';
    12 : card_char := 'K';
    13 : card_char := 'Q';
  end;

  hand[hand_pointer[player], player] := card_char;
  hand_pointer[player] := hand_pointer[player]+1;
end;
  This routine stores the random calculated value for the card at the "card_value" variable, whereas stores the suit at the "card_suit" variable. Then, it checks if the card is available on the deck. If not, another random values are calculated until the card is available.
  After validating the card, it is marked on the table "card". Then, it stores the card value at the "hand" table at the proper player hand.
  The matrix "hand" is a char type, storing the character corresponding to the card value. Once the number 10 has two digits, it is stored as letter "D".
  Let's take a look on how it works. Suppose that the third card from the player is (10,2). In that case, we have:

card
  1 2 3 4
1 true false false false
2 false false false false
3 false false false true
4 false false false false
5 false false false false
6 false false false false
7 false false false false
8 false false false false
9 false false true false
10 false true false false
11 false false false false
12 false false false false
13 true false false false


hand
  1 2 3 4 5
1 'A' '3' 'D' ' ' ' '
2 '9' 'K' ' ' ' ' ' '


  Player_score

  This routine calculates the hand value for the player (1) or the computer (2).
{ Player_score }
function player_score(player : integer) : integer;
var f, total, no_a : integer;
begin
  total := 0;
  no_a:= 0;

  for f := 1 to hand_pointer[player]-1 do
  begin
    case (hand[f, player]) of
      'A' : begin
              total := total + 1;
              no_a := no_a + 1;
            end;
      '2'..'9' : total := total + ord(hand[f, player]) - 48;
      'D', 'J', 'Q', 'K' : total := total + 10;
    end;
  end;

  { Card 'A' may value 11 instead of 1 }
  while (no_a > 0) and (total + 10 <= 21) do
  begin
    total := total + 10;
    no_a := no_a - 1;
  end;

  player_score := total;
end;
  Once the "A" card may value 1 or 11, the final routine check if the score exceeds 21 points when the "A" is valued as 11. If not, the "A" values 11. This routine take in account each "A" present in the player or computer hand.


  Print_hand

  This routine is responsible for printing the player or computer hand on the screen.
{ Print_hand }
procedure print_hand(player : integer);
var f, total : integer;
begin
  total := player_score(player);

  for f := 1 to hand_pointer[player]-1 do
  begin
    if (hand[f, player] <> 'D') then
      write(hand[f, player] + ' ')
    else
      write('10 ');
  end;

  writeln('- ', total);
end;


  Check_winner

  This procedure checks who is the winner.
{ Check_winner }
procedure check_winner;
var f, total_player, total_cpu : integer;
begin
  { Get score }
  total_player := player_score(1);
  total_cpu := player_score(2);

  { Check winner }
  writeln;
  if ((total_player>total_cpu) and (total_player<=21)) or (total_cpu>21) then
    writeln('You win!');
  if ((total_player<total_cpu) and (total_cpu<=21)) or (total_player>21) then
    writeln('I win!');
  if (total_player=total_cpu) then
    writeln('Draw!');

  { Write final hands }
  write('My hand: ');
  print_hand(2);
  write('Your hand: ');
  print_hand(1);

end;
  After calculating the scores, the winner is declared:
  • The player - must score more than the computer, at the same time do not exceeds 21 points. If the opponent exceeds, the player wins.
  • The computer - must score more than the player, at the same time do not exceeds 21 points. If the opponent exceeds, the computer wins.
  • Empate - if both score the same.
  Obs: it is not allowed to both players exceed 21 points together.


  Computer

  This procedure controls the computer in the game.
{ Computer }
procedure computer;
label break;
var total_cpu : integer;
    p : real;
    flag : boolean;
begin
  Randomize;
  flag := false;

  while (not flag) do
  begin
    total_cpu := player_score(2);

    if (total_cpu > 21) then
    begin
      writeln('Oh, I busted !!');
      goto break;
    end;

    if (hand_pointer[2] > 5) then
    begin
      writeln('My last hand !');
      goto break;
    end;

    if (total_cpu = 21) then
      goto break;

    { Probabilistic value used to decide if cpu hits or stands }
    p := random(3)*0.1 + (total_cpu/21)*0.7;

    if (p > 0.7) then
    begin
      flag := true;
      writeln('The computer stands ...');
    end
    else
    begin
      take_card(2);
      writeln('The computer hits ...');
    end;
  end;

  break :
end;
  The routine goes into a loop, making the computer plays. It will be interrupted when:
  • The computer exceeds 21 points.
  • The computer hits 21 points.
  • The computer has a full hand.
  • The computer stands.
  Obs: The full hand verification must come after the bust verification, otherwise the full hand bust will not be noticed by the program.

  A calculation using a combination of probability and random numbers is used in order to make the computer take a decision between stand or hit. In such case, the greater the computer score the greater is the probability of him to stand.


  Player

  This function controls the computer in the game.
{ Player }
function player:boolean;
label break;
var total_player : integer;
    c : char;
    flag : boolean;
begin
  flag := false;
  player := true;

  while (not flag) do
  begin
    total_player := player_score(1);

    if (total_player > 21) then
    begin
      writeln('Oh, you busted !!');
      player := false;
      goto break;
    end;

    if (hand_pointer[1] > 5) then
    begin
      writeln('Your last hand !');
      goto break;
    end;

    if (total_player = 21) then
      goto break;

    { Waits for the player decision }
    repeat
      writeln('(h) - hit / (s) - stand');
      read(kbd,c);
    until (c='h') or (c='s');

    if (c='s') then
    begin
      flag := true;
      writeln('The player stands ...');
    end
    else
      take_card(1);

    { Print player hand }
    write('Your hand: ');
    print_hand(1);
  end;

  break :
end;
  The routine goes into a loop, controlling the player plays. It will be interrupted when:
  • The player exceeds 21 points.
  • The player hits 21 points.
  • The player has a full hand.
  • The player stands.

  Game

  This is the game main control routine.
{ Game }
procedure game;
var f : integer;
begin
  clean;

  { Take 2 cards for player and cpu }
  for f := 1 to 2 do
  begin
    take_card(1);
    take_card(2);
  end;

  { Show initial hands }
  if (hand[1,2] = 'D') then
    writeln('My hand: 10 ?')
  else
    writeln('My hand: ' + hand[1,2] + ' ?');
  write('Your hand: ');
  print_hand(1);

  { Give handle to the player, then to the cpu }
  if player then
    computer;

  check_winner;
end;
  The first step is to give two cards for each player. Then, the cards are printed on screen. Only the first card from the computer is revealed now.
  Each player will perform all rounds at once, starting with the human player. If the human player busts, the function "player" returns false and the computer does not even play. This ensures that both players cannot bust together.
  After all players play, we check the winner.


  Pascal's main routine

  Here it is possible to handle multiple games. Nevertheless, we call the game once.
{ Main body }
begin
  clrscr;
  writeln('-= 21 Game - MarMSX 2017 =-');
  writeln;
  game;
end.


5. Complete source code
  The code was written in Pascal and must be compiled using Turbo Pascal.
{ Global Variables }
var card : array[1..13,1..4] of boolean;
    hand : array[1..5,1..2] of char;
    hand_pointer : array[1..2] of integer;


{ Clean }
procedure clean;
var f,g : integer;
begin
  { Clear cards }
  for f := 1 to 13 do
  begin
    for g := 1 to 4 do
      card[f,g] := false;
  end;

  { Clear hand }
  for f := 1 to 5 do
  begin
    hand[f,1] := ' ';
    hand[f,2] := ' ';
  end;

  { Clear cards from hand }
  hand_pointer[1] := 1;
  hand_pointer[2] := 1;
end;


{ Take_card }
procedure take_card(player : integer);
var card_value, card_suit : integer;
    flag : boolean;
    card_char : char;
begin
  Randomize;
  flag := false;

  while (not flag) do
  begin
    card_value := Random(13)+1;
    card_suit := Random(4)+1;

    if (card[card_value, card_suit] = false) then
    begin
      card[card_value, card_suit] := true;
      flag := true;
    end;
  end;

  case (card_value) of
    1 : card_char := 'A';
    2..9 : card_char := char(card_value + 48);
    10 : card_char := 'D';
    11 : card_char := 'J';
    12 : card_char := 'K';
    13 : card_char := 'Q';
  end;

  hand[hand_pointer[player], player] := card_char;
  hand_pointer[player] := hand_pointer[player]+1;
end;


{ Player_score }
function player_score(player : integer) : integer;
var f, total, no_a : integer;
begin
  total := 0;
  no_a:= 0;

  for f := 1 to hand_pointer[player]-1 do
  begin
    case (hand[f, player]) of
      'A' : begin
              total := total + 1;
              no_a := no_a + 1;
            end;
      '2'..'9' : total := total + ord(hand[f, player]) - 48;
      'D', 'J', 'Q', 'K' : total := total + 10;
    end;
  end;

  { Card 'A' may value 11 instead of 1 }
  while (no_a > 0) and (total + 10 <= 21) do
  begin
    total := total + 10;
    no_a := no_a - 1;
  end;

  player_score := total;
end;


{ Print_hand }
procedure print_hand(player : integer);
var f, total : integer;
begin
  total := player_score(player);

  for f := 1 to hand_pointer[player]-1 do
  begin
    if (hand[f, player] <> 'D') then
      write(hand[f, player] + ' ')
    else
      write('10 ');
  end;

  writeln('- ', total);
end;


{ Check_winner }
procedure check_winner;
var f, total_player, total_cpu : integer;
begin
  { Get score }
  total_player := player_score(1);
  total_cpu := player_score(2);

  { Check winner }
  writeln;
  if ((total_player>total_cpu) and (total_player<=21)) or (total_cpu>21) then
    writeln('You win!');
  if ((total_player<total_cpu) and (total_cpu<=21)) or (total_player>21) then
    writeln('I win!');
  if (total_player=total_cpu) then
    writeln('Draw!');

  { Write final hands }
  write('My hand: ');
  print_hand(2);
  write('Your hand: ');
  print_hand(1);

end;


{ Computer }
procedure computer;
label break;
var total_cpu : integer;
    p : real;
    flag : boolean;
begin
  Randomize;
  flag := false;

  while (not flag) do
  begin
    total_cpu := player_score(2);

    if (total_cpu > 21) then
    begin
      writeln('Oh, I busted !!');
      goto break;
    end;

    if (hand_pointer[2] > 5) then
    begin
      writeln('My last hand !');
      goto break;
    end;

    if (total_cpu = 21) then
      goto break;

    { Probabilistic value used to decide if cpu hits or stands }
    p := random(3)*0.1 + (total_cpu/21)*0.7;

    if (p > 0.7) then
    begin
      flag := true;
      writeln('The computer stands ...');
    end
    else
    begin
      take_card(2);
      writeln('The computer hits ...');
    end;
  end;

  break :
end;


{ Player }
function player:boolean;
label break;
var total_player : integer;
    c : char;
    flag : boolean;
begin
  flag := false;
  player := true;

  while (not flag) do
  begin
    total_player := player_score(1);

    if (total_player > 21) then
    begin
      writeln('Oh, you busted !!');
      player := false;
      goto break;
    end;

    if (hand_pointer[1] > 5) then
    begin
      writeln('Your last hand !');
      goto break;
    end;

    if (total_player = 21) then
      goto break;

    { Waits for the player decision }
    repeat
      writeln('(h) - hit / (s) - stand');
      read(kbd,c);
    until (c='h') or (c='s');

    if (c='s') then
    begin
      flag := true;
      writeln('The player stands ...');
    end
    else
      take_card(1);

    { Print player hand }
    write('Your hand: ');
    print_hand(1);
  end;

  break :
end;


{ Game }
procedure game;
var f : integer;
begin
  clean;

  { Take 2 cards for player and cpu }
  for f := 1 to 2 do
  begin
    take_card(1);
    take_card(2);
  end;

  { Show initial hands }
  if (hand[1,2] = 'D') then
    writeln('My hand: 10 ?')
  else
    writeln('My hand: ' + hand[1,2] + ' ?');
  write('Your hand: ');
  print_hand(1);

  { Give handle to the player, then to the cpu }
  if player then
    computer;

  check_winner;
end;


{ Main body }
begin
  clrscr;
  writeln('-= 21 Game - MarMSX 2017 =-');
  writeln;
  game;
end.

Marcelo Silveira
Systems and Computing Engineer
Expert in Image Processing and Artificial Intelligence
© MarMSX 1999-2024