HW03.py
''' 
Soren DeOrlow 
IDSN 599, Fall 2021 
deorlow@usc.edu 
Homework 3  
'''

from tkinter import *
import random
from PIL import Image, ImageTk
import time

images = []
cave = []
percepts = []

root = Tk()
root.title("Welcome to Wumpus World")
root.geometry("440x440")
root.grid()

#Player must be global so it can be moved

img = Image.open('player.png')
playerImage = ImageTk.PhotoImage(img)
#playerImage = PhotoImage(file="player.png")
playerLabel = Label(root, image=playerImage)

empty = Image.open('empty.png')
emptyImage = ImageTk.PhotoImage(empty)

class Room:
    def __init__(self, pit, wumpus, gold, walls):
        self.hasPit = pit
        self.hasWumpus = wumpus
        self.hasGold = gold
        self.walls = walls

    def __str__(self):
        return str(self.hasPit) + " " + str(self.hasWumpus) + " " + str(self.hasGold) + " " + str(self.walls)

class Player:
    def __init__(self):
        self.rowLocation = 0
        self.columnLocation = 0
        self.directionFacing = 'right'
        self.hasGold = False

    def __str__(self):
        return str(self.columnLocation) + ',' + str(self.rowLocation) + ',' + self.directionFacing

def makeCave():
    # Make the 10x10 cave

    pitChance = 1

    for i in range(10):
        caveRow = []
        for j in range(10):
            value = random.randrange(100)
            if value <= pitChance:
                pit = True
            else:
                pit = False

            if i == 0 and j == 0:
                # Can't have a pit at 0,0
                pit = False


            caveRow.append(Room(pit, False, False, []))

        cave.append(caveRow)

    # pick a random room for the wumpus. Remove any pit if there is one in the room
    while True:
        wumpusX = random.randrange(10)
        wumpusY = random.randrange(10)

        if wumpusX == 0 and wumpusY == 0:
            continue  # can't have the Wumpus at 0,0

        break

    # pick a random room for the gold. Remove any pit if there is one in the room. Can't be the wumpus room
    while True:
        goldX = random.randrange(10)
        goldY = random.randrange(10)

        if goldX == 0 and goldY == 0:
            continue  # can't have the gold at 0,0

        row = cave[goldX]
        if row[goldY].hasWumpus:
            #Can't have the gold where the Wumpus is
            continue

        break

    #Set the wumpus. Can't have a pit where the Wumpus is
    row = cave[wumpusX]
    row[wumpusY].hasWumpus = True
    row[wumpusY].hasPit = False

    #Set the gold. Can't have a pit where the gold is
    row = cave[goldX]
    row[goldY].hasGold = True
    row[goldY].hasPit = False

def makePercepts():
    # Create the blank percept lists for all rooms
    for i in range(10):
        perceptRow = []
        for j in range(10):
            percept = ['', '', '', '', '']
            perceptRow.append(percept)

        percepts.append(perceptRow)

    # Do the wumpus
    for i in range(10):
        caveColumn = cave[i]
        for j in range(10):
            if caveColumn[j].hasWumpus:
                rooms = [[j - 1, i], [j + 1, i], [j, i - 1], [j, i + 1]]
                for roomCoords in rooms:
                    column = roomCoords[0]
                    row = roomCoords[1]
                    if column == -1 or column == 10:
                        continue
                    if row == -1 or row == 10:
                        continue
                    tempColumn = percepts[column]
                    tempRoom = tempColumn[row]
                    tempRoom[0] = 'Stench'

    # Do the pits
    for i in range(10):
        caveRow = cave[i]
        perceptRow = percepts[i]
        for j in range(10):
            if caveRow[j].hasPit:
                rooms = [[j - 1, i], [j + 1, i], [j, i - 1], [j, i + 1]]
                for roomCoords in rooms:
                    row = roomCoords[0]
                    column = roomCoords[1]
                    if row == -1 or row == 10:
                        continue
                    if column == -1 or column == 10:
                        continue
                    tempRow = percepts[row]
                    tempRoom = tempRow[column]
                    tempRoom[1] = 'Breeze'

def getPercept(player):
    #Given the player's location, return the percept at that location
    perceptRow = percepts[player.rowLocation]
    return perceptRow[player.columnLocation]

