Obrigado a Julio Marchi pelo espaço cedido na MSX All
 

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
1 2
1 1


  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.

Marcelo Silveira
Engenheiro de Sistemas e Computação
Especialista em Processamento de Imagens e Inteligência Artificial
© MarMSX 1999-2025