Food Battle

Comment fonctionne Food Battle ?

Food Battle repose sur une idée simple : l'utilisateur se voit proposer une série de duels, dans lesquels il doit choisir son aliment préféré parmi deux propositions. Les notions d'aliment et de préféré ne sont pas strictement définies : cela peut être un plat, un condiment ou même quelque chose de difficilement mangeable, et l'utilisateur est libre de voter comme bon lui semble (nous recommandons évidemment de voter pour le mets préféré).

En accumulant ces votes, on construit un classement global qui estime la force relative de chaque aliment. Celui-ci repose sur un modèle de comparaison par paires (Bradley-Terry). Chaque aliment possède un score de force noté ss. Plus ce score est élevé, plus l'aliment est considéré comme fort. La probabilité qu'un aliment ii gagne contre un aliment jjs'écrit :

P(ij)=esiesi+esj=11+e(sjsi)P(i \succ j)=\frac{e^{s_i}}{e^{s_i}+e^{s_j}}=\frac{1}{1+e^{(s_j-s_i)}}

En pratique, cela veut dire qu'un aliment déjà très bien classé est censé gagner plus souvent. Mais si un outsider le bat, l'impact est important sur les scores. Après chaque vote, les deux scores sont ajustés. On commence par calculer la probabilité attendue de victoire du gagnant :

E=11+e(slsw)E=\frac{1}{1+e^{(s_l-s_w)}}

Ensuite, on utilise un pas d'apprentissage adaptatif :

k=max(0.045,0.221+mw+ml)k=\max\left(0.045,\frac{0.22}{\sqrt{1+m_w+m_l}}\right)

Avec :
- sws_w : score du gagnant avant le vote
- sls_l : score du perdant avant le vote
- mwm_w et mlm_l : nombre de matchs déjà joués par chacun

La variation appliquée est :

Δ=k(1E)\Delta=k(1-E)

Puis les scores sont mis à jour ainsi :

sw=clip(sw+Δ,6,6)s_w'=\operatorname{clip}(s_w+\Delta,-6,6)
sl=clip(slΔ,6,6)s_l'=\operatorname{clip}(s_l-\Delta,-6,6)

L'opérateur operatornameclip\\operatorname{clip}garde les scores dans l'intervalle [6,6][-6,6] pour éviter les extrêmes. Cette approche donne un classement réactif au début, puis plus stable quand les aliments ont déjà beaucoup de votes.

Concernant les duels proposés, la sélection n'est pas totalement aléatoire. Le système tire d'abord un sous-ensemble d'aliments actifs, puis choisit deux candidats avec une préférence pour ceux qui ont été moins vus.

L'idée de pondération est :

poids1(matches+1)0.75×random()\text{poids} \propto \frac{1}{(matches+1)^{0.75}} \times random()

Plus un aliment a peu de votes (nombre faible de duels), plus il a de chances d'apparaître. Cela évite que toujours les mêmes aliments tournent en boucle et aide à mieux équilibrer la couverture du classement. Enfin, chaque vote passe par des vérifications côté serveur pour protéger l'équité du jeu (cohérence du duel, anti-rejeu, anti-spam). Le classement reflète donc les choix valides des joueurs, pas des écritures directes depuis le navigateur.