def getRoom(player):
    caveRow = cave[player.columnLocation]
    return caveRow[player.rowLocation]

def doAction(action, player):
    #Perform the action given by the user
    direction = player.directionFacing
    row = player.columnLocation
    column = player.rowLocation
    directions = ['right','up','left','down']
    dirIndex = directions.index(direction)

    if action == "TL":
        dirIndex += 1
        if dirIndex == 4:
            dirIndex = 0

        direction = directions[dirIndex]
        player.directionFacing = direction
        return "done"
    elif action == "TR":
        dirIndex -= 1
        if dirIndex < 0:
            dirIndex = 3

        direction = directions[dirIndex]
        player.directionFacing = direction
        return "done"
    elif action == "FW":
        #Move the player forward one room if they don't hit a wall
        if dirIndex == 0:
            moveDirection = [1,0]
        elif dirIndex == 1:
            moveDirection = [0,-1]
        elif dirIndex == 2:
            moveDirection = [-1,0]
        else:
            moveDirection = [0,1]

        row += moveDirection[0]
        column += moveDirection[1]

        if row < 0 or row > 9:
            #player tried to move into wall
            print("You bumped into a wall. You didn't move")
            return 'bump'
        if column < 0 or column > 9:
            #player tried to move into wall
            print("You bumped into a wall. You didn't move")
            return 'bump'

        #This is a valid move. Remove the player image from this location. Put an empty image here
        oldRow = row - moveDirection[0]
        oldColumn = column - moveDirection[1]
        l = images[oldRow]
        l[oldColumn].config(image=emptyImage)

        #Move is correct. Check for a pit or an alive wumpus
        player.rowLocation = column
        player.columnLocation = row

        caveRow = cave[row]
        room = caveRow[column]

        if room.hasWumpus:
            #player is dead
            print("You were horribly eaten by the Wumpus")
            return 'dead'
        elif room.hasPit:
            #player is dead
            print("You fell into a pit and died a nasty death")
            return "dead"
        elif room.hasGold:
            print("You have found the gold")
            percept = getPercept(player)
            percept[2] = "Glitter"
            l = images[row]
            l[column].config(image=playerImage)
            return "gold"
        else:
            #Update player image location
            l = images[row]
            l[column].config(image=playerImage)

    elif action == "GR":
        room = getRoom(player)

        if room.hasGold:
            #correct Grab action
            print("You have the gold")
            player.hasGold = True
            percept = getPercept(player)
            percept[2] = ""
            room.hasGold = False
            return "gold"
        else:
            print("You are not in the room with the gold")
            return "no gold"
    elif action == "CL":
        if row == 0 and column == 0:
            #correct Climb action. Game is over
            print("You have successfully exited the cave!")
            if player.hasGold:
                print("You exited the cave with the gold. Congratulations!")
            else:
                print("You exited the cave without the gold. Shame on you!")
            return "over"
        else:
            print("You are not in the correct room")
            return "not over"

    return 'alive'

def main():
    player = Player()

    # modify the window
    makeCave()
    makePercepts()

    # Create the empty seeming cave
    for i in range(10):
        imagerow = []
        for j in range(10):
            emptyLabel = Label(root, image=emptyImage)
            imagerow.append(emptyLabel)
            emptyLabel.grid(row=j, column=i, rowspan=1, columnspan=1)

        images.append(imagerow)

#Remove all this code except for the Player when ready
    # Add the Player to the cave
    l = images[0]
    l[0].config(image=playerImage)

    while True:
        root.update()  # This is necessary so you see the graphics updated

        # This is where your code goes
        percept = getPercept(player)  # Gives you the percept for the room you are in
        print(percept)

        # This is where you have your code to decide what action to take given the current percept
        action = chooseAction(percept, player)

        # With a selected action, have it executed
        returnValue = doAction(action, player)
        if returnValue == "dead" or returnValue == "over":
            exit(0)

''' 
This is where your code is going to go. Define all your data structures and the chooseAction method below here. 
Do not change any code that has been provided to you without asking permission first. 
 
Define your data structures above the chooseAction function 
'''
actionList = []
emptyPercept = ['', '', '', '', '']
movingForward = True
roomDictionary = {}
nextActions = []
visitedRoomList = []
goHome = False
usedSafeRooms = []
roomSafetyDictionary = {}

