· 4 years ago · Mar 22, 2021, 03:42 AM
1#_ __ __ _ _ ____
2# | \/ | __ _| |_ ___| |__ / ___| __ _ _ __ ___ ___
3# | |\/| |/ _` | __/ __| '_ \ | | _ / _` | '_ ` _ \ / _ \
4# | | | | (_| | || (__| | | | | |_| | (_| | | | | | | __/
5# |_| |_|\__,_|\__\___|_| |_| \____|\__,_|_| |_| |_|\___|
6#
7botName='your-bot-name'
8import requests
9import json
10from random import sample, choice
11from time import sleep
12
13# See our help page to learn how to get a WEST EUROPE Microsoft API Key at
14# https://help.aigaming.com/game-help/signing-up-for-azure
15# *** Use westeurope API key for best performance ***
16headers_vision = {'Ocp-Apim-Subscription-Key': 'YOUR-API-Key'}
17vision_base_url = "https://westeurope.api.cognitive.microsoft.com/vision/v2.0/"
18
19analysed_tiles = []
20previous_move = []
21move_number = 0
22
23# =============================================================================
24# calculate_move() overview
25# 1. Analyse the upturned tiles and remember them
26# 2. Determine if you have any matching tiles
27# 3. If we have matching tiles:
28# use them as a move
29# 4. If no matching tiles:
30# Guess two tiles for this move
31#
32# **Important**: calculate_move() can only remember data between moves
33# if we store data in a global variable
34# Use the analysed_tiles global to remember the tiles we have seen
35# We recognise animals for you, you must add Landmarks and Words
36#
37# Get more help on the Match Game page at https://help.aigaming.com
38#
39def calculate_move(gamestate):
40 global analysed_tiles
41 global previous_move
42 global move_number
43 global flag
44 # Record the number of tiles so we know how many tiles we need to loop through
45 num_tiles = len(gamestate["Board"])
46
47 move_number += 1
48 if gamestate["UpturnedTiles"] == []:
49 print("{}. No upturned tiles for this move.".format(move_number))
50 else:
51 print("{}. ({}, {}) Upturned tiles for this move".format(move_number, gamestate["UpturnedTiles"][0]["Index"], gamestate["UpturnedTiles"][1]["Index"]))
52 print(" gamestate: {}".format(json.dumps(gamestate)))
53 print(" Bonus: {}".format(gamestate["Bonus"]))
54 print(" Multiplier: {}".format(gamestate["Multiplier"]))
55
56 # If we have not yet used analysed_tiles (i.e. It is the first turn of the game)
57 if analysed_tiles == []:
58 # Create a list to hold tile information and set each one as UNANALYSED
59 for index in range(num_tiles):
60 # Mark tile as not analysed
61 analysed_tiles.append({})
62 analysed_tiles[index]["State"] = "UNANALYSED"
63 analysed_tiles[index]["Subject"] = None
64
65 # The very first move in the game does not have any upturned tiles, and
66 # if your last move matched tiles, you will not have any upturned tiles
67
68 # Check to see if we have received some upturned tiles for this move.
69 if gamestate["UpturnedTiles"] != []:
70 # Analyse the tile images using the Microsoft API and store the results
71 # in analysed_tiles so that we don't have to analyse them againf if we
72 # see the same tile later in the game.
73 analyse_tiles(gamestate["UpturnedTiles"], gamestate)
74 # Else, it is either our first turn, or, our previous move was a match
75 else:
76 # If it is not our first move of the game
77 if previous_move != []:
78 # then our previous move successfully matched two tiles
79 # Update our analysed_tiles to mark the previous tiles as matched
80 print(" MATCH: ({}, {}) - {}".format(previous_move[0], previous_move[1], analysed_tiles[previous_move[0]]["Subject"]))
81 analysed_tiles[previous_move[0]]["State"] = "MATCHED"
82 analysed_tiles[previous_move[1]]["State"] = "MATCHED"
83
84 # TIP: Python print statements appear in column 3 of this Editor window
85 # and can be used for debugging
86 # Print out the updated analysed_tiles list to see what it contains
87 # print("Analysed Tiles: {}".format(json.dumps(analysed_tiles, indent=2)))
88
89 # Check the stored tile information in analysed_tiles
90 # to see if we know of any matching tiles
91 unanalysed_tiles = get_unanalysed_tiles()
92
93 # If we do have some matching tiles
94 if unanalysed_tiles != []:
95 move = sample(unanalysed_tiles, 2)
96 # Print out the move for debugging ----------------->
97 print(" New tiles move: {}".format(move))
98 # If the unanalysed_tiles list is empty (all tiles have been analysed)
99
100 # If we don't have any matching tiles
101 else:
102 match_pairs = search_for_matching_tiles()
103 # Create a list of all the tiles that we haven't analysed yet
104 print(match_pairs)
105 if match_pairs != []:
106 # If there are some tiles that we haven't analysed yet
107 if match_pairs[0]:
108 move = match_pairs[0]
109 for match in match_pairs:
110 print(" New tiles Pair: {}".format(match) + " " + analysed_tiles[match[0]]["Category"] + " " + gamestate["Bonus"])
111 if(analysed_tiles[match[0]]["Category"] == gamestate["Bonus"]):
112 move = match
113 print(" Matching Move with Bonus: {}".format(match))
114 flag = 1
115 break
116
117 print("Move Chosen: {}".format(match))
118
119 # If the unanalysed_tiles list is empty (all tiles have been analysed)
120 else:
121 # If all else fails, we will need to manually match each tile
122
123 # Create a list of all the unmatched tiles
124 unmatched_tiles = get_unmatched_tiles()
125
126 # Turn over two random tiles that haven't been matched
127 # TODO: It would be more efficient to remember which tiles you
128 # have tried to match.
129 move = sample(unmatched_tiles, 2)
130 # Print the move for debugging ----------------->
131 print(" Random guess move: {}".format(move))
132
133 # Store our move to look back at next turn
134 previous_move = move
135 # Return the move we wish to make
136 return {"Tiles": move}
137
138
139# Get the unmatched tiles
140#
141# Outputs:
142# list of integers - A list of unmatched tile numbers
143#
144# Returns the list of tiles that haven't been matched
145def get_unmatched_tiles():
146 # Create a list of all the unmatched tiles
147 unmatched_tiles = []
148 # For every tile in the game
149 for index, tile in enumerate(analysed_tiles):
150 # If that tile hasn't been matched yet
151 if tile["State"] != "MATCHED":
152 # Add that tile to the list of unmatched tiles
153 unmatched_tiles.append(index)
154 # Return the list
155 return unmatched_tiles
156
157
158# Identify all of the tiles that we have not yet analysed with the
159# Microsoft API.
160#
161# Output:
162# list of integers - only those tiles that have not yet been analysed
163# by the Microsoft API
164#
165# Returns the list of tiles that haven't been analysed
166# (according to analysed_tiles)
167def get_unanalysed_tiles():
168 # Filter out analysed tiles
169 unanalysed_tiles = []
170 # For every tile that hasn't been matched
171 for index, tile in enumerate(analysed_tiles):
172 # If the tile hasn't been analysed
173 if tile["State"] == "UNANALYSED":
174 # Add that tile to the list of unanalysed tiles
175 unanalysed_tiles.append(index)
176 # Return the list
177 return unanalysed_tiles
178
179
180# Analyses a list of tiles
181#
182# Inputs:
183# tiles: list of JSON objects - A list of tile objects that contain a
184# url and an index
185# gamestate: JSON object - The current state of the game
186#
187# Given a list of tiles we want to analyse and the animal list, calls the
188# analyse_tile function for each of the tiles in the list
189def analyse_tiles(tiles, gamestate):
190 # For every tile in the list 'tiles'
191 for tile in tiles:
192 # Call the analyse_tile function with that tile
193 # along with the gamestate
194 analyse_tile(tile, gamestate)
195
196
197# Analyses a single tile
198#
199# Inputs:
200# tile: JSON object - A tile object that contains a url and an index
201# gamestate: JSON object - The current state of the game
202#
203# Given a tile, analyse it to determine its subject and record the information
204# in analysed_tiles using the Microsoft APIs
205def analyse_tile(tile, gamestate):
206 # If we have already analysed the tile
207 if analysed_tiles[tile["Index"]]["State"] != "UNANALYSED":
208 # We don't need to analyse the tile again, so stop
209 return
210
211 # Call analysis
212 analyse_url = vision_base_url + "analyze"
213 params_analyse = {'visualFeatures': 'categories,tags,description,faces,imageType,color,adult',
214 'details': 'celebrities,landmarks'}
215 data = {"url": tile["Tile"]}
216 msapi_response = microsoft_api_call(analyse_url, params_analyse, headers_vision, data)
217 print(" API Result tile #{}: {}".format(tile["Index"], msapi_response))
218 # Check if the subject of the tile is a landmark
219 subject = check_for_landmark(msapi_response)
220 cat = "Landmarks"
221 # If we haven't determined the subject of the image yet
222 if subject is None:
223 # Check if the subject of the tile is an animal
224 subject = check_for_animal(msapi_response, gamestate["AnimalList"])
225 cat = "Animals"
226 # If we still haven't determined the subject of the image yet
227 if subject is None:
228 # TODO: Use the Microsoft OCR API to determine if the tile contains a
229 # word. You can get more information about the Microsoft Cognitive API
230 # OCR function at:
231 # https://westus.dev.cognitive.microsoft.com/docs/services/56f91f2d778daf23d8ec6739/operations/56f91f2e778daf14a499e1fc
232 # Use our previous example to check_for_animal as a guide
233 subject = check_for_text(tile)
234 cat = "Words"
235 else:
236 print(" Animal at tile #{}: {}".format(tile["Index"], subject))
237 # Remember this tile by adding it to our list of known tiles
238 # Mark that the tile has now been analysed
239 analysed_tiles[tile["Index"]]["State"] = "ANALYSED"
240 analysed_tiles[tile["Index"]]["Subject"] = subject
241 analysed_tiles[tile["Index"]]["Category"] = cat
242
243
244# Check Microsoft API response to see if it contains information about an animal
245#
246# Inputs:
247# msapi_response: JSON dictionary - A dictionary containing all the
248# information the Microsoft API has returned
249# animal_list: list of strings - A list of all the possible animals in
250# the game
251# Outputs:
252# string - The name of the animal
253#
254# Given the result of the Analyse Image API call and the list of animals,
255# returns whether there is an animal in the image
256def check_for_animal(msapi_response, animal_list):
257 # Initialise our subject to None
258 subject = None
259 # If the Microsoft API has returned a list of tags
260 if "tags" in msapi_response:
261 # Loop through every tag in the returned tags, in descending confidence order
262 for tag in sorted(msapi_response["tags"], key=lambda x: x['confidence'], reverse=True):
263 # If the tag has a name and that name is one of the animals in our list
264 if "name" in tag and tag["name"] in animal_list:
265 # Record the name of the animal that is the subject of the tile
266 # (We store the subject in lowercase to make comparisons easier)
267 subject = tag["name"].lower()
268 # Print out the animal we have found here for debugging ----------------->
269 print(" Animal: {}".format(subject))
270 # Exit the for loop
271 break
272 # Return the subject
273 return subject
274
275
276# ----------------------------------- TODO -----------------------------------
277#
278# Inputs:
279# msapi_response: JSON dictionary - A dictionary containing all the
280# information the Microsoft API has returned
281# Outputs:
282# string - The name of the landmark
283#
284# Given the result of the Analyse Image API call, returns whether there is a
285# landmark in the image
286#
287# NOTE: you don't need a landmark_list like you needed an animal_list in check_for_animal
288#
289def check_for_landmark(msapi_response): # NOTE: you don't need a landmark_list
290 # TODO: We strongly recommend copying the result of the Microsoft API into
291 # a JSON formatter (e.g. https://jsonlint.com/) to better understand what
292 # the API is returning and how you will access the landmark information
293 # that you need.
294 # Here is an example of accessing the information in the JSON:
295 # msapi_response["categories"][0]["detail"]["landmarks"][0]["name"].lower()
296
297 # Initialise our subject to None
298 subject = None
299 # For every category in the returned categories
300 for category in msapi_response["categories"]:
301 # If the category has detail provided, and the detail contains a landmark
302 if "detail" in category \
303 and "landmarks" in category["detail"] \
304 and category["detail"]["landmarks"]:
305 # Record the name of the landmark that is the subject of the tile
306 subject = category["detail"]["landmarks"][0]["name"]
307 # (We usually store the subject in lowercase to make comparisons easier)
308 subject = subject.lower()
309 # Print the landmark we have found here ----------------->
310 print(" Landmark: {}".format(subject))
311 # Exit the for loop
312 break
313 # Return the subject
314 return subject
315
316
317def check_for_text(tile):
318 # Initialise our subject to None
319 subject = None
320
321 analyse_url = vision_base_url + "ocr"
322
323 params_analyse = {}
324
325 data = {"url": tile["Tile"]}
326
327 msapi_response = microsoft_api_call(analyse_url, params_analyse, headers_vision, data)
328
329
330
331 if "regions" in msapi_response:
332 if msapi_response["regions"]:
333 if "lines" in msapi_response["regions"][0]:
334 if "words" in msapi_response["regions"][0]["lines"][0]:
335 if "text" in msapi_response["regions"][0]["lines"][0]["words"][0]:
336 subject = msapi_response["regions"][0]["lines"][0]["words"][0]["text"]
337 print("**OCR Text: {}".format(subject))
338 # Return the subject
339 return subject
340
341
342# Find matching tile subjects
343#
344# Outputs:
345# list of integers - A list of two tile indexes that have matching subjects
346#
347# Search through analysed_tiles for two tiles recorded
348# under the same subject
349def search_for_matching_tiles():
350 # For every tile subject and its index
351 matched_tiles_pair = []
352
353 for index_1, tile_1 in enumerate(analysed_tiles):
354 # Loop through every tile subject and index
355 for index_2, tile_2 in enumerate(analysed_tiles):
356 # If the two tile's subject is the same and isn't None and the tile
357 # hasn't been matched before, and the tiles aren't the same tile
358 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:
359 # Choose these two tiles
360 # Return the two chosen tiles as a list
361 matched_tiles_pair.append([index_1, index_2])
362 # If we have not matched any tiles, return no matched tiles
363 return matched_tiles_pair
364
365
366# Call the Microsoft API
367#
368# Inputs:
369# url: string - The url to the Microsoft API
370# params: dictionary - Configuration parameters for the request
371# headers: dictionary - Subscription key to allow request to be made
372# data: dictionary - Input to the API request
373# Outputs:
374# JSON dictionary - The result of the API call
375#
376# Given the parameters, makes a request to the specified url, the request is
377# retried as long as there is a volume quota error
378def microsoft_api_call(url, params, headers, data):
379 # Make API request
380 response = requests.post(url, params=params, headers=headers, json=data)
381 # Convert result to JSON
382 res = response.json()
383 # While we have exceeded our request volume quota
384 while "error" in res and res["error"]["code"] == "429":
385 # Wait for 1 second
386 sleep(1)
387 # Print that we are retrying the API call here ----------------->
388 print("Retrying")
389 # Make API request
390 response = requests.post(url, params=params, headers=headers, json=data)
391 # Convert result to JSON
392 res = response.json()
393 # Print the result of the API call here for debugging ----------------->
394 # print(" API Result: {}".format(res))
395 # Return JSON result of API request
396 return res
397
398
399# Test the user's subscription key
400#
401# Raise an error if the user's API key is not valid for the Microsoft
402# Computer Vision API call
403def valid_subscription_key():
404 # Make a computer vision api call
405 test_api_call = microsoft_api_call(vision_base_url + "analyze", {}, headers_vision, {})
406
407 if "error" in test_api_call:
408 raise ValueError("Invalid Microsoft Computer Vision API key for current region: {}".format(test_api_call))
409
410
411# Check the subscription key
412valid_subscription_key()
413