Afyu a parlé de la méthode. Vue que j'ai utilisé à peu près la même je vais le concentrer de l’outil. Je parlerais ici seulement de la version initiale, sans les modifications apportées par
SlyVTT (il en parlera bien mieux que moi).
Présentation rapideCe magnifique outil a été écrit en python, en se basant sur le code existant du concours, mais sans la partie raycasting. L’affichage est découpé en 4 parties :

- Vue 3D : Permet l’affichage en 3D de la carte. On peut se déplacer, à la souris.
- Informations : Quelques informations, comme le score ou le niveau actuel
- Zone de contrôle : Permet de se déplacer (supporte aussi les entrées clavier).
- Minimap : Affichage en vue du dessus. La couleur des souris dépend de leur niveau.
Il est mis à disposition de l’utilisateur, dans la vue 3D (en rouge), le chemin parcouru dans cette partie. L’outil permet aussi de revenir en arrière (bouton Undo), même si cette fonctionnalité est un peu buggé.
Premières expérimentationsJ’ai d’abord téléchargé l’outil puis fait une ou deux parties tel quel. J’ai ensuite développé un petit outil en python pour dumper la carte en 3D, ce qui m’a permis de la visualiser dans blender :

Show/Hide spoilerAfficher/Masquer le spoiler
- Code: Select all
from pyka3dmc import *
vertices = []
indices = []
offset = 1
for i in range(MAP_WIDTH):
for j in range(MAP_HEIGHT):
height = getBlockHeight(i, j) / 10
bottom = getBlockBottom(i, j) / 10
if height == 0 and bottom == 0:
continue
vertices.append((i + 0, j + 0, bottom)) # 0 0 0 0
vertices.append((i + 1, j + 0, bottom)) # 1 0 0 1
vertices.append((i + 0, j + 1, bottom)) # 0 1 0 2
vertices.append((i + 1, j + 1, bottom)) # 1 1 0 3
vertices.append((i + 0, j + 0, bottom + height)) # 0 0 1 4
vertices.append((i + 1, j + 0, bottom + height)) # 1 0 1 5
vertices.append((i + 0, j + 1, bottom + height)) # 0 1 1 6
vertices.append((i + 1, j + 1, bottom + height)) # 1 1 1 7
indices.append((offset + 0, offset + 1, offset + 3))
indices.append((offset + 0, offset + 2, offset + 3))
indices.append((offset + 4, offset + 5, offset + 7))
indices.append((offset + 4, offset + 6, offset + 7))
indices.append((offset + 0, offset + 1, offset + 5))
indices.append((offset + 0, offset + 4, offset + 5))
indices.append((offset + 2, offset + 3, offset + 7))
indices.append((offset + 2, offset + 6, offset + 7))
indices.append((offset + 0, offset + 2, offset + 6))
indices.append((offset + 0, offset + 4, offset + 6))
indices.append((offset + 1, offset + 3, offset + 7))
indices.append((offset + 1, offset + 5, offset + 7))
offset += 8
with open("map.obj", "w") as f:
for v in vertices:
f.write("v " + str(v[0]) + " " + str(v[2]) + " " + str(v[1]) + "\n")
for i in indices:
f.write("f " + str(i[0]) + " " + str(i[1]) + " " + str(i[2]) + "\n")
Ce code me donnait déjà une bonne base pour la génération du modèle 3D, donc j’ai décidé de passer aux choses sérieuses.
DéveloppementIl m’a d’abord fallu choisir une librairie 3D. L’outil allait être développé en python (parce que flemme de réimplémenter tout le code dans un autre langage ou d’en faire une librairie avec cython), j’ai donc choisi
pygfx, qui ressemble beaucoup à three.js (que j’ai utilisé dans le passé). J’ai donc commencé par adapter le code de génération 3D de la carte pour le faire fonctionner dans pygfx. J’avais donc à ce moment la carte en 3D, et je pouvais tourner autours.
Ajout des sourisJ’ai ensuite ajouté les souris, qui à la base étaient des carrés (mais se sont transformés en cylindres quand j’ai compris comment fonctionnait la gestion des collisions). Je stocke une liste de mes cylindres, que je mets à jour à chaque update du jeu. Les cylindres qui représentent des souris qui n’existent plus sont déplacés en -1 ;-1 pour qu’elles ne soient plus visibles.
Interface de contrôle et gestion clavierJ’ai décidé d’utiliser TkInter pour l’interface. Ça a l’avantage d’être simple mais le gros désavantage de forcer l’utilisateur à garder la fenêtre Tk focus pour utiliser les actions clavier.
Features en plusJ’ai décidé d’ajouter deux fonctionnalités qui m’ont vraiment amélioré la vie, la ligne rouge et la fonction undo. La ligne rouge permet de voir le chemin passé, ce qui permet de mieux visualiser et de comparer les différents chemins. Elle est implémentée à l’aide du type Line de pygfx, et est mise à jour à chaque action.
La fonctionnalité undo est quant à elle implémentée avec une liste de toutes les actions effectués. À chaque action, tous les états du jeu sont push sur cette liste (d’où le fait que le jeu ait été « objet-ifié », ce qui permet plus simplement d’identifier les variables à sauvegarder). Lorsque le bouton Undo est utilisé, je pop de la liste et je load les variables sauvegardés.
Scripts annexesJ’ai ajouté deux scripts annexes à ça, un qui permet d’opacifier mes runs (pour le plaisir, ça changeait rien au score au final, mais rendait juste les runs différentes les unes des autres même si les débuts étaient les mêmes) :
Show/Hide spoilerAfficher/Masquer le spoiler
- Code: Select all
from random import randint
from core.pyka3dlbcli import Pyka3D
from score import score
from tqdm import tqdm
out = []
for i in score:
action, arg = i
if action in [0, 1, 2, 3, 4]:
while arg > 10:
n = randint(2, int(arg) - 1)
out.append(action)
out.append(n)
arg -= n
out.append(action)
out.append(arg)
else:
out.append(action)
out.append(arg)
print(out)
et un qui nous a permis d’optimiser l’ordre d’envoi des scores du groupe pour maximiser les points (qui a surtout servi lors de la fusion avec V601, même s’il nous a permis de gagner seulement une dizaine de points sur le score du groupe) :
Show/Hide spoilerAfficher/Masquer le spoiler
- Code: Select all
from copy import copy
from itertools import permutations
from math import *
def group_mean(arr):
mini = min(arr)
maxi = max(arr)
kbonus = sqrt(len(arr))
n = len(arr)
s1 = 0
n1 = 0
k1 = 1
lbonus = []
for key, val in enumerate(arr):
if k1 == 1 and not (key in lbonus): lbonus.append(key)
if n > 2 and k1 == len(arr) and not (key in lbonus): lbonus.append(key)
if n > 4 and val == mini: lbonus.append(key)
if n > 8 and val == maxi and not (key in lbonus): lbonus.append(key)
if key in lbonus: val *= kbonus
n1 += k1
s1 += k1 * val
k1 += 1
return s1/n1
max_score = 0
max_val = []
scores = [2335.5, 2178.9, 2376.0, 2376.2, 2376.3, 2058.3, 2655.2]
for i in permutations(scores):
s = group_mean(i)
if (s > max_score):
max_val = copy(i)
max_score = s
print(max_score, max_val)
L’outil est
disponible en téléchargement, sans les modifications effectuées par Sly, je lui laisse le soin de les détailler et de publier sa version.
Des scoresBon, je vais quand-même un peu parler du cheminement sur les scores, parce que c'est quand-même un minimum important. À la base, je fonctionnait assez naïvement sur l’obtention des scores, à essayer d'avancer sans trop de poser de questions (ce qui m'a quand-même permis de monter à 1900 en faisant quelques fusions bien placés). Ce n'est qu'après avoir commencé à discuter avec
Afyu et
SlyVTT que j'ai commencé à vraiment optimiser mes fusions, ce qui m'a permis d'arriver à une bonne base (autour de 1800 points) pour la suite. Cette base m'a permis de taper dans les 2000, puis 2200, puis le final 2376. J'ai aussi eu un 2379 en live (mais il a mal été sauvegardé par l'outil j'ai toujours la haine d'ailleurs mdr). Les scores qu'on a envoyé avec
Redgl0w et
RapidZapper sont très proche, et c'est normal. On a beaucoup travaillé ensemble, à 3 cerveaux sur le problème (parfois en vocal sur Discord), ce qui fait qu'au final nous avions les mêmes routes mais avec une fin différente. Les modifications qu'a apporté
SlyVTT à mon outil ont vraiment été pratique, surtout pour gérer les collisions dans les murs et les déplacement assez chaotiques des souris.
ConclusionBon, je vais quand-même parler de l’éléphant au milieu de la pièce, la fusion avec V601. On a décidé de le faire, d’un commun accord, avant tout pour un souci de transparence. En effet, on a échangé beaucoup d’informations tout au long du concours, que ça soit au niveau des stratégies, des outils ou des optimisations à faire. On n’aurait sûrement pas réussi à taper des scores si hauts si on avait pas autant échangé avec eux (et on les remercie).
C’était mon idée, et je suis désolé si ça en a déçu certains qui pensaient arriver premier (vous allez devoir me croire sur parole, mais on s’est rendu compte après avoir décidé la fusion qu’on finirait premier, et donc on a quand-même décidé d’optimiser le score de groupe pour le beau jeu à la toute fin).
Je tenais à féliciter personnellement le groupe nsi4ever. Vous avez rien lâché, vous avez tout donné, et je pense que si vous aviez pas été la le concours aurait été vachement moins fun. Bravo à vous. Je voulais aussi dire un gros gros merci à Xavier pour ce concours, le sujet était génial, tu t’améliores tous les ans, c’est un plaisir de participer.
Choix du lotBon, les choses sérieuses…

Je vais prendre un lot N0120EX.
Pour les goodies Numworks :
- Le sac sans les maths en fond (premier sur les photos)
- Le poster "Comprendre le monde devient un jeu" (le 2e)
- Le livret 2022-2023
Pour les goodies Calcuso :
Pour les goodies XCas :
Pour les goodies TI-Planet :
- Un aimentin TI-Planet normal sur fond blanc
- Un autocollant tiplanet normal
PS: Attendez vous au petit live twitch unboxing + modding de la N0120EX
