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