time.sleep(.25) #Pauses the program so your agent doesn't run too fast
    #write global so that pyCharm can identify the global outside of def chooseAction

def makeDictKey(player):
    return str(player.rowLocation) + str(player.columnLocation)

def chooseAction(percept, player):
    global movingForward
    global actionList
    global visitedRoomList
    global nextActions

    print("moving forward:", movingForward)

    if len(roomSafetyDictionary) == 0:
        for i in range(10):
            for j in range(10):
                key = str(i) + str(j)
                roomSafetyDictionary[key] = 0
    key = makeDictKey(player)
    roomDictionary[key] = percept

    if len(visitedRoomList) > 0:
        lastRoom = visitedRoomList[-1]
        if key != lastRoom:
            visitedRoomList.append(key)
    else:
        visitedRoomList.append(key)

    print("Room:", key)

    if percept == emptyPercept:
        changeImages(percept, player, emptyImage)
        changeSafety(player, percept)

    if "Glitter" in percept:
        print("found the gold. Grabbing it")
        goHome = True
        return 'GR'

    if len(nextActions) > 0:
        print("Popping from next actions", nextActions)
        action = nextActions.pop()

        print(action, "na:", nextActions)
        return action

    if percept != emptyPercept:
        print("We have moved to a room that is not safe. Must turn around.")
        changeSafety(player, percept)
        #changeImages(percept, player, questionImage)

        nextActions = []
        nextActions.append('FW')
        nextActions.append('TL')
        movingForward = False

        print('TL')
        return 'TL'

    if not movingForward:
        action = chooseTurn(player)
        print("Choose turn action:", action)
        if action == "":
            print("Couldn't find a new room to move to.")

            #action = computeTurnToRoom(player)
            action = 'FW'
            actionList.append(action)
            return action
        print("Found another room to move to", action)
        nextActions = ['FW']
        movingForward = True
        print(action, "mf:",movingForward)
        return actionList

    if percept == emptyPercept and movingForward:
        action = 'FW'
    else:
        if len(visitedRoomList) == 1:
            exit(0)

        nextActions = []
        nextActions.append('FW')
        nextActions.append('TL')

        movingForward = False

        print('TL')
        return 'TL'

    if action == "FW":
        if bumpWall(player):
            print("Can't move forward would bump into wall")
            action = chooseTurn(player)
            if action == "":
                print("No safe room to move to")
                action = moveBackToPriorRoom(player)
    print(action)
    return action

def changeSafety(player, percept):
    # change safety levels for adjacent rooms based on the percept
    global roomSafetyDictionary
    i = player.rowLocation
    j = player.columnLocation
    rooms = [[i + 1, j], [i - 1, j], [i, j + 1], [i, j - 1]]

    # safe
    if percept == emptyPercept:
        for room in rooms:
            key = str(room[0]) + str(room[1])
            if len(key) == 2:
                roomSafetyDictionary[key] = -1

    elif 'Breeze' in percept or 'Stench' in percept:
        for room in rooms:
            key = str(room[0]) + str(room[1])
            if len(key) == 2 and roomSafetyDictionary[key] != -1:
                roomSafetyDictionary[key] = 1  # This is a safe room
            print(roomSafetyDictionary)

def chooseTurn(player):
    print("have to choose a turn to make. cant move forward.")
    turn = ""

    possibleRooms = chooseSafeAdjacentRooms(player)
    #iterate over possible rooms and remove any room that we've already visited

    goodRooms = []
    for room in possibleRooms:
        key = "" + str(room[0]) + str(room[1])
        if key not in roomSafetyDictionary:
            goodRooms.append([room[0], room[1]])

        if len(goodRooms) == 0:
            return "" #no room to turn to

        room = goodRooms[0]

        currentRoom = [player.rowLocation, player.columnLocation]

        turn = computeTurnToRoom( player, currentRoom, room )
    return turn

