#REGINALD HUEY TAN IAN JAY (S10239913) - IT01 (P01)

#============================== IMPORTING RESOURCES ===============================
import random, math, time
import os, asyncio
os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide" #hide pygame initialisation message
from pygame import mixer
from S10239913E_Assignment_gameData import game_vars, defender_list, monster_list, defenders, monsters, alphabet, field, turnEvents

#=============================== GAME FUNDAMENTALS ================================
def initialize_game(): #Initializes all the game variables for a new game
    game_vars['turn'] = 1
    game_vars['monster_kill_target'] = 20
    game_vars['monsters_killed'] = 0
    game_vars['num_monsters'] = 0
    game_vars['gold'] = 10
    game_vars['threat'] = 10
    game_vars['max_threat'] = 10
    game_vars['danger_level'] = 1
def show_main_menu(): #Displays the main menu & all of its options
    print()
    print(f'{" MAIN MENU ":-^55}') #f-formatting to show header of main menu
    print("1. Start new game       2. Load saved game")
    print("3. Alter game options   4. Show unit information")
    print("5. Timed Game Mode      6. Quit game")
    print('-' * 55) #f-formatting to end start of main menu
def show_combat_menu(game_vars, back): #Displays the main menu & all of its options
    if back == False:
        print(f'  Turn {game_vars.get("turn")} \t Threat = {threat_bar(game_vars)} \t Danger Level = {game_vars.get("danger_level")}') #Displays game status: Turn, Threat Metre, Danger Level
        print(f'  Gold = {game_vars.get("gold")} \t Monsters killed = {game_vars.get("monsters_killed")}/{game_vars.get("monster_kill_target")}') #Displays game status: Gold, Number of Monster Kills out of the Target Monster Kills
        print()
    print(f'{" COMBAT MENU ":-^55}') #f-formatting to show header of combat menu
    print("1. Buy unit          2. End turn")
    print("3. Upgrade Archers   4. Upgrade Walls")
    print("5. Save game         6. Quit")
    print('-'*55) #f-formatting to show end of combat menu
def alter_game_options(): #function to display alter game options menu
    print(f'{" ALTER GAME OPTIONS ":-^55}')  # f-formatting to show header of alter game options menu
    print("1. Field Size                  2. Defender Spawn Area")
    print("3. Number of Kills to Win      4. Gold Increase per Turn")
    print('-' * 55)  # f-formatting to end start of alter game options menu
def draw_field(field): #Draws the field of play
    columnHeader, divisor = '', '  +'
    for i in range(game_vars.get('defender_spawn_boundary', 3)): columnHeader += f'{i+1:6}' #concatenates a string to be the headers for the columns (1,2,3)
    fieldLength, fieldWidth = len(field[0]), len(field) #declaring dimensions of field
    for j in range(fieldLength): divisor += '-----+' #for loop to concatenate a string to be the horizontal divisor
    print(f'{columnHeader}\n{divisor}') #outputs the column headers and 1 horizontal divisor
    for lane in range(fieldWidth): #iterates through field with iterator, lane
        nameLane, hpLane = f'{alphabet[lane]} |', f'  |' #declares 2 strings, one for unit name, and one for unit hp
        for tile in range(fieldLength): #nested loop to iterate through lane with iterator, tile
            if field[lane][tile] == [None, None]: #checks that tile is emptyr
                nameLane += f'{"":5}|' #adds 5 blank spaces to unit name string since tile is empty
                hpLane += f'{"":5}|' #adds 5 blank spaces to unit hp string since tile is empty
            else: #tile is not empty
                nameLane += f'{field[lane][tile][0]:5}|' #adds name to unit name string using f-formatting to centralise name in string
                hpLane += f'{field[lane][tile][1]:^5}|' #adds hp to unit hp string using f-formatting to centralise hp in string
        print(f'{nameLane}\n{hpLane}\n{divisor}') #outputs the unit name string, creates a new line, outputs the unit hp string, creates a new line, outputs 1 horizontal divisor
def quit_game():
    #Function prints a default message whenever game is quit
    print('\nTHANKS FOR PLAYING! :)')
    print('CLOSING GAME', end='')
    time.sleep(0.25)
    for i in range(3): #loop to add a . to "CLOSING GAME" every 0.25s, gives a sense of progression
        print('.', end='')
        time.sleep(0.25)
    print()
    quit() #Exits code in interpreter

#================================ GAME SAVE & LOAD ================================
def save_game(): #Saves the game in the file 'save.txt'
    save_file = open("save.txt", "w")
    save_file.write(f'{game_vars["turn"]+1}\n') #stores game variable "turn" in 'save.text'
    save_file.write(f'{game_vars["monster_kill_target"]}\n') #stores game variable "monster_kill_target" in 'save.text'
    save_file.write(f'{game_vars["monsters_killed"]}\n') #stores game variable "monsters_killed" in 'save.text'
    save_file.write(f'{game_vars["num_monsters"]}\n') #stores game variable "num_monsters" in 'save.text'
    save_file.write(f'{game_vars["gold"]}\n') #stores game variable "gold" in 'save.text'
    save_file.write(f'{game_vars["threat"]}\n') #stores game variable "threat" in 'save.text'
    save_file.write(f'{game_vars["max_threat"]}\n') #stores game variable "max_threat" in 'save.text'
    save_file.write(f'{game_vars["danger_level"]}\n') #stores game variable "danger_level" in 'save.text'

    for lane in range(len(field)): #for loop that iterates through each tile in field, saving the tile data in a specified format
        for tile in range(len(field[0])):
            if field[lane][tile] is not None:
                save_file.write(f'{lane}|{tile}|{field[lane][tile][0]}|{field[lane][tile][1]}')
                save_file.write('\n')

    save_file.close()
    print("GAME SAVED")
def load_game(game_vars): #Loads the game data from 'save.txt'
    filename = 'save.txt'
    save_file = open(filename, "r")
    firstLine = save_file.readline() #stores first line of file in case it needs to be used multiple times
    if firstLine == '': #check if file is empty
        print(f'FILE <{filename}> IS EMPTY')
        quit_game()
    else:
        print('LOADING SAVED GAME', end='')
        time.sleep(0.25)
        for i in range(3): #loop to add a . to "LOADING SAVED GAME" every 0.25s, gives a sense of progression
            print('.', end='')
            time.sleep(0.25)
        print()
        game_vars["turn"] = int(firstLine) #stores game variable "turn" in game_vars dictionary
        game_vars["monster_kill_target"] = int(save_file.readline()) #stores game variable "monster_kill_target" in game_vars dictionary
        game_vars["monsters_killed"] = int(save_file.readline()) #stores game variable "monsters_killed" in game_vars dictionary
        game_vars["num_monsters"] = int(save_file.readline()) #stores game variable "num_monsters" in game_vars dictionary
        game_vars["gold"] = int(save_file.readline()) #stores game variable "gold" in game_vars dictionary
        game_vars["threat"] = int(save_file.readline()) #stores game variable "threat" in game_vars dictionary
        game_vars["max_threat"] = int(save_file.readline()) #stores game variable "max_threat" in game_vars dictionary
        game_vars["danger_level"] = int(save_file.readline()) #stores game variable "danger_level" in game_vars dictionary

        for line in save_file: #nested loop that iterates line by line through save.txt and obtains field data
            line = line.strip("\n")
            item = line.split("|")
            if [item[2], item[3]] == ['None', 'None']:
                field[int(item[0])][int(item[1])] = [None, None]
            else:
                field[int(item[0])][int(item[1])] = [item[2], item[3]]
    save_file.close()

#=================================== GAME LOGIC ===================================
def navigate_main_menu(choice, field): #Function to navigate main menu
    if choice == 1: #check to start new game
        initialize_game() #reset the game variables data to ensure a new game
        run_game(game_vars, field, monster_list, turnEvents) #begin the game, incrememnting by turns and ending when the monsters killed is equal to the monster kills target
    elif choice == 2: #load most recent saved game
        load_game(game_vars)
        run_game(game_vars, field, monster_list, turnEvents) #begin the game, incrememnting by turns and ending when the monsters killed is equal to the monster kills target
    elif choice == 3:
        alter_game_options()
        while True:
            choice = get_input('alter-game-settings')
            if choice == 1: #change field size
                field = change_field_size()
                draw_field(field)
            elif choice == 2: game_vars['defender_spawn_boundary'] = get_input('defender-spawn-area') #change defender spawn boundary
            elif choice == 3: game_vars['monster_kill_target'] = get_input('kills-to-win') #change number of kills required to win
            elif choice == 4: game_vars['gold_increase_by_turn'] = get_input('gold_increase_by_turn') #change the gold increase every turn
            playGame = get_input('play-game')
            if playGame == True: break #start game or alter more settings
            if playGame == False: alter_game_options()
        run_game(game_vars, field, monster_list, turnEvents) #begin the game, incrememnting by turns and ending when the monsters killed is equal to the monster kills target
    elif choice == 4: unit_information(defenders, monsters)
    elif choice == 5:
        game_vars['timed'] = get_input('timed')
        if game_vars['timed'] == True: print('TIMED MODE - ON')
        else: print('TIMED MODE - OFF')
    elif choice == 6: quit_game() #quit game
def navigate_combat_menu(choice): #Function to navigate combat menu
    if choice == 1: buy_unit() #allows player to choose which unit they want to buy and where to place it
    elif choice == 3: upgrade_archers(game_vars, field, turnEvents)
    elif choice == 4: upgrade_walls(game_vars, field, turnEvents)
    elif choice == 5:
        save_game()  # saves game progress in 'save.txt'
        quit_game()  # calls function to close game
    elif choice == 6: quit_game()  # calls function to close game
    elif choice == 2: pass  # end turn
def check_spawn(game_vars): #function to check spawn requirements
    if game_vars.get('threat') >= game_vars.get('max_threat'): #checks if the threat level is greater than the maximum threat level
        game_vars['threat'] -= game_vars.get('max_threat')
        return True #returns True, which is used to confirm the spawn of monsters when function is called
    elif game_vars['num_monsters'] == 0: return True #returns True, which is used to confirm the spawn of monsters when function is called
    else: return False #returns False, which prevents the spawn of monsters when function is called
def check_place(field, placement):
    selectedLane, selectedTile  = alphabet.index(placement[0]), int(placement[1]) #obtains data on placement argument
    if field[selectedLane][selectedTile-1] == [None, None]: return True #checks if the placement tile is empty
    else: return False
