paulo1205
(usa Ubuntu)
Enviado em 20/02/2021 - 11:36h
SrKon escreveu:
Estou encontrando os seguintes erros no programa
- Primeira letra digitada, estando certa ou não, é contabilizada como erro.
- Indicar se a letra já foi digitada, não funciona.
- Em alguns casos ele conta 2 erros na primeira tentativa.
Para mim, parece esta contundente. Não sei se esqueci de zerar algo ou adicionar algo.
O código que tenho:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <ctype.h>
#include <stdbool.h>
char banco[30][30]=
{
"notebook",
"smartphone",
"pendrive",
"computador",
"seguraça"
Se você vai considerar
ç como letra válida e diferente de
c , então provavelmente não vai poder usar apenas 26 elementos para representar conjuntos de letras já digitadas ou que compõem a palavra.
Além disso, parece que faltou um
n na palavra.
};
char wordSorteada[30] = {0};
char wordDigitada[30] = {0};
Eu acho meio feio misturar línguas diferentes nos nomes de variáveis de um mesmo programa, e pior ainda quando a mistura acontece na composição do nome de uma mesma variável.
Minha sugestão é que você escolha um padrão — tudo em Português ou tudo em Inglês — e que seja consistente ao longo de todo o programa.
Além disso, para variáveis estáticas, globais ou declaradas em escopo de arquivo, não é necessário atribuir valor inicial igual a zero, pois os valores são zerados por padrão. Só é necessário declarar valores iniciais quando você quiser que eles
não sejam nulos. Apenas as variáveis locais e de armazenamento automático, cujos valores
default são indeterminados, são as que precisam ser explícitas sobre valores iniciais que tenham de ser iguais a zero.
int sorteio()
{
srand(time(NULL));
O fato de você ter chamado a função de
sorteio () dá a entender que a função poderia ser chamada mais de uma vez ao longo do programa (você não fez isso neste programa ainda, mas, além de sugerido pelo nome, poderia vir a fazê-lo caso o programa fosse modificado para apresentar a opção de jogar novamente). Se esse for o caso, então você deve saber que a forma típica de usar
srand () é chamá-la apenas uma vez, no início do programa, não a cada sorteio realizado. Recomendo, portanto, tirar a chamada de dentro da função.
Se você de maneira nenhuma pretende que a função seja chamada mais de uma vez, então não faz muito sentido criar uma função separada cujo interior é tão simples.
Por que essa forma de calcular? Por que não fazer simplesmente “
rand()%5 ”?
Em tempo: muitas implementações de
rand () têm baixa aleatoriedade nos bits de mais baixa ordem, de modo que simplesmente pegar o resto da divisão por um valor
N também pequeno pode não produzir muito bons resultados. Isso pode ser melhorado através da transferência informação contida nos bits de mais alta ordem, com uma expressão parecida com a seguinte: “
(int)((rand()/(1.0+RAND_MAX))*N) ”.
}
void imprimir()
{
int tam = strlen(wordSorteada);
printf("\n%d letras\n", tam);
for(int r = 0; r < strlen(wordSorteada); r++)
{
printf("%c", wordDigitada[r]);
}
Não entendi a lógica de tomar o tamanho de uma
string (
wordSortead ) para varrer e imprimeir os caracteres de outra
string (
wordDigitada ). Eu perdi alguma coisa, ou foi você que se confundiu? Porque, se eu não tiver perdido nada, muito provavelmente você poderia substituir essa função por um mero “
printf("\n%zu letras\n%s", strlen(wordSorteada), wordDigitada); ”, que, por ser tão simples, novamente não precisaria residir numa função separada.
Em todo caso, note que a cláusula de parada do laço de repetição, que é testada a cada iteração do laço, contém uma chamada a
strlen (). A não ser que o compilador seja muito, MUITO bom em otimização, ele possivelmente não tem como prever que o tamanho da
string não mudou entre uma iteração e outra, e possivelmente será obrigado a recalcular o comprimento de
wordSorteada a cada iteração, elevando o custo do laço de repetição de
O(n) para
O(n²) . Curiosamente, você já tinha definido uma variável (
tam ) com o valor de
strlen(wordSorteada) , e eu não entendi por que você não usou essa variável na cláusula de parada, em lugar de repetir a chamada a
strlen () a cada iteração.
}
void copia(int lin)
{
for(int r = 0; r < 30; r++)
{
wordSorteada[r] = banco[lin][r];
}
Outra função extremamente simples demais para justificar sua existência, podendo ser substituída simplesmente por “
strcpy(wordSorteada, banco[lin]); ”.
}
void limpaWord()
{
for(int r = 0; r < strlen(wordSorteada); r++)
{
wordDigitada[r] = '_';
}
De novo, eu vejo com muita suspeição essa chamada a
strlen () na cláusula de parada, avaliada a cada iteração. Esse foi um vício que você usou ao longo de todo o programa.
Se você não quiser usar uma variável auxiliar para não ter de invocar a função a cada iteração, pode mudar a forma e a direção do laço de repetição, chamado
strlen () apenas uma vez, na cláusula de inicialização, e ir do fim para o início da
string .
for(size_t r = strlen(wordSorteada); r-- > 0; )
wordDigitada[r]='_';
}
void imprimirForca(int erro)
{
switch(erro)
{
case 0:
printf("\n-------\n");
printf("| \n");
printf("| \n");
printf("| \n");
break;
case 1:
printf("\n-------\n");
printf("| o\n");
printf("| \n");
printf("| \n");
break;
case 2:
printf("\n-------\n");
printf("| o\n");
printf("| / \n");
printf("| \n");
break;
case 3:
printf("\n-------\n");
printf("| o\n");
printf("| /| \n");
printf("| \n");
break;
case 4:
printf("\n-------\n");
printf("| o\n");
printf("| /|\\ \n");
printf("| \n");
break;
case 5:
printf("\n-------\n");
printf("| o\n");
printf("| /|\\ \n");
printf("| / \n");
break;
case 6://default:
printf("\n-------\n");
printf("| o\n");
printf("| /|\\ \n");
printf("| / \\ \n");
break;
}
O
break não é necessário no último
case label de um
switch (tanto
switch quanto
break são formas disfarçadas de
goto ).
A primeira linha impressa em todos os casos é exatamente a mesma. Assim sendo, ela poderia ser impressa antes do
switch , e ser removida de cada um dos casos.
As demais linhas poderiam ser impressas com uma única chamada a
printf () ou
puts (), em vez de três. Não sei se lhe foi ensinado mas, em C, a declaração
const char *const str_array[5]={
"paulo1205",
"paulo""1205",
"paulo" "1205",
"paulo"
"1025",
"p" "a" "u" "l" "o" /* bla, bla, bla */ "1" "2" "0" "5"
}; produz um
array com cinco elementos que são
strings constantes com exatamente o mesmo conteúdo. Então você também poderia trocar algo como
printf("| o\n");
printf("| /|\\ \n");
printf("| / \\ \n"); pelo seguinte (economizando a quantidade de chamadas a funções, sem perder a formatação visual do código fonte).
printf("| o\n"
"| /|\\ \n"
"| / \\ \n");
}
bool verifyLetter(char letra, char vetor[26])
{
for(int i = 0; i < strlen(vetor); i++)
{
if(letra == vetor[ i])
{
return true;
}
}
De novo o
strlen () na cláusula de parada. E, de novo, para fazer uma operação que já existe na biblioteca padrão; você poderia ter usado simplesmente “
strchr(vetor, letra)!=NULL ”.
return false;
}
void alocandoLetra(char letra)
{
for(int i = 0; i < strlen(wordSorteada); i++)
De novo...
{
if(wordSorteada[ i] == letra)
{
wordDigitada[ i] = letra;
}
}
}
void letrasDigitadas(char let[26], int tam)
{
printf("\nJá fora digitadas: ");
for(int r = 0; r < strlen(let); r++)
... e de novo...
{
printf("%c,", let[r]);
}
printf("\n");
Toda esta função poderia ser substituída simplesmente por uma única chamada a
printf ().
}
bool wordCompleta()
{
for(int r=0;r<strlen(wordSorteada);r++)
... e de novo.
{
if(wordDigitada[r] == '_')
{
return false;
}
}
return true;
Em outras palavras, esta função testa se
wordDigitada é igual a
wordSorteada . Efeito semelhante poderia ser conseguido com “
strcmp(wordDigitada, wordSorteada)==0 ”.
}
int main()
{
int palavra = sorteio();
copia(palavra);
limpaWord();
int contLetras = 0;
int contaErros = 0;
char letraDig[26] = {0};
char letter;
do
{
system("clear");
Evite usar
system (), pois seu uso indiscriminado pode ser bastante ineficiente e inseguro.
Procure restringir seu uso a situações em que ele seja estritamente necessário, para executar operações que seriam inviáveis de fazer de outra forma. Mesmo quando isso for necessário, tome medidas para minimizar a chance de dar problema de segurança, tais como usar o caminho completo do programa que você quer invocar (por exemplo: em vez de apenas
"clear" , prefira
"/usr/bin/clear" )
e predefinir o valor da variável de ambiente
PATH para um conjunto mínimo e seguro de diretórios (fazendo, por exemplo, algo como “
setenv("PATH", "/bin:/usr/bin", 1); ” logo no início do programa), e possivelmente alterando também as variáveis de ambiente que definem aspectos de
locale .
Veja as considerações sobre o uso de
system )() e alternativas para limpeza de tela nas mensagens de nº 10 a 12 no tópico
https://www.vivaolinux.com.br/topico/C-C++/Preciso-fazer-um-programa-em-C-para-cadastra-alunos-consu... . Uma discussão ainda mais aprofundada de possíveis problemas com
system () pode ser encontrada na 3ª mensagem no tópico
https://www.vivaolinux.com.br/topico/C-C++/Duvida-com-realloc-em-C , a partir do 12º parágrafo.
imprimirForca(contaErros);
imprimir();
letrasDigitadas(letraDig, contLetras);
printf("\n\nTentativas restantes: %d\n", 6-contaErros);
printf("\n\n");
printf("Digite uma letra: \n");
scanf("%c", &letter);
Esta operação de leitura possivelmente é uma parte importante do problema que você reportou, porque não basta que você aperte uma letra, mas tem de digitar uma letra seguida da tecla <Enter> (ou <Return>, se você estiver num computador da Apple), que provoca o aparecimento de dois caracteres na entrada: a letra em si, e um caráter de fim-de-linha (
'\n' ) correspondente ao <Enter>. Cada um desses caracteres será consumido numa iteração diferente do laço de repetição, e você não fez nenhuma validação de se o valor lido realmente corresponde a uma letra ou a um caráter válido.
A leitura de dados de entrada a partir do terminal exige certos cuidados, porque ela é normalmente orientada a linha (i.e. o sistema procura ler sempre linhas inteiras; mesmo que você solicite apenas um caráter, o sistema vai verificar se esse caráter já existe num
buffer interno e, se existir, ele devolve o tal caráter imediatamente, mas, caso não exista, o sistema vai procurar encher o
buffer com tanto caracteres quantos forem possíveis, até encontrar uma marca de fim-de-linha ou até não haver mais espaço no tal
buffer interno antes de lhe entregar o único caráter que você pediu). Além disso, a função
scanf () é muito complexa (possivelmente a mais complexa de todas as funções da biblioteca padrão do C), e requer uma boa e cautelosa leitura de sua documentação — e possivelmente alguma prática — para ser usada de modo efetivo e competente, com o devido conhecimento de quando acontecem descartes automáticos de caracteres e quando eles não ocorrem, como usar conversões, conversões sem atribuição/alteração de valor ou textos fixos, limitações de largura ou de conjuntos de caracteres, alocação automática de memória, indicadores de quantos caracteres foram consumidos, alteração de ordem de argumentos que podem receber atribuições e sinalização de erros ou grau de sucesso das conversões, entre outros aspectos. E eu não estou exagerando.
No mínimo, eu sugiro a você fazer as seguintes mudanças referentes a este pedaço:
• teste o valor de retorno de
scanf (), para ter certeza de que não ocorreu erro de leitura;
• dentro da própria
string de formatação de
scanf (), procure descartar todos os caracteres que não interessarem (especialmente espaços, incluindo quebras de linha, cujo tratamento é muito fácil);
• após a leitura, verifique que fruto de leitura seja válido, e dê o tratamento adequado caso não seja.
Acima, uma das palavras do dicionário incluía um
ç . Lembre-se de que
ç e vogais acentuadas são caracteres distintos do
c comum e das vogais não-acentuadas. Além disso, dependendo do conjunto de caracteres que esteja em uso tanto no momento da compilação quanto no momento da execução, a representação dos caracteres acentuados ou com cedilha pode ser diferente, inclusive com mais do que um
byte (ou caráter, pois em C cada
char corresponde exatamente a um
byte ) por sinal gráfico (por exemplo, com o conjunto de caracteres europeu e codificação ISO-8859-1, o
c corresponde ao
byte com valor 99 e o
c corresponde ao valor 231; já com o Unicode e codificação UTF-8, que é a mais comum no Linux hoje em dia, o
c corresponde ao mesmo
byte com valor 99, mas o
ç corresponde a um par de
bytes , sendo o primeiro com valor 195 e o segundo com o valor 167). Se você quiser tratar caracteres com marcas diacríticas, seu programa certamente vai ter de ser mais sofisticado.
letter = tolower(letter);
Aqui já há um exemplo de possível problema com caracteres com marcas diacríticas. Mesmo num conjunto de caracteres que usam estritamente apenas um
byte , como o ISO-8859-1, todos os caracteres acentuados utilizam o oitavo
bit do
byte , o que, nos nossos PCs, corresponde a um valor negativo. A função
tolower () não trata tais valores, a não ser que você os converta antes para uma representação de valor sem sinal. Assim sendo, depois de ter certeza de que a leitura foi bem sucedida, você deveria fazer algo parecido com o seguinte.
letter=tolower((unsigned char)letter);
if(verifyLetter(letter, letraDig))
{
printf("\nEssa letra já foi digitada\nTente novament\n");
system("pause");
Anteriormente você chamou
system("clear") , dando a entender que estava num sistema UNIX-like, visto que no mundo Microsoft, o comando equivalente a
clear seria
CLS . Mas aqui você usa uma chamada a
pause , que até onde eu sei, não existe no Linux ou sistemas UNIX em geral. Não existindo, esse comando só serve para dar erro.
} else
{
letraDig[contLetras] = letter;
contLetras++;
if(verifyLetter(letter, wordSorteada))
{
alocandoLetra(letter);
} else
{
printf("Letra incorreta\nTente novamente\n\n");
contaErros++;
system("pause");
}
}
if(contaErros>=6)
{
system("clear");
printf("Game Over for you");
system("stop");
Esse
system("stop") é algo que eu nunca vi, e não faria muito sentido, mesmo que existisse tal comando “
stop ”.
A forma de encerrar o programa em C é fazer como na linha que vem logo abaixo (i.e. através do comando
return dentro de
main ()), ou por meio das funções da biblioteca
exit () ou
abort ().
return 0;
} else if(wordCompleta())
{
system("clear");
printf("YOU WIN");
system("stop");
return 0;
}
}while(contaErros<=6);
return 0;
}
Outros problemas que eu acredito que você deveria considerar e corrigir ao longo do programa como um todo:
• Várias constantes numéricas, tais como
30 ,
26 ou
6 , espalhadas pelo programa não são geralmente uma boa prática. O programa provavelmente ficaria mais simples de compreender se essas constantes numéricas fossem substituídas por nomes descritivos de seu propósito, tais como
TAM_MAX_PALAVRA ,
N_CARACTERES_VALIDOS e
MAX_RODADAS , sendo cada um desses nomes associados apenas uma vez a seu respectivo valor numérico constante. Fazer desse modo teria ainda a vantagem adicional de facilitar eventuais alterações nesses valores — por exemplo, caso você decida trabalhar com palavras com mais de 30 caracteres, poderia alterar somente a definição do valor de
TAM_MAX_PALAVRA , e automaticamente todos os outros lugares em que esse nome aparecesse teriam o valor correto automaticamente substituído, o que é bem melhor do que sair procurando a constante 30 ao longo do programa, verificar se cada ocorrência corresponde ao comprimento de uma palavra (porque poderia ser um outro valor 30, usado para outro propósito e que não deveria ser alterado) e então trocar manualmente cada ocorrência.
• Você não especificou se seu programa é em C ou em C++. Parece ser em C, e eu vou considerar que realmente o seja, então eu acho importante que você saiba que, em C, uma lista de parâmetros vazia significa que a função poder receber uma quantidade qualquer de argumentos de quaisquer tipos; para declarar funções que não têm parâmetros (i.e. que não devem receber argumentos), a forma de fazer é declarar a função com a palavra-chave
void como única coisa dentro da lista de parâmetros. No seu programa você tem várias funções, incluindo
main , que são declaradas com lista de parâmetros vazias, mas que provavelmente deveriam ter sido declaradas explicitamente como não podendo receber argumentos. Desse modo, você provavelmente deveria ter dito, por exemplo, “
int main(void) ”, em vez de “
int main() ”, e o mesmo para diversas outras funções.
• Acho que a escolha entre variáveis globais e variáveis locais não ficou muito consistente, bem como casos em que as funções separadas de
main () recebem argumentos ou usam variáveis globais. Essa inconsistência sugere que você pode não ter planejado o programa de antemão, e pode ter juntado partes de modo meio improvisado na medida em que foi programando. Se esse realmente tiver sido o caso, procure planejar melhor antes de começar a codificar.
... Então Jesus afirmou de novo: “(...) eu vim para que tenham vida, e a tenham plenamente.” (João 10:7-10)