Uma funcionalidade que ainda nos atrapalha no jogo da forca é a palavra secreta, que atualmente está fixa. Se queremos que a palavra seja diferente, devemos modificá-la no código.
A nossa ideia é ler palavras de um arquivo de texto, e dentre elas escolhemos uma palavra aleatoriamente, que será a palavra secreta do jogo.
Escrita de um arquivo
Para abrir um arquivo, o Python possui a função open()
. Ela recebe dois parâmetros: o primeiro é o nome do arquivo a ser aberto, e o segundo parâmetro é o modo que queremos trabalhar com esse arquivo - se queremos ler ou escrever. O modo é passado através de uma string: "w" (abreviação para write) para escrita e "r" (abreviação para read) para leitura.
No jogo, faremos a leitura de um arquivo. Antes, vamos testar como funciona a escrita no terminal do Python 3:
>>> arquivo = open('palavras.txt', 'w')
O modo é opcional e o modo padrão é o "r" de leitura (reading) que veremos mais adiante.
O arquivo criado se chama 'palavras.txt' e está no modo de escrita. É importante saber que o modo de escrita sobrescreve o arquivo, se o mesmo existir. Se a intenção é apenas adicionar conteúdo ao arquivo, utilizamos o modo "a" (abreviação para append).
Agora que temos o arquivo, vamos aprender a escrever algum conteúdo nele. Basta chamar a partir do arquivo a função write()
, passando para ela o que se quer escrever no arquivo:
>>> arquivo.write('banana')
6
>>> arquivo.write('melancia')
8
O retorno dessa função é o número de caracteres de cada texto adicionado no arquivo.
Fechando um arquivo
Quando estamos trabalhando com arquivos, devemos nos preocupar em fechá-lo. Para fechá-lo usamos a função close()
:
>>> arquivo.close()
Após isso, podemos verificar o conteúdo do arquivo. Repare que ele foi criado na mesma pasta em que o comando para abrir o console do Python 3 foi executado. Se você tentar fechar um arquivo que já está fechado, não vai surtir efeito algum, nem mesmo um erro. Abra o arquivo na pasta criada e verifique seu conteúdo:
bananamelancia
As palavras foram escritas em uma mesma linha. Mas como escrever uma nova linha?
Escrevendo palavras em novas linhas
A primeira coisa que devemos fazer é abrir o arquivo novamente, dessa vez utilizando o modo 'a', de append
:
arquivo = open('palavras.txt', 'a')
Vamos escrever novamente no arquivo, mas dessa vez com a preocupação de criar uma nova linha após cada conteúdo escrito. Para representar uma nova linha em código, adicionamos o \n ao final do que queremos escrever:
>>> arquivo.write('morango\n')
8
>>> arquivo.write('manga\n')
Ao fechar o arquivo e verificar novamente o seu conteúdo, vemos:
bananamelanciamorango
manga
A palavra morango ainda ficou na mesma linha, mas como especificamos na sua adição que após a palavra deverá ter uma quebra de linha, a palavra manga foi adicionada abaixo, em uma nova linha.
Por fim, vamos mover este arquivo para nosso projeto e ajeitar suas palavras quebrando as linhas.
Exercícios
Vamos navegar até nossa pasta jogos dentro de home. Lembrando que nossa estrutura de arquivos está assim:
|_ home |_ jogos |_ advinhacao.py |_ forca.py |_ menu.py
Crie um outro arquivo, chamado arquivo.py e insira o seguinte código:
arquivo = open("palavras.txt", "w")
Rode o arquivo e veja o resultado. O seu diretório deverá estar similar a:
|_ home |_ jogos |_ advinhacao.py |_ forca.py |_ arquivo.py |_ menu.py |_ palavras.txt
Vamos começar a escrever no nosso arquivo utilizando a função
write()
as palavras que usaremos no nosso jogo da forca:arquivo.write('banana\n') arquivo.write('melancia\n') arquivo.write('morango\n') arquivo.write('manga\n')
Note que ao final de cada palavra temos que acrescentar o "\n" para a quebra de linha, que vai facilitar na hora da leitura.
É uma boa prática fechar o arquivo depois de utilizá-lo, assim outros programas ou processos podem ter acesso ao arquivo e ele não fica preso apenas ao nosso programa Python.
arquivo.close()
Para saber mais
Além do r, w e a existe o modificador b que é utilizado quando se deseja trabalhar no modo binário. Para abrir uma imagem no modo leitura, devemos usar:
imagem = open('foto.jpg', 'rb')
Lendo um arquivo
Ainda no terminal do Python 3, veremos o funcionamento da leitura de um arquivo. Como agora o arquivo palavras.txt está na pasta do projeto jogos
, devemos executar o comando que abre o terminal do Python 3 na pasta do projeto.
$ cd jogos
$ python3
Vamos então abrir o arquivo no modo de leitura, basta passar o nome do arquivo e a letra "r" para a função open()
, como já visto anteriormente.
arquivo = open('palavras.txt', 'r')
Diferente do modo "w", abrir um arquivo que não existe no modo "r" não vai criar um arquivo. Se "palavras.txt" não existir, o Python vai lançar o erro FileNotFoundError
:
arquivo.open('frutas.txt', 'r')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
FileNotFoundError: [Errno 2] No such file or directory: 'frutas.txt'
Como o arquivo frutas.txt não existe na pasta jogos, o Pyhton não consegue encontrar e acusa que não existe nenhum arquivo ou diretório com este nome na pasta raiz.
Como abrimos o arquivo no modo de leitura, a função write()
não é suportada:
arquivo.write("oi")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
io.UnsupportedOperation: not writable
Para ler o arquivo inteiro, utilizamos a função read()
:
arquivo.read()
'banana\nmelancia\nmorango\nmanga\n'
Mas ao executar a função novamente, será retornado uma string vazia:
arquivo.read()
''
Isso acontece porque o arquivo é como um fluxo de linhas, que começa no início do arquivo como se fosse um cursor. Ele vai descendo e lendo o arquivo. Após ler tudo, ele fica posicionado no final do arquivo. Quando chamamos a função read()
novamente, não há mais conteúdo pois ele todo já foi lido.
Portanto, para ler o arquivo novamente, devemos fechá-lo e abrí-lo outra vez:
arquivo.close()
arquivo = open('palavras.txt', 'r')
arquivo.read()
Lendo linha por linha do arquivo
Não queremos ler todo o conteúdo do arquivo mas ler linha por linha. Como já foi visto, um arquivo é um fluxo de linhas, ou seja, uma sequência de linhas. Sendo uma sequência, podemos utilizar um laço for
para ler cada linha do arquivo:
arquivo = open('palavras.txt', 'r')
for linha in arquivo:
print(linha)
...
banana
melancia
morango
manga
Repare que existe uma linha vazia entre cada fruta. Isso acontece porque estamos utilizando a função print()
que também acrescenta, por padrão, um \n
. Agora vamos utilizar outra função, a readline()
, que lê apenas uma linha do arquivo:
arquivo = open('palavras.txt', 'r')
linha = arquivo.readline()
print(linha)
'banana\n'
Há um \n
ao final de cada linha, de cada palavra, mas queremos somente a palavra. Para tirar espaços em branco no início e no fim da string, basta utilizar a função strip()
, que também remove caracteres especiais, como o \n
- para mais informações consulte a documentação de strings. Sabendo disso tudo, já podemos implementar a funcionalidade de leitura de arquivo no nosso jogo:
arquivo = open('palavras.txt', 'r')
palavras = []
for linha in arquivo:
linha = linha.strip()
palavras.append(linha)
arquivo.close()
Agora já temos todas as palavras na lista, mas como selecionar uma delas aleatoriamente?
Gerando um número aleatório
Sabemos que cada elemento da lista possui uma posição e vimos no treinamento anterior como gerar um número aleatório. A biblioteca que sabe gerar um número aleatório é a random. Vamos testá-la no terminal do Python 3, primeiro importando-a:
>>> import random
Para gerar o número aleatório utilizamos a biblioteca e chamamos a função randrange()
, que recebe o intervalo de valores que o número aleatório deve estar. Então vamos passar o valor 0 (equivalente à primeira posição da nossa lista) e 4 (lembrando que o número é exclusivo, ou seja, o número aleatório será entre 0 e 3, equivalente à última posição da nossa lista):
>>> import random
>>> random.randrange(0, 4)
0
>>> random.randrange(0, 4)
1
>>> random.randrange(0, 4)
3
>>> random.randrange(0, 4)
1
>>> random.randrange(0, 4)
3
Sabendo disso, vamos implementar esse código no nosso jogo.
Exercícios - Leitura de arquivos
1.Vamos começar abrindo o arquivo "palavras.txt" no nosso script forca.py
que criamos no exercício anterior. É importante já acrescentar o comando para fechá-lo para não esquecer no futuro:
def jogar():
print('*********************************')
print('***Bem vindo ao jogo da Forca!***')
print('*********************************')
arquivo = open('palavras.txt', 'r')
arquivo.close()
#restante do código
Agora vamos criar uma lista chamada
palavras
e fazer um laçofor
para acessar cada linha para guardar na lista:arquivo = open('palavras.txt', 'r') palavras = [] for linha in arquivo: palavras.append(linha) arquivo.close()
Como precisamos remover o \n ao final da linha, usaremos a função
strip()
em cada linha:def jogar(): arquivo = open('palavras.txt', 'r') palavras = [] for linha in arquivo: linha - linha.strip() palavras.append(linha) arquivo.close()
Devemos importar a biblioteca random para gerar um número que vai de 0 até a quantidade de palavras da nossa lista. Usaremos a função
len()
para saber o tamanho da lista e arandrange()
para gerar um número randômico de um intervalo específico:import random print('*********************************') print('***Bem vindo ao jogo da Forca!***') print('*********************************') arquivo = open('palavras.txt', 'r') palavras = [] for linha in arquivo: linha = linha.strip() palavras.append(linha) arquivo.close() numero = random.randrange(0, len(palavras))
Agora que temos o número aleatório, vamos utilizá-lo como índice para acessar a lista e atribuir essa palavra à variável palavra_secreta:
import random print("*********************************") print("***Bem vindo ao jogo da Forca!***") print("*********************************") arquivo = open("palavras.txt", "r") palavras = [] for linha in arquivo: linha = linha.strip() palavras.append(linha) arquivo.close() numero = random.randrange(0, len(palavras)) palavra_secreta = palavras[numero].upper() letras_acertadas = ['_' for letra in palavra_secreta]
Por fim, temos que deixar nossa variável
letras_acertadas
dinâmica, com número de letras de acordo com nossapalavra_secreta
. Vamos utilizar umfor
dentro da lista para gerar um '_' para cada letra de acordo com o tamanho da palavra_secreta:letras_acertadas = ['_' for letra in palavra_secreta]
Como já garantimos que a
palavra_secreta
está toda em letras maiúsculas com o códigopalavras[numero].upper()
, modificaremos ochute
para o primeiro if continuar funcionandochute = input('Qual a letra? ') chute = chute.strip().upper()
Podemos executar o jogo e notar que a palavra é selecionada aleatoriamente!
Mas agora a nossa função cresceu bastante, com várias funcionalidades e responsabilidades. No próximo capítulo organizaremos melhor o nosso código, separando-o em funções e deixando-o mais fácil de entender.
Para saber mais - comando with
Já falamos da importância de fechar o arquivo, certo? Veja o código abaixo que justamente usa a função close()
:
arquivo = open('palavras.txt', 'r')
palavras = logo.read()
arquivo.close()
Imagine que algum problema aconteça na hora da leitura quando a função read() é chamada. Será que o arquivo é fechado quando o erro ocorre?
Se for algum erro grave, o programa pode parar a execução sem ter fechado o arquivo e isto seria bastante ruim. Para evitar esse tipo de situação, existe no Python uma sintaxe especial para abertura de arquivo:
with open('palavras.txt') as arquivo:
for linha in arquivo:
print(linha)
Repare o comando with
usa a função open()
, mas não a função close()
. Isso não será mais necessário, já que o comando with
vai se encarregar de fechar o arquivo para nós, mesmo que aconteça algum erro no código dentro de seu escopo. Muito melhor, não?
Melhorando nosso código
Nos capítulos anteriores criamos dois jogos, avançamos no jogo da Forca e implementamos leitura de palavras em um arquivo. Agora vamos utilizar o que aprendemos de funções para encapsular nosso código e deixá-lo mais organizado. Vamos começar, com os conhecimentos que temos até aqui, para estruturar nosso jogo da Forca.
A função jogar()
possui um código muito complexo, com muitas funcionalidades e responsabilidades.
Entre as funcionalidades que o código possui, está a apresentação do jogo, a leitura do arquivo e inicialização da palavra secreta, entre outras. Vamos então separar as responsabilidades do código em funções, melhorando a sua legibilidade e organização.
Vamos começar com a mensagem de apresentação do nosso jogo e exportar o código para a função imprime_mensagem_abertura()
. Não podemos nos esquecer de chamar essa função no início da função jogar()
:
import random
def jogar():
imprime_mensagem_abertura()
#código omitido
def imprime_mensagem_abertura():
print('*********************************')
print('***Bem vindo ao jogo da Forca!***')
print('*********************************')
Aqui não importa o local da função, ela pode ser declarada antes ou depois da função jogar().
O que fizemos foi refatorar nosso código. Refatoração é o processo de modificar um programa para melhorar a estrutura interna do código, sem alterar seu comportamento externo. Veja que se executarmos nosso jogo da Forca, tudo funciona como antes:
*********************************)
***Bem vindo ao jogo da Forca!***
*********************************
['_', '_', '_', '_', '_', '_']
Qual letra?
No próximo exercício, vamos refatorar as demais partes do nosso código.
Exercício - Refatorando o jogo da Forca
Crie a função imprime_mensagem_abertura que vai isolar a mensagem de abertura do jogo:
import random def jogar(): imprime_mensagem_abertura() #código omitido def imprime_mensagem_abertura(): print('*********************************') print('***Bem vindo ao jogo da Forca!***') print('*********************************')
Agora, vamos separar o código que realiza a leitura do arquivo e inicializa a palavra secreta na função
carrega_palavra_secreta()
:def carrega_palavra_secreta(): arquivo = open('palavras.txt', 'r') palavras = [] for linha in arquivo: linha = linha.strip() palavras.append(linha) arquivo.close() numero = random.randrange(0, len(palavras)) palavra_secreta = palavras[numero].upper()
Só que a função
jogar()
irá reclamar que a palavra_secreta não existe. O que queremos é que, ao executar a funçãocarrega_palavra_secreta()
, que ela retorne a palavra secreta para nós, assim poderemos guardá-la em uma variável:import random def jogar(): imprime_mensagem_abertura() palavra_secreta = carrega_palavra_secreta() letras_acertadas = ["_" for letra in palavra_secreta] # restante do código omitido
Só que como faremos a função
carrega_palavra_secreta()
retornar um valor, no caso apalavra_secreta
? Apalavra_secreta
já existe, mas só dentro da funçãocarrega_palavra_secreta()
. Para que ela seja retornada, utilizamos a palavra-chave return:def carrega_palavra_secreta(): arquivo = open('palavras.txt', 'r') palavras = [] for linha in arquivo: linha = linha.strip() palavras.append(linha) arquivo.close() numero = random.randrange(0, len(palavras)) palavra_secreta = palavras[numero].upper() return palavra_secreta
Agora, vamos criar uma função que inicializa a lista de letras acertadas com o caractere '_'. Criaremos a função
inicializa_letras_acertadas()
:import random def jogar(): imprime_mensagem_abertura() palavra_secreta = carrega_palavra_secreta() letras_acertadas = inicializa_letras_acertadas() # código omitido def inicializa_letras_acertadas(): return ['_' for letra in palavra_secreta]
Mas a função
inicializa_letras_acertadas()
precisa ter acesso à palavra_secreta, pois ela não existe dentro da função, já que uma função define um escopo, e as variáveis declaradas dentro de uma função só estão disponíveis dentro dela. Então, ao chamar a funçãoinicializa_letras_acertadas()
, vamos passarpalavra_secreta
para ela por parâmetro:import random def jogar(): imprime_mensagem_abertura() palavra_secreta = carrega_palavra_secreta() letras_acertadas = inicializa_letras_acertadas(palavra_secreta) # restante do código omitido def inicializa_letras_acertadas(palavra): return ["_" for letra in palavra]
Vamos continuar refatorando nosso código. Criaremos a função
pede_chute()
, que ficará com o código que pede o chute do usuário, remove os espaços antes e depois, e o coloca em caixa alta. Não podemos nos esquecer de retornar o chute:def jogar(): # código omitido while (not acertou and not enforcou): chute = pede_chute() #código omitido #código omitido def pede_chute(): chute = input('Qual letra? ') chute = chute.strip().upper() return chute
Ainda temos o código que coloca o chute na posição correta, dentro da lista. Vamos colocá-lo dentro da função
marca_chute_correto()
:while (not acertou and not enforcou): chute = pede_chute() if (chute in palavra_secreta): marca_chute_correto() else: erros += 1 enforcou = erros == 6 acertou = '_' not in letras_acertadas print(letras_acertadas) #código omitido def marca_chute_correto(): posicao = 0 for letra in palavra_secreta: if (chute == letra): letras_acertadas[posicao] = letra posicao += 1
Mas a função marca_chute_correto()
precisa ter acesso a três valores: palavra_secreta
, chute
e letras_acertadas
. Assim, vamos passar esses valores por parâmetro:
if (chute in palavra_secreta):
marca_chute_correto(chute, letras_acertadas, palavra_secreta)
E modificamos nossa função para receber esses parâmetros:
def marca_chute_correto(chute, letras_acertadas, palavra_secreta):
posicao = 0
for letra in palavra_secreta:
if (chute == letra):
letras_acertadas[posicao] = letra
posicao += 1
Por fim, vamos remover a mensagem de fim de jogo e exportar os códigos que imprimem as mensagens de vencedor e perdedor do jogo:
if (acertou): imprime_mensagem_vencedor() else: imprime_mensagem_perdedor()
E criar as funções:
def imprime_mensagem_vencedor(): print('Você ganhou!') def imprime_mensagem_perdedor(): print('Você perdeu!')
Agora o nosso código está muito mais organizado e legível. Ao chamar todas as funções dentro da função
jogar()
, nosso código ficará assim:def jogar(): imprime_mensagem_abertura() palavra_secreta = carrega_palavra_secreta() letras_acertadas = inicializa_letras_acertadas(palavra_secreta) print(letras_acertadas) enforcou = False acertou = False erros = 0 while(not enforcou and not acertou): chute = pede_chute() if(chute in palavra_secreta): marca_chute_correto(chute, letras_acertadas, palavra_secreta) else: erros += 1 enforcou = erros == 6 acertou = "_" not in letras_acertadas print(letras_acertadas) if(acertou): imprime_mensagem_vencedor() else: imprime_mensagem_perdedor()
Por fim, podemos executar o nosso código, para verificar que o mesmo continua funcionando normalmente.
********************************* ***Bem vindo ao jogo da Forca!*** ********************************* ['_', '_', '_', '_', '_', '_'] Qual letra?
(opcional) Com a melhor organização do nosso código, vamos melhorar a exibição, a apresentação da forca, deixando o jogo mais amigável. Vamos começar com a mensagem de perdedor, alterando a função
imprime_mensagem_perdedor
. Ela ficará assim:def imprime_mensagem_perdedor(palavra_secreta): print('Puxa, você foi enforcado!') print('A palavra era {}'.format(palavra_secreta)) print(" _______________ ") print(" / \ ") print(" / \ ") print("// \/\ ") print("\| XXXX XXXX | / ") print(" | XXXX XXXX |/ ") print(" | XXX XXX | ") print(" | | ") print(" \__ XXX __/ ") print(" |\ XXX /| ") print(" | | | | ") print(" | I I I I I I I | ") print(" | I I I I I I | ") print(" \_ _/ ") print(" \_ _/ ") print(" \_______/ ")
Precisamos passar a
palavra_secreta
para funçãoimprime_mensagem_perdedor()
:if(acertou): imprime_mensagem_vencedor() else: imprime_mensagem_perdedor(palavra_secreta)
E modificamos o código de
imprime_mensagem_vencedor()
para:def imprime_mensagem_vencedor(): print('Parabéns, você ganhou!') print(" ___________ ") print(" '._==_==_=_.' ") print(" .-\\: /-. ") print(" | (|:. |) | ") print(" '-|:. |-' ") print(" \\::. / ") print(" '::. .' ") print(" ) ( ") print(" _.' '._ ") print(" '-------' ")
Vá até a pasta do curso e copie o código destas funções que estão em um arquivo chamado _funcoesforca.py.
(opcional) Por fim, crie a função
desenha_forca()
, que irá desenhar uma parte da forca, baseado nos erros do usuário. Como ela precisa acessar os erros, passe-o por parâmetro para a função:def desenha_forca(erros): pass
E copie o conteúdo do código da função
desenha_forca()
do arquivo funcoes forca.py na pasta do curso para sua função.Para finalizar, chame a função
desenha_forca
quando o jogador errar e aumente o limite de erros para 7.if(chute in palavra_secreta): marca_chute_correto(chute, letras_acertadas, palavra_secreta) else: erros += 1 desenha_forca(erros) enforcou = erros == 7 acertou = "_" not in letras_acertadas
Tente fazer o mesmo com o jogo da adivinhação, refatore partes do código e isole em funções. Além disso, use sua criatividade para customizar mensagens para o usuário do seu jogo.
Neste exercício, praticamos bastante do que aprendemos no capítulo de função e finalizamos o jogo da forca.
Você pode estar se perguntando por que encapsulamos uma simples linha de código em uma função. Fizemos isso somente para deixar claro o que estamos fazendo, melhorando a legibilidade do código. Mas precisamos tomar cuidado com a criação de funções, pois criar funções desnecessariamente pode aumentar a complexidade do código.