def buy_unit(): #function to handle the purchasing
    i = 0
    for unit, info in defenders.items():  # loop to iterate through defenders dictionary
        print(
            f'{i + 1}. {info.get("name").capitalize():6} {"-" * 15} {info.get("price")} Gold')  # outputs unit number, unit name, and unit cost
        i += 1  # counts for unit number
    print(
        f"{i + 1}. Don't Purchase")  # outputs a 'cancel' option that allows user to not purchase anything and skip the turn
    choice = get_input('buy-unit')  # obtains validated input on which unit to buy
    flag = True
    while True:
        if choice <= len(defender_list) and flag is True:  # checks if user purchased a defender
            unitName = defender_list[choice - 1]
            if game_vars["gold"] < defenders[unitName].get("price"):
                print('NOT ENOUGH GOLD')  # validates if there is enough gold
                show_combat_menu(game_vars, True)
            else:
                game_vars["gold"] -= defenders[unitName].get("price")
                for unit, info in defenders.items():  # loop to obtain unit name and max hp for placing
                    if unitName == unit:
                        hp = f"{info.get('maxHP')}/{info.get('maxHP')}"
                        break
                place_unit(field, unitName, hp)
            break
        elif choice == len(
                defender_list) + 1:  # changes option number for dont purchase depending on number of defenders
            print('NO PURCHASE')  # checks if user cancelled purchase
            show_combat_menu(game_vars, True)  # displays combat menu again
            flag = False
        choice = get_input('combat')  # obtains validated input on combat menu navigation
        if choice == 2: break  # special exception for end turn
        navigate_combat_menu(choice)  # calls function to navigate combat menu
def place_unit(field, unitName, hp): #function to check if user-chosen placement space is available
    while True:
        placement = get_input('place-unit')  # obtains validated input on where to place unit
        if check_place(field, placement) == True:  # if True, defender will be placed there
            selectedLane, selectedTile = alphabet.index(placement[0]), int(placement[1])
            field[selectedLane][selectedTile - 1] = [unitName, hp]  # changes placement tile data
            break
        else:
            print('POSITION ERROR: POSITION ALREADY OCCUPIED')  # outputs error message if the placement tile is already occupied
def spawn_monster(field, monster_list, turnEvents): #function to spawn in monsters
    while True: #infinite loop to check if position is empty
        spawnPosition = random.randint(0,len(field)-1) #generates a random number within the number of lanes
        placement = f'{alphabet[spawnPosition]}{len(field[0])}'
        if check_place(field, placement) == True: #calls function, check_place(), to verify that placement tile is empty
            selectedLane  = alphabet.index(placement[0])
            monsterName = monster_list[random.randint(0,len(monster_list)-1)] #generates a monster from monster_list
            hp = f"{monsters[monsterName].get('maxHP')}/{monsters[monsterName].get('maxHP')}" #generates the monster at full HP
            field[selectedLane][-1] = [monsterName, hp] #replaces the placement tile with randomly generated monster name and monster HP
            break
    game_vars['num_monsters'] += 1 #adds to the total number of monsters alive on the field
    turnEvents += [f'A monster has spawned in Lane {alphabet[selectedLane]}'] #adds string to turnEvents to be output as a turn event
def monster_advance(field, turnEvents): #function that advances monster & deals damage
    for lane in field: #iterates through field
        for tile in lane:
            if tile[0] in monster_list:
                speed = monsters[tile[0]].get('moves') #obtains how many tiles a monster can move at once from monsters dictionary
                monsterPosition = tile
                i, j = lane.index(tile) - speed, lane.index(tile) #intended tile to advance to, current tile
                flag = False
                for index in range(j-i):
                    if lane[i + index][0] in defender_list:
                        flag = True
                        break
                if flag is False and i < 0:
                    game_vars['game_lost'] = True
                    game_vars['monster_end_game'] = monsters[tile[0]].get("name")
                for index in range(j - i):
                    if lane[i + index][0] in defender_list:
                        monsterDamage = random.randint(monsters[tile[0]].get('min_damage'), monsters[tile[0]].get('max_damage'))
                        if tile[0] == 'ZOMBI': #special string for zombies, as they bite
                            turnEvents += [f'{monsters[tile[0]].get("name")} on {alphabet[field.index(lane)]}{lane.index(monsterPosition)+1} bites {defenders[lane[i][0]].get("name")} for {monsterDamage} damage!'] #adds string to turnEvents to be output as a turn event
                        else:
                            turnEvents += [f'{monsters[tile[0]].get("name")} on {alphabet[field.index(lane)]}{lane.index(monsterPosition) + 1} hits {defenders[lane[i+index][0]].get("name")} for {monsterDamage} damage!'] #adds string to turnEvents to be output as a turn event
                        unitTotalHp = lane[i+index][1].split('/')
                        unitHp = int(unitTotalHp[0])
                        unitHp -= monsterDamage
                        if unitHp <= 0:  #checks if the defender died to monster
                            turnEvents += [f'{defenders[lane[i+index][0]].get("name")} on {alphabet[field.index(lane)]}{lane.index(monsterPosition) + 1} was slain by {monsters[tile[0]].get("name")}'] #adds string to turnEvents to be output as a turn event
                            lane[i+index] = [None, None] #resets the tile
                            lane[i+index], lane[j] = lane[j], lane[i+index]  # swaps tiles to advance monster
                        else:  #change the defender HP after being attacked
                            unitTotalHp = f'{unitHp}/{defenders[lane[i+index][0]].get("maxHP")}'
                            lane[i+index][1] = unitTotalHp
                    elif lane[i][0] in monster_list and lane[j][0] in monster_list: #monster is blocked by a monster in front
                        if lane[i + index][0] is None:
                            lane[i + index], lane[j] = lane[j], lane[i + index]  # swaps tiles to advance monster
                            turnEvents += [f'{monsters[tile[0]].get("name")} on {alphabet[field.index(lane)]}{lane.index(monsterPosition) + 1} is blocked from advancing!']  # adds string to turnEvents to be output as a turn event
                            break
                    else:  #advance monster by speed
                        if lane[i][0] in monster_list and lane[j][0] in monster_list: #checks if the unit in tiles are monsters
                            turnEvents += [f'{monsters[tile[0]].get("name")} on {alphabet[field.index(lane)]}{lane.index(monsterPosition) + 1} is blocked from advancing'] #adds string to turnEvents to be output as a turn event
                        else: #monster can advance
                            if monsters[tile[0]].get("moves") > 1:
                                turnEvents += [f'{monsters[tile[0]].get("name")} on {alphabet[field.index(lane)]}{lane.index(monsterPosition)+1} advances by {monsters[tile[0]].get("moves")} spaces!'] #adds string to turnEvents to be output as a turn event
                            else:
                                turnEvents += [f'{monsters[tile[0]].get("name")} on {alphabet[field.index(lane)]}{lane.index(monsterPosition) + 1} advances!'] #adds string to turnEvents to be output as a turn event
                            lane[i + index], lane[j] = lane[j], lane[i + index]  # swaps tiles to advance monster
                            break
def defender_attack(field, turnEvents): #function to handle the attacks of the defenders
    for lane in field: #iterates through field
        defenderName, damageDealt = '', 0 #protects code from crashes due to undeclared variables
        for defenderTile in lane[:game_vars.get('defender_spawn_boundary', 3)]: #iterates through the defenders spawn area
            if defenderTile[0] in defender_list: #checks if the iterated tile is a defender
                if defenderTile[0] == 'CANON':
                    defenderName = defenders[defenderTile[0]].get('name')
                    damageDealt = random.randint(defenders[defenderTile[0]].get('min_damage'), defenders[defenderTile[0]].get('max_damage'))
                    for monsterTile in lane: #iterates through lane to detect first monster
                        if monsterTile[0] in monster_list:  # checks if the iterated tile is a monster
                            if damageDealt != 0 and defenderName != '':  # prevents turn events that show 0 damage was done
                                if defenderName == 'Cannon':
                                    turnEvents += [f'{defenderName} in Lane {alphabet[field.index(lane)]} shot a cannonball at {monsters[monsterTile[0]].get("name")} for {damageDealt} damage!']  # adds string to turnEvents to be output as a turn event
                                if game_vars.get('turn') % 2 == 0:
                                    hp = monsterTile[1].split('/')  # obtains the HP of unit
                                    monsterHp = int(hp[0]) - damageDealt  # subtracts damage dealt from unit HP
                                    if monsterHp <= 0:  # MONSTER DIES
                                        turnEvents += [f'{defenderName} in Lane {alphabet[field.index(lane)]} slays {monsters[monsterTile[0]].get("name")} on {alphabet[field.index(lane)]}{lane.index(monsterTile) + 1}!']
                                        turnEvents += [f'You gain {monsters[monsterTile[0]].get("reward")} gold for slaying a {monsters[monsterTile[0]].get("name")}!']
                                        game_vars['monsters_killed'] += 1  # increases monsters_killed game variable by 1 every time a monster is killed
                                        game_vars['gold'] += monsters[monsterTile[0]].get('reward')  # increases gold game variable by monster reward every time a monster is killed
                                        game_vars['threat'] += monsters[monsterTile[0]].get('reward')  # increases threat level game variable by monster reward every time a monster is killed
                                        spawn_monster(field, monster_list, turnEvents)  # spawns a new monster to compensate for death of monster
                                        game_vars['num_monsters'] -= 1  # decreases total number of monsters by 1 every time a monster is killed
                                        monsterTile[0], monsterTile[1] = None, None  # resets the tile to be empty
                                    else:
                                        monsterTile[1] = f'{str(monsterHp)}/{hp[1]}'  # changes the tile data to show the monster HP after being damaged
                                    chance = random.randint(0,1)
                                    if chance == 1: #knockback monsters in lane by 1
                                        for index in range(len(lane)):
                                            if lane[index][0] in monster_list and lane[index+1][0] is None:
                                                lane[index+1] = lane[index]
                                                lane[index] = [None, None]
                                                break
                                    break
                else:
                    defenderName = defenders[defenderTile[0]].get('name')
                    damageDealt = random.randint(defenders[defenderTile[0]].get('min_damage'), defenders[defenderTile[0]].get('max_damage'))
                    for monsterTile in lane: #iterates through lane to detect first monster
                        if monsterTile[0] in monster_list: #checks if the iterated tile is a monster
                            if damageDealt != 0 and defenderName != '': #prevents turn events that show 0 damage was done
                                if defenderName == 'Archer': turnEvents += [f'{defenderName} in Lane {alphabet[field.index(lane)]} fires an arrow at {monsters[monsterTile[0]].get("name")} for {damageDealt} damage!'] #adds string to turnEvents to be output as a turn event
                                elif defenderName == 'Ninja': turnEvents += [f'{defenderName} in Lane {alphabet[field.index(lane)]} throws a shuriken at {monsters[monsterTile[0]].get("name")} for {damageDealt} damage!'] #adds string to turnEvents to be output as a turn event
                                else: turnEvents += [f'{defenderName} in Lane {alphabet[field.index(lane)]} deals {damageDealt} damage to {monsters[monsterTile[0]].get("name")}!'] #adds string to turnEvents to be output as a turn event
                            hp = monsterTile[1].split('/') #obtains the HP of unit
                            monsterHp = int(hp[0]) - damageDealt #subtracts damage dealt from unit HP
                            if monsterHp <= 0:  # MONSTER DIES
                                turnEvents += [f'{defenderName} in Lane {alphabet[field.index(lane)]} slays {monsters[monsterTile[0]].get("name")} on {alphabet[field.index(lane)]}{lane.index(monsterTile) + 1}!']
                                turnEvents += [f'You gain {monsters[monsterTile[0]].get("reward")} gold for slaying a {monsters[monsterTile[0]].get("name")}!']
                                game_vars['monsters_killed'] += 1 #increases monsters_killed game variable by 1 every time a monster is killed
                                game_vars['gold'] += monsters[monsterTile[0]].get('reward') #increases gold game variable by monster reward every time a monster is killed
                                game_vars['threat'] += monsters[monsterTile[0]].get('reward') #increases threat level game variable by monster reward every time a monster is killed
                                spawn_monster(field, monster_list, turnEvents) #spawns a new monster to compensate for death of monster
                                game_vars['num_monsters'] -= 1 #decreases total number of monsters by 1 every time a monster is killed
                                monsterTile[0], monsterTile[1] = None, None #resets the tile to be empty
                            else: monsterTile[1] = f'{str(monsterHp)}/{hp[1]}' #changes the tile data to show the monster HP after being damaged
                            break
