FÉLICITATIONS à @kuroineko pour la meilleure candidature et au gain de 200 $ de @TheBestOne (excellente sportivité!).
Ecrivez un programme pour colorer autant d'images que possible avant les programmes d'opposition.
Règles en bref
- Votre programme recevra une image, votre couleur et le nombre entier N.
- Chaque tour, d'autres programmes vous envoient des mises à jour de pixels et vous demandent vos mises à jour de N.
- Vous pouvez mettre à jour tout pixel blanc situé à côté d'un pixel de votre couleur.
- Le programme qui a ajouté le plus de pixels gagne.
Règles en détail
Votre programme recevra un nom de fichier d'image PNG, une couleur de base et un nombre N. Le nombre N est le nombre maximal de pixels que votre programme peut colorier à chaque tour.
Exemple: MyProg arena.png (255,0,0) 30
L'image d'entrée sera un rectangle avec des côtés compris entre 20 et 1000 pixels de long. Il sera composé de pixels noirs, blancs et de couleur. Votre programme peut choisir une séquence de pixels blancs à colorier, à la condition que chaque nouveau pixel ait au moins un de ses quatre pixels voisins de votre propre couleur. L'image aura initialement au moins un pixel de votre couleur. Il peut également avoir des pixels de couleurs auxquels aucun programme n'est affecté. Le canal alpha n'est pas utilisé.
Votre but est de bloquer vos adversaires et d’écrire votre couleur en autant de pixels que vous pouvez.
À chaque tour, votre programme acceptera une ou plusieurs lignes de message sur STDIN et écrira une ligne composée de coordonnées de pixels sur STDOUT. N'oubliez pas d'affecter STDOUT comme non tamponné ou de vider la mémoire tampon de STDOUT à chaque tour.
L'ordre des joueurs appelés à chaque tour sera attribué au hasard. Cela signifie qu'un adversaire (ou votre programme) peut avoir 2 tours d'affilée.
Des colour (N,N,N) chose X,Y X,Y ... X,Y
messages d’information décrivant les pixels renseignés par les programmes du joueur seront envoyés à votre programme . Si un joueur ne fait pas ou pas de coups valides, aucun message concernant les coups de ce joueur ne vous sera envoyé. Votre programme recevra également un message sur vos mouvements acceptés (si vous avez spécifié au moins un mouvement valide). Le pixel 0,0 est dans le coin supérieur gauche de l'image.
À la réception pick pixels
, votre programme affichera X,Y X,Y ... X,Y
jusqu'à N pixels (une chaîne vide composée uniquement d'un '\ n' est autorisée). Les pixels doivent être dans l'ordre du tracé. Si un pixel est invalide, il sera ignoré et ne sera pas dans le rapport aux joueurs. Votre programme a 2 secondes pour s’initialiser après le démarrage, mais seulement 0,1 seconde pour répondre avec une réponse à chaque tour, sinon il ratera ce tour. Une mise à jour de pixel envoyée après 0,1 seconde enregistre une erreur. Après 5 erreurs, votre programme est suspendu et aucune mise à jour ou pick pixels
demande ne sera envoyée .
Lorsque le programme du juge reçoit un choix de pixels vide ou non valide de chaque programme de joueur non suspendu, l'image est considérée comme terminée et les programmes reçoivent le message "exit". Les programmes doivent se terminer après avoir reçu "exit".
Notation
Le juge marquera des points une fois l'image terminée. Votre score sera votre nombre de pixels mis à jour divisé par la capture de pixels moyenne de ce tour, exprimée en pourcentage.
Le nombre de pixels ajoutés à l'image par votre lecteur est A. Le nombre total de pixels ajoutés par tous les lecteurs P est égal à T.
avg = T/P
score = 100*A/avg
Affichage des scores
Un adversaire de référence "The Blob" est donné. Pour chaque réponse, nommez votre bot avec un nom, une langue et votre score (moyenne des arènes 1 à 4) contre l'adversaire de référence. Une image ou une animation de l’une de vos batailles serait également utile. Le gagnant est le programme avec le score le plus élevé contre le bot de référence.
Si le blob s'avère trop facile à battre, je pourrais ajouter un deuxième tour avec un adversaire de référence plus puissant.
Vous voudrez peut-être aussi expérimenter avec 4 programmes de joueur ou plus. Vous pouvez également tester votre bot contre d'autres bots postés comme réponses.
Le juge
Le programme juge nécessite la bibliothèque commune d'imagerie Python (PIL) et doit être facile à installer à partir du gestionnaire de packages de votre système d'exploitation sous Linux. J'ai signalé que PIL ne fonctionnait pas avec Python 64 bits sous Windows 7, veuillez donc vérifier si PIL fonctionnera pour vous avant de commencer ce défi (mis à jour le 2015-01-29).
#!/usr/bin/env python
# Judge Program for Image Battle challenge on PPCG.
# Runs on Python 2.7 on Ubuntu Linux. May need edits for other platforms.
# V1.0 First release.
# V1.1 Added Java support
# V1.2 Added Java inner class support
# usage: judge cfg.py
import sys, re, random, os, shutil, subprocess, datetime, time, signal
from PIL import Image
ORTH = ((-1,0), (1,0), (0,-1), (0,1))
def place(loc, colour):
# if valid, place colour at loc and return True, else False
if pix[loc] == (255,255,255):
plist = [(loc[0]+dx, loc[1]+dy) for dx,dy in ORTH]
if any(pix[p]==colour for p in plist if 0<=p[0]<W and 0<=p[1]<H):
pix[loc] = colour
return True
return False
def updateimage(image, msg, bot):
if not re.match(r'(\s*\d+,\d+)*\s*', msg):
return []
plist = [tuple(int(v) for v in pr.split(',')) for pr in msg.split()]
plist = plist[:PIXELBATCH]
return [p for p in plist if place(p, bot.colour)]
class Bot:
botlist = []
def __init__(self, name, interpreter=None, colour=None):
self.prog = name
self.botlist.append(self)
callarg = re.sub(r'\.class$', '', name) # Java fix
self.call = [interpreter, callarg] if interpreter else [callarg]
self.colour = colour
self.colstr = str(colour).replace(' ', '')
self.faults = 0
self.env = 'env%u' % self.botlist.index(self)
try: os.mkdir(self.env)
except: pass
if name.endswith('.class'): # Java inner class fix
rootname = re.sub(r'\.class$', '', name)
for fn in os.listdir('.'):
if fn.startswith(rootname) and fn.endswith('.class'):
shutil.copy(fn, self.env)
else:
shutil.copy(self.prog, self.env)
shutil.copy(imagename, self.env)
os.chdir(self.env)
args = self.call + [imagename, self.colstr, `PIXELBATCH`]
self.proc = subprocess.Popen(args, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
os.chdir('..')
def send(self, msg):
if self.faults < FAULTLIMIT:
self.proc.stdin.write(msg + '\n')
self.proc.stdin.flush()
def read(self, timelimit):
if self.faults < FAULTLIMIT:
start = time.time()
inline = self.proc.stdout.readline()
if time.time() - start > timelimit:
self.faults += 1
inline = ''
return inline.strip()
def exit(self):
self.send('exit')
from cfg import *
for i, (prog, interp) in enumerate(botspec):
Bot(prog, interp, colourspec[i])
image = Image.open(imagename)
pix = image.load()
W,H = image.size
time.sleep(INITTIME)
total = 0
for turn in range(1, MAXTURNS+1):
random.shuffle(Bot.botlist)
nullbots = 0
for bot in Bot.botlist:
bot.send('pick pixels')
inmsg = bot.read(TIMELIMIT)
newpixels = updateimage(image, inmsg, bot)
total += len(newpixels)
if newpixels:
pixtext = ' '.join('%u,%u'%p for p in newpixels)
msg = 'colour %s chose %s' % (bot.colstr, pixtext)
for msgbot in Bot.botlist:
msgbot.send(msg)
else:
nullbots += 1
if nullbots == len(Bot.botlist):
break
if turn % 100 == 0: print 'Turn %s done %s pixels' % (turn, total)
for msgbot in Bot.botlist:
msgbot.exit()
counts = dict((c,f) for f,c in image.getcolors(W*H))
avg = 1.0 * sum(counts.values()) / len(Bot.botlist)
for bot in Bot.botlist:
score = 100 * counts[bot.colour] / avg
print 'Bot %s with colour %s scored %s' % (bot.prog, bot.colour, score)
image.save(BATTLE+'.png')
Exemple de configuration - cfg.py
BATTLE = 'Green Blob vs Red Blob'
MAXTURNS = 20000
PIXELBATCH = 10
INITTIME = 2.0
TIMELIMIT = 0.1
FAULTLIMIT = 5
imagename = 'arena1.png'
colourspec = (0,255,0), (255,0,0)
botspec = [
('blob.py', 'python'),
('blob.py', 'python'),
]
The Blob - l'adversaire de référence
# Blob v1.0 - A reference opponent for the Image Battle challenge on PPCG.
import sys, os
from PIL import Image
image = Image.open(sys.argv[1])
pix = image.load()
W,H = image.size
mycolour = eval(sys.argv[2])
pixbatch = int(sys.argv[3])
ORTH = ((-1,0), (1,0), (0,-1), (0,1))
def canchoose(loc, colour):
if pix[loc] == (255,255,255):
plist = [(loc[0]+dx, loc[1]+dy) for dx,dy in ORTH]
if any(pix[p]==colour for p in plist if 0<=p[0]<W and 0<=p[1]<H):
return True
return False
def near(loc):
plist = [(loc[0]+dx, loc[1]+dy) for dx,dy in ORTH]
pboard = [p for p in plist if 0<=p[0]<W and 0<=p[1]<H]
return [p for p in pboard if pix[p] == (255,255,255)]
def updateimage(image, msg):
ctext, colourtext, chose, points = msg.split(None, 3)
colour = eval(colourtext)
plist = [tuple(int(v) for v in pr.split(',')) for pr in points.split()]
for p in plist:
pix[p] = colour
skin.discard(p)
if colour == mycolour:
for np in near(p):
skin.add(np)
board = [(x,y) for x in range(W) for y in range(H)]
skin = set(p for p in board if canchoose(p, mycolour))
while 1:
msg = sys.stdin.readline()
if msg.startswith('colour'):
updateimage(image, msg.strip())
if msg.startswith('pick'):
plen = min(pixbatch, len(skin))
moves = [skin.pop() for i in range(plen)]
movetext = ' '.join('%u,%u'%p for p in moves)
sys.stdout.write(movetext + '\n')
sys.stdout.flush()
if msg.startswith('exit'):
break
image.save('blob.png')
Arena 1
Arena 2
Arena 3
Arena 4
Un exemple de bataille - Blob vs Blob
Cette bataille a eu un résultat prévisible:
Bot blob.py with colour (255, 0, 0) scored 89.2883333333
Bot blob.py with colour (0, 255, 0) scored 89.365
la source
Réponses:
ColorFighter - C ++ - mange quelques avaleurs au petit déjeuner
MODIFIER
Dieu que je déteste les serpents (prétendez qu'ils sont des araignées, Indy)
En fait, j'adore Python. J'aimerais être moins paresseux et commencer à bien apprendre, c'est tout.
Tout cela étant dit, je devais lutter avec la version 64 bits de ce serpent pour que le juge fonctionne. Pour que PIL fonctionne avec la version 64 bits de Python sous Win7, il faut plus de patience que ce que j'étais prêt à consacrer à ce défi. J'ai donc finalement basculé (péniblement) vers la version Win32.
En outre, le juge a tendance à se bloquer brutalement lorsqu'un bot est trop lent pour réagir.
Étant donné que je ne connaissais pas bien Python, je n’ai pas résolu le problème, mais il s’agit de lire une réponse vide après une temporisation sur stdin.
Une amélioration mineure consisterait à placer la sortie stderr dans un fichier pour chaque bot. Cela faciliterait le traçage pour le débogage post-mortem.
Hormis ces problèmes mineurs, j’ai trouvé le juge très simple et agréable à utiliser.
Bravo pour encore un autre défi inventif et amusant.
Le code
Construire l'exécutable
J'ai utilisé LODEpng.cpp et LODEpng.h pour lire des images png.
J'ai trouvé le moyen le plus simple d'enseigner à ce langage C ++ retardé comment lire une image sans avoir à construire une demi-douzaine de bibliothèques.
Il suffit de compiler et de lier LODEpng.cpp avec le principal et Bob, votre oncle.
J'ai compilé avec MSVC2013, mais comme je n'utilisais que quelques conteneurs STL de base (deque et vecteurs), cela pourrait fonctionner avec gcc (si vous avez de la chance).
Si ce n'est pas le cas, je pourrais essayer une version MinGW, mais franchement, j'en ai assez des problèmes de portabilité C ++.
J’ai beaucoup travaillé sur le C / C ++ portable à l’époque (sur des compilateurs exotiques pour divers processeurs de 8 à 32 bits, ainsi que pour SunOS, Windows de 3.11 à Vista et Linux, depuis ses débuts jusqu’à Ubuntu, roucoulant de zèbres, etc. J'ai une assez bonne idée de ce que signifie portabilité), mais à l'époque, il n'était pas nécessaire de mémoriser (ni de découvrir) les innombrables divergences entre les interprétations GNU et Microsoft des spécifications cryptiques et gonflées du monstre STL.
Résultats contre Swallower
Comment ça marche
À la base, il s’agit d’un simple chemin d’inondation en force brute.
La frontière de la couleur du joueur (c'est-à-dire les pixels ayant au moins un voisin blanc) est utilisée comme germe pour exécuter l'algorithme classique d'inondation de distance.
Lorsqu'un point atteint le voisinage d'une couleur ennemie, une trajectoire en arrière est calculée pour produire une chaîne de pixels se déplaçant vers le point ennemi le plus proche.
Le processus est répété jusqu'à ce que suffisamment de points aient été rassemblés pour une réponse de la longueur souhaitée.
Cette répétition est incroyablement chère, surtout lorsque vous combattez près de l'ennemi.
Chaque fois qu'une chaîne de pixels menant de la frontière à un pixel ennemi a été trouvée (et qu'il nous faut plus de points pour compléter la réponse), le remplissage de l'inondation est refait depuis le début, avec le nouveau chemin ajouté à la frontière. Cela signifie que vous pourriez avoir à effectuer 5 remplissages d’inondation ou plus pour obtenir une réponse de 10 pixels.
Si aucun autre pixel ennemi n'est accessible, les voisins arbitraire des pixels de frontière sont sélectionnés.
L'algorithme est dévolu à un remplissage d'inondation plutôt inefficace, mais cela ne se produit qu'une fois que l'issue du jeu a été décidée (c'est-à-dire qu'il n'y a plus de territoire neutre à défendre).
Je l'ai optimisé pour que le juge ne passe pas des heures à remplir la carte une fois la compétition réglée. Dans son état actuel, le temps d'exécution est négligeable par rapport au juge lui-même.
Comme les couleurs de l'ennemi ne sont pas connues au début, l'image d'arène initiale est conservée afin de copier les zones de départ de l'ennemi lors de son premier mouvement.
Si le code est lu en premier, il remplira simplement quelques pixels arbitraires.
Cela rend l'algorithme capable de combattre un nombre arbitraire d'adversaires, voire de nouveaux adversaires arrivant à un moment aléatoire, ou de couleurs apparaissant sans zone de départ (bien que cela n'ait absolument aucune utilisation pratique).
La gestion ennemie couleur par couleur permettrait également de faire coopérer deux instances du bot (en utilisant les coordonnées de pixels pour transmettre un signe de reconnaissance secret).
Ca a l'air amusant, je vais probablement essayer ça :).
Le cheminement lourd en calcul est effectué dès que de nouvelles données sont disponibles (après une notification de déplacement), et certaines optimisations (la mise à jour de la frontière) sont effectuées juste après qu'une réponse a été donnée (pour effectuer le plus de calculs possible pendant les autres tours de bots ).
Là encore, il pourrait y avoir moyen de faire des choses plus subtiles s'il y avait plus d'un adversaire (par exemple, abandonner un calcul si de nouvelles données devenaient disponibles), mais de toute façon, je ne vois pas où le multitâche est nécessaire, tant que l'algorithme est capable de travailler à pleine charge.
Les problèmes de performance
Tout cela ne peut fonctionner sans un accès rapide aux données (et à une puissance de calcul supérieure à celle du programme Appolo complet, c’est-à-dire à votre PC moyen utilisé pour autre chose que poster quelques tweets).
La vitesse dépend fortement du compilateur. Généralement, GNU bat Microsoft par une marge de 30% (c’est le chiffre magique que j’ai remarqué sur 3 autres problèmes de code lié aux chemins), mais ce kilométrage peut varier bien sûr.
Le perfmètre Windows signale environ 4 à 7% d'utilisation du processeur. Il devrait donc être capable de gérer une carte 1000x1000 dans le délai de réponse de 100 ms.
Au cœur de chaque algorithme de cheminement se trouve une FIFO (éventuellement proritisée, mais pas dans ce cas), qui à son tour nécessite une allocation rapide d'éléments.
Comme le PO fixait obligatoirement une limite à la taille de l'arène, j'ai fait quelques calculs et constaté que des structures de données fixes dimensionnées au maximum (1 000 000 pixels) ne consommeraient pas plus d'une vingtaine de mégaoctets, ce que votre PC moyen mange au petit déjeuner.
En effet sous Win7 et compilé avec MSVC 2013, le code consomme environ 14 Mo sur l’arène 4, alors que les deux threads de Swallower utilisent plus de 20 Mo.
J'ai commencé avec les conteneurs STL pour un prototypage plus facile, mais STL a rendu le code encore moins lisible, car je ne souhaitais pas créer une classe pour encapsuler chaque bit de données afin de masquer l'obfuscation (que cela soit dû à mes propres inaptitudes à faire face au TSL est laissé à l'appréciation du lecteur).
Quoi qu'il en soit, le résultat était si terriblement lent que j'ai d'abord pensé créer une version de débogage par erreur.
Je pense que cela est dû en partie à la très mauvaise implémentation de la STL par Microsoft (où, par exemple, les vecteurs et les bits effectuent des contrôles liés ou d’autres opérations cryptées sur l’opérateur [], en violation directe des spécifications), et en partie à la conception de la STL. lui-même.
Je pouvais faire face aux problèmes de syntaxe et de portabilité atroces (Microsoft vs GNU) si les performances étaient là, mais ce n'est certainement pas le cas.
Par exemple, elle
deque
est intrinsèquement lente, car elle mélange beaucoup de données de comptabilité en attendant l'occasion de procéder à son redimensionnement très intelligent, ce dont je me moquais bien.Bien sûr, j'aurais pu implémenter un allocateur personnalisé et d'autres types de gabarits personnalisés, mais un allocateur personnalisé coûte à lui seul quelques centaines de lignes de code et la plus grande partie de la journée à tester, avec la douzaine d'interfaces qu'il doit implémenter, alors qu'un La structure équivalente faite à la main correspond à zéro ligne de code (bien que plus dangereuse, mais l'algorithme n'aurait pas fonctionné si je ne savais pas - ou ne pensais pas savoir ce que je faisais de toute façon).
Alors, finalement, j'ai conservé les conteneurs STL dans des parties non critiques du code et construit mon propre allocateur brutal et FIFO avec deux tableaux vers 1970 et trois courts métrages non signés.
Avaler le avaleur
Comme son auteur l'a confirmé, les schémas irréguliers de Swallower sont dus à un décalage entre les notifications de mouvements ennemis et les mises à jour du fil pathing.
Le perfmeter du système indique clairement que le thread en route consomme à 100% le processeur en permanence et que les motifs en dents de scie tendent à apparaître lorsque l'objectif de la lutte se déplace vers un nouveau domaine. Ceci est également assez évident avec les animations.
Une optimisation simple mais efficace
Après avoir regardé les combats épiques entre Swallower et mon combattant, je me suis souvenu d'un vieil adage du jeu Go: défendre de près, mais attaquer à distance.
Il y a de la sagesse dans cela. Si vous essayez de trop vous en tenir à votre adversaire, vous perdrez de précieux mouvements en essayant de bloquer chaque chemin possible. Au contraire, si vous restez à un pixel de distance, vous éviterez probablement de combler de petits écarts qui ne gagneraient que très peu et utiliserez vos gestes pour contrer des menaces plus importantes.
Pour mettre en œuvre cette idée, j'ai simplement étendu les mouvements d'un ennemi (en marquant les 4 voisins de chaque mouvement comme un pixel ennemi).
Cela stoppe l'algorithme de trajectoire à un pixel de la frontière ennemie, permettant ainsi à mon combattant de contourner un adversaire sans se faire prendre à trop de combats aériens.
Vous pouvez voir l'amélioration
(bien que toutes les exécutions ne soient pas aussi réussies, vous pouvez remarquer les contours beaucoup plus lisses):
la source
Profondeur-premier blob contre blob
Langue = Python (3.2)
Score =
111.475388276153.34210035Mise à jour: utilisez maintenant une
Set
classe personnalisée pour que lapop()
méthode produise une sorte de motif de grille qui améliore considérablement la zone couverte au début en coupant de grandes parties de l'image de l'ennemi. Remarque: j’utilise pour ce tableau une grille de 12 x 12 qui, sur un échantillon aléatoire de tailles de grille, semble donner les meilleurs résultats pour arena3 (celui qui a obtenu le plus mauvais score avant la mise à jour). Cependant, il est très probable qu’une la taille de la grille existe pour la sélection donnée d'arénas.J'ai opté pour une simple modification du bot de référence afin de lui donner la préférence pour choisir des points réalisables bordés par le moins de points de couleur propre possible. Une amélioration consisterait peut-être également à favoriser le choix de points réalisables bordés par autant de points de couleur ennemie que possible.
dfblob.py:
Le juge d'origine a été légèrement modifié pour fonctionner avec Python 3.2 (et pour ajouter une fonctionnalité de journalisation brute aux bots + sauvegarder l'image de l'arène périodiquement pour créer une animation):
Les résultats de l'arène suivent. Le bot dfblob a reçu la couleur rouge pour toutes les arènes.
Arena 1:
Arena 2:
Arena 3:
Arena 4:
la source
convert -delay 5 -loop 0 result*.png animated.gif
même si certains des gifs ont dû être supprimés manuellement pour pouvoir être téléchargés iciAvaleur
Langue = Java
Score =
162.3289512601408075169.4020975612382575Cherche les ennemis et les entoure.
Vous devrez peut-être lui donner une limite de temps plus longue. Pourrait être amélioré un peu. Imprime parfois des pixels non valides.Mise à jour: Entoure beaucoup plus vite. Utilise un autre thread pour mettre à jour les priorités. Retourne toujours dans un délai de 0,1 seconde. Le score devrait être impossible à battre sans augmenter
MAX_TURNS
.Comment ça marche:
Ce bot maintient une file d'attente prioritaire de pixels qu'il peut ajouter. La priorité d'un pixel ennemi est 0. La priorité d'un pixel vide est supérieure de 1 à la priorité la plus basse qui l'entoure. Tous les autres pixels ont la priorité Integer.MAX_VALUE. Le thread de mise à jour met constamment à jour les priorités des pixels. A chaque tour, les N pixels les plus bas sont extraits de la file d'attente prioritaire.
Green Blob vs Red Swallower
Score de Blob = 1.680553372583887225
Score de l'avaleur = 169.4020975612382575
Arena 1:
Arena 2:
Arena 3:
Arena 4:
Green Swallower vs. Red Blob
Score de Blob = 1.6852943642218457375
Score de l'avaleur = 169.3923095387498625
Arena 1:
Arena 2:
Arena 3:
Arena 4:
Avaleur Rouge vs Vert Profond Premier Blob
Score de l'avaleur = 157.0749775233111925
Profondeur Premier Blob Score = 18.192783547939744
Arena 1:
Arena 2:
Arena 3:
Arena 4:
Green Swallower vs Red Depth First Blob
Score de l'avaleur = 154.3368355651281075
Profondeur Premier Blob Score = 18.84463249420435425
Arena 1:
Arena 2:
Arena 3:
Arena 4:
Green Blob vs Red Depth First Blob vs Blue Swallower:
Score de Blob = 6.347962032393275525
Profondeur Premier Blob Score = 27.34842554331698275
Score de l'avaleur = 227.720728953415375
Arena 1:
Arena 2:
Arena 3:
Arena 4:
Voici le juge de Sam Yonnou avec quelques modifications pour que vous spécifiiez les fichiers et la commande séparément:
Exemple cfg:
Remarque: Toute personne qui parvient à avaler le Swallower obtient une prime de 100 points de réputation. S'il vous plaît poster dans les commentaires ci-dessous si vous réussissez à cela.
la source
Aléatoire, Langue = Java, Score = 0.43012126100275
Ce programme met au hasard des pixels sur l'écran. Certains pixels (sinon tous) ne seront pas valides. Sur une note de côté, il devrait être difficile de faire un programme plus rapide que celui-ci.
Arena 1:
Arena 2:
Arena 3:
Arena 4:
la source