Pular para o conteúdo principal

O menu e os seus textos

Depois de desenhar o labirinto, o programa não pode simplesmente terminar. O enunciado exige que, com o programa rodando, o usuário consiga gerar outro labirinto, mostrar ou esconder o caminho mais curto e trocar a cor das paredes. O programa precisa repetir sempre o mesmo ciclo: desenhar, perguntar o que o usuário quer, reagir, e voltar a desenhar, até o usuário pedir para sair. A classe Menu (amazeing/ui/menu.py) faz esse ciclo. Esta página mostra os textos que ela imprime, por que ela lê o teclado com input() e as peças que ela recebe; o laço em si está na próxima página.

Na tela, depois de cada desenho, aparece isto (parte 1):

=== A-Maze-ing ===
1. Re-generate a new maze
2. Show / Hide the shortest path
3. Rotate the wall colours
4. Quit
Choice? (1-4):

O Menu é a camada de interface: junto com o main do a_maze_ing.py, é o único lugar do projeto que usa print e input, isto é, que conversa com o usuário.

Os textos do menu​

MENU = ("=== A-Maze-ing ===", "1. Re-generate a new maze",
"2. Show / Hide the shortest path", "3. Rotate the wall colours",
"4. Quit")
PROMPT = "Choice? (1-4): "
PATTERN_MISSING = 'Error: maze too small for the "42" pattern'
CHOICES = ("1", "2", "3", "4")
  • MENU: as cinco linhas do menu, uma por item da tupla.
  • PROMPT: a pergunta que fica esperando a resposta. Termina com um espaço e sem quebra de linha, então o cursor fica na mesma linha, logo depois dos dois-pontos.
  • PATTERN_MISSING: o aviso para quando o "42" não coube no labirinto (página 4). Ele começa com Error: como os erros do programa, mas é o único que não encerra a execução: o labirinto sem o "42" continua válido, e o menu segue. Por isso o próprio Menu o imprime, em vez de deixá-lo subir até o main do a_maze_ing.py, que só imprime os erros que terminam o programa (página 7).
  • CHOICES: as quatro respostas válidas, como texto.

Os textos ficam em constantes no topo do arquivo, e não espalhados pelos métodos: são o que o usuário vê do programa, e mudar um deles é mudar uma linha só. Os textos em inglês e as quatro opções são escolha do projeto; o enunciado pede no mínimo as três interações (gerar de novo, mostrar ou esconder o caminho, trocar a cor das paredes), e a opção 4 é a saída.

Por que input()​

input(texto) mostra o texto, espera o usuário digitar uma linha e apertar Enter, e devolve o que foi digitado, sem o Enter. O menu lê a escolha assim: o usuário digita 2 e confirma com Enter.

A alternativa seria reagir a cada tecla, sem Enter, pondo o terminal num modo especial (o modo raw, "cru"). O projeto escolheu input() porque o modo raw:

  • depende do sistema: no Linux e no macOS é um módulo do Python (termios), no Windows é outro;
  • não funciona quando a entrada vem de um pipe (abaixo);
  • deixa o terminal desconfigurado, sem mostrar o que se digita, se o programa morrer antes de restaurá-lo.

Um pipe é o | do terminal: ele entrega a saída de um comando como entrada de outro, como se alguém digitasse. Neste comando, o printf escreve 2, Enter, 4, Enter, e o programa lê essas duas respostas como se o usuário as tivesse digitado: mostra o caminho e sai.

printf '2\n4\n' | .venv/bin/python a_maze_ing.py config.txt

O comando roda o programa com o Python do ambiente virtual (.venv/bin/python); é o mesmo que o python3 a_maze_ing.py config.txt do enunciado. Ele só funciona no fim desta parte, quando o a_maze_ing.py estiver completo; a página 10 o roda de verdade.

Com input(), o menu funciona igual num terminal e num pipe, e é assim que se pode rodar o programa sem ninguém no teclado.

Os colaboradores​

def __init__(self, generate: GenerateMaze, renderer: RenderService,
state: ViewState) -> None:
"""Keep the collaborators of the menu.

Args:
generate: Use case that builds and stores a maze.
renderer: Service that draws a maze.
state: Path visibility and wall colour of the session.
"""
self._generate = generate
self._renderer = renderer
self._state = state

O Menu recebe três objetos prontos, e só os guarda:

ParâmetroO que é
generateo GenerateMaze, a operação que gera um labirinto, grava o arquivo de saída e devolve o retrato GeneratedMaze (parte 12)
renderero RenderService, que transforma o labirinto em linhas coloridas (parte 13)
stateo ViewState, com o caminho visível ou não e a cor das paredes (parte 13)

O _ no começo de self._generate é uma convenção do Python: avisa que o atributo é de uso interno da classe.

O Menu não cria nada disso. Quem cria e liga as peças é o main (página 6). Por isso o menu não sabe que existe um arquivo de configuração, nem qual arquivo de saída é gravado: ele só pede "gere outro" ao GenerateMaze.