def run_game(game_vars, field, monster_list, turnEvents): #runs game, each iteration counts as 1 turn
    while True:  #infinite loop, each iteration of the loop counts as 1 turn
        if game_vars.get('game_lost') is True: #checks if game has been lost
            print(f'A {game_vars["monster_end_game"]} has reached the city! All is lost!')
            print('You have lost the game :(')
            quit_game() #calls function to close game

        if game_vars['timed'] is True:
            start_time = time.time()

        if check_spawn(game_vars) is True: spawn_monster(field, monster_list, turnEvents) #spawns a monster if spawn conditions are met
        defender_attack(field, turnEvents) #defenders attack monsters
        game_transcript(turnEvents) #outputs all of the events that occured over the turn

        if game_vars.get('monsters_killed') >= game_vars.get('monster_kill_target'): #checks if game has been won
            print('You have protected the city! You win!')
            quit_game() #calls function to close game

        draw_field(field) #displays updated field
        show_combat_menu(game_vars, False) #displays combat menu

        choice = get_input('combat') #obtains validated user input to use for combat menu navigation
        navigate_combat_menu(choice) #calls function to navigate combat menu
        monster_advance(field, turnEvents)

        if game_vars['timed'] is True:
            end_time = time.time()
            if timer(start_time, end_time) > 12.5:
                if game_vars.get('time_out_chance') == 0:
                    print('You ran out of time!')
                    print('You have lost the game :(')
                    quit_game()  # calls function to close game
                else:
                    game_vars['time_out_chance'] -= 1
                    print('You ran out of time!')
                    print(f'{game_vars.get("time_out_chance")} chances left!')

        game_vars['turn'] += 1 #increases game variable 'turn' by 1
        game_vars['gold'] += game_vars.get('gold_increase_by_turn') #increases game variable 'gold' by the game variable 'gold_increase_by_turn'
        game_vars['threat'] += random.randint(1, game_vars.get('danger_level')) #increases threat level by a random number between 1 and danger level (inclusive)

        if (game_vars.get('turn') % 12) == 0 and (game_vars.get('turn') != 0): #checks if conditions are met for a danger level increase
            danger_level_increase(game_vars, turnEvents)

#=========================== EXTRA & ADVANCED FUNCTIONS ===========================
def timer(start, end): #function to record time taken for something (can only be used one at a time as there is no threading)
    sec = math.floor(end - start)
    return sec
def change_field_size(): #changes number of lanes and number of tiles per lane
    field = []
    dimensions = get_input('change-field-size') #return as a list instead of string in case of double digit dimensions
    fieldWidth, fieldLength = dimensions[0], dimensions[1]
    for i in range(fieldWidth): #iterates the number of times the player defined the number of lanes in field
        row = [] #declares an empty lane for every iteration of a lane
        for j in range(fieldLength): row.append([None, None]) #iterates the number of times the player defined the length of each lane
        field.append(row) #adds the updated lane for every iteration of a lane
    return field #outputs the new player-defined field
def threat_bar(game_vars): #concatenates strings to form the threat bar used in combat menu game status
    threatLevel = '['
    threatLevel += '-' * game_vars.get('threat', 0) #.get() second parameter to handle data file errors
    threatLevel += ' ' * (game_vars.get('max_threat', 10) - game_vars.get('threat', 0)) #.get() second parameter to handle data file errors
    threatLevel += ']'
    return threatLevel
def upgrade_archers(game_vars, field, turnEvents): #function to upgrade archers
    if game_vars['gold'] >= defenders['ARCHR']['upgrade_cost']:
        game_vars['gold'] -= defenders['ARCHR']['upgrade_cost']
        defenders['ARCHR']['maxHP'] += 1
        defenders['ARCHR']['min_damage'] += 1
        defenders['ARCHR']['max_damage'] += 1
        defenders['ARCHR']['upgrade_cost'] += 2
        defenders['ARCHR']['level'] += 1
        turnEvents += [f"Archers upgraded to Level {defenders['ARCHR'].get('level', 1)}!"]  # adds string to turnEvents to be output as a turn event
        for lane in field:
            for tile in lane:
                if tile[0] == 'ARCHR':
                    unitHp = tile[1].split('/')
                    tile[1] = f'{int(unitHp[0])+1}/{defenders["ARCHR"].get("maxHP")}'
    else:
        print('NOT ENOUGH GOLD')
        show_combat_menu(game_vars, True)
def upgrade_walls(game_vars, field, turnEvents): #function to upgrade walls
    if game_vars['gold'] >= defenders['WALL']['upgrade_cost']:
        game_vars['gold'] -= defenders['WALL']['upgrade_cost']
        defenders['WALL']['maxHP'] += 5
        defenders['WALL']['upgrade_cost'] += 2
        defenders['WALL']['level'] += 1
        turnEvents += [f"Walls upgraded to Level {defenders['WALL'].get('level', 1)}!"]  # adds string to turnEvents to be output as a turn event
        for lane in field:
            for tile in lane:
                if tile[0] == 'WALL':
                    unitHp = tile[1].split('/')
                    tile[1] = f'{int(unitHp[0]) + 5}/{defenders["WALL"].get("maxHP")}'
    else:
        print('NOT ENOUGH GOLD')
        show_combat_menu(game_vars, True)
def unit_information(defenders, monsters): #function to output all information on both defenders and monsters
    print(f'{"Defenders":~^55}', end='')
    for unit, unitInfo in defenders.items():
        print()
        print(f'Name: {unitInfo.get("name")}')
        print(f'CodeName: {unit}')
        print(f'Full HP: {unitInfo.get("maxHP")}')
        print(f'Min-Max Damage: {unitInfo.get("min_damage")}-{unitInfo.get("max_damage")} ')
        print(f'Price: {unitInfo.get("price")}')
    print(f'{"Monsters":~^55}', end ='')
    for unit, unitInfo in monsters.items():
        print()
        print(f'Name: {unitInfo.get("name")}')
        print(f'CodeName: {unit}')
        print(f'Full HP: {unitInfo.get("maxHP")}')
        print(f'Min-Max Damage: {unitInfo.get("min_damage")}-{unitInfo.get("max_damage")} ')
        print(f'Speed: {unitInfo.get("moves")}')
        print(f'Rewards: {unitInfo.get("rewards")}')
    print('~' * 55)
def get_input(menuClass): #function to obtain input and validate it, menuClass parameter identifies which input it is obtaining and validating accordingly, depending on function arguments
    x = 1
    while True: #input loop
        if menuClass == 'main' or menuClass == 'combat': choice = input('>>> Your choice: ')
        elif menuClass == 'buy-unit': choice = input('>>> Unit to Purchase: ')
        elif menuClass == 'place-unit': choice = input('>>> Position to Place Unit: ')
        elif menuClass == 'alter-game-settings': choice = input('>>> Your choice: ')
        elif menuClass == 'change-field-size':
            while True:
                fieldWidth = input('>>> Number of Lanes: ')
                if fieldWidth.isnumeric() is not True: print('INPUT TYPE ERROR: NUMBER OF LANES SHOULD BE A NUMBER')
                elif int(fieldWidth) == 0: print('RANGE ERROR: NUMBER OF LANES CANNOT BE 0')
                elif int(fieldWidth) < 3: print('RANGE ERROR: NUMBER OF LANES CANNOT BE LESS THAN 3')
                elif int(fieldWidth) > 26: print('RANGE ERROR: NUMBER OF LANES CANNOT BE GREATER THAN 26')
                else: break
            while True:
                fieldLength = input('>>> Number of Tiles in Each Lane: ')
                if fieldLength.isnumeric() is not True: print('INPUT TYPE ERROR: NUMBER OF TILES PER LANE SHOULD BE A NUMBER')
                elif int(fieldLength) == 0: print('RANGE ERROR: NUMBER OF TILES PER LANE CANNOT BE 0')
                elif int(fieldLength) < 5: print('RANGE ERROR: NUMBER OF TILES PER LANE CANNOT BE LESS THAN 5')  #CHANGE LATER
                else: break
        elif menuClass == 'defender-spawn-area': boundary = input('>>> New Furthest Tile for Defender Spawn: ')
        elif menuClass == 'kills-to-win': kills = input('>>> New Monster Kills Target: ')
        elif menuClass == 'gold_increase_by_turn': gold = input('>>> New Gold Increase per Turn: ')
        elif menuClass == 'play-game': choice = input('>>> Start Game? [Y/n]: ')
        elif menuClass == 'timed': time = input('>>> Timed Mode? [On/Off]: ')
        else: #in case function is called with an incorrect menuClass, prevents crashes with expansion of program
            print('PROGRAM ERROR: "menuClass" VARIABLE UNDETECTED')
            menuClass = input('>>> PLEASE MANUALLY INPUT "menuClass" VARIABLE: ')
            if x == 3:
                print('\nGAME SHUT DOWN DUE TO REPEATED ERROR', end='')
                quit_game()
            x += 1

        if menuClass == 'main': #data validation for main menu
            if not choice.isnumeric():
                print('TYPE ERROR: PLEASE ENTER A NUMBER')
                continue #prevents a ValueError because of the type casting on the next line
            choice = int(choice)
            if not (choice >= 1 and choice <= 6): print('RANGE ERROR: PLEASE ENTER A NUMBER BETWEEN 1 TO 6')
            else:
                output = choice
                break
        elif menuClass == 'combat': #data validation for combat menu
            if not choice.isnumeric():  # check for number
                print('INPUT TYPE ERROR: PLEASE ENTER A NUMBER')
                continue #prevents a ValueError because of the type casting on the next line
            choice = int(choice)
            if not (choice >= 1 and choice <= 6): print('RANGE ERROR: PLEASE ENTER A NUMBER BETWEEN 1 TO 6')
            else:
                output = choice
                break
        elif menuClass == 'buy-unit': #data validation for purchasing defenders
            if choice.isalpha() is True:
                defenderFullName = []
                for unit, info in defenders.items(): defenderFullName += [info.get('name').upper()] #loop to iterate through defenders dictionary and obtain names, capitalise them, and add them to the list, defenderFullName
                if choice.upper() in defenderFullName:
                    output = defenderFullName.index(choice.upper()) + 1
                    break
                else: print("INPUT ERROR: PLEASE ENTER UNIT NAME CORRECTLY")
            elif choice.isnumeric():
                output = int(choice)
                if output <= len(defender_list): break
                elif output == len(defender_list)+1: break
                else: print("INPUT ERROR: PLEASE ENTER UNIT NUMBER CORRECTLY")
        elif menuClass == 'place-unit':  # data validation for placing of purchased defenders
            if not choice.isalnum():
                print('TYPE ERROR: PLEASE ENTER ALPHANUMERIC CHARACTERS')
            else:
                choice = choice.capitalize()
            if len(choice) != 2:
                print('LENGTH ERROR: PLEASE INPUT USING THE FOLLOWING FORMAT: LaneColumn (e.g. A1)')
            elif (not choice[0].isalpha()) and (not choice[1].isnumeric()):
                print('FORMAT ERROR: PLEASE INPUT USING THE FOLLOWING FORMAT: LaneColumn (e.g. A1)')
            elif alphabet.index(choice[0]) >= alphabet.index(alphabet[len(field)]):
                print(f'RANGE ERROR: PLEASE ENSURE LANE LETTER COMES BETWEEN A & {alphabet[len(field)-1]}')
            elif int(choice[1]) > game_vars.get('defender_spawn_boundary', 3):
                print(f'RANGE ERROR: PLEASE ENSURE SELECTED TILE IS BETWEEN 1 & {game_vars.get("defender_spawn_boundary", 3)} (INCLUSIVE)')
            else:
                output = choice
                break
        elif menuClass == 'alter-game-settings':
            if not choice.isnumeric():
                print('TYPE ERROR: PLEASE ENTER A NUMBER')
                continue #prevents a ValueError because of the type casting on the next line
            choice = int(choice)
            if not (choice >= 1 and choice <= 4): print('RANGE ERROR: PLEASE ENTER A NUMBER BETWEEN 1 TO 4')
            else:
                output = choice
                break
        elif menuClass == 'change-field-size':
            output = [int(fieldWidth), int(fieldLength)]
            break
        elif menuClass == 'defender-spawn-area':
            if not boundary.isnumeric():
                print('INPUT TYPE ERROR: PLEASE ENTER A NUMBER')
                continue #prevents a ValueError because of the type casting on the next line
            elif int(boundary) == 0: print('RANGE ERROR: DEFENDER SPAWN BOUNDARY CANNOT BE 0')
            elif int(boundary) < 3: print('RANGE ERROR: DEFENDER SPAWN BOUNDARY CANNOT BE LESS THAN 3')
            else:
                output = int(boundary)
                break
        elif menuClass == 'kills-to-win':
            if kills.isnumeric() is not True: print('INPUT TYPE ERROR: PLEASE INPUT A NUMBER')
            elif int(kills) == 0:
                print('RANGE ERROR: MONSTER KILLS TARGET CANNOT BE 0')
            elif int(kills) < 5:
                print('RANGE ERROR: MONSTER KILLS TARGET CANNOT BE LESS THAN 5')
            else:
                output = int(kills)
                break
        elif menuClass == 'gold_increase_by_turn':
            if gold.isnumeric() is not True: print('INPUT TYPE ERROR: PLEASE INPUT A NUMBER')
            elif int(gold) == 0: print('RANGE ERROR: GOLD INCREASE PER TURN CANNOT BE 0')
            else:
                output = int(gold)
                break
        elif menuClass == 'play-game':
            if choice.isalpha() is not True: print('INPUT TYPE ERROR: PLEASE INPUT "YES" OR "NO"')
            elif choice.upper() == 'YES' or choice.upper() == 'Y':
                output = True
                break
            elif choice.upper() == 'NO' or choice.upper() == 'N':
                output = False
                break
            else: print('INPUT ERROR: PLEASE INPUT "YES" OR "NO"')
        elif menuClass == 'timed':
            if time.isalpha() is not True: print('INPUT TYPE ERROR: PLEASE INPUT "ON" OR "OFF"')
            elif time.upper() == 'ON':
                output = True
                break
            elif time.upper() == 'NO':
                output = False
                break
            else: print('INPUT ERROR: PLEASE INPUT "ON" OR "OFF"')

    return output #returns user input after validation
