import time
import random
from machine import Pin, PWM

# =========================
# PIN CONFIG
# =========================

RED_PIN = 5
BLUE_PIN = 9
YELLOW_PIN = 22
GREEN_PIN = 28

RED_B_PIN = 4
BLUE_B_PIN = 10
YELLOW_B_PIN = 21
GREEN_B_PIN = 27

START_B_PIN = 13

BUZZER_PIN = 16

# =========================
# HARDWARE SETUP
# =========================

red = Pin(RED_PIN, Pin.OUT)
blue = Pin(BLUE_PIN, Pin.OUT)
yellow = Pin(YELLOW_PIN, Pin.OUT)
green = Pin(GREEN_PIN, Pin.OUT)

redButton = Pin(RED_B_PIN, Pin.IN, Pin.PULL_UP)
blueButton = Pin(BLUE_B_PIN, Pin.IN, Pin.PULL_UP)
yellowButton = Pin(YELLOW_B_PIN, Pin.IN, Pin.PULL_UP)
greenButton = Pin(GREEN_B_PIN, Pin.IN, Pin.PULL_UP)

startButton = Pin(START_B_PIN,Pin.IN, Pin.PULL_UP)

buzzer = PWM(BUZZER_PIN)

LEDS = {
    "red": red,
    "blue": blue,
    "yellow": yellow,
    "green": green
}

BUTTONS = {
    "red": redButton,
    "blue": blueButton,
    "yellow": yellowButton,
    "green": greenButton
}

TONES = {
    "red": 415,
    "blue": 310,
    "yellow": 252,
    "green": 209,
    "error": 42
}

COLORS = ["red", "green", "blue", "yellow"]
MEMORY = []

# =========================
# LED CONTROL
# =========================

def trigColor(color: str, duration=0.5) -> None:
    LEDS[color].value(1)
    time.sleep(duration)
    LEDS[color].value(0)

def newColor() -> None:
    color = random.choice(COLORS)
    MEMORY.append(color)
    trigColor(color)

def printSequence() -> None:
    if not MEMORY:
        newColor()
    else:
        for color in MEMORY:
            trigColor(color)
            time.sleep(0.2)
        newColor()

# =========================
# INPUT WITH DEBOUNCE
# =========================

def getButton() -> str:
    while True:
        for color, button in BUTTONS.items():
            if button.value() == 0:  
                time.sleep(0.02)     
                
                while button.value() == 0:
                    pass            
                
                time.sleep(0.02)    
                return color

def playSound(color):
    if color in TONES:
        buzzer.freq(TONES[color])
        buzzer.duty_u16(32768)
        time.sleep(0.3)
        buzzer.duty_u16(0)              

# =========================
# GAME LOGIC
# =========================

def inputSequence() -> bool:
    for color in MEMORY:
        button = getButton()

        if button == color:
            playSound(color)
            trigColor(color, 0.3)
        else:
            return False

    return True

def resetGame():
    random.seed(time.ticks_us())
    MEMORY.clear()

def startAnimation():
    for led in LEDS.values():
        led.value(1)
    time.sleep(1)
    for led in LEDS.values():
        led.value(0)
    time.sleep(1)

def loseAnimation():
    playSound("error")
    for _ in range(3):
        for led in LEDS.values():
            led.value(1)
        time.sleep(0.2)
        for led in LEDS.values():
            led.value(0)
        time.sleep(0.2)

def startGame() -> None:
    resetGame()
    startAnimation()

    while True:
        printSequence()

        if not inputSequence():
            loseAnimation()
            break

        time.sleep(1)    

# =========================
# MAIN LOOP
# =========================


while True:
    if startButton.value() == 0:
        while startButton.value() == 0: pass

        startGame()
