O Genius, conhecido também como Simon Says, é um jogo eletrônico que desafia a memória do jogador, exigindo que ele repita a sequência de cores apresentada. Para construir essa versão usando o Raspberry Pi, é necessário entender tanto a montagem do hardware quanto a programação do sistema, que controlará LEDs, botões e o buzzer.
A imagem acima mostra a ligação completa dos componentes do jogo Genius em uma protoboard, utilizando um Raspberry Pi 3. Todos os fios estão conectados de forma que os LEDs, botões e o buzzer possam ser controlados diretamente pelos pinos GPIO. A seguir, veja como essa montagem está organizada:
Na imagem, da esquerda para a direita:
Cada LED está ligado da seguinte forma:
Obs: Mesmo que os LEDs verde e azul não estejam com as cores originais, o código do jogo manterá a mesma lógica de cores: o LED laranja equivale ao verde e o cinza equivale ao azul.
Da direita para a esquerda na imagem:
Cada botão está conectado da seguinte forma:
O buzzer, posicionado acima dos LEDs, é conectado da seguinte forma:
Após montar corretamente o circuito, é hora de programar o sistema que controlará os LEDs, os botões e o buzzer. Abaixo está o código completo dividido em partes:
Neste bloco, são importadas as bibliotecas necessárias para manipular os GPIO do Raspberry Pi, executar operações em paralelo (threading), controlar o tempo, gerar números aleatórios e limpar a tela do terminal. Também são definidos os pinos usados para LEDs, botões e buzzer, assim como as frequências das notas musicais, configurações do jogo e variáveis para controlar o estado atual da partida.
import RPi.GPIO as GPIO
import threading
import time
import random
import os
LEDS = [11, 15, 13, 7]
BOTOES = [33, 37, 35, 31]
FREQUENCIAS = [261, 294, 330, 392]
PIN_BUZZER = 32
velocidade = 0.2
usar_som = True
tempo_max_resposta = 5
niveis_maximo = 10
exibindo_padrao = False
nivel_concluido = False
jogo_terminado = False
nivel_atual = 1
passo_atual = 0
sequencia = []
Esta função configura os pinos do Raspberry Pi para o modo físico (BOARD), desativa avisos e define os LEDs como saída e os botões como entrada com resistores internos pull-down. Além disso, cria interrupções para detectar quando os botões são pressionados, associando cada evento à função verificar_jogada.
def configurar_gpio():
GPIO.setmode(GPIO.BOARD)
GPIO.setwarnings(False)
GPIO.setup(LEDS, GPIO.OUT, initial=GPIO.LOW)
GPIO.setup(BOTOES, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setup(PIN_BUZZER, GPIO.OUT)
for i in range(4):
GPIO.add_event_detect(BOTOES[i], GPIO.FALLING, verificar_jogada, bouncetime=300)
Esta função emite um tom no buzzer com a frequência e duração especificadas, utilizando PWM para gerar o som.
def tocar_tono(frequencia, duracao=0.2):
if usar_som:
buzzer = GPIO.PWM(PIN_BUZZER, frequencia)
buzzer.start(100)
time.sleep(duracao)
buzzer.stop()
Quando um botão é pressionado, esta função identifica o LED correspondente e o acende brevemente para dar um feedback visual ao jogador.
def piscar_led_do_botao(canal_botao):
led = LEDS[BOTOES.index(canal_botao)]
GPIO.output(led, GPIO.HIGH)
time.sleep(0.1)
GPIO.output(led, GPIO.LOW)
Essa função é chamada sempre que um botão é pressionado, verificando se o botão corresponde ao próximo elemento da sequência gerada pelo jogo. Se estiver correto, avança o passo atual, caso contrário, indica que o jogo terminou por erro. Além disso, acende o LED e toca a nota correspondente ao botão pressionado.
def verificar_jogada(canal):
global passo_atual, nivel_atual, nivel_concluido, jogo_terminado
try:
if not exibindo_padrao and not nivel_concluido and not jogo_terminado:
indice = BOTOES.index(canal)
tocar_tono(FREQUENCIAS[indice])
piscar_led_do_botao(canal)
ultimo_tempo_input = time.time()
if indice == sequencia[passo_atual]:
passo_atual += 1
if passo_atual >= nivel_atual:
nivel_concluido = True
nivel_atual += 1
else:
jogo_terminado = True
except (ValueError, IndexError):
jogo_terminado = True
A cada nível novo, o jogo acrescenta uma cor aleatória (representada por números de 0 a 3) na sequência que o jogador deverá repetir.
def adicionar_cor_a_sequencia():
global nivel_concluido, passo_atual
nivel_concluido = False
passo_atual = 0
sequencia.append(random.randint(0, 3))
Esta função percorre a sequência atual, acendendo o LED e tocando a nota correspondente para que o jogador memorize a ordem correta.
def exibir_sequencia_ao_jogador():
global exibindo_padrao
exibindo_padrao = True
GPIO.output(LEDS, GPIO.LOW)
for i in range(nivel_atual):
cor = sequencia[i]
tocar_tono(FREQUENCIAS[cor])
GPIO.output(LEDS[cor], GPIO.HIGH)
time.sleep(velocidade)
GPIO.output(LEDS[cor], GPIO.LOW)
time.sleep(velocidade)
exibindo_padrao = False
Enquanto o jogador não terminar o nível ou o jogo, o sistema fica aguardando a entrada do usuário, monitorando o tempo para garantir que ele responda dentro do limite definido.
def aguardar_jogador_repetir_sequencia():
global jogo_terminado, ultimo_tempo_input
ultimo_tempo_input = time.time()
while not nivel_concluido and not jogo_terminado:
time.sleep(0.1)
if time.time() - ultimo_tempo_input > tempo_max_resposta:
print(" Tempo esgotado!")
jogo_terminado = True
Estes métodos criam efeitos visuais (piscar LEDs) e sonoros específicos para quando o jogador vence ou perde, tornando o jogo mais interativo e agradável.
def piscar_todos_leds(vezes=3, duracao=0.2):
for _ in range(vezes):
GPIO.output(LEDS, GPIO.HIGH)
time.sleep(duracao)
GPIO.output(LEDS, GPIO.LOW)
time.sleep(duracao)
def tocar_melodia_derrota():
melodia = [392, 330, 294, 261]
duracoes = [0.3, 0.3, 0.4, 0.6]
for freq, dur in zip(melodia, duracoes):
tocar_tono(freq, dur)
time.sleep(0.05)
def tocar_melodia_vitoria():
melodia = [261, 294, 330, 392, 392, 440, 494, 523]
duracoes = [0.2, 0.2, 0.2, 0.2, 0.2, 0.3, 0.3, 0.6]
for freq, dur in zip(melodia, duracoes):
tocar_tono(freq, dur)
time.sleep(0.05)
Esta função reseta todas as variáveis que controlam o estado do jogo para que uma nova partida possa começar do nível inicial.
def reiniciar_jogo():
global exibindo_padrao, nivel_concluido, jogo_terminado
global nivel_atual, passo_atual, sequencia
exibindo_padrao = False
nivel_concluido = False
jogo_terminado = False
nivel_atual = 1
passo_atual = 0
sequencia = []
GPIO.output(LEDS, GPIO.LOW)
Neste laço, o jogo inicia e mantém o fluxo principal: adiciona uma nova cor, exibe a sequência, aguarda o jogador, verifica se o jogo terminou por erro ou vitória, e oferece a possibilidade de reiniciar.
def iniciar_jogo():
while True:
os.system('clear')
print(f"Iniciando rodada de nível {nivel_atual}...")
adicionar_cor_a_sequencia()
exibir_sequencia_ao_jogador()
aguardar_jogador_repetir_sequencia()
if jogo_terminado:
piscar_todos_leds(5, 0.1)
tocar_melodia_derrota()
print(f"\nGame Over! Você alcançou {nivel_atual - 1} cores.")
jogar_novamente = input("Deseja jogar novamente? (Y/n): ")
if jogar_novamente.lower() == 'y':
reiniciar_jogo()
else:
print("Obrigado por jogar!")
break
if nivel_atual > niveis_maximo:
piscar_todos_leds(10, 0.1)
tocar_melodia_vitoria()
print("\n Parabéns! Você venceu o jogo!")
jogar_novamente = input("Deseja jogar novamente? (Y/n): ")
if jogar_novamente.lower() == 'y':
reiniciar_jogo()
else:
print("Obrigado por jogar!")
break
else:
piscar_todos_leds(2, 0.1)
time.sleep(1)
Essas funções iniciam o jogo em um processo paralelo para manter a responsividade do sistema, configuram os GPIOs ao começar e limpam os recursos ao finalizar, garantindo que os pinos sejam liberados corretamente.
def monitorar_jogo():
t = threading.Thread(target=iniciar_jogo)
t.daemon = True
t.start()
t.join()
def main():
try:
os.system('clear')
print("Simon Says com Raspberry Pi")
configurar_gpio()
monitorar_jogo()
finally:
GPIO.cleanup()
if __name__ == '__main__':
main()
Com a montagem finalizada e o código em execução, o jogo Genius está pronto para ser utilizado. Agora é só testar, se divertir e, quem sabe, personalizar ainda mais o projeto com novas ideias e desafios.