def game_transcript(turnEvents): #outputs all the events that occured in a single turn
    header = f' TURN {game_vars.get("turn")} EVENTS: ' #f-formatting to make the header for each time function is called
    print(f'{header:~^55}') #f-formatting to show header of turn events
    while turnEvents != []: #iterates through turnEvents while removing elements
        print(turnEvents[0])
        turnEvents.pop(0)
    print('~' * 55) #f-formatting to show end of main menu
def danger_level_increase(game_vars, turnEvents): #changes game data every time danger level increases
    game_vars['danger_level'] += 1 #increases game variable 'danger_level' by 1 every time the function is called
    turnEvents += ['The evil grows stronger!'] #adds string to turnEvents to be output as a turn event
    for mob in monster_list: #iterates through monster_list to obtain each element inside
        monsters[mob]['maxHP'] += 1 #increases every monsters maximum HP by 1 every time the function is called
        monsters[mob]['min_damage'] += 1 #increases every monsters minimum damage by 1 every time the function is called
        monsters[mob]['max_damage'] += 1 #increases every monsters maximum damage by 1 every time the function is called
        monsters[mob]['reward'] += 1 #increases every monsters reward by 1 every time the function is called
async def play_music(): #asynchronous function to play background music 'evolution.mp3'
    mixer.init()
    mixer.music.load("evolution.mp3")
    mixer.music.set_volume(0.15)
    mixer.music.play(10)

#================================= START PROGRAM ==================================
asyncio.run(play_music()) #running the asynchronous function for background music
print(f'{"="*55}\n{"Desperate Defenders":^55}\n{"Defend the city from undead monsters!":^55}\n{"="*55}') #display start of game header
while True:
    show_main_menu()  # show start menu options
    choice = get_input('main') #obtain validated input to use as navigation
    navigate_main_menu(choice, field)

#==================================== CREDITS =====================================
#Music I Used: https://www.bensound.com/free-music-for-videos


"""Tests for common_utils.py."""

import common_utils
import pytest

class TestCommonUtils:
  def testGetFilePathShouldRaiseError(self):
    common_utils.input = lambda _: 'foo'
    with pytest.raises(FileNotFoundError):
      common_utils.get_file_path()
    common_utils.input = input

  def testGetFilePathShouldNotRaiseError(self, tmp_path):
    directory = tmp_path / 'audio'
    directory.mkdir()
    file_path = directory / 'test.wav'
    file_path.write_text('')
    common_utils.input = lambda _:  file_path
    try:
      common_utils.get_file_path()
      assert True
    except FileNotFoundError as err:
      assert False, err
    finally:
      common_utils.input = input


import requests
import urllib.parse

from models import PlayByPlay
from constants import headers
from db_utils import insert_many

class PlayByPlayRequester:

    url = 'https://stats.nba.com/stats/playbyplayv2'

    def __init__(self, settings):
        self.settings = settings
        self.settings.db.bind([PlayByPlay])

    def create_ddl(self):
        """
        Initialize the table schema.
        """
        self.settings.db.create_tables([PlayByPlay], safe=True)

    def fetch_game(self, game_id):
        """
        Build GET REST request to the NBA for a game, iterate over
        the results and return them.
        """
        params = self.build_params(game_id)

        # Encode without safe '+', apparently the NBA likes unsafe url params.
        params_str = urllib.parse.urlencode(params, safe=':+')

        response = requests.get(url=self.url, headers=headers, params=params_str).json()

        # pulling just the data we want
        player_info = response['resultSets'][0]['rowSet']

        rows = []

        # looping over data to return.
        for row in player_info:
            new_row = {
                'game_id': row[0],
                'event_num': row[1],
                'event_msg_type': row[2],
                'event_msg_action_type': row[3],
                'period': row[4],
                'wc_time': row[5],
                'home_description': row[7],
                'neutral_description': row[8],
                'visitor_description': row[9],
                'score': row[10],
                'score_margin': row[11],
                'player1_id': self.get_null_id(row[13]),
                'player1_team_id': self.get_null_id(row[15]),
                'player2_id': self.get_null_id(row[20]),
                'player2_team_id': self.get_null_id(row[22]),
                'player3_id': self.get_null_id(row[27]),
                'player3_team_id': self.get_null_id(row[29])
            }

            rows.append(new_row)
        return rows

    def insert_batch(self, rows, player_id_set):
        """
        Batch insertion of records.
        """

        # It looks like the NBA API returns some bad data that
        # doesn't conform to their advertized schema:
        # (team_id in the player_id spot).
        # We can maybe get away with ignoring it.
        # Check if id is in player_id cache.
        # We need to preserve the row in general becuase it could still have
        # good data for the correctly returned players.

        for row in rows:
            for key in ['player1_id', 'player2_id', 'player3_id']:
                if row[key] is not None and row[key] not in player_id_set:
                    row[key] is None
        insert_many(self.settings, PlayByPlay, rows)

    def build_params(self, game_id):
        """
        Create required parameters dict for the request.
        """
        return {
            'EndPeriod': 6,
            'GameId': game_id,
            'StartPeriod': 1
        }

    def get_null_id(self, id):
        """
        This endpoint will return a player's id or player's team id as 0
        sometimes.  We will store 'null', as 0 breaks the foriegn key
        constraint.
        """
        if id == 0:
            return None
        return id


lst = [10, 5, 2, 7, 4, 9, 12, 1, 15]

l = list()
r = list()
#print(len(lst))
#print(len(list)//2)

