· 5 years ago · May 13, 2020, 04:16 PM
1#_ __ __ _ _ ____
2# | \/ | __ _| |_ ___| |__ / ___| __ _ _ __ ___ ___
3# | |\/| |/ _` | __/ __| '_ \ | | _ / _` | '_ ` _ \ / _ \
4# | | | | (_| | || (__| | | | | |_| | (_| | | | | | | __/
5# |_| |_|\__,_|\__\___|_| |_| \____|\__,_|_| |_| |_|\___|
6#
7botName='rasheeq132-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': 'f31dd75c62ad4118a8e5e79f6c69f338'}
17vision_base_url = "https://boogey-man.cognitiveservices.azure.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
90 match = search_for_matching_tiles()
91 # If we do have some matching tiles
92 if match is not None:
93 # Print out the move for debugging ----------------->
94 print(" Matching Move: {}".format(match))
95 # Set our move to be these matching tiles
96 move = match
97 # If we don't have any matching tiles
98 else:
99 # Create a list of all the tiles that we haven't analysed yet
100 unanalysed_tiles = get_unanalysed_tiles()
101 # If there are some tiles that we haven't analysed yet
102 if unanalysed_tiles != []:
103 # Choose the unanalysed tiles that you want to turn over
104 # in your next move. We turn over a random pair of
105 # unanalysed tiles, but, could you make a more intelligent
106 # choice?
107 move = sample(unanalysed_tiles, 2)
108 # Print out the move for debugging ----------------->
109 print(" New tiles move: {}".format(move))
110 # If the unanalysed_tiles list is empty (all tiles have been analysed)
111 else:
112 # If all else fails, we will need to manually match each tile
113
114 # Create a list of all the unmatched tiles
115 unmatched_tiles = get_unmatched_tiles()
116
117 # Turn over two random tiles that haven't been matched
118 # TODO: It would be more efficient to remember which tiles you
119 # have tried to match.
120 move = sample(unmatched_tiles, 2)
121 # Print the move for debugging ----------------->
122 print(" Random guess move: {}".format(move))
123
124 print("ALL TILES ANALYSED!\n\n\n")
125
126 # If all else fails, we will need to manually match each tile
127
128 # Create a list of all the unmatched tiles
129 # unmatched_tiles = get_unmatched_tiles()
130 # move = sample(unmatched_tiles, 2)
131 # if move in gamestate["Failed moves"]:
132 # pass
133
134 # # Print the move out here ----------------->
135 # print("Random Combination Move: {}".format(move))
136
137 # # Store our move in persistent_data to look back at next turn
138 # gamestate["PreviousMove"] = move
139 # # Print out the current gamestate here ----------------->
140 # print("Gamestate: {}".format(json.dumps(gamestate)))
141 # #Return the move we wish to make
142
143 # Store our move to look back at next turn
144 previous_move = move
145
146 # match = search_for_matching_tiles()
147 # # If we do have some matching tiles
148 # if match is not None:
149 # # Print out the move for debugging ----------------->
150 # print(" Matching Move: {}".format(match))
151 # # Set our move to be these matching tiles
152 # move = match
153 # # If we don't have any matching tiles
154 # else:
155 # # Create a list of all the tiles that we haven't analysed yet
156 # unanalysed_tiles = get_unanalysed_tiles()
157 # # If there are some tiles that we haven't analysed yet
158 # if unanalysed_tiles != []:
159 # # Choose the unanalysed tiles that you want to turn over
160 # # in your next move. We turn over a random pair of
161 # # unanalysed tiles, but, could you make a more intelligent
162 # # choice?
163 # move = sample(unanalysed_tiles, 2)
164 # # Print out the move for debugging ----------------->
165 # print(" New tiles move: {}".format(move))
166 # # If the unanalysed_tiles list is empty (all tiles have been analysed)
167 # else:
168 # # If all else fails, we will need to manually match each tile
169
170 # # Create a list of all the unmatched tiles
171 # unmatched_tiles = get_unmatched_tiles()
172
173 # # Turn over two random tiles that haven't been matched
174 # # TODO: It would be more efficient to remember which tiles you
175 # # have tried to match.
176 # move = sample(unmatched_tiles, 2)
177 # # Print the move for debugging ----------------->
178 # print(" Random guess move: {}".format(move))
179
180 # print("ALL TILES ANALYSED!\n\n\n")
181
182 # # If all else fails, we will need to manually match each tile
183
184 # # Create a list of all the unmatched tiles
185 # # unmatched_tiles = get_unmatched_tiles()
186 # # move = sample(unmatched_tiles, 2)
187 # # if move in gamestate["Failed moves"]:
188 # # pass
189
190 # # # Print the move out here ----------------->
191 # # print("Random Combination Move: {}".format(move))
192
193 # # # Store our move in persistent_data to look back at next turn
194 # # gamestate["PreviousMove"] = move
195 # # # Print out the current gamestate here ----------------->
196 # # print("Gamestate: {}".format(json.dumps(gamestate)))
197 # # #Return the move we wish to make
198
199 # # Store our move to look back at next turn
200 # previous_move = move
201 # Return the move we wish to make
202 return {"Tiles": move}
203
204
205# Get the unmatched tiles
206#
207# Outputs:
208# list of integers - A list of unmatched tile numbers
209#
210# Returns the list of tiles that haven't been matched
211def get_unmatched_tiles():
212 # Create a list of all the unmatched tiles
213 unmatched_tiles = []
214 # For every tile in the game
215 for index, tile in enumerate(analysed_tiles):
216 # If that tile hasn't been matched yet
217 if tile["State"] != "MATCHED":
218 # Add that tile to the list of unmatched tiles
219 unmatched_tiles.append(index)
220 # Return the list
221 return unmatched_tiles
222
223
224# Identify all of the tiles that we have not yet analysed with the
225# Microsoft API.
226#
227# Output:
228# list of integers - only those tiles that have not yet been analysed
229# by the Microsoft API
230#
231# Returns the list of tiles that haven't been analysed
232# (according to analysed_tiles)
233def get_unanalysed_tiles():
234 # Filter out analysed tiles
235 unanalysed_tiles = []
236 # For every tile that hasn't been matched
237 for index, tile in enumerate(analysed_tiles):
238 # If the tile hasn't been analysed
239 if tile["State"] == "UNANALYSED":
240 # Add that tile to the list of unanalysed tiles
241 unanalysed_tiles.append(index)
242 # Return the list
243 return unanalysed_tiles
244
245
246# Analyses a list of tiles
247#
248# Inputs:
249# tiles: list of JSON objects - A list of tile objects that contain a
250# url and an index
251# gamestate: JSON object - The current state of the game
252#
253# Given a list of tiles we want to analyse and the animal list, calls the
254# analyse_tile function for each of the tiles in the list
255def analyse_tiles(tiles, gamestate):
256 # For every tile in the list 'tiles'
257 for tile in tiles:
258 # Call the analyse_tile function with that tile
259 # along with the gamestate
260 analyse_tile(tile, gamestate)
261
262
263# Analyses a single tile
264#
265# Inputs:
266# tile: JSON object - A tile object that contains a url and an index
267# gamestate: JSON object - The current state of the game
268#
269# Given a tile, analyse it to determine its subject and record the information
270# in analysed_tiles using the Microsoft APIs
271def analyse_tile(tile, gamestate):
272 # If we have already analysed the tile
273 if analysed_tiles[tile["Index"]]["State"] != "UNANALYSED":
274 # We don't need to analyse the tile again, so stop
275 return
276
277 # Call analysis
278 analyse_url = vision_base_url + "analyze"
279 params_analyse = {'visualFeatures': 'categories,tags,description,faces,imageType,color,adult',
280 'details': 'celebrities,landmarks'}
281 data = {"url": tile["Tile"]}
282 msapi_response = microsoft_api_call(analyse_url, params_analyse, headers_vision, data)
283 print(" API Result tile #{}: {}".format(tile["Index"], msapi_response))
284 # Check if the subject of the tile is a landmark
285
286 if gamestate['Bonus'] == 'Landmarks':
287 subject = check_for_landmark(msapi_response)
288 if subject is None:
289 # Check if the subject of the tile is an animal
290 subject = check_for_animal(msapi_response, gamestate["AnimalList"])
291 # If we still haven't determined the subject of the image yet
292 if subject is None:
293 subject = check_for_text(tile)
294 # TODO: Use the Microsoft OCR API to determine if the tile contains a
295 # word. You can get more information about the Microsoft Cognitive API
296 # OCR function at:
297 # https://westus.dev.cognitive.microsoft.com/docs/services/56f91f2d778daf23d8ec6739/operations/56f91f2e778daf14a499e1fc
298 # Use our previous example to check_for_animal as a guide
299 pass
300 else:
301 print(" Animal at tile #{}: {}".format(tile["Index"], subject))
302
303 elif gamestate['Bonus'] == 'Animals':
304 subject = check_for_animal(msapi_response, gamestate["AnimalList"])
305 if subject is None:
306 # Check if the subject of the tile is land
307 subject = check_for_landmark(msapi_response)
308 # If we still haven't determined the subject of the image yet
309 if subject is None:
310 subject = check_for_text(tile)
311 # TODO: Use the Microsoft OCR API to determine if the tile contains a
312 # word. You can get more information about the Microsoft Cognitive API
313 # OCR function at:
314 # https://westus.dev.cognitive.microsoft.com/docs/services/56f91f2d778daf23d8ec6739/operations/56f91f2e778daf14a499e1fc
315 # Use our previous example to check_for_animal as a guide
316 pass
317 else:
318 print(" Animal at tile #{}: {}".format(tile["Index"], subject))
319 # elif gamestate['Bonus'] == 'Words':
320 else:
321 subject = check_for_text(tile)
322 if subject is None:
323 # Check if the subject of the tile is land
324 subject = check_for_animal(msapi_response, gamestate["AnimalList"])
325
326 # If we still haven't determined the subject of the image yet
327 if subject is None:
328 subject = check_for_landmark(msapi_response)
329 # TODO: Use the Microsoft OCR API to determine if the tile contains a
330 # word. You can get more information about the Microsoft Cognitive API
331 # OCR function at:
332 # https://westus.dev.cognitive.microsoft.com/docs/services/56f91f2d778daf23d8ec6739/operations/56f91f2e778daf14a499e1fc
333 # Use our previous example to check_for_animal as a guide
334 pass
335 else:
336 print(" Animal at tile #{}: {}".format(tile["Index"], subject))
337
338 # If we haven't determined the subject of the image yet
339 # if subject is None:
340 # # Check if the subject of the tile is an animal
341 # subject = check_for_animal(msapi_response, gamestate["AnimalList"])
342 # # If we still haven't determined the subject of the image yet
343 # if subject is None:
344 # subject = check_for_text(tile)
345 # # TODO: Use the Microsoft OCR API to determine if the tile contains a
346 # # word. You can get more information about the Microsoft Cognitive API
347 # # OCR function at:
348 # # https://westus.dev.cognitive.microsoft.com/docs/services/56f91f2d778daf23d8ec6739/operations/56f91f2e778daf14a499e1fc
349 # # Use our previous example to check_for_animal as a guide
350 # pass
351 # else:
352 # print(" Animal at tile #{}: {}".format(tile["Index"], subject))
353 # Remember this tile by adding it to our list of known tiles
354 # Mark that the tile has now been analysed
355 analysed_tiles[tile["Index"]]["State"] = "ANALYSED"
356 analysed_tiles[tile["Index"]]["Subject"] = subject
357
358
359# Check Microsoft API response to see if it contains information about an animal
360#
361# Inputs:
362# msapi_response: JSON dictionary - A dictionary containing all the
363# information the Microsoft API has returned
364# animal_list: list of strings - A list of all the possible animals in
365# the game
366# Outputs:
367# string - The name of the animal
368#
369# Given the result of the Analyse Image API call and the list of animals,
370# returns whether there is an animal in the image
371def check_for_animal(msapi_response, animal_list):
372 # Initialise our subject to None
373 subject = None
374 # If the Microsoft API has returned a list of tags
375 if "tags" in msapi_response:
376 # Loop through every tag in the returned tags, in descending confidence order
377 for tag in sorted(msapi_response["tags"], key=lambda x: x['confidence'], reverse=True):
378 # If the tag has a name and that name is one of the animals in our list
379 if "name" in tag and tag["name"] in animal_list:
380 # Record the name of the animal that is the subject of the tile
381 # (We store the subject in lowercase to make comparisons easier)
382 subject = tag["name"].lower()
383 # Print out the animal we have found here for debugging ----------------->
384 print(" Animal: {}".format(subject))
385 print("***API: {}".format(json.dumps(msapi_response,indent=2)))
386 # Exit the for loop
387 break
388 # Return the subject
389 return subject
390def check_for_text(tile):
391 # Initialise our subject to None
392 subject = None
393
394 analyse_url = vision_base_url + "ocr"
395 params_analyse = {}
396 data = {"url": tile["Tile"]}
397 msapi_response = microsoft_api_call(analyse_url, params_analyse, headers_vision, data)
398 print("OCR response: {}".format(msapi_response))
399
400 # Return the subject
401 return subject
402
403# ----------------------------------- TODO -----------------------------------
404#
405# Inputs:
406# msapi_response: JSON dictionary - A dictionary containing all the
407# information the Microsoft API has returned
408# Outputs:
409# string - The name of the landmark
410#
411# Given the result of the Analyse Image API call, returns whether there is a
412# landmark in the image
413#
414# NOTE: you don't need a landmark_list like you needed an animal_list in check_for_animal
415#
416def check_for_landmark(msapi_response): # NOTE: you don't need a landmark_list
417 # TODO: We strongly recommend copying the result of the Microsoft API into
418 # a JSON formatter (e.g. https://jsonlint.com/) to better understand what
419 # the API is returning and how you will access the landmark information
420 # that you need.
421 # Here is an example of accessing the information in the JSON:
422 # msapi_response["categories"][0]["detail"]["landmarks"][0]["name"].lower()
423
424 # Initialise our subject to None
425 subject = None
426 # For every category in the returned categories
427
428 # If the category has detail provided, and the detail contains a landmark
429
430 # Record the name of the landmark that is the subject of the tile
431 # (We usually store the subject in lowercase to make comparisons easier)
432
433 # Print the landmark we have found here ----------------->
434
435 # Exit the for loop
436 # If the Microsoft API has returned a list of tags
437
438 # Loop through every tag in the returned tags, in descending confidence order
439 for category in msapi_response["categories"]:
440 # If the tag has a name and that name is one of the animals in our list
441
442 if "detail" in category \
443 and "landmarks" in category["detail"]\
444 and category["detail"]["landmarks"]:
445
446 # Record the name of the animal that is the subject of the tile
447 # (We store the subject in lowercase to make comparisons easier)
448
449 subject = category["detail"]["landmarks"][0]["name"].lower()
450 # Print out the animal we have found here for debugging ----------------->
451 print(" landmarks: {}".format(subject))
452 print("***API: {}".format(json.dumps(msapi_response,indent=2)))
453 # Exit the for loop
454 break
455 # Return the subject
456 return subject
457
458
459# Find matching tile subjects
460#
461# Outputs:
462# list of integers - A list of two tile indexes that have matching subjects
463#
464# Search through analysed_tiles for two tiles recorded
465# under the same subject
466def search_for_matching_tiles():
467 # For every tile subject and its index
468 for index_1, tile_1 in enumerate(analysed_tiles):
469 # Loop through every tile subject and index
470 for index_2, tile_2 in enumerate(analysed_tiles):
471 # If the two tile's subject is the same and isn't None and the tile
472 # hasn't been matched before, and the tiles aren't the same tile
473 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:
474 # Choose these two tiles
475 # Return the two chosen tiles as a list
476 return [index_1, index_2]
477 # If we have not matched any tiles, return no matched tiles
478 return None
479
480
481# Call the Microsoft API
482#
483# Inputs:
484# url: string - The url to the Microsoft API
485# params: dictionary - Configuration parameters for the request
486# headers: dictionary - Subscription key to allow request to be made
487# data: dictionary - Input to the API request
488# Outputs:
489# JSON dictionary - The result of the API call
490#
491# Given the parameters, makes a request to the specified url, the request is
492# retried as long as there is a volume quota error
493def microsoft_api_call(url, params, headers, data):
494 # Make API request
495 response = requests.post(url, params=params, headers=headers, json=data)
496 # Convert result to JSON
497 res = response.json()
498 # While we have exceeded our request volume quota
499 while "error" in res and res["error"]["code"] == "429":
500 # Wait for 1 second
501 sleep(1)
502 # Print that we are retrying the API call here ----------------->
503 print("Retrying")
504 # Make API request
505 response = requests.post(url, params=params, headers=headers, json=data)
506 # Convert result to JSON
507 res = response.json()
508 # Print the result of the API call here for debugging ----------------->
509 # print(" API Result: {}".format(res))
510 # Return JSON result of API request
511 return res
512
513
514# Test the user's subscription key
515#
516# Raise an error if the user's API key is not valid for the Microsoft
517# Computer Vision API call
518def valid_subscription_key():
519 # Make a computer vision api call
520 test_api_call = microsoft_api_call(vision_base_url + "analyze", {}, headers_vision, {})
521
522 if "error" in test_api_call:
523 raise ValueError("Invalid Microsoft Computer Vision API key for current region: {}".format(test_api_call))
524
525
526# Check the subscription key
527valid_subscription_key()