C’est le cas d’une solution informatique capable de jouer au jeu Fruit Ninja. L’auteur lui-même fait le résumé de l’approche utilisée pour sa mise sur pied : « Le joueur doit découper tout objet en mouvement qui n’est pas une bombe. Notre programme doit donc effectuer des captures d’écran, y détecter chaque objet en mouvement puis simuler des mouvements de la souris pour trancher les fruits. » Le programme rédigé en langage Python s’appuie sur des fonctions de la bibliothèque OpenCV pour la détection des objets. Le découpage des fruits pour sa part fait usage d’un API Windows pour la simulation du déplacement de la souris et la pression sur les boutons de la souris.
Pas d’implémentation des réseaux de neurones, pas d’apprentissage… Pourtant, l’auteur parle « d’intelligence artificielle parfaite pour le jeu Fruit Ninja. »
Le code source complet du programme est disponible :
Code Python : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 | ''' Source code for Fruit Ninja AI ( https://www.youtube.com/watch?v=Vw3vU9OdWAs ) The AI only loses when a bomb is overlapped with a fruit on its whole path, as the AI won't find a good opportunity to slice it. The game as a chrome extension: https://chrome.google.com/webstore/detail/fruit-ninja-game/fdkhnibpmdfmpgaipjiodbpdngccfibp Simply place the chrome extension on the top right corner of your screen and run this file :) Some heuristics and timings might differ depending on your machine. (too much/little computing time between frames might affect the AI's decisions) ''' import time from datetime import datetime import cv2 import mss import numpy as np import os import win32api, win32con import pyautogui import sys import threading from time import sleep import math import keyboard DELAY_BETWEEN_SLICES = 0.19 # for sleep(DELAY_BETWEEN_SLICES) DRAW_BOMBS = True DEBUG = True # pylint: disable=no-member, screenHeight = win32api.GetSystemMetrics(1) screenWidth = win32api.GetSystemMetrics(0) ''' The game resolution is 750x500 ''' width = 750 height = 500 ''' I'm displaying my game at the top right corner of my screen ''' gameScreen = {'top': 25, 'left': screenWidth - width, 'width': width, 'height': height} today = datetime.now() timeString = today.strftime('%b_%d_%Y__%H_%M_%S') # Set writeVideo to True for saving screen captures for youtube writeVideo = ( len(sys.argv) > 1 and sys.argv[1] == 'save' ) if(writeVideo): outputDir = './out/' + timeString os.mkdir(outputDir) fourcc1 = cv2.VideoWriter_fourcc(*'XVID') outScreen = cv2.VideoWriter(outputDir + '/screen.avi', fourcc1, 25, (width, height)) fourcc2 = cv2.VideoWriter_fourcc(*'XVID') outResult = cv2.VideoWriter(outputDir + '/result.avi', fourcc2, 25, (width, height)) fourcc3 = cv2.VideoWriter_fourcc(*'XVID') outMask = cv2.VideoWriter(outputDir + '/mask.avi', fourcc3, 25, (width, height)) def quit(): if(writeVideo): outScreen.release() outResult.release() outMask.release() exit() ''' Check if margins match screen coordinates ''' def sanitizeMargins(rx, ry): margin = 10 if (rx > width - margin): rx = width - margin if (rx < margin): rx = margin if ry > height - margin: ry = height - margin if ry < margin: ry = margin return (rx, ry) ''' Translates from game screen coordinates to your monitor's screen coordinates ''' def realCoord(x, y): rx = int(x) ry = int(y) rx, ry = sanitizeMargins(int(x), int(y)) rx += gameScreen['left'] ry += gameScreen['top'] return rx, ry def gameCoord(x, y): rx = int(x) - gameScreen['left'] ry = int(y) - gameScreen['top'] return sanitizeMargins(rx, ry) ''' Moves mouse to (x,y) in screen coordinates ''' def moveMouse(x, y): win32api.mouse_event(win32con.MOUSEEVENTF_MOVE | win32con.MOUSEEVENTF_ABSOLUTE, int(x/screenWidth*65535.0), int(y/screenHeight*65535.0)) ''' Moves cursor from (x1,y1) to (x2, y2); ''' def swipe(_x1, _y1, _x2, _y2): x1, y1 = realCoord(_x1, _y1) x2, y2 = realCoord(_x2, _y2) # one line swipe is made of multiple cursor moves # if we instantly move the cursor too much, the game might not register the swipe points = 251 for i in range(points+1): moveMouse((x1 * (points - i ) + x2 * i)/points, (y1 * (points -i ) + y2 * i)/points) if (i == 0): win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN,0, 0, 0, 0) moveMouse((x1 * (points -i ) + x2 * i)/points, (y1 * (points -i ) + y2 * i)/points) win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP,0, 0,0,0) moveMouse(x2, y2) time.sleep(DELAY_BETWEEN_SLICES) swipeThread = None cacheCoord = {} # keeps recently swiped points so we don't get stuck slicing the same apple cacheDistance = 55 # distance between cached recent swipe points cacheTime = 1 # seconds after which a cached point expires def distPoints(x1, y1, x2, y2): return math.sqrt( ((x1-x2)**2)+((y1-y2)**2) ) ''' Checks if another thread is already swiping ''' def canSwipe(): return (swipeThread is None ) or False == swipeThread.is_alive() bombMinDistance = 58 bomdDown = 120 bomgHeightCond = height * 1/2 ''' Returns true if the line determined by [(x1, y1), (x2, y2)] doesn't slice a bomb or a bomb's path ''' def lineIsSafe(x1S, y1S, x2S, y2S): global bombMinDistance for bomb in bombs: xBomb, yBomb = bomb minBombDistance = 999999999 xPrev, yPrev = 0, 0 # from the previous frame, find which bomb is closest to our current bomb, to predict the bomb's path # (closest bomb from the previous frame is probably the same bomb) for prevBomb in prevBombs: dist = distPoints(prevBomb[0], prevBomb[1], xBomb, yBomb) if minBombDistance > dist: xPrev, yPrev = prevBomb dist = minBombDistance if xPrev == 0 and yPrev == 0: # no close bomb found (maybe the bomb just entered the scene?) continue # where do we expect the bomb to be, considering its previous position from the previous frame? predicts = [(xBomb + (xBomb - xPrev) * 3, yBomb + (yBomb - yPrev) * 3)] if (yBomb < bomgHeightCond): predicts.append((xBomb + (xBomb - xPrev) * 3 , yBomb + bomdDown)) predicts.append((xBomb, yBomb + bomdDown)) # check if our swiping line interferes with the bomb's path for predict in predicts: points = 10 # points to check among the swiped line for i in range(points+1): xInterm = (xBomb * (points - i ) + predict[0] * i)/points yInterm = (yBomb * (points - i ) + predict[1] * i)/points points2 = 10 # points to check among the bomb's path for i in range(points2+1): xPoint = (x1S * (points2 - i ) + x2S * i)/points2 yPoint = (y1S * (points2 - i ) + y2S * i)/points2 dist = distPoints(xInterm, yInterm, xPoint, yPoint) if (dist < bombMinDistance): return False # check if our swiping lane interferes with the bomb's position points = 10 for i in range(points+1): xPoint = (x1S * (points - i ) + x2S * i)/points yPoint = (y1S * (points - i ) + y2S * i)/points dist = distPoints(xBomb, yBomb, xPoint, yPoint) if (dist < bombMinDistance): return False return True def shouldSwipe(_x1, _y1): global swipeThread, img x1, y1 = realCoord(_x1, _y1) res = True for key in list(cacheCoord): x2, y2 = key # remove cached swipes which are too old if (cacheCoord[key] < time.time() - cacheTime): del cacheCoord[key] continue dist = distPoints(x1, y1, x2, y2) # is our current position too close to a cached area? if dist < cacheDistance: res = False if res == False: return False # try different angles to swipe on fruit swipeTries = [ (_x1, _y1 + 120, _x1, _y1 - 100), (_x1, _y1 + 80, _x1, _y1 - 50), (_x1 + 12, _y1 + 80, _x1 + 12, _y1 - 50), (_x1 - 12, _y1 + 80, _x1 - 12, _y1 - 50), (_x1 + 15, _y1 + 50, _x1 + 15, _y1 - 30), (_x1 - 15, _y1 + 50, _x1 - 15, _y1 - 30), ] for curTry in swipeTries: x1S, y1S, x2S, y2S = curTry # choose first line which doesn't cut a bomb # (usually the first line, hence why most slices are vertical) if lineIsSafe( x1S, y1S, x2S, y2S): #swipe in a separate thread swipeThread = threading.Thread(target=swipe, args=(x1S, y1S, x2S, y2S)) swipeThread.start() points = 10 # mark points along the line as recently swiped # so we don't slice in the same area continously for i in range(points+1): xPoint = (x1S * (points - i ) + x2S * i)/points yPoint = (y1S * (points - i ) + y2S * i)/points cacheCoord[realCoord(xPoint, yPoint)] = time.time() return True return False bombs = [] possibleSwipes = [] def orderFruitsBy(tup): # always slice the fruit closer to the edge x, y = tup toFilter = [height - y, x] if x > width * 4/5: toFilter.append(width - x) return min(toFilter) with mss.mss() as sct: while True: last_time = time.time() screen = np.array(sct.grab(gameScreen)) screen = np.flip(screen[:, :, :3], 2) screen = cv2.cvtColor(screen, cv2.COLOR_BGR2RGB) img = screen.copy() # color ranges for object detections objectBoundaries = [([15, 180, 130], [35, 237, 209], 120, True), # fruit ([0, 40, 10], [30, 170, 50], 100, True), # fruit ([10, 50, 220], [40, 250, 255], 80, True), # fruit ([30, 30, 20], [60, 70, 60], 60, False), # bomb ] mask = None tmpMask = None prevSwipes = possibleSwipes possibleSwipes = [] prevBombs = bombs bombs = [] for boundary in objectBoundaries: lower, upper, minPoints, isFruit = boundary tmpMask = cv2.inRange(img, np.array(lower), np.array(upper)) contours, hierarchy = cv2.findContours(tmpMask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) if (mask is None): mask = tmpMask else: mask = cv2.bitwise_or(mask, tmpMask) for c in contours: if (len(c) < minPoints) : # contour is too small, probably not a fruit/bomb continue centerX, centerY, __1, __2 = map(int, cv2.mean(c)) sumX, sumY = 0, 0 rows = 70 cols = 70 cnt = 0 # get center of current object for i in range(rows): for j in range(cols): x = int(centerX + i - rows/2) y = int(centerY + j - cols/2) if x < 0 or x >= width or y < 0 or y >= height: continue val = tmpMask[y, x] if val: sumX += x sumY += y cnt += 1 if cnt == 0: continue sumX = int(sumX/cnt) sumY = int(sumY/cnt) # pretty white circle around the detected object cv2.circle(img, (sumX, sumY), 35, (255, 255, 255), 2) if isFruit: # mark all swipable fruits good = True for swipePoint in possibleSwipes: x, y = swipePoint dist = distPoints(x, y, sumX, sumY) if dist < 15: good = False break yBarrier = int(height * 4/5) if (sumY > yBarrier): break if good: possibleSwipes.append((sumX, sumY)) else: bombs.append((sumX, sumY)) # pretty circles for the video output :) if DRAW_BOMBS: for bomb in bombs: xBomb, yBomb = bomb minBombDistance = 999999999 xPrev, yPrev = 0, 0 for prevBomb in prevBombs: dist = distPoints(prevBomb[0], prevBomb[1], xBomb, yBomb) if minBombDistance > dist: xPrev, yPrev = prevBomb dist = minBombDistance if xPrev == 0 and yPrev == 0: continue predicts = [(xBomb + (xBomb - xPrev) * 3, yBomb + (yBomb - yPrev) * 3)] for predict in predicts: cv2.circle(img, predict, bombMinDistance, (255, 0, 0), 10) cv2.circle(img, bomb, bombMinDistance, (255, 0, 0), 10) debug = img.copy() possibleSwipes.sort(key=lambda tup: orderFruitsBy(tup), ) if canSwipe(): for elem in possibleSwipes: centerX, centerY = elem if (shouldSwipe(centerX, centerY)): break if canSwipe(): # still nothing swiped? probably because every area was # already cached, so we remove the cache and retry ! cacheCoord = {} for elem in possibleSwipes: centerX, centerY = elem if (shouldSwipe(centerX, centerY)): break if DEBUG: for coords in cacheCoord: cv2.circle(debug, gameCoord(coords[0], coords[1]), cacheDistance, (123, 123, 0), 1) #cv2.imshow('result', tmpMask) cv2.imshow('debug', debug) cv2.imshow('img', img) if writeVideo: outResult.write(img) outScreen.write(screen) outMask.write(debug) cv2.waitKey(1) # Press 'q' to quit if keyboard.is_pressed('q'): cv2.destroyAllWindows() quit() quit() |
Le Turc mécanique ou l’automate joueur d'échecs est un célèbre canular construit à la fin du XVIIIe siècle : il s’agissait d'un prétendu automate doté de la faculté de jouer aux échecs. L’automate était prétendument capable de jouer une partie d’échecs contre un adversaire humain. Mais en réalité, il possédait un compartiment secret dans lequel un joueur humain pouvait se glisser. Pendant 84 ans, et grâce au talent des joueurs cachés, le Turc mécanique put remporter la plupart des parties d'échecs auxquelles il participa en Europe et en Amérique, y compris contre certains hommes d'État tels que Napoléon Bonaparte, Catherine II de Russie et Benjamin Franklin.
Plus de deux siècles plus tard, il apparaît que ce même leurre fonctionne toujours. Et pour cause, il est difficile de concevoir un service propulsé par une intelligence artificielle. Une tâche si difficile que certaines startups se sont résolues de se tourner vers des humains et les faire passer pour des robots plutôt que le contraire. En 2008, Spinvox, une entreprise qui convertissait les messages vocaux en messages textes, a été accusée de recourir à des employés humains étrangers dans des centres d’appel au lieu de machines pour faire le travail. En 2016, une autre entreprise relevée par Bloomberg cette fois a obligé des employés à passer 12 heures par jour à prétendre qu’ils sont des chatbots pour des services d’agenda comme X.ai et Clara. Ce travail a été si contraignant que les employés ont dit qu’ils avaient hâte de voir une IA venir les remplacer.
En 2017, l’application de gestion de dépenses Expensify a admis avoir employé des humains pour transcrire au moins quelques-uns des reçus supposés être analysés par sa “technologie smartscreen”. L’entreprise a utilisé le service de travail collaboratif Mechanical Turk d’Amazon, où des travailleurs faiblement rémunérés ont dû lire et transcrire les reçus.
L’intelligence artificielle est devenue une expression fourre-tout que les gens utilisent sans limites, juste pour faire le buzz ou par ignorance. Le terme « intelligence artificielle » a en effet été appliqué à diverses technologies, allant de simples programmes informatiques automatisant des tâches à des réseaux neuronaux plus complexes, en passant par des algorithmes d'apprentissage automatique. Cet état de choses peut tromper les investisseurs en capital-risque qui n'arrivent pas à faire la distinction entre une vraie IA et une technologie qui est vendue comme une IA, mais qui ne l'est pas en réalité.
Source : GitHub
Et vous ?
Quand peut-on parler d'intelligence artificielle ou pas ?
« IA » est-il plus un terme marketing qu’autre chose ?
Voir aussi :
Pseudo-IA : des entreprises de tech préfèrent recourir à des humains et les font passer pour une intelligence artificielle
Baidu construit un abri intelligent pour chats qui utilise l'intelligence artificielle pour les identifier et dépister leurs maladies courantes
Gartner démystifie cinq idées fausses sur l'intelligence artificielle ou « IA », l'argument marketing du moment, d'après l'un des inventeurs de Siri
La révolution de l'automatisation et de l'intelligence artificielle sera surtout néfaste pour les hommes, selon une analyse
« L'intelligence artificielle n'existe pas et la voiture autonome n'existera jamais », selon un expert en IA