#autodesk_wii_tanks_main_ver5

    ##IMPORT LIBRARIES

import time
from machine import Pin, freq, PWM
from ir_rx.print_error import print_error # IR Receiver Print Error Library
from ir_rx.nec import NEC_8 # IR Receiver Library
from ir_tx.nec import NEC # IR Transmitter Library

    ##BUZZER MUSIC SETUP

# note and timing dictionaries
notes_freq = {'C#4':277,'D4':294,'E4':330,'F4':349,'F#4':370,'G4':392,'A4':440,'B4':494,
              'C5':523,'C#5':554,'D5':587,'D#5':622,'E5':659,'F5':698,'G5':784,'A5':880,'B5':988,
              'C6':1046,'F6':1397,'G6':1568,
              'REST':20000,'SNARE':25
              } # {Note:Frequency (HZ)}
intro_timing = {0:'A4',62:'REST',83:'A4',143:'REST',167:'A4',229:'REST',250:'C5',375:'F5',500:'G5',625:'A5',1500:'REST'}
theme_timing = {0:'G5',83:'A5',167:'B5',
               250:'C6',375:'B5',500:'C6',750:'G6',1000:'G5',
               1250:'C6',1375:'B5',1500:'C6',1750:'G6',2000:'G5',2083:'A5',2167:'B5',
               2250:'C6',2375:'B5',2500:'C6',2750:'F5',3000:'C6',
               3250:'E5',3375:'D#5',3500:'E5',3750:'G5',4000:'D5',
               4250:'C#5',4500:'A5',4583:'B5',4667:'C#5',4750:'D5',5000:'A4',
               5250:'B4',5500:'G4',5583:'A4',5667:'B4',5750:'C5',6000:'G4',
               6250:'A4',6500:'F4',6583:'G4',6667:'A4',6750:'B4',7000:'F#4',
               7250:'A4',7500:'REST',7750:'F5'
               } # {Time (ms):Note}
lose_timing = {0:'C6',125:'REST',250:'G5',375:'F5',500:'E5',625:'REST',750:'D5',875:'REST',1000:'C#5',1500:'REST'}
# 1:2000, 1/2:1000, 1/4:500, 1/8:250, 1/8T:166.667, 1/16:125, 1/16T:83.334, 1/32:62.5 #120BPM to miliseconds conversion
# Sheet music source: https://flat.io/score/5fe3f00a6699255f467b4a68-wii-play-tanks-theme

