Can greatly simplify code and improve/correct various details:
Choose the appropriate data structure
For the board, you have created a dictionary with the keys "pos1", "pos2", etc. Whenever you have a dictionary whose keys are "something 1", "something 2", etc., it is a sign that probably You should use a list, not a dictionary. Lists have sequential numerical positions by definition, and using dictionaries in this case does not bring any advantage and only complicates the code randomly.
It may sound like a stupid detail, but choosing the right data structure is halfway towards a better program. Know what structures the language makes available (and mainly, how they work) and use the most suitable for each case.
The only "complication" is that the indices of a string start with zero, but get used to it, because that’s how languages work ("pretty much everything" in programming starts from zero).
Give better names
You called the board posicao
, but the variable should be called tabuleiro
(or else posicoes
, plural), because it is not one position, rather the set of all positions (i.e., it is the board).
It may sound like a stupid detail, too, but giving better names helps a lot when programming. In the code below you will see that I changed other names too.
Validate if a number has been entered
Do not use isdigit()
, because there are several characters for which isdigit()
returns True
, but error when converting to number with int()
. An example is the ²
(SUPERSCRIPT TWO), and there are several others (see full list here).
"Ah, but it’s just an exercise, the user will always enter valid numbers."
If so, just convert to int
direct, without having to test whether it is digit (i.e., isdigit()
would also not be necessary in this case). If the user is going to enter something, be prepared to receive "anything" and only proceed if you are sure that the data is valid.
The most guaranteed way is simply to convert to int
, and capture the ValueError
to see if there was a mistake.
There are other details to tidy up, such as:
- do not call the function
denovo()
within itself. Despite "working", you are making a recursive call, and there may be a stack overflow if the user does not type either "S" or "N" for many times in a row (see here an example - scroll to the bottom of the page and see the error of stack overflow). Use a loop simple and ready.
- put all repetitive tasks into function (even the
print('-=-'*15)
, that despite being a single line, repeats itself several times, so if you want to change it, you will have to change in several places; already using a function, you only change in one place and that’s it)
- do not need to pass the parameters with unpack (that is, with the asterisks before:
funcao(**valor)
is not necessary in your case - see here an example of where it would be necessary)
- it is also interesting to check if the chosen position is valid, because the user can type
100
, and when trying to access position 100 of the board, will give error
- has other little details that you can see in the following code
def mostrar_separador():
print('-=-' * 15)
def criar_tabuleiro(): # cria um tabuleiro vazio
return list(range(1, 10)) # lista com os números entre 1 e 9
def mostrar_tabuleiro(tabuleiro):
print(' {} | {} | {} '.format(*tabuleiro[:3])) # mostra as 3 primeiras posições
print('------+------+------')
print(' {} | {} | {} '.format(*tabuleiro[3:6])) # mostra da quarta à sexta posição
print('------+------+------')
print(' {} | {} | {} '.format(*tabuleiro[6:])) # mostra as 3 últimas posições
def pedir_jogada(tabuleiro, jogador):
mostrar_separador()
while True:
try: # não use isdigit(), capture o ValueError
print(f'jogador atual: {jogador}') # incluí esta mensagem para facilitar um pouco
jogada = int(input('Digite uma posição no tabuleiro para jogar: '))
if jogada <= 0 or jogada > len(tabuleiro): # verifica se o valor não está fora dos limites do tabuleiro
print(f'Posição inválida: {jogada}')
elif jogada in tabuleiro:
mostrar_separador()
return jogada # retorna direto, não precisa do break
else:
print('Você digitou um número já pedido, tente novamente')
except ValueError:
print('Você não digitou um número válido, tente novamente')
def jogo_terminou(tabuleiro):
posicoes_vencedoras = [
[0, 1, 2], [3, 4, 5], [6, 7, 8], # horizontal
[0, 3, 6], [1, 4, 7], [2, 5, 8], # vertical
[0, 4, 8], [2, 4, 6] # diagonal
]
# verifica todas as combinações de posições vencedoras
for pos1, pos2, pos3 in posicoes_vencedoras:
if tabuleiro[pos1] == tabuleiro[pos2] == tabuleiro[pos3]:
print(f'Temos um vencedor: {tabuleiro[pos1]}! Parabéns!') # mudei a mensagem para mostrar quem ganhou
# se já encontrei um vencedor, posso retornar direto (não precisa continuar verificando as outras combinações)
return True
# não tem vencedor, verifica se todas as posições estão ocupadas (não preciso mais do contador de jogadas)
if all(posicao in ('X', 'O') for posicao in tabuleiro):
print('Deu velha! Ninguém ganhou.')
return True
# jogo ainda não terminou
return False
import sys
def verificar_jogar_novamente(): # mudei o nome "denovo" para algo mais significativo
while True: # use um loop em vez de chamar a função dentro dela mesma
jogar_denovo = input('''
Deseja jogar novamente?
Digite S para SIM ou N para NÃO.
''').upper() # já transforma em maiúscula aqui
if jogar_denovo == 'S':
mostrar_separador()
break # sai do while
elif jogar_denovo == 'N':
print('Até logo!')
sys.exit() # sai do programa
else: # incluí uma mensagem a mais para ajudar o usuário
print('Você deve digitar "S" ou "N"')
def dados_iniciais(): # o jogo começa com o jogador X e tabuleiro vazio
print('Bem Vindo ao Jogo da Velha! Vamos começar!')
return ('X', criar_tabuleiro())
jogador, tabuleiro = dados_iniciais()
while True:
mostrar_tabuleiro(tabuleiro)
# como o tabuleiro agora é uma lista, posso simplesmente marcar a jogada mudando-o diretamente
# e ele substitui a lista_totalpalpites, que se torna redundante
jogada = pedir_jogada(tabuleiro, jogador)
tabuleiro[jogada - 1] = jogador
jogador = 'O' if jogador == 'X' else 'X' # troca o jogador de X para O ou vice-versa
if jogo_terminou(tabuleiro):
mostrar_tabuleiro(tabuleiro)
verificar_jogar_novamente()
# reinicio o tabuleiro
jogador, tabuleiro = dados_iniciais()
I used sys.exit()
to exit the program. The problem is that it actually exits, that is, if there were any other code after the while True
, he would not be executed.
Another option is to change the function verificar_jogar_novamente
to return True
or False
, indicating whether the game should continue or not, and then I check it in the code:
def jogar_novamente(): # mudei o nome para "jogar_novamente"
while True: # use um loop em vez de chamar a função dentro dela mesma
jogar_denovo = input('''
Deseja jogar novamente?
Digite S para SIM ou N para NÃO.
''').upper() # já transforma em maiúscula aqui
if jogar_denovo == 'S':
mostrar_separador()
return True # o jogo deve continuar
elif jogar_denovo == 'N':
print('Até logo!')
return False # o jogo não deve continuar
else: # incluí uma mensagem a mais para ajudar o usuário
print('Você deve digitar "S" ou "N"')
...
jogador, tabuleiro = dados_iniciais()
while True:
mostrar_tabuleiro(tabuleiro)
# como o tabuleiro agora é uma lista, posso simplesmente marcar a jogada mudando-o diretamente
# e ele substitui a lista_totalpalpites, que se torna redundante
jogada = pedir_jogada(tabuleiro, jogador)
tabuleiro[jogada - 1] = jogador
jogador = 'O' if jogador == 'X' else 'X' # troca o jogador de X para O ou vice-versa
if jogo_terminou(tabuleiro):
mostrar_tabuleiro(tabuleiro)
if not jogar_novamente(): # se não é para jogar novamente, sai do while
break # sai do while True
# é para jogar novamente, reinicio o tabuleiro
jogador, tabuleiro = dados_iniciais()
So he comes out of the while True
, but does not terminate the program (i.e., if it had any code after the while
, he would be executed, which would not occur with sys.exit()
).