for i in range(0, (len(lst)//2)):
     l.append(lst[i])
print(l.__len__())
print(len(l))

for i in range((len(lst)//2), len(lst)):
    r.append(lst[i])
print(r)


import random

def get_random_word():
    words = ["pizza", "cheese", "apples"]
    word = words[random.randint(0, len(words)-1)]
    return word

def show_word(word):
    for character in word:
        print(character, end="")

def play_word_game():
    strikes = 0
    max_strikes = 3
    playing = True

    word = get_random_word()
    blanked_word = "_" * len(word)

    while playing:
        show_word(blanked_word)
        letter = get_guess()

        strikes += 1

        if strikes >= max_strikes:
            playing = False

    if strikes >= max_strikes:
        print("Loser!")
    else:
        print("Winner!")

print("Game started")
play_word_game()
print("Game over")


# The following code was adapted from Week 3 Programming Assignment 2 in the Convolutional Neural Networks course by DeepLearning.AI offered on Coursera
# https://www.coursera.org/learn/convolutional-neural-networks/home/week/3

import tensorflow as tf
import numpy as np

from tensorflow.keras.layers import Input
from tensorflow.keras.layers import Conv2D
from tensorflow.keras.layers import MaxPooling2D
from tensorflow.keras.layers import Dropout
from tensorflow.keras.layers import Conv2DTranspose
from tensorflow.keras.layers import concatenate

import os
import numpy as np
import pandas as pd
import imageio
import matplotlib.pyplot as plt
get_ipython().run_line_magic('matplotlib', 'inline')

# load and split data
path = ''
image_path = os.path.join(path, './data/CameraRGB/')
mask_path = os.path.join(path, './data/CameraMask/')
image_list = os.listdir(image_path)
mask_list = os.listdir(mask_path)
image_list = [image_path+i for i in image_list]
mask_list = [mask_path+i for i in mask_list]

image_list_ds = tf.data.Dataset.list_files(image_list, shuffle=False)
mask_list_ds = tf.data.Dataset.list_files(mask_list, shuffle=False)

image_filenames = tf.constant(image_list)
masks_filenames = tf.constant(mask_list)

dataset = tf.data.Dataset.from_tensor_slices((image_filenames, masks_filenames))

# preprocess data
def process_path(image_path, mask_path):

    img = tf.io.read_file(image_path)
    img = tf.image.decode_png(img, channels=3)
    img = tf.image.convert_image_dtype(img, tf.float32)

    mask = tf.io.read_file(mask_path)
    mask = tf.image.decode_png(mask, channels=3)
    mask = tf.math.reduce_max(mask, axis=-1, keepdims=True)
    return img, mask

def preprocess(image, mask):
    input_image = tf.image.resize(image, (96, 128), method='nearest')
    input_mask = tf.image.resize(mask, (96, 128), method='nearest')

    return input_image, input_mask

image_ds = dataset.map(process_path)
processed_image_ds = image_ds.map(preprocess)

"""
This function implements the convolutional decoder block
"""
def upsampling_block(expansive_input, contractive_input, n_filters=32):

    # expansive_input is the input from the previous layer
    # contractive_input is the input from the previous skip layer

    up = Conv2DTranspose(n_filters, 3, strides=2, padding='same')(expansive_input)

    # merge the previous output and the contractive_input
    merge = concatenate([up, contractive_input], axis=3)
    conv = Conv2D(n_filters, 3, activation='relu', padding='same', kernel_initializer='HeNormal')(merge)
    conv = Conv2D(n_filters, 3, activation='relu', padding='same', kernel_initializer='HeNormal')(conv)

    return conv

"""
This function implements the U-Net model by combining the encoder and decoder paths
"""
def unet_model(input_size=(96, 128, 3), n_filters=32, n_classes=23):

    # input_size is the shape of the input
    # n_filters is the number of filters for convolutional layers
    # n_classes is the number of output classes
    inputs = Input(input_size)

    # encoding path
    cblock1 = conv_block(inputs, n_filters)

    # the first element of the output of each block will be the input of the next block
    # the number of filters at each new step doubles
    cblock2 = conv_block(cblock1[0], n_filters*2)
    cblock3 = conv_block(cblock2[0], n_filters*4)
    cblock4 = conv_block(cblock3[0], n_filters*8, dropout_prob=0.3)
    cblock5 = conv_block(cblock4[0], n_filters*16, dropout_prob=0.3, max_pooling=False)

    # decoding path
    ublock6 = upsampling_block(cblock5[0], cblock4[1],  n_filters*8)

    # the output of the previous block is the expansive_input and the corresponding encoder output skip connection is the contractive_input
    # the number of filters at each new step halves
    ublock7 = upsampling_block(ublock6, cblock3[1],  n_filters*4)
    ublock8 = upsampling_block(ublock7, cblock2[1],  n_filters*2)
    ublock9 = upsampling_block(ublock8, cblock1[1],  n_filters)

    conv9 = Conv2D(n_filters, 3, activation='relu', padding='same', kernel_initializer='he_normal')(ublock9)
    conv10 = Conv2D(n_classes, 1, padding='same')(conv9)

    model = tf.keras.Model(inputs=inputs, outputs=conv10)

    return model

# set model dimensions
img_height = 96
img_width = 128
num_channels = 3

unet = unet_model((img_height, img_width, num_channels))

unet.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              metrics=['accuracy'])

"""
This function displays both the input image and the corresponding true mask (desired output)
"""
def display(display_list):
    plt.figure(figsize=(15, 15))

    title = ['Input Image', 'True Mask', 'Predicted Mask']

    for i in range(len(display_list)):
        plt.subplot(1, len(display_list), i+1)
        plt.title(title[i])
        plt.imshow(tf.keras.preprocessing.image.array_to_img(display_list[i]))
        plt.axis('off')
    plt.show()

# create prediction
def create_mask(pred_mask):
    pred_mask = tf.argmax(pred_mask, axis=-1)
    pred_mask = pred_mask[..., tf.newaxis]
    return pred_mask[0]

# train the model
EPOCHS = 40
VAL_SUBSPLITS = 5
BUFFER_SIZE = 500
BATCH_SIZE = 32
processed_image_ds.batch(BATCH_SIZE)
train_dataset = processed_image_ds.cache().shuffle(BUFFER_SIZE).batch(BATCH_SIZE)
print(processed_image_ds.element_spec)
model_history = unet.fit(train_dataset, epochs=EPOCHS)

"""
This function displays the input, prediction, and true images
"""
def show_predictions(dataset=None, num=1):
    """
    Displays the first image of each of the num batches
    """
    if dataset:
        for image, mask in dataset.take(num):
            pred_mask = unet.predict(image)
            display([image[0], mask[0], create_mask(pred_mask)])
    else:
        display([sample_image, sample_mask,
             create_mask(unet.predict(sample_image[tf.newaxis, ...]))])

# show results
show_predictions(train_dataset, 6)

# References
# [1] https://www.coursera.org/learn/convolutional-neural-networks/programming/omqTR/image-segmentation-with-u-net


#!/usr/bin/env python3

import argparse
import random
import json
import re
import importlib.util
import os.path
import sys
import types
import inspect
import pandas as pd
import numpy as np

MAX_SLOT=8

SMART_COMMENT="\\s*#+\\s*(fastscore|odg)\\.(\\S*)\\s*:\\s*(\\S*)\\s*$"

def is_input_slot(s):
	return s % 2 == 0

## Parse command line
parser = argparse.ArgumentParser()
parser.add_argument("source_file", metavar="MODEL")
parser.add_argument("-i", "--input:0", action="store", help="Name of the input file (slot 0)", metavar="FILE")
parser.add_argument("-o", "--output:1", action="store", help="Name of the output file (slot 1)", metavar="FILE")
parser.add_argument("-b", "--batch-size", action="store", type=int, default=10, help="Name of the output file (slot 1)", metavar="NUM")
#parser.add_argument("-v", "--verbose", action="store_true", help="Increase verbosity")
# Add more --input/--output options
for s in range(2,MAX_SLOT):
	tag = "input" if is_input_slot(s) else "output"
	parser.add_argument("--{}:{}".format(tag,s), action="store", help="Name of the {} file (slot {})".format(tag,s), metavar="FILE")

args = parser.parse_args()

def check_scope(scope):
	if scope in ["$all", "$in", "$out"]:
		return scope
	else:
		try:
			return int(scope)
		except ValueError:
			print("Smart comment not recognized")

def default_scope(item):
	if item == "recordsets":
		return "$all"
	elif item == "action":
		return "$in"
	elif item == "slot":
		return "$all"
	elif item == "schema":
		return "$all"
	else:
		return None

def parse_comments1(line, slots):
	if re.match(SMART_COMMENT, line):
		tokens = re.split(SMART_COMMENT,line)
		item0=tokens[2]
		value=tokens[3]
		x=item0.split(".")
		item=x[0]
		scope = check_scope(x[1]) if len(x) > 1 else default_scope(item)
		for s in range(0,MAX_SLOT):
			if scope is None:
				continue
			is_deprecated=False
			if (scope == "$all" or
					scope == "$in" and is_input_slot(s) or
					scope == "$out" and not is_input_slot(s) or
					scope == s):
				if (item == "recordsets"):
					if (value in ["both","none","input","output"]):
						# deprecated style
						if scope != "$all":
							sys.exit("Invalid scope")
						is_deprecated=True
						if (s == 0 or s == 1):
							if (value == "both"):
								slots[s]['recordsets'] = True
							elif (value == "none"):
								slots[s]['recordsets'] = False
							elif (value == "input"):
								slots[s]['recordsets'] = (s == 0)
							elif (value == "output"):
								slots[s]['recordsets'] = (s == 1)
					else:
						# new style
						flag = None
						if (value == "true" or value == "yes"):
							flag = True
						elif (value == "false" or value == "no"):
							flag = False
						else:
							sys.exit("Value '{}' not recognized (use 'true', 'false', 'yes', or 'no')".format(value))
						slots[s]['recordsets']=flag
				elif (item == "action"):
					if not is_input_slot(s):
						sys.exit("An action callback being assigned to an output slot {}".format(s))
					slots[s]['action'] = None if (value == "unused") else value

				# Any mention of a slot makes it active
				if (value != "unused"):
					if (not is_deprecated or s == 0 or s == 1):
						slots[s]['active'] = True

				if (item == "slot"):
					if (value != "unused"):
						sys.exit("Value '{}' not supported (set to 'unused' to disable the slot)".format(value))
					slots[s]['active'] = False

def parse_comments():
	slots = []
	for s in range(0,MAX_SLOT):
		slots.append({
			'action': None,
			'recordsets': False,
			'active': False,
			'file': None
		})
		if is_input_slot(s):
			slots[s]['action'] = "action"

	# By default, slots 0 and 1 are active
	slots[0]['active'] = True
	slots[1]['active'] = True

	f = open(args.source_file)
	for l in f:
		parse_comments1(l, slots)
	f.close()

	return slots

model_slots = parse_comments()

# Collect all data-related options
for k in args.__dict__:
	if (k.startswith("input:") or k.startswith("output:")):
		s = int(k.split(":")[1])
		data_file = args.__dict__[k]
		if not data_file is None:
			if (is_input_slot(s) and not os.path.isfile(data_file)):
				sys.exit("{} not found".format(data_file))
			model_slots[s]['file'] = data_file

# Either all or none input slots must have action set
all_actions=True
none_actions=True
for s in range(0,MAX_SLOT):
	if (not is_input_slot(s)):
		continue
	if (model_slots[s]['action'] is None):
		all_actions=False
	else:
		none_actions=False

if (not all_actions and not none_actions):
	sys.exit("Either all input slots must have action callbacks set or none of them should")

# Check for dangling slots/files
for s in range(0,MAX_SLOT):
	active=model_slots[s]['active']
	data_file=model_slots[s]['file']
	if (active and data_file is None and s != 0 and s != 1):
		sys.exit("Model uses slot {} but there is no data file attached to it".format(s))
	if (not active and not data_file is None):
		sys.exit("Model does not use slot {} but the data file {} is attached to it".format(s,data_file))

inputs=[]
outputs=[]
for s in range(0,MAX_SLOT):
	if not model_slots[s]['active']:
		continue
	if is_input_slot(s):
		inputs.append({
			'slot': s,
			'seq_no': 1,
			'conn': None,
			'entry': None
		})
	else:
		outputs.append({
			'slot': s,
			'conn': None
		})

# Open input files
for i in inputs:
	s = i['slot']
	data_file = model_slots[s]['file']
	conn = open(0) if (s == 0 and data_file is None) else open(data_file)
	i['conn'] = conn

# Open output files
for i in outputs:
	s = i['slot']
	data_file = model_slots[s]['file']
	conn = open(1,'w') if (s == 1 and data_file is None) else open(data_file,'w')
	i['conn'] = conn

# Read a batch of records from the slot
def read_records(s):
	ii = None
	for i in range(0,len(inputs)):
		if inputs[i]['slot'] == s:
			ii = i
			break
	if ii is None:
		if model_slots[s]['active']:
			return None
		else:
			sys.exit("Slot {} is not in use".format(s))

	conn = inputs[ii]['conn']
	seq_no = inputs[ii]['seq_no']

	records = []
	at_eof = False
	while len(records) < args.batch_size:
		l = conn.readline()
		if len(l) == 0:
			if conn.name != 0:
				conn.close()
			inputs.pop(ii)
			at_eof = True
			break

		x = json.loads(l)
		#TODO
    #if (is.list(x) && x$`$fastscore` == "set"):
    #	break
		records.append(x)

	if at_eof and len(records) == 0:
		return []

	if not at_eof:
		old_seq_no = seq_no
		seq_no = seq_no + len(records)
		inputs[ii]['seq_no'] = seq_no

	return records

def as_recordset(records):
	if records is None:
		return None

	return records

	#TODO
	#if (len(records) == 0):
	#	pd.DataFrame(records)
	#else:
	#	head = records[0]

class Slot(object):
	def __init__(self, n):
		self.n = n

	def __iter__(self):
		return self

	def __next__(self):
		data = self.read()
		if data is None:
			raise StopIteration
		else:
			return data

	def read(self):
		if (not is_input_slot(self.n)):
			sys.exit("Model attempts to explicitly read from an output slot {}".format(self.n))
		records = read_records(self.n)
		data = as_recordset(records)
		return data

	def write(self, rec):
		if (is_input_slot(self.n)):
			sys.exit("Model emits data to an input slot {}:".format(s))
		conn = None
		for i in outputs:
			if i['slot'] == self.n:
				conn = i['conn']
				break
		if conn is None:
			sys.exit("Model emits data to an unknown slot {}".format(self.n))

		recordsets = model_slots[self.n]['recordsets']

		if not recordsets:
			print(json.dumps(rec), file=conn)
		else:
			sys.exit("TOODOO")

fio = types.ModuleType("fastscore.io")
fio.__dict__['Slot'] = Slot
sys.modules["fastscore.io"] = fio

spec = importlib.util.spec_from_file_location('model', args.source_file)
mod = importlib.util.module_from_spec(spec)
# Callbacks not resolved yet
spec.loader.exec_module(mod)

# TODO Check/wrap action callbacks
for s in range(0,MAX_SLOT):
	slot = model_slots[s]
	if not slot['active']:
		continue
	if not slot['action'] is None:
		entry = getattr(mod,slot['action'])
		if entry is None:
			sys.exit("A slot {} callback function named '{}' not found".format(s,slot['action']))
		if not isinstance(entry, types.FunctionType):
			sys.exit("A slot {} callback named '{}' must be a function".format(s,slot['action']))
		sig = inspect.signature(entry)
		arity = len(sig.parameters)
		if (arity < 1 or arity > 3):
			sys.exit("A slot {} callback function named '{}' must have arity 1, 2, or 3 (not {})".format(s,slot['action'],arity))
		wrapped_entry = entry
		if arity == 1:
			wrapped_entry = lambda data,slot,seqno: entry(data)
		elif arity == 2:
			wrapped_entry = lambda data,slot,seqno: entry(data,seqno)

		for i in inputs:
			if i['slot'] == s:
				i['entry'] = wrapped_entry

model_uses_callbacks = False
if len(inputs) > 0:
	model_uses_callbacks = isinstance(inputs[0]['entry'], types.FunctionType)

if model_uses_callbacks:
	while len(inputs) > 0:
		# Pick a random slot
		select = random.choice(inputs)
		s = select['slot']

		seq_no = select['seq_no']
		records = read_records(s)

		action = model_slots[s]['action']
		recordsets = model_slots[s]['recordsets']

		if recordsets:
			sys.exit("TODO recordests")
		else:
			# Invoke the callback for each record
			for rec in records:
				print(*select['entry'](rec, s, seq_no)) ## calls 'action'
				seq_no = seq_no + 1

Product Manuals/Model Launchers/Python Launcher/lh.py


from random import randint
y = randint(1,5)
hscore = 0
cscore = 0
print("Winners and Losers - Human is Even, Computer is Odd")
for i in range (1, 6) :
    print("Round: {}".format(i))
    x = int(input("Enter Your Guess: "))
    print ("Human Guess: {} - Computer Guess: {}".format(x , y))
    sum = x + y
    if sum % 2 :
        print("Sum is Even")
        hscore = hscore + 1
    else:
        print("Sum is Odd")
        cscore = cscore + 1
    print ("Human Score: {} - Computer Score: {}".format (hscore, cscore))
if hscore > cscore :
        print ("Human Wins")
else :
        print ("Computer Wins")


import time
from datetime import datetime, timedelta
from pydantic import BaseModel
from fastapi import FastAPI, Depends, File, UploadFile, HTTPException, Request, status
from fastapi.responses import HTMLResponse, JSONResponse, FileResponse
import uvicorn
import os
from pytube import YouTube
from pytube import Playlist
from pytube.cli import on_progress

app = FastAPI()

class Info(BaseModel):
    author: str
    title: str
    view: float
    length: int
    description: str
    thumbnail: str
    fileSizeHD: str

@app.get("/")
async def hello():
    return {"result": "Its working YES! This is a miracle!"}

@app.get("/info")
async def info(url: str = None):
    try:
        yt = YouTube(url)
        videoSize = yt.streams.get_highest_resolution()
        Info.author = yt.author
        Info.title = yt.title
        Info.view = yt.views
        Info.length = ("%.2f" % float(yt.length / 60))
        Info.description = yt.description
        Info.thumbnail = yt.thumbnail_url
        Info.fileSizeHD = str(round(videoSize.filesize * 0.000001, 2)) + " Mb"
        res = {None}
        for i in yt.streams:
            res.add(i.resolution)
        res.remove(None)
        res = [int(i) for i in [sub.replace('p', '') for sub in res]]
        sres = sorted(res)
        return { "Title": Info.title, "Resolution": sres, "Author": Info.author, "Thumbnail": Info.thumbnail, "View": Info.view,
                "Length": Info.length, "Description": Info.description,
                 "File size": Info.fileSizeHD}
    except Exception as e:
        return JSONResponse(
            status_code=status.HTTP_400_BAD_REQUEST,
            content={'message': str(e)})
    else:
        return JSONResponse(
            status_code=status.HTTP_200_OK,
            content={"result": 'success'})

@app.get("/video")
async def video(url: str = None):
    try:
        yt = YouTube(url, on_progress_callback=on_progress)
        yd = yt.streams.get_highest_resolution()
        folder_name = "FILE_NAME"
        file_path = os.getcwd() + "/" + folder_name
        video = yd.download(file_path)
        yt.title
        print(file_path)
        headers = {'success': f'video is ready, filename= {yt.title}'}
        return FileResponse(path=video, headers=headers, media_type='application/mp4', filename=(yt.title + ".mp4"))

    except Exception as e:
        return JSONResponse(
            status_code=status.HTTP_400_BAD_REQUEST,
            content={'message': str(e)}
        )
    else:
        return JSONResponse(
            status_code=status.HTTP_200_OK,
            content={"result": 'success, video is ready'}
        )

@app.get("/audio")
async def audio(url: str = None):
    try:
        ya = YouTube(url)
        folder_name = "FILE_NAME"
        # DEPENDS ON WHERE YOUR FILE LOCATES
        file_path = os.getcwd() + "/" + folder_name
        video = ya.streams.filter(only_audio=True).first()
        downloaded_file = video.download(file_path)
        base, ext = os.path.splitext(downloaded_file)
        audio = base + 'Audio.mp3'
        os.rename(downloaded_file, audio)
        ya.title
        print(file_path)
        print(audio)
        headers = {'success': f'audio is ready, filename= {ya.title}'}
        return FileResponse(path=audio, headers=headers, media_type='application/mp4', filename=(ya.title+".mp3"))
    except Exception as e:
        return JSONResponse(
            status_code=status.HTTP_400_BAD_REQUEST,
            content={'message': str(e)}
        )
    else:
        return JSONResponse(
            status_code=status.HTTP_200_OK,
            content={"result": 'success, audio is ready'}
        )

@app.get("/delete")
async def delete_files(min: int = 10):
    try:
        response_msg = []
        folder_name = "FILE_NAME"
        file_path = os.getcwd() + "/" + folder_name
        path = file_path
        files = os.listdir(file_path)
        print(files)
        print(f"file_path: {file_path}")
        file_name_delete = []
        dir_name = file_path
        # Get list of all files only in the given directory
        list_of_files = filter(lambda x: os.path.isfile(os.path.join(dir_name, x)),
                               os.listdir(dir_name))
        # Sort list of files based on last modification time in ascending order
        list_of_files = sorted(list_of_files,
                               key=lambda x: os.path.getmtime(os.path.join(dir_name, x))
                               )
        for file_name in list_of_files:
            file_path = os.path.join(dir_name, file_name)
            timestamp_str = time.strftime('%m/%d/%Y :: %H:%M:%S', time.gmtime(os.path.getmtime(file_path)))
            print(timestamp_str, ' -->', file_name)
            #filter by minite
            now = datetime.now()
            timestamp = datetime.timestamp(now)
            f = os.path.getmtime(file_path)
            file_date = datetime.fromtimestamp(f)
            file_date_age = file_date + timedelta(minutes=min)
            duration = now - file_date_age
            duration_in_s = duration.total_seconds()
            minut = (int(round(duration_in_s / 60, 0)))
            print(f"duration: {minut}")
            if minut > min:
                file_name_delete.append(file_name)
        ### create a list of old file by date
        print(f"file_name_delete {len(file_name_delete)}, {file_name_delete}")
        #print(os.listdir(path))
        for i in file_name_delete:
        #for file_name_delete in os.listdir(path):
            print(f"file_name: {file_name_delete}")
            #print(f"path: {path}")
            # construct full file path
            del_file = path + "/" + i
            #print(f"file: {del_file}")
            if os.path.isfile(del_file):
                response_msg.append(f"Deleting file: {del_file}")
                print('Deleting file:', del_file)
                os.remove(del_file)
                print(len(del_file))
        if len(response_msg) < 1:
        #     response_msg.clear()
            response_msg.append(f"no files to delete after: {min} min")
        return response_msg
    except Exception as e:
        return JSONResponse(
            status_code=status.HTTP_400_BAD_REQUEST,
            content={'message': str(e)})
    else:
        return JSONResponse(
            status_code=status.HTTP_200_OK,
            content={"result": 'success'})

@app.get("/aws")
async def aws_files():
    folder_name = "FILE_NAME"
    file_path = os.getcwd() + "/" + folder_name
    files = os.listdir(file_path)
    result = []
    dir_name = file_path
    # Get list of all files only in the given directory
    list_of_files = filter(lambda x: os.path.isfile(os.path.join(dir_name, x)),
                           os.listdir(dir_name))
    # Sort list of files based on last modification time in ascending order
    list_of_files = sorted(list_of_files,
                           key=lambda x: os.path.getmtime(os.path.join(dir_name, x))
                           )
    for file_name in list_of_files:
        file_path = os.path.join(dir_name, file_name)
        size = ("%.2f" % float(os.path.getsize(file_path)*0.000001))
        timestamp_str = time.strftime('%m/%d/%Y :: %H:%M:%S', time.gmtime(os.path.getmtime(file_path)))
        result.append(f"({timestamp_str}, ' -->', {file_name}, ' -->', {size}' MB')")
    return result

def raise_exception():
    return HTTPException(status_code=404,
                         detail="Input is Not valid!",
                         headers={"X-Header_Error":
                                  f"Nothing to be seen"})

if __name__ == "__main__":
    uvicorn.run("main:app", host="127.0.0.1", port=5000, reload=True, log_level="info", workers=2)

print('Download Complete')


#
# @lc app=leetcode.cn id=40 lang=python3
#
# [40] 组合总和 II
#
from typing import List
# @lc code=start

class Solution:
  def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
    ans = []
    current = []

    def dfs(i, target):
      if target == 0:
        ans.append(current[:])
        return
      if i >= len(candidates):
        return

      next_target = target - candidates[i]
      if next_target >= 0:
        current.append(candidates[i])
        dfs(i + 1, next_target)
        current.pop()
      j = i + 1
      while j < len(candidates) and candidates[i] == candidates[j]:
        j += 1
      dfs(j, target)
    candidates.sort()
    dfs(0, target)
    return ans
# @lc code=end

Solution().combinationSum2([2,5,2,1,2], 5)


import numpy as np
import cv2
from PIL import Image
#В этом скрипте делаем маску
#С помощью манипуляций с opencv создаем два файла
#первый - с контрастными крышами, второй - с выделенными дорогами
#затем - используя второй файл, убираем дороги с первого
image = "ZRYNEEUSVQ213QTY.png"

input = cv2.imread(image)

_, th = cv2.threshold(input, 130, 160, cv2.THRESH_TOZERO)

cv2.imwrite("th.png", th)

image = "th.png"
img = cv2.imread(image)

lower = np.array([0, 1, 1])
upper = np.array([0, 255, 255])
mask = cv2.inRange(img, lower, upper)
roads = cv2.bitwise_and(img, img, mask=mask)

height = int(np.size(img, 0))
width = int(np.size(img, 1))

for h in range(1, 500):
    for w in range(1, 500):
        color = str(roads[w, h])
        if color != "[0, 145, 153]":
            pass
        else:
            roads[h, w] = [0, 0, 255]

cv2.imwrite("roads.png", roads)

mask = Image.open("roads.png")
mask1 = Image.open("th.png")

for i in range (1, 6528):
    for j in range (1, 7733):
        if mask1.getpixel((i,j))[0] != 0 or mask1.getpixel((i,j))[1] != 0 or \
                mask1.getpixel((i,j))[2] != 0:
            mask1.putpixel((i, j), (255, 255, 255))
        if (mask.getpixel((i, j))[0] != 0) or (mask.getpixel((i, j))[1] != 0) or (mask.getpixel((i, j))[2] != 0):
            mask1.putpixel((i, j), (0, 0, 0))
mask1.save("dengi3.jpg", "JPEG")


import unittest
import player_factory
from inning_creator import Inning
from positional_data import StandardPosition

def collect_benched_players(inning):
    bench_assignments = [
        assignment for assignment in inning.assignments
        if assignment.position.title == "Bench"
    ]
    return bench_assignments

def find_by_player(assignments, search_player):
    player_list = [
        position for position, player in assignments.items()
        if player == search_player
    ]
    if len(player_list) > 0:
        return player_list.pop()

def player_has_assignment(player, assignments):
    assignment = find_by_player(assignments, player)
    return assignment is not None

class MyTestCase(unittest.TestCase):
    def setUp(self) -> None:
        self.players = player_factory.generate_full_cardinals()
        self.inning = Inning(self.players.copy())
        self.assignments = self.inning.assignments

    def test_all_positions_covered_on_inning_creation(self):
        assignments_dict = {assignment.position: assignment.player
            for assignment
            in self.assignments}

        for position in StandardPosition:
            self.assertTrue(position.value in assignments_dict.keys())

            player_at_position = assignments_dict[position.value]
            self.assertIsNotNone(player_at_position)

    def test_all_players_assigned_to_position_and_bench(self):
        assigned_players = set(
            [assignment.player for assignment in self.assignments]
        )
        self.assertEqual(len(self.players), len(assigned_players))

        benched_players = collect_benched_players(self.inning)
        self.assertEqual(4, len(benched_players))

    def test_players_assign_to_fielder_group(self):
        non_bench_assignments = [
            assignment for assignment in self.assignments
            if assignment.position.title != 'Bench'
        ]

        for assignment in non_bench_assignments:
            player = assignment.player
            self.assertEqual(
                player.field_group,
                assignment.position.field_group
            )

import unittest

if __name__ == '__main__':
    unittest.main()


import numpy as np
import torch

from snownlp import SnowNLP
from common_utils import *
from preprocessing.clean_data import batchify

def get_overlap(list1, list2):
    """
    Returns a list of words that occur in both list1 and list2.
    Also returns total number of words in list1 and in list2 (can be used
    to compute similarity as a percentage, if desired)

    Args:
        list1 (string): filename corresponding to a list of words
        list2 (string): filename corresponding to a list of words

    Returns:
        overlap (list of string): words occuring in both list1 and list2
        len1 (int): number of words in list1
        len2 (int): number of words in list2
    """
    # File to List
    wordlist1 = file_to_str(list1)
    wordlist2 = file_to_str(list2)

    # Tokenize Lists
    s1 = SnowNLP(wordlist1)  # need to use SnowNLP for Chinese-character lists
    s2 = SnowNLP(wordlist2)

    # Get List Lengths
    wordlist1 = list(set(s1.words))
    wordlist2 = list(set(s2.words))
    len1 = len(wordlist1)
    len2 = len(wordlist2)

    # Count Overlapping Words
    # overlap = [w2 for w2 in wordlist2 if w2 in wordlist1]

    # Alternative to "Count Overlapping Words" with potentially better time complexity
    combined_wordset = set(wordlist1).union(set(wordlist2))
    overlap = len1 + len2 - len(combined_wordset)

    return overlap, len1, len2

def get_embedding_similarity(list1, list2, emb):
    """
    Given an embedding for a vocabulary and two lists of words from
    that vocabulary, return the cosine distance between the average
    word embeddings from each list.

    Args:
        list1 (string): filename corresponding to a list of words
        list2 (string): filename corresponding to a list of words
        emb (??): word embedding
    """
    raise NotImplementedError

def get_political_diff(list1, list2, model_file):
    # Load model and data
    model = torch.load(model_file)
    model.eval()
    data1 = batchify(list1)
    data2 = batchify(list2)

    # Inference
    positives1, positives2 = 0, 0
    for in1 in data1:
        output1 = model(in1[0])  # no labels
        positives1 += (output1.argmax(1) == 1).sum().item()
    for in2 in data2:
        output2 = model(in2[0])
        positives2 += (output2.argmax(1) == 1).sum().item() / len(data2)
    return positives1, positives2, len(data1.dataset), len(data2.dataset)

def get_political_ratio(list, model_file):
    # Load model and data
    model = torch.load(model_file)
    model.eval()
    data = batchify(list)

    # Inference
    positives = 0
    for in1 in data:
        output = model(in1[0])  # no labels
        positives += (output.argmax(1) == 1).sum().item()
    return positives / len(data.dataset)

def get_longest_subsequence_length(list1, list2):
    """
    Returns length of longest subsequence common to list1 and list2.
    Also returns total number of words in list1 and in list2 (can be used
    to compute similarity as a percentage, if desired)

    Args:
        list1 (string): filename corresponding to a list of words
        list2 (string): filename corresponding to a list of words

    Returns:
        length (int): length of longest subsequence
        len1 (int): number of words in list1
        len2 (int): number of words in list2
    """
    # File to List
    wordlist1 = file_to_str(list1)
    wordlist2 = file_to_str(list2)

    # Tokenize Lists
    s1 = SnowNLP(wordlist1)
    s2 = SnowNLP(wordlist2)

    # Get List Lengths
    wordlist1 = list(s1.words)
    wordlist2 = list(s2.words)
    len1 = len(wordlist1)
    len2 = len(wordlist2)

    length = longest_subsequence(wordlist1, wordlist2)

    return length, len1, len2

def longest_subsequence(list1, list2):
    """
    Return length of longest subsequence common to list1 and list2.

    Here, a subsequence is defined as a list of ordered entries
    occuring consecutively in a list.

    Example:
        [1,2,3] and [4] are subsequences of [1,2,3,4]
        [1,3,4] and [1,3,2] are not subsequences of [1,2,3,4]

    Args:
        list1 (list): a list whose elements can be of any class
            that implements __eq__
        list2 (list): a list whose elements are the same class as
            those of list1

    Returns:
        l (int): the length of the longest subsequence
    """
    # T[i, j] will store the length of longest substring
    # ending at list1[i] and list2[j]
    n = len(list1)
    m = len(list2)
    T = np.zeros((n, m))

    for i in range(n):
        for j in range(m):
            if list1[i] == list2[j]:
                if i == 0 or j == 0:
                    T[i, j] = 1
                else:
                    T[i, j] = T[i-1, j-1] + 1

    return np.max(T)

def _cosine_dist(v1, v2):
    return np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))

def _euclidean_dist(v1, v2):
    return np.linalg.norm(v1 - v2)

def _manhattan_dist(v1, v2):
    raise np.linalg.norm(v1 - v2, ord=1)


from django.utils.module_loading import import_string
from django.urls import (RegexURLResolver, RegexURLPattern)
from CRM import settings
from collections import OrderedDict

def recursion_urls(pre_namespace, pre_url, urlpatterns, url_ordered_dict):
    # None, '/', urlpatterns, url_ordered_dict
    '''
    第一次递归：
        url(r'^', include('web.urls')),
        url(r'^rbac/', include('rbac.urls', namespace='rbac')),
    第二次递归：
        'rbac','/^rbac/', rbac.urls文件下的urlpatterns变量, url_ordered_dict
    '''
    for url in urlpatterns:
        if isinstance(url, RegexURLResolver):
            if pre_namespace:
                if url.namespace:
                    namespace = '%s:%s' % (pre_namespace, url.namespace)
                else:
                    namespace = pre_namespace
            else:
                if url.namespace:
                    namespace = url.namespace  # 'rbac'
                else:
                    namespace = None
            recursion_urls(namespace, pre_url + url.regex.pattern, url.url_patterns, url_ordered_dict)

        else:
            if pre_namespace:
                name = '%s:%s' % (pre_namespace, url.name)  # rbac:role_list
            else:
                name = url.name
            if not url.name:
                raise Exception('URL路由中必须设置name属性')
            url = pre_url + url._regex  # /^^login/
            url_ordered_dict[name] = {
                'url_name': name, 'url': url.replace('^', '').replace('$', '')
            }  # {'login':{'url_name': name, 'url': /login/},}

def all_url(ignore_namespace_list=None):
    ignore_list = ignore_namespace_list or []
    # 存放项目所有URL的有序字典
    url_ordered_dict = OrderedDict()
    # 获取项目的所以URL
    urls = import_string(settings.ROOT_URLCONF)
    urlpatterns = []
    '''
    # urlpatterns = [
    #     url(r'^', include('web.urls')),
    #     url(r'^rbac/', include('rbac.urls', namespace='rbac')),
    # ]
    '''
    for url in urls.urlpatterns:
        if isinstance(url, RegexURLResolver) and url.namespace in ignore_list:
            continue
        urlpatterns.append(url)
    recursion_urls(None, '/', urlpatterns, url_ordered_dict)
    return url_ordered_dict


from collections import defaultdict

class Solution:
    def valid_tree(self, n: int, edges: list[list[int]]) -> bool:
        seen: set[int] = set()
        children: dict[int, list[int]] = defaultdict(list)

        for x, y in edges:
            children[x].append(y)
            children[y].append(x)

        def dfs(node: int, prev: int) -> bool:
            if node in seen:
                return False

            seen.add(node)

            for child in children[node]:
                if child == prev:
                    continue
                if not dfs(child, node):
                    return False

            return True

        return dfs(0, 0) and len(seen) == n


import pandas as pd
from math import log

from init_var import *

#-----------------------------------------------------------------------------#

all_term = []
with open('all_term.txt', 'r') as all_term_file:
    for line in all_term_file:
        term = line.split()[0]
        all_term.append(term)

tf_matrix = [[0] * len(all_term) for _ in range(docnum)]
with open('tf_matrix.txt', 'r') as tf_matrix_file:
    i = -1
    for row in tf_matrix_file:
        i += 1
        row = row.split()
        for j in range(len(row)):
            ele = row[j]
            tf_matrix[i][j] = int(ele)

df_matrix = [0] * len(all_term)
def calcDF():
    # df: num of docs containing the term 't'
    # return a 1d array
    for col in range(len(tf_matrix[0])):
        for row in range(len(tf_matrix)):
            if tf_matrix[row][col] == 0:
                pass
            else:
                df_matrix[col] += 1
print('Calculating df_matrix ...')
calcDF()
with open('df_matrix.txt', 'w') as df_matrix_file:
    print(*df_matrix, sep='\n', file=df_matrix_file)

tf_idf_matrix = [[0] * len(all_term) for _ in range(docnum)]
def calcTFIDF():
    # tf: term frequency - inverse document frequency
    # return a 2d array
    for row in range(len(tf_idf_matrix)):
        for col in range(len(tf_idf_matrix[0])):
            tf_idf_matrix[row][col] = round(tf_matrix[row][col] * log(docnum / df_matrix[col]), 3)

print('Calculating tfidf_matrix ...')
calcTFIDF()
with open('tf_idf_matrix.txt', 'w') as tf_idf_matrix_file:
    for row in tf_idf_matrix:
        print(*row, sep=' ', file=tf_idf_matrix_file)

def calcKLD(vec1, vec2):
    # kld: KL divergence
    kld = 0
    for i in range(len(vec1)):
        if vec1[i]==0 or vec2[i]==0:
            pass
        else:
            kld += vec1[i] * log(vec1[i] / vec2[i])
    return kld

kld_matrix  = [[0] * docnum for _ in range(docnum)]
akld_matrix = [[0] * docnum for _ in range(docnum)]

def calcAKLD():
    # akld: average KL divergence
    for row in range(docnum):
        for col in range(docnum):
            kld_matrix[row][col] = calcKLD(tf_idf_matrix[row], tf_idf_matrix[col])

    for row in range(docnum):
        for col in range(docnum):
            akld_matrix[row][col] = round(1/2 * (kld_matrix[row][col] + kld_matrix[col][row]), 3)
print('Calculating alkd_matrix ...')
calcAKLD()
with open('akld_matrix.txt', 'w') as alkd_matrix_file:
    for row in akld_matrix:
        print(*row, sep=' ', file=alkd_matrix_file)


import sys
import IPython
import numpy as np
import pprint as pp
from IPython.display import display
import sklearn
import matplotlib.pyplot as plt
from MembershipFunc import MemberFunc

# import Membership
class FuzzyLogic(object):
    """docstring for FuzzyLogic."""
    def __init__(self, data2tes):
        super(FuzzyLogic, self).__init__()
        self.allrule = [];
        self.endResult = {};
        self.MemberFunc = MemberFunc()
        self.academy = data2tes[0]
        self.relevancy = data2tes[1]
        self.interview = data2tes[2]
        self.fuzzyFucation()

    def findMew(self,x,data):
        res = 0;
        if x <= data[0] or x >= data[3]:
            res = 0
        if data[0] <= x <= data[1]:
            res = (x-data[0]) / (data[1]-data[0])
        if data[1] <= x <= data[2]:
            res = 1
        if data[2] <= x <= data[3]:
            res = (data[3]-x) / (data[3]-data[2])
        return float("{0:.2f}".format(res))

    def fuzzyFucation(self):
        self.acad_data = {'high': [3.0,3.0,3.3,3.5], 'vhigh': [3.3,3.5,4.0,4.0]}
        self.rele_data = {'low': [0,0,2,5], 'medium': [2,5,5,8], 'high': [5,8,10,10]}
        self.inte_data = {'low': [0,0,2,5], 'medium': [2,5,5,8], 'high': [5,8,10,10]}
        self.cand_data = {'least': [0,0,2,4], 'less': [2,4,4,6], 'prefer': [4,6,6,8], 'most': [6,8,10,10]}

        self.acad_range = np.arange(3, 4, 0.1)
        self.rele_range = np.arange(0, 11, 1)
        self.inte_range = np.arange(0, 11, 1)
        self.cand_range = np.arange(0, 11, 1)

        self.mew_akademik = {'mhigh':self.findMew(self.academy,self.acad_data['high']),'mvhigh':self.findMew(self.academy,self.acad_data['vhigh'])}
        self.mew_relevansi = {'low':self.findMew(self.relevancy,self.rele_data['low']),'medium':self.findMew(self.relevancy,self.rele_data['medium']),'high':self.findMew(self.relevancy,self.rele_data['high'])}
        self.mew_interview = {'low':self.findMew(self.interview,self.inte_data['low']),'medium':self.findMew(self.interview,self.inte_data['medium']),'high':self.findMew(self.interview,self.inte_data['high'])}

    def newRule(self,ismin,candidate):
        tomin = min(ismin)
        resbawah = []
        if candidate[0] == candidate[1]:
            for x in range(0,11):
                resbawah.append(self.MemberFunc.leftTrapezoid(x,tomin,candidate))
        if candidate[1] == candidate[2]:
            for x in range(0,11):
                resbawah.append(self.MemberFunc.centerTriangular(x,tomin,candidate))
        if candidate[2] == candidate[3]:
            for x in range(0,11):
                resbawah.append(self.MemberFunc.rightTrapezoid(x,tomin,candidate))
        self.allrule.append(resbawah)

    def addRule(self):
        rule1 = self.newRule([self.mew_akademik['mhigh'],self.mew_relevansi['low'],self.mew_interview['low']],self.cand_data['least'])
        rule2 = self.newRule([self.mew_akademik['mhigh'],self.mew_relevansi['low'],self.mew_interview['medium']],self.cand_data['least'])
        rule3 = self.newRule([self.mew_akademik['mhigh'],self.mew_relevansi['low'],self.mew_interview['high']],self.cand_data['less'])
        rule4 = self.newRule([self.mew_akademik['mhigh'],self.mew_relevansi['medium'],self.mew_interview['low']],self.cand_data['least'])
        rule5 = self.newRule([self.mew_akademik['mhigh'],self.mew_relevansi['medium'],self.mew_interview['medium']],self.cand_data['less'])
        rule6 = self.newRule([self.mew_akademik['mhigh'],self.mew_relevansi['medium'],self.mew_interview['high']],self.cand_data['prefer'])
        rule7 = self.newRule([self.mew_akademik['mhigh'],self.mew_relevansi['high'],self.mew_interview['low']],self.cand_data['less'])
        rule8 = self.newRule([self.mew_akademik['mhigh'],self.mew_relevansi['high'],self.mew_interview['medium']],self.cand_data['prefer'])
        rule9 = self.newRule([self.mew_akademik['mhigh'],self.mew_relevansi['high'],self.mew_interview['high']],self.cand_data['prefer'])
        # pp.pprint(allrule)
        rule10 = self.newRule([self.mew_akademik['mvhigh'],self.mew_relevansi['low'],self.mew_interview['low']],self.cand_data['less'])
        rule11 = self.newRule([self.mew_akademik['mvhigh'],self.mew_relevansi['low'],self.mew_interview['low']],self.cand_data['less'])
        rule12 = self.newRule([self.mew_akademik['mvhigh'],self.mew_relevansi['low'],self.mew_interview['medium']],self.cand_data['prefer'])
        rule13 = self.newRule([self.mew_akademik['mvhigh'],self.mew_relevansi['low'],self.mew_interview['high']],self.cand_data['less'])
        rule14 = self.newRule([self.mew_akademik['mvhigh'],self.mew_relevansi['medium'],self.mew_interview['low']],self.cand_data['prefer'])
        rule15 = self.newRule([self.mew_akademik['mvhigh'],self.mew_relevansi['medium'],self.mew_interview['medium']],self.cand_data['most'])
        rule16 = self.newRule([self.mew_akademik['mvhigh'],self.mew_relevansi['medium'],self.mew_interview['high']],self.cand_data['prefer'])
        rule17 = self.newRule([self.mew_akademik['mvhigh'],self.mew_relevansi['high'],self.mew_interview['low']],self.cand_data['most'])
        rule18 = self.newRule([self.mew_akademik['mvhigh'],self.mew_relevansi['high'],self.mew_interview['medium']],self.cand_data['most'])

    def doCompute(self):
        bawahPerRule = np.array(self.allrule)
        # pp.pprint(bawahPerRule)
        agregasi = bawahPerRule.max(axis=0)
        # pp.pprint(agregasi)
        defuz = 0
        atas = 0
        for i in range (0,11):
            atas = atas + (i*agregasi[i])
        defuz = atas / np.sum(agregasi)
        self.endResult['pembilang'] = atas
        self.endResult['penyebut'] = np.sum(agregasi)
        self.endResult['hasil'] = defuz

    def show(self):
        print()
        print("=== Proses Mew Tiap Inputan ===")
        print("Academi:"+str(self.academy)+" | Relevancy:"+str(self.relevancy)+" | Interview:"+str(self.interview))
        print()
        print("=== Proses Mew Tiap Inputan ===")
        # pp.pprint(self.allrule)
        pp.pprint(self.mew_akademik)
        pp.pprint(self.mew_relevansi)
        pp.pprint(self.mew_interview)
        print()
        print("=== Proses Perhitungan Rule Dan Penentuan Agregasi ===")
        # pp.pprint(bawahPerRule)
        # pp.pprint(agregasi)
        print() #ini belum tau apa yang mau ditampilakn disini
        print("=== Proses Hasil Akhir ===")
        pp.pprint(self.endResult)
        print()

myFuzzy = FuzzyLogic([3.1,8,9])
myFuzzy.addRule()
myFuzzy.doCompute()
myFuzzy.show()