buzzer = PWM(Pin(18))
buzzer.duty_u16(((2**16)-1)//100) # Volume control

last_time = time.ticks_ms() # Save current ms tick count

button20 = Pin(20, Pin.IN) # Lose condition placeholder

def intro_theme(): # Function for playing the intro theme
    global last_time
    try:
        buzzer.freq(notes_freq[intro_timing[time.ticks_diff(time.ticks_ms(), last_time)]])
    except:
        pass
        
def tanks_theme(): # Function for playing the main theme
    global last_time
    if time.ticks_diff(time.ticks_ms(), last_time) == 7999: #final time
        last_time = time.ticks_ms()
    try:
        buzzer.freq(notes_freq[theme_timing[time.ticks_diff(time.ticks_ms(), last_time)]])
    except:
        pass

def lose_theme(): # Function for playing the losing theme
    try:
        buzzer.freq(notes_freq[lose_timing[time.ticks_diff(time.ticks_ms(), last_time)]])
    except:
        pass

    ##IR REMOTE SET UP

#Define the IR receiver pin and motor pins
pin_ir = Pin(8, Pin.IN)
ENA = PWM(Pin(2))
IN1 = Pin(3,Pin.OUT)
IN2 = Pin(4,Pin.OUT)
IN3 = Pin(5,Pin.OUT)
IN4 = Pin(6,Pin.OUT)
ENB = PWM(Pin(7))
SERVO = PWM(Pin(9))

# Tank speed
speed = 60000 # 0 - 65025
ENA.duty_u16(speed)
ENB.duty_u16(speed)

# initiate servo and position
servo_position = [2310, 3310, 4310, 5310, 6310, 7310, 8310] #~2% to ~12% of PWM MAX for 0 to 180 degrees
pos_num = 3 # Start at 90 degrees
SERVO.duty_u16(servo_position[pos_num])
SERVO.freq(50)

#IR remote call data
Up = 24 #2
Down = 82 #8
Left = 8 #4
Right = 90 #6
Stop = 28 #5
Servo_left = 22 #0
Servo_right = 13 #200+
Shoot = 25 #100+
Hit_debug = 9 #EQ
Reset = 67 #Play/Pause

def decodeKeyValue(data): # Return data from IR receiver
    return data
 
def forward(): # Both Wheels Fowards 
    IN1.on()
    IN2.off()
    IN3.on()
    IN4.off()
    
def backward(): # Both Wheels backwards
    IN1.off()
    IN2.on()
    IN3.off()
    IN4.on()
    
def left(): # Right wheel fowards, left wheel backwards
    IN1.on()
    IN2.off()
    IN3.off()
    IN4.on()
    
def right(): # Left wheel fowards, right wheel backwards
    IN1.off()
    IN2.on()
    IN3.on()
    IN4.off()
    
def stop(): # Both wheels off
    IN1.off()
    IN2.off()
    IN3.off()
    IN4.off()

def servo_right(): # Turns servo right approximately 30 degrees
    global pos_num
    if pos_num < len(servo_position) - 1: # If position of servo is less than length of servo library
        pos_num += 1 # Incremet one up on servo position library
    SERVO.duty_u16(servo_position[pos_num])

def servo_left(): # Turns servo left approximately 30 degrees
    global pos_num
    if pos_num > 0: # If position of servo is greater than 0
        pos_num -= 1 # Incremet one down on servo position library
    SERVO.duty_u16(servo_position[pos_num])

RELOAD_TIME_MS = const(5000) # Adjust reload time in ms
def shoot():
    global reload_timer
    if time.ticks_diff(time.ticks_ms(), reload_timer) > RELOAD_TIME_MS: # Counter for reload speed
        nec = NEC(CANNON_IR) # Instantiate IR LED pin as an NEC protocol output
        nec.transmit(1, 9)  # Transmit signal, .transmit(address, data)
        reload_timer = time.ticks_ms() # Reset reload counter
    else:
        pass

def reload_check(): # Check if the tank is reloading
    global reload_timer
    if time.ticks_diff(time.ticks_ms(), reload_timer) < RELOAD_TIME_MS: # If reloading
        RELOAD.on() # Yellow LED on
    else: #If not reloading
        RELOAD.off() # Yellow LED off

hit_count = 0 
def hit(): # Count number of times tank has been hit
    global hit_count
    hit_count += 1

def reset(): # Reset the game from the IR Remmote Control
    machine.reset()

# User callback
def callback(data, addr, ctrl):
    if data < 0:  # NEC protocol sends repeat codes.
        pass
    else:
        print(data)
        if data == Up:
            forward()
        elif data == Down:
            backward()
        elif data == Left:
            left()
        elif data == Right:
            right()
        elif data == Stop:
            stop()
        elif data == Servo_left:
            servo_left()
        elif data == Servo_right:
            servo_right()
        elif data == Shoot:
            shoot()
        elif data == Hit_debug:
            hit()
        elif data == Reset:
            reset()

def callback_gameover(data, addr, ctrl):
    if data < 0:  # NEC protocol sends repeat codes.
        pass
    else:
        print(data)
        if data == Reset:
            reset()

ir = NEC_8(pin_ir, callback)  # Instantiate receiver

def ir_open():
    global ir
    ir = NEC_8(pin_ir, callback)  # Instantiate receiver
    ir.error_function(print_error)  # Show debug information
    DEAD.off() # Turn off red LED
    
def ir_block():
    global ir
    ir.close() # Close IR communication
    stop() # Stop the chassi
    DEAD.on() # Light red LED

    ##INITIALIZE LEDS AND HITPOINT SYSTEM

DEAD = Pin(10,Pin.OUT) #RED
HP_3 = Pin(11,Pin.OUT) #GREEN
HP_2 = Pin(12,Pin.OUT) #GREEN
HP_1 = Pin(13,Pin.OUT) #GREEN
RELOAD = Pin(14,Pin.OUT) #YELLOW
CANNON_IR = Pin(15,Pin.OUT, value = 0) #INFRARED

def hp_3(): # 3 green LEDs on
    HP_3.on()
    HP_2.on()
    HP_1.on()
    
def hp_2(): # 2 green LEDs on
    HP_3.off()
    HP_2.on()
    HP_1.on()

def hp_1(): # 1 green LED on
    HP_3.off()
    HP_2.off()
    HP_1.on()

def game_over(): # All green LEDs off
    HP_3.off()
    HP_2.off()
    HP_1.off()

    ##MAIN CODE

# 3 LIVES LEFT

hp_3()
ir_block()
while True: 
    intro_theme()
    if time.ticks_diff(time.ticks_ms(), last_time) == 2625:
        last_time = time.ticks_ms()
        break
ir_open()
reload_timer = time.ticks_ms()
while True:
    tanks_theme()
    reload_check()
    if button20.value() == 0 or hit_count == 1:
        last_time = time.ticks_ms()
        hp_2()
        ir_block()
        break
while True:
    lose_theme()
    if time.ticks_diff(time.ticks_ms(), last_time) == 2000:
        last_time = time.ticks_ms()
        break
    
# 2 LIVES LEFT
            
while True: 
    intro_theme()
    if time.ticks_diff(time.ticks_ms(), last_time) == 2625:
        last_time = time.ticks_ms()
        break
ir_open()
reload_timer = time.ticks_ms()
while True:
    tanks_theme()
    reload_check()
    if button20.value() == 0 or hit_count == 2:
        last_time = time.ticks_ms()
        hp_1()
        ir_block()
        break
while True:
    lose_theme()
    if time.ticks_diff(time.ticks_ms(), last_time) == 2000:
        last_time = time.ticks_ms()
        break
    
# 1 LIFE LEFT
    
while True: 
    intro_theme()
    if time.ticks_diff(time.ticks_ms(), last_time) == 2625:
        last_time = time.ticks_ms()
        break
ir_open()
reload_timer = time.ticks_ms()
while True:
    tanks_theme()
    reload_check()
    if button20.value() == 0 or hit_count == 3:
        last_time = time.ticks_ms()
        game_over()
        ir_block()
        break
while True:
    lose_theme()
    if time.ticks_diff(time.ticks_ms(), last_time) == 2000:
        last_time = time.ticks_ms()
        break

# GAMEOVER

ir = NEC_8(pin_ir, callback_gameover)  # Instantiate receiver but only allow resetting the game
ir.error_function(print_error)  # Show debug information



