Last active
January 16, 2024 19:36
-
-
Save heyAyushh/e2f0e9424203d0e725342fbb63767b8e to your computer and use it in GitHub Desktop.
AIGaming Match bot
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#implemented word and Landmark Matching | |
#. __ __ _ _ ____ | |
# | \/ | __ _| |_ ___| |__ / ___| __ _ _ __ ___ ___ | |
# | |\/| |/ _` | __/ __| '_ \ | | _ / _` | '_ ` _ \ / _ \ | |
# | | | | (_| | || (__| | | | | |_| | (_| | | | | | | __/ | |
# |_| |_|\__,_|\__\___|_| |_| \____|\__,_|_| |_| |_|\___| | |
# | |
botName='Ayush-15-defbot' | |
import requests | |
import json | |
from random import sample, choice | |
from time import sleep | |
# See our help page to learn how to get a WEST EUROPE Microsoft API Key at | |
# https://help.aigaming.com/game-help/signing-up-for-azure | |
# *** Use westeurope API key for best performance *** | |
headers_vision = {'Ocp-Apim-Subscription-Key': 'YOUR-WESTEUROPE-MICROSOFT-COMPUTER-VISION-API-KEY'} | |
vision_base_url = "https://westeurope.api.cognitive.microsoft.com/vision/v2.0/" | |
analysed_tiles = [] | |
previous_move = [] | |
move_number = 0 | |
# ============================================================================= | |
# calculate_move() overview | |
# 1. Analyse the upturned tiles and remember them | |
# 2. Determine if you have any matching tiles | |
# 3. If we have matching tiles: | |
# use them as a move | |
# 4. If no matching tiles: | |
# Guess two tiles for this move | |
# | |
# **Important**: calculate_move() can only remember data between moves | |
# if we store data in a global variable | |
# Use the analysed_tiles global to remember the tiles we have seen | |
# We recognise animals for you, you must add Landmarks and Words | |
# | |
# Get more help on the Match Game page at https://help.aigaming.com | |
# | |
def calculate_move(gamestate): | |
global analysed_tiles | |
global previous_move | |
global move_number | |
# Record the number of tiles so we know how many tiles we need to loop through | |
num_tiles = len(gamestate["Board"]) | |
move_number += 1 | |
if gamestate["UpturnedTiles"] == []: | |
print("{}. No upturned tiles for this move.".format(move_number)) | |
else: | |
print("{}. ({}, {}) Upturned tiles for this move".format(move_number, gamestate["UpturnedTiles"][0]["Index"], gamestate["UpturnedTiles"][1]["Index"])) | |
print(" gamestate: {}".format(gamestate)) | |
# If we have not yet used analysed_tiles (i.e. It is the first turn of the game) | |
if analysed_tiles == []: | |
# Create a list to hold tile information and set each one as UNANALYSED | |
for index in range(num_tiles): | |
# Mark tile as not analysed | |
analysed_tiles.append({}) | |
analysed_tiles[index]["State"] = "UNANALYSED" | |
analysed_tiles[index]["Subject"] = None | |
# The very first move in the game does not have any upturned tiles, and | |
# if your last move matched tiles, you will not have any upturned tiles | |
# Check to see if we have received some upturned tiles for this move. | |
if gamestate["UpturnedTiles"] != []: | |
# Analyse the tile images using the Microsoft API and store the results | |
# in analysed_tiles so that we don't have to analyse them againf if we | |
# see the same tile later in the game. | |
analyse_tiles(gamestate["UpturnedTiles"], gamestate) | |
# Else, it is either our first turn, or, our previous move was a match | |
else: | |
# If it is not our first move of the game | |
if previous_move != []: | |
# then our previous move successfully matched two tiles | |
# Update our analysed_tiles to mark the previous tiles as matched | |
print(" MATCH: ({}, {}) - {}".format(previous_move[0], previous_move[1], analysed_tiles[previous_move[0]]["Subject"])) | |
analysed_tiles[previous_move[0]]["State"] = "MATCHED" | |
analysed_tiles[previous_move[1]]["State"] = "MATCHED" | |
# TIP: Python print statements appear in column 3 of this Editor window | |
# and can be used for debugging | |
# Print out the updated analysed_tiles list to see what it contains | |
#print("Analysed Tiles: {}".format(json.dumps(analysed_tiles, indent=2))) | |
# Check the stored tile information in analysed_tiles | |
# to see if we know of any matching tiles | |
match = search_for_matching_tiles() | |
# If we do have some matching tiles | |
if match is not None: | |
# Print out the move for debugging -----------------> | |
print(" Matching Move: {}".format(match)) | |
# Set our move to be these matching tiles | |
move = match | |
# If we don't have any matching tiles | |
else: | |
# Create a list of all the tiles that we haven't unanalysed yet | |
unanalysed_tiles = get_unanalysed_tiles() | |
# If there are some tiles that we haven't analysed yet | |
if unanalysed_tiles != []: | |
# Choose the unanalysed tiles that you want to turn over | |
# in your next move. We turn over a random pair of | |
# unanalysed tiles, but, could you make a more intelligent | |
# choice? | |
move = sample(unanalysed_tiles, 2) | |
# Print out the move for debugging -----------------> | |
print(" New tiles move: {}".format(move)) | |
# If the unanalysed_tiles list is empty (all tiles have been analysed) | |
else: | |
# If all else fails, we will need to manually match each tile | |
# Create a list of all the unmatched tiles | |
unmatched_tiles = get_unmatched_tiles() | |
# Turn over two random tiles that haven't been matched | |
# TODO: It would be more efficient to remember which tiles you | |
# have tried to match. | |
move = sample(unmatched_tiles, 2) | |
# Print the move for debugging -----------------> | |
print(" Random guess move: {}".format(move)) | |
# Store our move to look back at next turn | |
previous_move = move | |
# Return the move we wish to make | |
return {"Tiles": move} | |
# Get the unmatched tiles | |
# | |
# Outputs: | |
# list of integers - A list of unmatched tile numbers | |
# | |
# Returns the list of tiles that haven't been matched | |
def get_unmatched_tiles(): | |
# Create a list of all the unmatched tiles | |
unmatched_tiles = [] | |
# For every tile in the game | |
for index, tile in enumerate(analysed_tiles): | |
# If that tile hasn't been matched yet | |
if tile["State"] != "MATCHED": | |
# Add that tile to the list of unmatched tiles | |
unmatched_tiles.append(index) | |
# Return the list | |
return unmatched_tiles | |
# Identify all of the tiles that we have not yet analysed with the | |
# Microsoft API. | |
# | |
# Output: | |
# list of integers - only those tiles that have not yet been analysed | |
# by the Microsoft API | |
# | |
# Returns the list of tiles that haven't been analysed | |
# (according to analysed_tiles) | |
def get_unanalysed_tiles(): | |
# Filter out analysed tiles | |
unanalysed_tiles = [] | |
# For every tile that hasn't been matched | |
for index, tile in enumerate(analysed_tiles): | |
# If the tile hasn't been analysed | |
if tile["State"] == "UNANALYSED": | |
# Add that tile to the list of unanalysed tiles | |
unanalysed_tiles.append(index) | |
# Return the list | |
return unanalysed_tiles | |
# Analyses a list of tiles | |
# | |
# Inputs: | |
# tiles: list of JSON objects - A list of tile objects that contain a | |
# url and an index | |
# gamestate: JSON object - The current state of the game | |
# | |
# Given a list of tiles we want to analyse and the animal list, calls the | |
# analyse_tile function for each of the tiles in the list | |
def analyse_tiles(tiles, gamestate): | |
# For every tile in the list 'tiles' | |
for tile in tiles: | |
# Call the analyse_tile function with that tile | |
# along with the gamestate | |
analyse_tile(tile, gamestate) | |
# Analyses a single tile | |
# | |
# Inputs: | |
# tile: JSON object - A tile object that contains a url and an index | |
# gamestate: JSON object - The current state of the game | |
# | |
# Given a tile, analyse it to determine its subject and record the information | |
# in analysed_tiles using the Microsoft APIs | |
def analyse_tile(tile, gamestate): | |
# If we have already analysed the tile | |
if analysed_tiles[tile["Index"]]["State"] != "UNANALYSED": | |
# We don't need to analyse the tile again, so stop | |
return | |
# Call analysis | |
analyse_url = vision_base_url + "analyze" | |
params_analyse = {'visualFeatures': 'categories,tags,description,faces,imageType,color,adult', | |
'details': 'celebrities,landmarks'} | |
data = {"url": tile["Tile"]} | |
msapi_response = microsoft_api_call(analyse_url, params_analyse, headers_vision, data) | |
print(" API Result tile #{}: {}".format(tile["Index"], msapi_response)) | |
# Check if the subject of the tile is an animal | |
subject = check_for_animal(msapi_response, gamestate["AnimalList"]) | |
# If we haven't determined the subject of the image yet | |
if subject is None: | |
# Check if the subject of the tile is a landmark | |
subject = check_for_landmark(msapi_response) | |
else: | |
print(" Animal at tile #{}: {}".format(tile["Index"], subject)) | |
# If we still haven't determined the subject of the image yet | |
if subject is None: | |
subject = check_for_text(tile) | |
# TODO: Use the Microsoft OCR API to determine if the tile contains a | |
# word. You can get more information about the Microsoft Cognitive API | |
# OCR function at: | |
# https://westus.dev.cognitive.microsoft.com/docs/services/56f91f2d778daf23d8ec6739/operations/56f91f2e778daf14a499e1fc | |
# Use our previous example to check_for_animal as a guide | |
# Remember this tile by adding it to our list of known tiles | |
# Mark that the tile has now been analysed | |
analysed_tiles[tile["Index"]]["State"] = "ANALYSED" | |
analysed_tiles[tile["Index"]]["Subject"] = subject | |
# Check Microsoft API response to see if it contains information about an animal | |
# | |
# Inputs: | |
# msapi_response: JSON dictionary - A dictionary containing all the | |
# information the Microsoft API has returned | |
# animal_list: list of strings - A list of all the possible animals in | |
# the game | |
# Outputs: | |
# string - The name of the animal | |
# | |
# Given the result of the Analyse Image API call and the list of animals, | |
# returns whether there is an animal in the image | |
def check_for_animal(msapi_response, animal_list): | |
# Initialise our subject to None | |
subject = None | |
# For every tag in the returned tags, in descending confidence order | |
for tag in sorted(msapi_response["tags"], key=lambda x: x['confidence'], reverse=True): | |
# If the tag has a name and that name is one of the animals in our list | |
if "name" in tag and tag["name"] in animal_list: | |
# Record the name of the animal that is the subject of the tile | |
# (We store the subject in lowercase to make comparisons easier) | |
subject = tag["name"].lower() | |
# Print out the animal we have found here for debugging -----------------> | |
# print(" Animal: {}".format(subject)) | |
# Exit the for loop | |
break | |
# Return the subject | |
return subject | |
# ----------------------------------- TODO ----------------------------------- | |
# | |
# Inputs: | |
# msapi_response: JSON dictionary - A dictionary containing all the | |
# information the Microsoft API has returned | |
# Outputs: | |
# string - The name of the animal | |
# | |
# Given the result of the Analyse Image API call, returns whether there is a | |
# landmark in the image | |
def check_for_landmark(msapi_response): | |
# TODO: We strongly recommend copying the result of the Microsoft API into | |
# a JSON formatter (e.g. https://jsonlint.com/) to better understand what | |
# the API is returning and how you will access the landmark information | |
# that you need. | |
# Here is an example of accessing the information in the JSON: | |
# msapi_response["categories"][0]["detail"]["landmarks"][0]["name"].lower() | |
# Initialise our subject to None | |
subject = None | |
for category in msapi_response["categories"]: | |
if "detail" in category \ | |
and "landmarks" in category["detail"] \ | |
and category["detail"]["landmarks"]: | |
# (We store the subject in lowercase to make comparisons easier) | |
subject = category["detail"]["landmarks"][0]["name"].lower() | |
# Print out the animal we have found here for debugging -----------------> | |
#print(" Landmark: {}".format(subject)) | |
# Exit the for loop | |
break | |
# Return the subject | |
return subject | |
def check_for_text(tile): | |
subject = None | |
analyse_url = vision_base_url + "ocr" | |
params_analyse = {} | |
data = {"url":tile["Tile"]} | |
microsoft_api_response = microsoft_api_call(analyse_url,params_analyse,headers_vision,data) | |
for region in microsoft_api_response: | |
if microsoft_api_response["regions"]: | |
if "lines" in microsoft_api_response["regions"][0]: | |
if "words" in microsoft_api_response["regions"][0]["lines"][0]: | |
if "text" in microsoft_api_response["regions"][0]["lines"][0]["words"][0]: | |
subject = microsoft_api_response["regions"][0]["lines"][0]["words"][0]["text"] | |
print(" Word: {}".format(subject)) | |
return subject | |
# Find matching tile subjects | |
# | |
# Outputs: | |
# list of integers - A list of two tile indexes that have matching subjects | |
# | |
# Search through analysed_tiles for two tiles recorded | |
# under the same subject | |
def search_for_matching_tiles(): | |
# For every tile subject and its index | |
for index_1, tile_1 in enumerate(analysed_tiles): | |
# Loop through every tile subject and index | |
for index_2, tile_2 in enumerate(analysed_tiles): | |
# If the two tile's subject is the same and isn't None and the tile | |
# hasn't been matched before, and the tiles aren't the same tile | |
if tile_1["State"] == tile_2["State"] == "ANALYSED" and tile_1["Subject"] == tile_2["Subject"] and tile_1["Subject"] is not None and index_1 != index_2: | |
# Choose these two tiles | |
# Return the two chosen tiles as a list | |
return [index_1, index_2] | |
# If we have not matched any tiles, return no matched tiles | |
return None | |
# Call the Microsoft API | |
# | |
# Inputs: | |
# url: string - The url to the Microsoft API | |
# params: dictionary - Configuration parameters for the request | |
# headers: dictionary - Subscription key to allow request to be made | |
# data: dictionary - Input to the API request | |
# Outputs: | |
# JSON dictionary - The result of the API call | |
# | |
# Given the parameters, makes a request to the specified url, the request is | |
# retried as long as there is a volume quota error | |
def microsoft_api_call(url, params, headers, data): | |
# Make API request | |
response = requests.post(url, params=params, headers=headers, json=data) | |
# Convert result to JSON | |
res = response.json() | |
# While we have exceeded our request volume quota | |
while "statusCode" in res and res["statusCode"] == 429: | |
# Wait for 1 second | |
sleep(1) | |
# Print that we are retrying the API call here -----------------> | |
print("Retrying") | |
# Make API request | |
response = requests.post(url, params=params, headers=headers, json=data) | |
# Convert result to JSON | |
res = response.json() | |
# Print the result of the API call here for debugging -----------------> | |
# print(" API Result: {}".format(res)) | |
# Return JSON result of API request | |
return res | |
# Test the user's subscription key | |
# | |
# Raise an error if the user's API key is not valid for the Microsoft | |
# Computer Vision API call | |
def valid_subscription_key(): | |
# Make a computer vision api call | |
test_api_call = microsoft_api_call(vision_base_url + "analyze", {}, headers_vision, {}) | |
if "error" in test_api_call: | |
raise ValueError("Invalid Microsoft Computer Vision API key for current region") | |
# Check the subscription key | |
valid_subscription_key() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment