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