Genius com Raspberry Pi

Como fazer um Genius com Raspberry Pi?

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.

Materiais necessários

Montagem do Circuito

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:

Alimentação Geral

LEDs

Na imagem, da esquerda para a direita:

  1. LED Amarelo → conectado ao GPIO 4 (pino 7)
  2. LED Laranja (representa o verde no jogo) → GPIO 17 (pino 11)
  3. LED Cinza (representa o azul no jogo) → GPIO 27 (pino 13)
  4. LED Vermelho → GPIO 22 (pino 15)

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.

Botões

Da direita para a esquerda na imagem:

  1. Botão Amarelo → GPIO 4 (pino 31)
  2. Botão Laranja (verde) → GPIO 13 (pino 33)
  3. Botão Cinza (azul) → GPIO 19 (pino 35)
  4. Botão Vermelho → GPIO 26 (pino 37)

Cada botão está conectado da seguinte forma:

Buzzer

O buzzer, posicionado acima dos LEDs, é conectado da seguinte forma:

Dicas importantes para a montagem

Código em Python

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:

1. Importação de bibliotecas e definição dos pinos

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 = []

2. Configuração dos pinos GPIO e interrupções nos botões

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)

3. Controle do buzzer para tocar sons

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()

4. Acender o LED correspondente ao botão pressionado

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)

5. Verificação da jogada do jogador

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

6. Gerar e adicionar uma nova cor à sequência do jogo

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))

7. Exibir a sequência para o jogador

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

8. Esperar que o jogador repita a sequência

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

9. Efeitos visuais e sonoros para vitória e derrota

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)

10. Reiniciar o jogo para uma nova partida

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)

11. Loop principal do jogo

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)

12. Funções auxiliares para monitoramento e execução principal

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.