def moveBackToPriorRoom(player):
    global visitedRoomList
    global movingForward
    global nextActions

    print("visited rooms:", visitedRoomList)
    key = makeDictKey(player)
    currentRoomIndex = visitedRoomList.index(key)
    priorRoomIndex = currentRoomIndex - 1

    if priorRoomIndex < 0:
        print("we are in room 0,0. Find another safe room to move to")
        room = chooseSafeRoomWithMoves(player)
        if room == []:
            return 'CL'
    print("safe room to move into:", room)
    actionList(player, room)
    print("sr na:", nextActions)
    action = nextActions.pop()
    print(action)
    return action

    print("moving backward to room:", visitedRoomList[priorRoomIndex])
    currentRoom = [player.rowLocation, player.columnLocation]
    priorRoom = visitedRoomList[priorRoomIndex]
    priorRow = int(priorRoom[0])
    priorColumn = int(priorRoom[1])
    action = computeTurnToRoom(player, currentRoom, [priorRow, priorColumn])
    prior("compute turn to room: ", action)

    nextActions = ['FW']
    print('action in move back to prior room', action)
    movingForward = False

    return action

def generateActions(player, currentRoom, nextRoom):
    global nextActions

    print("in generate actions")
    turn = computeTurnToRoom(player, currentRoom, nextRoom)
    print(turn)
    if turn != "":
        nextActions.append(turn)
    nextActions.append('FW')
    print("in ga:", nextActions)

def bumpWall(player):
    directions = ['right', 'up', 'left', "down"]
    if player.directionFacing== directions[0] and player.columnLocation == 9:
        return True
    elif player.directionFacing== directions[2] and player.columnLocation == 0:
        return True
    elif player.directionFacing== directions[1] and player.columnLocation == 0:
        return True
    elif player.directionFacing== directions[3] and player.columnLocation == 9:
        return True

    return False

def chooseSafeAdjacentRooms(player):
    i = player.rowLocation
    j = player.columnLocation
    rooms = [[i + 1, j], [i - 1, j], [i, j + 1], [i, j - 1]]

    possibleRooms = [1]

    for room in rooms:
        key = str(room[0]) + str(room[1])
        if key in roomSafetyDictionary:
            continue
        elif len(key) > 2:
            continue
        elif roomSafetyDictionary[key] == -1:
            return  # added

def computeTurnToRoom( player, currentRoom, room ): #defines the parameters of the cave
    rowChange = int(room[0]) - int(currentRoom[0])
    #print("rowChange", rowChange)
    colChange = int(room[1]) - int(currentRoom[1])
    #print("colChange", colChange)
    turn = ""
    # determining left or right turn
    if rowChange > 0:
        if player.directionFacing == "right":
            turn = "TR"
        elif player.directionFacing == "left":
            turn = "TL"
    elif colChange > 0:
        if player.directionFacing == "up":
            turn = "TR"
        elif player.directionFacing == "down":
            turn = "TL"
    if rowChange < 0:
        if player.directionFacing == "right":
            turn = "TL"
        elif player.directionFacing == "left":
            turn = "TR"
    elif colChange < 0:
        if player.directionFacing == "up":
            turn = "TL"
        elif player.directionFacing == "down":
            turn = "TR"
    print("turn is:", turn)
    return turn

def chooseSafeRoomWithMoves(player):
    global usedSafeRooms

    print(roomDictionary)
    for key in roomDictionary:
        print(key, roomDictionary[key])

        if roomDictionary[key] == emptyPercept:
            j = int(key[0])
            i = int(key[1])
            rooms = [[i + 1, j], [i - 1, j], [i, j + 1], [i, j - 1]]
            print(rooms)
            possibleRooms = []
            for room in rooms:
                row = room[0]
                column = room[1]
                key = str(row) + str(column)
                print(key)
                if len(key) > 2 or key in roomSafetyDictionary:
                    continue
                elif key in usedSafeRooms:
                    continue

                possibleRooms.append(room)
                print(possibleRooms)

                goodRooms = []
                if len(possibleRooms) > 0:
                    for room in possibleRooms:
                        key = str(room[0]) + str(room[1])
                        if key in usedSafeRooms:
                            continue
                        goodRooms.append(room)

                if len(goodRooms) == 0:
                    return goodRooms[0]

    print("No safe rooms available to move to")
    return []

def findUnvisitedRoom(player):
    global roomDictionary
    i = player.rowLocation
    j = player.columnLocation
    rooms = [[i + 1, j], [i - 1, j], [i, j + 1], [i, j - 1]]

    for room in rooms:
        key = str(room[0]) + str(room[1])
        if key in roomDictionary:
            #we've already visited this room
            continue
        elif len(key) > 2:
            # eliminates -1 & 10 perimeter rooms
            continue
        return room
    return ""
main()