├── assets ├── Wanted │ ├── Luigi.png │ ├── Mario.png │ ├── Wario.png │ ├── Yoshi.png │ ├── LuigiIcon.png │ ├── MarioIcon.png │ ├── WarioIcon.png │ └── YoshiIcon.png ├── Bob-omb Squad │ ├── Bomb.png │ └── CannonBall.png ├── Memory Master │ ├── Card0.png │ ├── Card1.png │ ├── Card2.png │ ├── Card3.png │ ├── Card4.png │ ├── Card5.png │ ├── Card6.png │ ├── Card7.png │ ├── Card8.png │ ├── Card9.png │ └── Card10.png ├── Whack a Monty │ └── Monty.png ├── Sort or 'Splode │ ├── RedBomb.png │ ├── BlackBomb.png │ ├── RedBombBounds.png │ └── BlackBombBounds.png └── Danger, Bob-omb! Danger! │ ├── Bob-omb.png │ ├── LeftEye.png │ └── Fireball.png ├── properties.txt ├── README.md ├── Whack_a_Monty.py ├── image.py ├── main.py ├── properties.py ├── Sort_or_Splode.py ├── Bobomb_Squad.py ├── Memory_Master.py ├── Snowball_Slalom.py ├── Wanted.py └── Danger_Bobomb_Danger.py /assets/Wanted/Luigi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Minigame-Madness/HEAD/assets/Wanted/Luigi.png -------------------------------------------------------------------------------- /assets/Wanted/Mario.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Minigame-Madness/HEAD/assets/Wanted/Mario.png -------------------------------------------------------------------------------- /assets/Wanted/Wario.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Minigame-Madness/HEAD/assets/Wanted/Wario.png -------------------------------------------------------------------------------- /assets/Wanted/Yoshi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Minigame-Madness/HEAD/assets/Wanted/Yoshi.png -------------------------------------------------------------------------------- /assets/Wanted/LuigiIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Minigame-Madness/HEAD/assets/Wanted/LuigiIcon.png -------------------------------------------------------------------------------- /assets/Wanted/MarioIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Minigame-Madness/HEAD/assets/Wanted/MarioIcon.png -------------------------------------------------------------------------------- /assets/Wanted/WarioIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Minigame-Madness/HEAD/assets/Wanted/WarioIcon.png -------------------------------------------------------------------------------- /assets/Wanted/YoshiIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Minigame-Madness/HEAD/assets/Wanted/YoshiIcon.png -------------------------------------------------------------------------------- /assets/Bob-omb Squad/Bomb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Minigame-Madness/HEAD/assets/Bob-omb Squad/Bomb.png -------------------------------------------------------------------------------- /assets/Memory Master/Card0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Minigame-Madness/HEAD/assets/Memory Master/Card0.png -------------------------------------------------------------------------------- /assets/Memory Master/Card1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Minigame-Madness/HEAD/assets/Memory Master/Card1.png -------------------------------------------------------------------------------- /assets/Memory Master/Card2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Minigame-Madness/HEAD/assets/Memory Master/Card2.png -------------------------------------------------------------------------------- /assets/Memory Master/Card3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Minigame-Madness/HEAD/assets/Memory Master/Card3.png -------------------------------------------------------------------------------- /assets/Memory Master/Card4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Minigame-Madness/HEAD/assets/Memory Master/Card4.png -------------------------------------------------------------------------------- /assets/Memory Master/Card5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Minigame-Madness/HEAD/assets/Memory Master/Card5.png -------------------------------------------------------------------------------- /assets/Memory Master/Card6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Minigame-Madness/HEAD/assets/Memory Master/Card6.png -------------------------------------------------------------------------------- /assets/Memory Master/Card7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Minigame-Madness/HEAD/assets/Memory Master/Card7.png -------------------------------------------------------------------------------- /assets/Memory Master/Card8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Minigame-Madness/HEAD/assets/Memory Master/Card8.png -------------------------------------------------------------------------------- /assets/Memory Master/Card9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Minigame-Madness/HEAD/assets/Memory Master/Card9.png -------------------------------------------------------------------------------- /assets/Whack a Monty/Monty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Minigame-Madness/HEAD/assets/Whack a Monty/Monty.png -------------------------------------------------------------------------------- /assets/Memory Master/Card10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Minigame-Madness/HEAD/assets/Memory Master/Card10.png -------------------------------------------------------------------------------- /assets/Bob-omb Squad/CannonBall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Minigame-Madness/HEAD/assets/Bob-omb Squad/CannonBall.png -------------------------------------------------------------------------------- /assets/Sort or 'Splode/RedBomb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Minigame-Madness/HEAD/assets/Sort or 'Splode/RedBomb.png -------------------------------------------------------------------------------- /assets/Sort or 'Splode/BlackBomb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Minigame-Madness/HEAD/assets/Sort or 'Splode/BlackBomb.png -------------------------------------------------------------------------------- /assets/Sort or 'Splode/RedBombBounds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Minigame-Madness/HEAD/assets/Sort or 'Splode/RedBombBounds.png -------------------------------------------------------------------------------- /assets/Danger, Bob-omb! Danger!/Bob-omb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Minigame-Madness/HEAD/assets/Danger, Bob-omb! Danger!/Bob-omb.png -------------------------------------------------------------------------------- /assets/Danger, Bob-omb! Danger!/LeftEye.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Minigame-Madness/HEAD/assets/Danger, Bob-omb! Danger!/LeftEye.png -------------------------------------------------------------------------------- /assets/Sort or 'Splode/BlackBombBounds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Minigame-Madness/HEAD/assets/Sort or 'Splode/BlackBombBounds.png -------------------------------------------------------------------------------- /assets/Danger, Bob-omb! Danger!/Fireball.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Minigame-Madness/HEAD/assets/Danger, Bob-omb! Danger!/Fireball.png -------------------------------------------------------------------------------- /properties.txt: -------------------------------------------------------------------------------- 1 | # Refers to the top screen of the DS 2 | mainScreenScale=3.75 3 | mainScreenX=0 4 | mainScreenY=182 5 | mainScreenWidth=960 6 | mainScreenHeight=720 7 | 8 | # Refers to the bottom screen of the DS 9 | subScreenScale=3.75 10 | subScreenX=960 11 | subScreenY=182 12 | subScreenWidth=960 13 | subScreenHeight=720 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Minigame Madness 2 | 3 | Welecome to Minigame Madness! This is a Python program that can play minigames from NSMB and SM64DS. 4 | 5 | # What can it play? 6 | 7 | This program can play the following minigames... 8 | 9 | - Wanted 10 | - Sort or 'Splode 11 | - Bob-omb Squad 12 | - Memory Master 13 | - Whack a Monty 14 | - Snowball Slalom 15 | - Danger, Bob-omb! Danger! 16 | 17 | # Dependencies 18 | 19 | - Pyautogui 20 | - Pillow 21 | - Opencv-python 22 | 23 | # Notes 24 | 25 | If you want this program to actually work then make sure to update the properties file to match your display. 26 | -------------------------------------------------------------------------------- /Whack_a_Monty.py: -------------------------------------------------------------------------------- 1 | from image import * 2 | 3 | refreshRate = 10 4 | 5 | montyTexture = None 6 | 7 | def setup(): 8 | global montyTexture 9 | montyTexture = Image.open("assets/Whack a Monty/Monty.png") 10 | def update(): 11 | subScreen = getScreenshot(mainScreen=False) 12 | 13 | locatedMontyBounds = pyautogui.locate(montyTexture, subScreen, confidence=0.85) 14 | 15 | # If a monty is located, then click on it. 16 | if locatedMontyBounds is not None: 17 | boundCenter = pyautogui.center(locatedMontyBounds) 18 | montyPosition = localToGlobalPosition(boundCenter, mainScreen=False) 19 | 20 | pyautogui.moveTo(montyPosition[0], montyPosition[1], _pause=False) 21 | pyautogui.drag(0, 1, 0.11, _pause=False) -------------------------------------------------------------------------------- /image.py: -------------------------------------------------------------------------------- 1 | import pyautogui 2 | from PIL import Image, ImageDraw 3 | import cv2 4 | 5 | from properties import * 6 | 7 | dsWidth = 256 8 | dsHeight = 192 9 | 10 | def getScreenshot(mainScreen=True) -> Image: 11 | # Takes a screenshot based on which screen is required. 12 | screenshot = pyautogui.screenshot( 13 | region=properties.mainScreen if mainScreen else properties.subScreen) 14 | size = screenshot.size 15 | scale = properties.mainScreenScale if mainScreen else\ 16 | properties.subScreenScale 17 | # Scales image to match original DS screen size. 18 | screenshot = screenshot.resize( ( int(size[0] / scale), 19 | int(size[1] / scale) ), Image.NEAREST) 20 | return screenshot 21 | def localToGlobalPosition(position, mainScreen=True) -> tuple: 22 | # Converts a position on the subscreen to a position on the total screen. 23 | scale = properties.mainScreenScale if mainScreen else\ 24 | properties.subScreenScale 25 | offset = properties.mainScreen if mainScreen else\ 26 | properties.subScreen 27 | return ( 28 | ( position[0] * scale ) + offset[0], 29 | ( position[1] * scale ) + offset[1] 30 | ) -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import time 2 | import sys 3 | 4 | from image import * 5 | 6 | gameScripts = [ 7 | "Wanted", "Sort_or_Splode", "Bobomb_Squad", "Memory_Master", "Whack_a_Monty", 8 | "Snowball_Slalom", "Danger_Bobomb_Danger" 9 | ] 10 | gameNames = [ 11 | "Wanted", "Sort or 'Splode", "Bob-omb Squad", "Memory Master", "Whack a Monty", 12 | "Snowball Slalom", "Danger, Bob-omb! Danger!" 13 | ] 14 | gameScriptIndex = 1 15 | 16 | def loadApplication(): 17 | properties.loadFile() 18 | # This failsafe isn't neccesary as I have built one in already. 19 | pyautogui.FAILSAFE = False 20 | 21 | if __name__ == "__main__": 22 | try: 23 | loadApplication() 24 | 25 | sys.path.append("./scripts/Minigames") 26 | 27 | # Loads required Python scripts. 28 | for script in gameScripts: 29 | exec(f"import {script}") 30 | except Exception as exception: 31 | print(exception) 32 | exit() 33 | 34 | # Confirmation box to start the program. 35 | buttonSelected = pyautogui.confirm( 36 | "Select a minigame to have the program play.", 37 | title="Minigame Madness", 38 | buttons=gameNames + ["Cancel"] 39 | ) 40 | 41 | if buttonSelected == "Cancel" or buttonSelected is None: 42 | exit() 43 | else: 44 | gameScriptIndex = gameNames.index(buttonSelected) 45 | 46 | applicationClosed = False 47 | gameScript = gameScripts[gameScriptIndex] 48 | 49 | eval(f"{gameScript}.setup()") 50 | 51 | while not applicationClosed: 52 | # Amount of time in seconds required for each frame. 53 | frameDuration = 1.0 / eval(f"{gameScript}.refreshRate") 54 | 55 | startTime = time.time() 56 | 57 | eval(f"{gameScript}.update()") 58 | 59 | endTime = time.time() 60 | difference = endTime - startTime 61 | time.sleep(max(frameDuration - difference, 0)) 62 | 63 | # Emergency exits the program when the mouse is in the top left corner. 64 | mousex, mousey = pyautogui.position() 65 | applicationClosed = mousex + mousey == 0 66 | -------------------------------------------------------------------------------- /properties.py: -------------------------------------------------------------------------------- 1 | from os.path import exists 2 | 3 | class Properties(object): 4 | def __init__(self): 5 | self.fileName = "properties.txt" 6 | self.mainScreen = [ 0, 0, 1920, 1080 ] 7 | self.mainScreenScale = 1.0 8 | self.subScreen = [ 0, 0, 1920, 1080 ] 9 | self.subScreenScale = 1.0 10 | def loadFile(self): 11 | # Creates a properties file if one is not found. 12 | if not exists(self.fileName): 13 | self.saveFile() 14 | return 15 | 16 | properties = open(self.fileName, 'r') 17 | 18 | for line in properties.readlines(): 19 | wordPair = Properties.seperate(line) 20 | 21 | if "Scale" in line: 22 | if "mainScreen" in line: 23 | self.mainScreenScale = float(wordPair[1]) 24 | else: 25 | self.subScreenScale = float(wordPair[1]) 26 | elif "mainScreen" in line: 27 | for i in range(len(self.mainScreen)): 28 | variableName = Properties.generateScreenVariable( 29 | "mainScreen", i) 30 | 31 | # Checks if variable matches. 32 | if wordPair[0] == variableName[:-1]: 33 | # If so, copy the value to the array. 34 | self.mainScreen[i] = int(wordPair[1]) 35 | elif "subScreen" in line: 36 | for i in range(len(self.subScreen)): 37 | variableName = Properties.generateScreenVariable( 38 | "subScreen", i) 39 | 40 | if wordPair[0] == variableName[:-1]: 41 | self.subScreen[i] = int(wordPair[1]) 42 | 43 | properties.close() 44 | def saveFile(self): 45 | properties = open(self.fileName, 'w') 46 | 47 | # Writes the top screen variables 48 | properties.write("# Refers to the top screen of the DS\n") 49 | properties.write(f"mainScreenScale={self.mainScreenScale}\n") 50 | for i, element in enumerate(self.mainScreen): 51 | properties.write(Properties.generateScreenVariable("mainScreen", i) 52 | + str(element) + '\n') 53 | # Writes the bottom screen variables 54 | properties.write("\n# Refers to the bottom screen of the DS\n") 55 | properties.write(f"subScreenScale={self.subScreenScale}\n") 56 | for i, element in enumerate(self.subScreen): 57 | properties.write(Properties.generateScreenVariable("subScreen", i) 58 | + str(element) + '\n') 59 | 60 | properties.close() 61 | @staticmethod 62 | def generateScreenVariable(name, index) -> str: 63 | # Generates the names of the variables in the properties file. 64 | if index < 2: 65 | return f"{name}{'X' if index % 2 == 0 else 'Y'}=" 66 | return name + ("Width=" if index == 2 else "Height=") 67 | @staticmethod 68 | def seperate(string, symbol = '=') -> list: 69 | # Splits a string into 2 parts based on a seperator symbol. 70 | symbolIndex = string.find(symbol) 71 | return [ 72 | string[:symbolIndex if symbolIndex != -1 else len(string)], 73 | string[symbolIndex+1:] if symbolIndex != -1 else "" 74 | ] 75 | 76 | properties = Properties() -------------------------------------------------------------------------------- /Sort_or_Splode.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | import time 3 | import math 4 | 5 | from image import * 6 | 7 | refreshRate = 12 8 | 9 | class Bomb(Enum): 10 | Black = 0 11 | Red = 1 12 | 13 | bombTextures = [] 14 | bombBoundTextures = [] 15 | bombBounds = [] 16 | bombBoundCenters = [] 17 | 18 | currentBomb = None 19 | 20 | def loadBombTextures(): 21 | for bomb in Bomb: 22 | bombTexture = Image.open(f"assets/Sort or 'Splode/{bomb.name}Bomb.png") 23 | bombTextures.append(bombTexture) 24 | 25 | bombBoundTexture = Image.open( 26 | f"assets/Sort or 'Splode/{bomb.name}BombBounds.png") 27 | bombBoundTextures.append(bombBoundTexture) 28 | def distance(vec1, vec2) -> float: 29 | return math.sqrt( 30 | pow(vec1[0] - vec2[0], 2) + pow(vec1[1] - vec2[1], 2) 31 | ) 32 | 33 | def setup(): 34 | loadBombTextures() 35 | def update(): 36 | global currentBomb, refreshRate 37 | 38 | #mainScreen = getScreenshot() 39 | subScreen = getScreenshot(mainScreen=False) 40 | 41 | if bombBounds == []: 42 | blackBombBounds = pyautogui.locate(bombBoundTextures[Bomb.Black.value], 43 | subScreen, confidence=0.7) 44 | redBombBounds = pyautogui.locate(bombBoundTextures[Bomb.Red.value], 45 | subScreen, confidence=0.7) 46 | bounds = ( blackBombBounds, redBombBounds ) 47 | 48 | # Only sets the bounds if both the red and black bomb areas are located. 49 | if not None in bounds: 50 | for bound in bounds: 51 | bombBounds.append(bound) 52 | 53 | boundCenter = pyautogui.center(bound) 54 | bombBoundCenters.append(boundCenter) 55 | else: 56 | drawing = ImageDraw.Draw(subScreen) 57 | 58 | for bound in bombBounds: 59 | # Draws boxes over the bomb areas to stop them from being located. 60 | drawing.rectangle((( bound[0], bound[1] ), 61 | ( bound[0] + bound[2], bound[1] + bound[3] )), fill=(0, 0, 0)) 62 | 63 | currentBomb = None 64 | 65 | for bombColor in Bomb: 66 | bomb = pyautogui.locate(bombTextures[bombColor.value], subScreen, 67 | confidence=0.95) 68 | 69 | # If bomb is located set it to be moved. 70 | if bomb is not None: 71 | bombCenter = pyautogui.center(bomb) 72 | 73 | dist = 115 74 | 75 | # Finds distance to nearest bomb area. 76 | for bound in bombBoundCenters: 77 | dist = min(dist, distance(bombCenter, bound)) 78 | 79 | # Only continues process if bomb isn't in the spawn area. 80 | if dist < 115: 81 | currentBomb = ( bombCenter, bombColor ) 82 | break 83 | 84 | # Moves bomb if one is located. 85 | if currentBomb is not None: 86 | bombPosition = localToGlobalPosition(currentBomb[0], 87 | mainScreen=False) 88 | bombBoundCenter = bombBoundCenters[currentBomb[1].value] 89 | bombBoundPosition = localToGlobalPosition(bombBoundCenter, 90 | mainScreen=False) 91 | 92 | pyautogui.moveTo(bombPosition[0], bombPosition[1], _pause=False) 93 | pyautogui.mouseDown(_pause=False) 94 | time.sleep(0.04) 95 | pyautogui.moveTo(bombBoundPosition[0], bombBoundPosition[1], _pause=False) 96 | time.sleep(0.04) 97 | pyautogui.mouseUp(_pause=False) -------------------------------------------------------------------------------- /Bobomb_Squad.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | from image import * 4 | 5 | refreshRate = 10 6 | 7 | bombTextures = [] 8 | cannonBallTexture = None 9 | cloudTextures = [] 10 | 11 | bombs = [] 12 | cannonBallPosition = None 13 | 14 | def calculateBombHeight(bomb) -> int: 15 | # Finds how far down the screen a bob-omb is (in pixels). 16 | height = bomb[0][1] 17 | # Adds the height of the screen if on the subscreen. 18 | if bomb[1] == 1: 19 | height += dsHeight 20 | return height 21 | 22 | def setup(): 23 | global cannonBallTexture 24 | 25 | bombTexture = Image.open("assets/Bob-omb Squad/Bomb.png") 26 | bombTextures.append(bombTexture) 27 | 28 | # Generates rotated images to use for image recognition. 29 | for angle in range(20, 60, 5): 30 | # Rotates image in both directions. 31 | rightTurn = bombTexture.rotate(angle, Image.NEAREST) 32 | leftTurn = bombTexture.rotate(-angle, Image.NEAREST) 33 | 34 | # Crops images towards the center to remove transparent pixels when 35 | # rotating. 36 | rightTurn = rightTurn.crop((2, 2, 8, 8)) 37 | leftTurn = leftTurn.crop((2, 2, 8, 8)) 38 | 39 | bombTextures.append(rightTurn) 40 | bombTextures.append(leftTurn) 41 | 42 | cannonBallTexture = Image.open("assets/Bob-omb Squad/CannonBall.png") 43 | def update(): 44 | global cannonBallTexture, bombs, cannonBallPosition 45 | 46 | mainScreen = getScreenshot() 47 | subScreen = getScreenshot(mainScreen=False) 48 | screens = ( mainScreen, subScreen ) 49 | 50 | # Finds the cannon ball before searching for bob-ombs. 51 | if cannonBallPosition is None: 52 | cannonBallBounds = pyautogui.locate(cannonBallTexture, subScreen, 53 | confidence=0.9) 54 | 55 | # Sets position of cannon ball only if one is located. 56 | if cannonBallBounds is not None: 57 | cannonBallCenter = pyautogui.center(cannonBallBounds) 58 | cannonBallPosition = ( cannonBallCenter, 1 ) 59 | else: 60 | bombs = [] 61 | 62 | # Searches for bob-ombs. 63 | for i, screen in enumerate(screens): 64 | for bombTexture in bombTextures: 65 | for bomb in pyautogui.locateAll(bombTexture, screen, 66 | confidence=0.9): 67 | bombCenter = pyautogui.center(bomb) 68 | bombs.append(( bombCenter, i )) 69 | 70 | # If a bob-omb is found then try to shoot it. 71 | if bombs != []: 72 | targetBomb = None 73 | target = 0 74 | 75 | farthest = calculateBombHeight(bombs[target]) 76 | 77 | # Finds which bob-omb is lowest on the screen. 78 | for bombIndex in range(1, len(bombs)): 79 | currentHeight = calculateBombHeight(bombs[bombIndex]) 80 | 81 | if currentHeight > farthest: 82 | farthest = currentHeight 83 | target = bombIndex 84 | 85 | targetBomb = bombs[target] 86 | 87 | # Offset from target bob-omb to cannon ball. 88 | deltax = targetBomb[0][0] - cannonBallPosition[0][0] 89 | deltay = calculateBombHeight(targetBomb) -\ 90 | calculateBombHeight(cannonBallPosition) 91 | 92 | angle = math.atan2(deltay, deltax) 93 | 94 | # Aims ball to move towards the bob-omb if it is higher up. 95 | if deltay < 0: 96 | angle += math.pi 97 | 98 | # Calculates a vector for the cannon ball to hit the bob-omb. 99 | offsetx = 150 * math.cos(angle) * properties.subScreenScale 100 | offsety = 150 * math.sin(angle) * properties.subScreenScale 101 | 102 | cannonBallPoint = localToGlobalPosition(cannonBallPosition[0], 103 | mainScreen=False) 104 | 105 | pyautogui.moveTo(cannonBallPoint[0], cannonBallPoint[1], 106 | _pause=False) 107 | pyautogui.drag(offsetx, offsety, 0.2, button="left", _pause=False) -------------------------------------------------------------------------------- /Memory_Master.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | import time 3 | 4 | from image import * 5 | 6 | refreshRate = 10 7 | 8 | class Card(Enum): 9 | Unknown = 0 10 | Mushroom = 1 11 | Star = 2 12 | Cloud = 3 13 | Flower = 4 14 | Luigi = 5 15 | Mario = 6 16 | Goomba = 7 17 | Bowser = 8 18 | Boo = 9 19 | Yoshi = 10 20 | 21 | cardTextures = [] 22 | 23 | cards = [] 24 | newCardTicks = 0 25 | 26 | def emptyCards(cards) -> bool: 27 | for card in cards: 28 | if card[1] is not Card.Unknown: 29 | return False 30 | return True 31 | def combineCards(newCards): 32 | global cards, newCardTicks 33 | 34 | if (cards == [] and len(newCards) >= 16 and len(newCards) % 2 == 0) or\ 35 | (len(newCards) > len(cards) and len(newCards) % 2 == 0): 36 | cards = newCards.copy() 37 | newCardTicks = 0 38 | elif cards != [] and len(cards) == len(newCards): 39 | for i, newCard in enumerate(newCards): 40 | newCardValue = newCard[1].value 41 | 42 | if newCardValue > cards[i][1].value: 43 | cards[i] = newCard 44 | elif len(newCards) == 0: 45 | cards = [] 46 | def locateCardPair(cards) -> list: 47 | for i, card0 in enumerate(cards): 48 | for j, card1 in enumerate(cards): 49 | if i == j: 50 | continue 51 | 52 | # Finds if cards are the same. 53 | if card0[1] != Card.Unknown and card0[1] == card1[1]: 54 | # Flips cards to make card higher in array appear first. 55 | return ( i, j ) if i > j else ( j, i ) 56 | 57 | return None 58 | def sortCards(cards) -> list: 59 | # Creates a hash value that increases as the position approaches the top 60 | # right corner. 61 | def positionHash(position) -> int: 62 | return position[0] + (-position[1] * 3) 63 | 64 | i = 1 65 | while i < len(cards): 66 | # Reorders cards so higher hashes are later in array. 67 | if positionHash(cards[i][0]) < positionHash(cards[i-1][0]): 68 | cards[i], cards[i-1] = cards[i-1], cards[i] 69 | i = 0 70 | i += 1 71 | 72 | return cards 73 | 74 | def setup(): 75 | for cardIndex in range(11): 76 | cardTexture = Image.open(f"assets/Memory Master/Card{cardIndex}.png") 77 | cardTextures.append(cardTexture) 78 | def update(): 79 | global cards, newCardTicks 80 | 81 | #mainScreen = getScreenshot() 82 | subScreen = getScreenshot(mainScreen=False) 83 | 84 | newCards = [] 85 | 86 | for i, cardTexture in enumerate(cardTextures): 87 | for card in pyautogui.locateAll(cardTexture, subScreen, 88 | confidence=0.8 if i > 0 else 0.95): 89 | cardCenter = pyautogui.center(card) 90 | match = False 91 | 92 | # Removes cards that are duplicates. Note: I added this because for 93 | # some reason some cards get put in twice. 94 | for addedCard in newCards: 95 | deltax = abs(addedCard[0][0] - cardCenter[0]) 96 | deltay = abs(addedCard[0][1] - cardCenter[1]) 97 | 98 | if deltax + deltay < 5: 99 | match = True 100 | break 101 | 102 | if not match: 103 | newCards.append(( cardCenter, list(Card)[i] )) 104 | 105 | newCards = sortCards(newCards) 106 | combineCards(newCards) 107 | 108 | cardPair = locateCardPair(cards) 109 | 110 | newCardTicks += 60 / refreshRate 111 | 112 | # Waits to run program until cards have been fully placed. 113 | if newCardTicks < 180: 114 | return 115 | 116 | if cardPair is not None: 117 | time.sleep(0.5) 118 | 119 | # Clicks on a pair of cards if a match is found. 120 | for index in cardPair: 121 | cardPosition = cards[index][0] 122 | cardPosition = localToGlobalPosition(cardPosition, 123 | mainScreen=False) 124 | 125 | pyautogui.moveTo(cardPosition[0], cardPosition[1]) 126 | pyautogui.mouseDown() 127 | pyautogui.mouseUp() 128 | 129 | # Removes cards from array after they have been pressed. 130 | for index in cardPair: 131 | cards.pop(index) 132 | 133 | time.sleep(1) 134 | else: 135 | for card in cards: 136 | # Clicks on random card if no matches have been located. 137 | if card[1] is Card.Unknown: 138 | cardPosition = card[0] 139 | cardPosition = localToGlobalPosition(cardPosition, 140 | mainScreen=False) 141 | 142 | pyautogui.moveTo(cardPosition[0], cardPosition[1]) 143 | pyautogui.mouseDown() 144 | pyautogui.mouseUp() 145 | 146 | pyautogui.moveTo(1, 1) 147 | time.sleep(0.5) 148 | break -------------------------------------------------------------------------------- /Snowball_Slalom.py: -------------------------------------------------------------------------------- 1 | import time 2 | import math 3 | import random 4 | 5 | from image import * 6 | 7 | refreshRate = 4 8 | 9 | playAgainTexture = None 10 | activeAttempt = False 11 | restartTime = 0 12 | restarted = False 13 | furthestDistance = 0 14 | currentDistance = 0 15 | prvsDistance = 0 16 | timeStuck = 0 17 | bestAttempt = [] 18 | currentAttempt = [] 19 | nextAttemptQueue = [] 20 | timeAfterAttemptQueue = 0 21 | 22 | def setup(): 23 | global playAgainTexture 24 | playAgainTexture = Image.open("assets/Play Again.png") 25 | pyautogui.MINIMUM_DURATION = 0 26 | def update(): 27 | global activeAttempt, restartTime, restarted, furthestDistance, currentDistance,\ 28 | prvsDistance, timeStuck, bestAttempt, currentAttempt, nextAttemptQueue,\ 29 | timeAfterAttemptQueue 30 | 31 | subScreen = getScreenshot(mainScreen=False) 32 | 33 | if not activeAttempt: 34 | playAgainBounds = pyautogui.locate(playAgainTexture, subScreen, confidence=0.75) 35 | 36 | # Resets the attempt if the Play Again button is located. 37 | if playAgainBounds is not None: 38 | boundCenter = pyautogui.center(playAgainBounds) 39 | playAgainPosition = localToGlobalPosition(boundCenter, mainScreen=False) 40 | 41 | time.sleep(1.0) 42 | pyautogui.moveTo(playAgainPosition[0], playAgainPosition[1]) 43 | pyautogui.drag(0, 1, 0.2) 44 | activeAttempt = True 45 | restarted = False 46 | restartTime = time.time() 47 | currentDistance = 0 48 | prvsDistance = 0 49 | timeStuck = 0 50 | currentAttempt = [] 51 | timeAfterAttemptQueue = 0 52 | else: 53 | if not restarted: 54 | currentTime = time.time() 55 | restartDelta = currentTime - restartTime 56 | 57 | # Waits 4 seconds after the Play Again button is clicked before starting. 58 | if restartDelta > 4: 59 | restarted = True 60 | else: 61 | progressBarBottomPixelPosition = (239, 156) 62 | currentPixelPosition = tuple(list(progressBarBottomPixelPosition)) 63 | currentPixelColor = subScreen.getpixel(currentPixelPosition) 64 | currentPixelAverage = (currentPixelColor[0] + currentPixelColor[1] + currentPixelColor[2]) / 3 65 | 66 | # Calculates the snowball's current distance. 67 | while currentPixelAverage < 220 and currentPixelPosition[1] > 0: 68 | currentPixelPosition = (currentPixelPosition[0], currentPixelPosition[1] - 1) 69 | currentPixelColor = subScreen.getpixel(currentPixelPosition) 70 | currentPixelAverage = (currentPixelColor[0] + currentPixelColor[1] + currentPixelColor[2]) / 3 71 | 72 | currentDistance = progressBarBottomPixelPosition[1] - currentPixelPosition[1] 73 | 74 | if currentDistance <= prvsDistance: 75 | timeStuck += 1 76 | else: 77 | prvsDistance = currentDistance 78 | timeStuck = 0 79 | 80 | # Resets the attempt if the snowball gets stuck. 81 | if timeStuck >= refreshRate / 2 and currentDistance > 10: 82 | if currentDistance > furthestDistance or currentDistance * 1.5 < furthestDistance: 83 | furthestDistance = currentDistance 84 | bestAttempt = currentAttempt[:-(refreshRate+1)] 85 | 86 | activeAttempt = False 87 | nextAttemptQueue = bestAttempt[:] 88 | 89 | pushAngle = math.pi / 2 90 | 91 | # Move the cursor in a random direction. 92 | if furthestDistance > 0: 93 | if random.randint(0, refreshRate * (5 if timeAfterAttemptQueue >= 1 else 0)) == 0: 94 | pushAngle += random.randint(-100, 100) / 50.0 95 | 96 | # Uses the previous attempt if the queue is still loaded. 97 | if len(nextAttemptQueue) > 0: 98 | pushAngle = nextAttemptQueue.pop(0) 99 | else: 100 | timeAfterAttemptQueue += 1 101 | 102 | pushVerticalOffset = 0 103 | pushOrigin = ( dsWidth / 2, dsHeight - pushVerticalOffset) 104 | pushDistance = dsHeight - pushVerticalOffset 105 | pushDestination = ( 106 | pushOrigin[0] + pushDistance * math.cos(pushAngle), 107 | pushOrigin[1] + pushDistance * -math.sin(pushAngle) 108 | ) 109 | 110 | # The start of the mouse stroke. 111 | globalPushOrigin = localToGlobalPosition(pushOrigin, mainScreen=False) 112 | # The end of the mouse stroke. 113 | globalPushDestination = localToGlobalPosition(pushDestination, mainScreen=False) 114 | 115 | # Move the mouse along the calculated stroke. 116 | pyautogui.moveTo(globalPushOrigin[0], globalPushOrigin[1], _pause=False) 117 | pyautogui.mouseDown(_pause=False) 118 | pyautogui.moveTo(globalPushDestination[0], globalPushDestination[1], 0.2, _pause=False) 119 | pyautogui.mouseUp(_pause=False) 120 | 121 | currentAttempt.append(pushAngle) -------------------------------------------------------------------------------- /Wanted.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | from image import * 4 | 5 | refreshRate = 2 6 | 7 | class Character(Enum): 8 | Mario = 0 9 | Luigi = 1 10 | Wario = 2 11 | Yoshi = 3 12 | 13 | characterMatch = None 14 | prvsCharacterMatch = None 15 | characterMatchTicks = 0 16 | 17 | characterTextures = [] 18 | characterIcons = [] 19 | characterSubIcons = [] 20 | 21 | def loadCharacterTextures(): 22 | for character in Character: 23 | texture = Image.open(f"assets/Wanted/{character.name}.png") 24 | characterTextures.append(texture) 25 | 26 | icon = Image.open(f"assets/Wanted/{character.name}Icon.png") 27 | characterIcons.append(icon) 28 | def generateIconSubregions(iconIndex) -> Image: 29 | iconSize = characterIcons[iconIndex].size 30 | 31 | # Num of columns to split icon into. 32 | xSubRegions = 7 33 | # Num of rows to split icon into. 34 | ySubRegions = 7 35 | subRegionWidth = iconSize[0] / xSubRegions 36 | subRegionHeight = iconSize[1] / ySubRegions 37 | 38 | for xRegion in range(0, xSubRegions): 39 | for yRegion in range(ySubRegions): 40 | # Area of new sub-icon. 41 | bounds = ( 42 | xRegion * subRegionWidth, yRegion * subRegionHeight, (xRegion 43 | * subRegionWidth) + subRegionWidth, (yRegion * subRegionHeight) 44 | + subRegionWidth 45 | ) 46 | yield characterIcons[iconIndex].crop(bounds) 47 | def generateCharacterSubIcons(): 48 | for character in Character: 49 | subIcons = [] 50 | for subIcon in generateIconSubregions(character.value): 51 | pixelCount = 0 52 | emptyPixels = 0 53 | 54 | # Allows for reading color data from each pixel. 55 | subIcon = subIcon.convert("RGBA") 56 | 57 | for pixel in Image.Image.getdata(subIcon): 58 | # Pixel is empty if it's alpha component is zero. 59 | if pixel[3] == 0: 60 | emptyPixels += 1 61 | pixelCount += 1 62 | 63 | # Adds sub-icon to array if doesn't contain transparent pixels. 64 | if emptyPixels == 0: 65 | subIcons.append(subIcon) 66 | characterSubIcons.append(subIcons) 67 | def removeDuplicateSubIcons(): 68 | for character1 in Character: 69 | for character2 in Character: 70 | # Ensures same characters icons arent compared. 71 | if character1.value == character2.value: 72 | continue 73 | 74 | for i, subIcon in enumerate(characterSubIcons[character2.value]): 75 | iconBounds = pyautogui.locate(subIcon, 76 | characterIcons[character1.value], confidence=0.98) 77 | 78 | # Removes sub-icon if it can be found in a different character. 79 | if iconBounds is not None: 80 | characterSubIcons[character2.value].pop(i) 81 | 82 | def setup(): 83 | loadCharacterTextures() 84 | generateCharacterSubIcons() 85 | removeDuplicateSubIcons() 86 | def update(): 87 | global characterMatch, prvsCharacterMatch, characterMatchTicks 88 | 89 | mainScreen = getScreenshot() 90 | subScreen = getScreenshot(mainScreen=False) 91 | 92 | foundCharacter = None 93 | 94 | # Searches to find the character that is wanted. 95 | for character in Character: 96 | characterBounds = pyautogui.locate(characterTextures[character.value], 97 | mainScreen, confidence=0.75) 98 | 99 | if characterBounds is not None: 100 | foundCharacter = character 101 | characterMatchTicks += 60 / refreshRate 102 | break 103 | 104 | if foundCharacter is None or prvsCharacterMatch != characterMatch: 105 | characterMatchTicks = 0 106 | characterMatch = None 107 | else: 108 | # Starts icon search after one second. 109 | if characterMatchTicks >= 60: 110 | characterMatch = foundCharacter 111 | 112 | prvsCharacterMatch = characterMatch 113 | 114 | if characterMatch is not None: 115 | # A box that represents the on screen area of the icon searched for. 116 | iconBounds = pyautogui.locate(characterIcons[characterMatch.value], 117 | subScreen, confidence=0.9) 118 | 119 | # If the icon is found, press the icon. 120 | if iconBounds is not None: 121 | pressIcon(iconBounds) 122 | # Otherwise search using small sections of the icon. 123 | else: 124 | for i, subIcon in enumerate(characterSubIcons[characterMatch.value]): 125 | iconBounds = pyautogui.locate(subIcon, subScreen, confidence=0.98) 126 | 127 | if iconBounds is not None: 128 | pressIcon(iconBounds) 129 | return 130 | 131 | def pressIcon(bounds): 132 | # Finds center of the icon. 133 | iconCenter = pyautogui.center(bounds) 134 | position = ( 135 | ( iconCenter[0] * properties.subScreenScale ) 136 | + properties.subScreen[0], 137 | ( iconCenter[1] * properties.subScreenScale ) 138 | + properties.subScreen[1] 139 | ) 140 | 141 | # Clicks on the icon. 142 | pyautogui.moveTo(position[0], position[1]) 143 | pyautogui.drag(0.0, 1.0, 0.15) -------------------------------------------------------------------------------- /Danger_Bobomb_Danger.py: -------------------------------------------------------------------------------- 1 | import math 2 | import random 3 | 4 | from image import * 5 | 6 | refreshRate = 5 7 | 8 | bobombTexture = None 9 | fireballTexture = None 10 | leftEyeTexture = None 11 | bobombLocated = False 12 | bobombPosition = None 13 | leftEyeCenter = 0 14 | timeSinceLastLeftEyeLocation = 0 15 | ticks = 0 16 | 17 | def distance(vec1, vec2) -> float: 18 | return math.sqrt( 19 | pow(vec1[0] - vec2[0], 2) + pow(vec1[1] - vec2[1], 2) 20 | ) 21 | def generateBobombBounds(screen) -> any: 22 | bobombBounds = pyautogui.locate(bobombTexture, screen, confidence=0.85) 23 | return bobombBounds 24 | 25 | def setup(): 26 | global bobombTexture, fireballTexture, leftEyeTexture 27 | bobombTexture = Image.open("assets/Danger, Bob-omb! Danger!/Bob-omb.png") 28 | fireballTexture = Image.open("assets/Danger, Bob-omb! Danger!/Fireball.png") 29 | leftEyeTexture = Image.open("assets/Danger, Bob-omb! Danger!/LeftEye.png") 30 | def update(): 31 | global bobombLocated, bobombPosition, ticks, leftEyeCenter, timeSinceLastLeftEyeLocation 32 | 33 | mainScreen = getScreenshot() 34 | subScreen = getScreenshot(mainScreen=False) 35 | 36 | # Checks for bob-omb if it hasn't yet been located. 37 | if not bobombLocated: 38 | bobombBounds = generateBobombBounds(subScreen) 39 | 40 | if bobombBounds is not None: 41 | boundCenter = pyautogui.center(bobombBounds) 42 | 43 | # Grabs the bob-bomb. 44 | pyautogui.moveTo(boundCenter[0], boundCenter[1], _pause=False) 45 | pyautogui.mouseDown() 46 | pyautogui.move(0, 1) 47 | 48 | bobombLocated = True 49 | bobombPosition = boundCenter 50 | else: 51 | # Checks if the bob-omb can still be located, if it can't, then the program stops. 52 | if ticks % (refreshRate * 2) == 0: 53 | bobombBounds = generateBobombBounds(subScreen) 54 | 55 | if bobombBounds is None: 56 | pyautogui.mouseUp() 57 | exit() 58 | 59 | fireballs = pyautogui.locateAll(fireballTexture, subScreen, confidence=0.75) 60 | fireballCenters = [] 61 | leftEyeBounds = pyautogui.locate(leftEyeTexture, mainScreen, confidence=0.75) 62 | 63 | if fireballs is not None or leftEyeBounds is not None: 64 | edgeOffset = 40 65 | edgeStep = 20 66 | 67 | # Adds fireballs to the edges of the map. 68 | for xpos in range(edgeOffset, dsWidth - edgeOffset, edgeStep): 69 | edgePoint1 = ( xpos, edgeOffset ) 70 | edgePoint2 = ( xpos, dsHeight - edgeOffset ) 71 | fireballCenters.append(edgePoint1) 72 | fireballCenters.append(edgePoint2) 73 | for ypos in range(edgeOffset, dsHeight - edgeOffset, edgeStep): 74 | edgePoint1 = ( edgeOffset, ypos ) 75 | edgePoint2 = ( dsWidth - edgeOffset, ypos ) 76 | fireballCenters.append(edgePoint1) 77 | fireballCenters.append(edgePoint2) 78 | 79 | if leftEyeBounds is not None: 80 | timeSinceLastLeftEyeLocation = 0 81 | boundCenter = pyautogui.center(leftEyeBounds) 82 | leftEyeCenter = boundCenter[0] + 10 83 | else: 84 | timeSinceLastLeftEyeLocation += 1 85 | 86 | # Adds fireballs where Bowser's fire is. 87 | if timeSinceLastLeftEyeLocation < refreshRate * 2: 88 | for ypos in range(edgeOffset, dsHeight - edgeOffset, edgeStep): 89 | edgePoint = ( leftEyeCenter, ypos ) 90 | fireballCenters.append(edgePoint) 91 | 92 | # Centers all of the normal fireballs. 93 | for fireballBounds in fireballs: 94 | boundCenter = pyautogui.center(fireballBounds) 95 | fireballCenters.append(boundCenter) 96 | 97 | angleStep = 2 98 | bestAngle = 0 99 | bestDistance = 0 100 | furthestDistance = 0 101 | 102 | # This loop is used to find the best angle/distance pair to move the mouse along. 103 | for angle in range(-angleStep, 360, angleStep): 104 | radians = angle / (180 / math.pi) 105 | currentDistance = 0 if angle == -angleStep else 3 * (1 + ((angle // angleStep) % 5)) 106 | currentBobombPosition = ( 107 | bobombPosition[0] + (currentDistance * math.cos(radians)), 108 | bobombPosition[1] + (currentDistance * math.sin(radians)) 109 | ) 110 | 111 | closestDistance = dsWidth 112 | 113 | for fireballCenter in fireballCenters: 114 | transformedBobombPosition = ( 115 | currentBobombPosition[0], 116 | currentBobombPosition[1] - 10 117 | ) 118 | bobombFireballDistance = distance(fireballCenter, transformedBobombPosition) 119 | closestDistance = min(closestDistance, bobombFireballDistance) 120 | 121 | # Only considers attempts where the shortest fireball distance is larger than the 122 | # previous attempts. 123 | if closestDistance > furthestDistance: 124 | bestAngle = radians 125 | bestDistance = currentDistance 126 | furthestDistance = closestDistance 127 | 128 | # Calculates the new bob-omb position. 129 | bobombPosition = ( 130 | bobombPosition[0] + (bestDistance * math.cos(bestAngle)), 131 | bobombPosition[1] + (bestDistance * math.sin(bestAngle)) 132 | ) 133 | 134 | globalBobombPosition = localToGlobalPosition(bobombPosition, mainScreen=False) 135 | pyautogui.moveTo(globalBobombPosition[0], globalBobombPosition[1]) 136 | 137 | ticks += 1 --------------------------------------------------------------------------------