|
Curso de Jogos |
Jogo 21 Atualizado
English
21 é um famoso jogo de cartas, onde dois jogadores compram cartas de uma pilha e o objetivo é somar o maior número de pontos que o adversário, desde que no máximo até 21 pontos. Caso a soma dos valores das cartas de qualquer jogador ultrapasse o valor 21, ele perde a partida.
1. Regras do jogo
O jogador e o computador recebem inicialmente duas cartas cada. O jogador inicia a partida, decidindo entre permanecer com a mão que está (opção stand), ou comprar mais cartas, na tentativa de atingir o máximo de pontos, desde que seja menor ou igual a 21 (opção hit). Quando o jogador passa a vez, o computador joga da mesma maneira, tentando atingir a melhor pontuação. Cada jogador pode no máximo acumular 5 cartas na mão e sempre que alguém estoura o limite de 21 pontos, o adversário vence.
Valor das cartas:
- A - vale 1 ou 11 pontos, de acordo com a vontade do jogador.
- 2 a 10 - vale o mesmo valor da carta.
- J, Q e K - vale 10 pontos cada uma.
2. Download
Versão em Pascal:
Código fonte e binário: 21.zip
O arquivo 21.zip contém:
- 21.PAS - Fonte para compilar no Free Pascal do PC.
- 21PT.PAS - Fonte em português para compilar no MSX.
- 21EN.PAS - Fonte em inglês para compilar no MSX.
- 21PT.COM - Arquivo executável em português para rodar no MSX-DOS.
- 21EN.COM - Arquivo executável em inglês para rodar no MSX-DOS.
Versão em C:
Código fonte: 21_c.zip
Compilar utilizando o Hitech-C, no qual o patch "time.obj" foi adicionado para o rand() funcionar corretamente.
3. Estrutura do jogo
O jogo está dividido em funções e procedimentos, que são responsáveis por diversas tarefas. A tabela a seguir apresenta essas funções e procedimentos.
Função / Procedure |
Descrição |
clean |
Limpa todas as variáveis do
sistema. |
take_card |
Retira uma carta para um dos jogadores. |
player_score |
Calcula a pontuação de um dos jogadores. |
print_hand |
Imprime as cartas na mão de um dos jogadores. |
check_winner |
Verifica quem venceu o jogo. |
computer |
Rotina de jogo do computador. |
player |
Rotina de jogo do jogador. |
game |
Rotina de controle do jogo |
principal |
Inicia o jogo |
4. Explicação do código
O código está escrito em Pascal e deve ser compilado com o Turbo Pascal.
Variáveis Globais
Primeiro, vamos entender as variáveis globais do jogo:
var card : array[1..13,1..4] of boolean;
hand : array[1..5,1..2] of char;
hand_pointer : array[1..2] of integer;
A matriz "card" representa o baralho, onde os valores de 1 a 13 representam os valores A, 2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K e os valores de 1 a 4 os naipes ouros, paus, valete e damas. o tipo da matriz é booleano, uma vez que desejamos apenas marcar as cartas retiradas do baralho.
A matriz "hand" contém as mãos do jogador e do computador. O valor do naipe é irrelevante.
O vetor "hand_pointer" contém a próxima posição da matriz "hand" de cada jogador. Nesse caso, o total de cartas é calculador da seguinte maneira:
total := hand_pointer[jogador] - 1;
Clean
Esta função tem como por objetivo ajustar as tabelas com os valores iniciais, ou seja, limpar as tabelas. Ela somente atribui valores às tabelas, não sendo necessários quaisquer comentários.
{ Clean }
procedure clean;
var f,g : integer;
begin
{ Limpa cartas }
for f := 1 to 13 do
begin
for g := 1 to 4 do
card[f,g] := false;
end;
{ Limpa mãos }
for f := 1 to 5 do
begin
hand[f,1] := ' ';
hand[f,2] := ' ';
end;
{ Limpa os ponteiros }
hand_pointer[1] := 1;
hand_pointer[2] := 1;
end;
Depois de rodarem, as tabelas ficam:
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 |
' ' |
' ' |
' ' |
' ' |
' ' |
hand_pointer
Take_card
Esta função retira uma carta do baralha para um dos jogadores. O parâmetro "player" escolhe entre o jogador (player := 1) ou o computador (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;
A rotina armazena o valor sorteado da carta na variável "card_value" e o naipe na variável "card_suit". Então, ele verifica se esta carta já foi comprada, consultando a tabela. Se ela já tiver sido comprada, um novo sorteio é realizado até que a carta seja inédita.
Após sortear a carta, o programa marca ela na tabela "card". Em seguida, anota a carta sorteada na matriz "hand", na mão do jogador correspondente.
A matriz "hand" é do tipo char, que irá armazenar o caractere correspondente à carta sorteada. Há uma exceção que é a carta 10, no qual possui dois algarismos. Nesse caso, armazena-se o caractere "D" para ela.
Vejamos um exemplo. Suponha que a terceira carta retirada pelo jogador tenha sido (10,2). Dessa forma, teremos:
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
Essa rotina tem como objetivo calcular o valor das cartas na mão do jogador (1) ou computador (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;
{ A carta 'A' pode valer 11 em vez de 1 }
while (no_a > 0) and (total + 10 <= 21) do
begin
total := total + 10;
no_a := no_a - 1;
end;
player_score := total;
end;
Como a carta "A" pode valer 1 ou 11, a rotina final verifica se com 11 pontos a mão irá estourar. Se não estourar, faz o "A" valer 11 pontos. A rotina é feita individualmente para cada "A" presente na mão do jogador.
Print_hand
Esta rotina é responsável por imprimir as cartas de cada um dos jogadores na tela.
{ 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
Esta procedure verifica quem venceu o jogo.
{ Check_winner }
procedure check_winner;
var f, total_player, total_cpu : integer;
begin
{ Calcula pontos }
total_player := player_score(1);
total_cpu := player_score(2);
{ Verifica o vencedor }
writeln;
if ((total_player>total_cpu) and (total_player<=21)) or (total_cpu>21) then
writeln('Voce ganhou!');
if ((total_player<total_cpu) and (total_cpu<=21)) or (total_player>21) then
writeln('Eu ganhei!');
if (total_player=total_cpu) then
writeln('Deu empate!');
{ Imprime as mãos }
write('Minhas cartas: ');
print_hand(2);
write('Suas cartas: ');
print_hand(1);
end;
Após calcular a pontuação de cada jogador, declara-se o vencedor:
- O jogador - tem que fazer mais pontos que o computador, ao mesmo tempo que não estoura. Caso o adversário estoure, é declarado vencedor.
- O computador - tem que fazer mais pontos que o jogador, ao mesmo tempo que não estoura. Caso o adversário estoure, é declarado vencedor.
- Empate - Caso a pontuação de ambos seja a mesma.
Obs: não é permitido que ambos estourem o placar.
Computer
Esta procedure controla o jogo do computador.
{ 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('Ah nao, eu estourei !!');
goto break;
end;
if (hand_pointer[2] > 5) then
begin
writeln('Minha ultima carta !');
goto break;
end;
if (total_cpu = 21) then
goto break;
{ Probabilidade utilizada para decidir se o cumputador pára }
p := random(3)*0.1 + (total_cpu/21)*0.7;
if (p > 0.7) then
begin
flag := true;
writeln('O computador permanece ...');
end
else
begin
take_card(2);
writeln('O computador compra ...');
end;
end;
break :
end;
A rotina entra em um loop, realizando todas as jogadas do computador. Ela será interrompida, quando:
- O computador estoura a mão.
- O computador atinge 21 pontos.
- O computador está com a mão cheia.
- O computador decide parar (stand).
Obs: a verificação da mão cheia deverá estar após a verificação do estouro, senão o estouro com a mão cheia não será percebido.
Um cálculo contendo uma combinação de probabilidade e números aleatórios é utilizado para fazer com que o computador decida entre parar (stand) ou continuar comprando (hit). Nesse caso, quanto maior for a mão do computador, maior a probabilidade dele parar.
Player
Esta função controla o jogador.
{ 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, voce estourou !!');
player := false;
goto break;
end;
if (hand_pointer[1] > 5) then
begin
writeln('Sua ultima carta !');
goto break;
end;
if (total_player = 21) then
goto break;
{ Aguarda a decisão do jogador }
repeat
writeln('(h) - hit / (s) - stand');
read(kbd,c);
until (c='h') or (c='s');
if (c='s') then
begin
flag := true;
writeln('O jogador permanece ...');
end
else
take_card(1);
{ Imprime a mão do jogador }
write('Suas cartas: ');
print_hand(1);
end;
break :
end;
A rotina entra em um loop, controlando as jogadas do jogador. Ela será interrompida, quando:
- O jogador estoura a mão.
- O jogador atinge 21 pontos.
- O jogador está com a mão cheia.
- O jogador decide parar (stand).
Game
Esta função gerencia o jogo todo.
{ Game }
procedure game;
var f : integer;
begin
clean;
{ Retira duas cartas para cada jogador }
for f := 1 to 2 do
begin
take_card(1);
take_card(2);
end;
{ Mostra as mãos iniciais }
if (hand[1,2] = 'D') then
writeln('Minhas cartas: 10 ?')
else
writeln('Minhas cartas: ' + hand[1,2] + ' ?');
write('Suas cartas: ');
print_hand(1);
{ Dá o controle para o jogador, depois para a cpu }
if player then
computer;
check_winner;
end;
O primeiro passo é distribuir duas cartas para cada jogador. Em seguida, as cartas são impressas na tela. Somente a primeira carta do computador é revelada nesse momento.
Cada jogador irá realizar suas jogadas todas de uma vez, começando pelo jogador humano. Caso o jogador humano estoure sua mão, a função "player" retorna falso e o computador nem joga. Isso garante que ambos não irão estourar.
Após as jogadas de cada um, verifica-se o vencedor.
Rotina principal
Nesta rotina, podemos controlar quantos jogos serão realizados. Neste
exemplo, somente um jogo é realizado.
{ Main body }
begin
clrscr;
writeln('-= Jogo 21 - MarMSX 2017 =-');
writeln;
game;
end.
5. Código fonte completo
O código fonte está na linguagem Pascal e deve ser compilado com o Turbo Pascal.
{ Variáveis globais }
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
{ Limpa cartas }
for f := 1 to 13 do
begin
for g := 1 to 4 do
card[f,g] := false;
end;
{ Limpa mãos }
for f := 1 to 5 do
begin
hand[f,1] := ' ';
hand[f,2] := ' ';
end;
{ Limpa os ponteiros }
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;
{ A carta 'A' pode valer 11 em vez de 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
{ Calcula pontos }
total_player := player_score(1);
total_cpu := player_score(2);
{ Verifica o vencedor }
writeln;
if ((total_player>total_cpu) and (total_player<=21)) or (total_cpu>21) then
writeln('Voce ganhou!');
if ((total_player<total_cpu) and (total_cpu<=21)) or (total_player>21) then
writeln('Eu ganhei!');
if (total_player=total_cpu) then
writeln('Deu empate!');
{ Imprime as mãos }
write('Minhas cartas: ');
print_hand(2);
write('Suas cartas: ');
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('Ah nao, eu estourei !!');
goto break;
end;
if (hand_pointer[2] > 5) then
begin
writeln('Minha ultima carta !');
goto break;
end;
if (total_cpu = 21) then
goto break;
{ Probabilidade utilizada para decidir se o cumputador pára }
p := random(3)*0.1 + (total_cpu/21)*0.7;
if (p > 0.7) then
begin
flag := true;
writeln('O computador permanece ...');
end
else
begin
take_card(2);
writeln('O computador compra ...');
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, voce estourou !!');
player := false;
goto break;
end;
if (hand_pointer[1] > 5) then
begin
writeln('Sua ultima carta !');
goto break;
end;
if (total_player = 21) then
goto break;
{ Aguarda a decisão do jogador }
repeat
writeln('(h) - hit / (s) - stand');
read(kbd,c);
until (c='h') or (c='s');
if (c='s') then
begin
flag := true;
writeln('O jogador permanece ...');
end
else
take_card(1);
{ Imprime a mão do jogador }
write('Suas cartas: ');
print_hand(1);
end;
break :
end;
{ Game }
procedure game;
var f : integer;
begin
clean;
{ Retira duas cartas para cada jogador }
for f := 1 to 2 do
begin
take_card(1);
take_card(2);
end;
{ Mostra as mãos iniciais }
if (hand[1,2] = 'D') then
writeln('Minhas cartas: 10 ?')
else
writeln('Minhas cartas: ' + hand[1,2] + ' ?');
write('Suas cartas: ');
print_hand(1);
{ Dá o controle para o jogador, depois para a cpu }
if player then
computer;
check_winner;
end;
{ Main body }
begin
clrscr;
writeln('-= Jogo 21 - MarMSX 2017 =-');
writeln;
game;
end.
|