|
Introduction
Principe du jeuLe Krikkit War (que nous appelerons par la suite Corewar) est une guerre virtuelle où s'affrontent des programmes (qui peuvent être assimilés à des robots, pour ceux qui voudraient faire le lien avec l'oeuvre de Douglas Adams). Cette guerre se déroule dans une zone de mémoire ou des processus naissent, évoluent, meurent. Le but d'un processus est d'être le dernier en vie. Pour cela, il va devoir faire tout ce qu'il peut pour éliminer les processus adverses, tout en signalant qu'il est en vie. Tout processus ne signalant pas qu'il est en vie est considéré comme mort. Ces processus représentent en fait des processeurs virtuels qui lisent et exécutent le code situé dans la mémoire. Ils exécutent une instruction particulière pour signaler aux joueurs qu'ils sont en vie. Au cours d'une partie, les processeurs pourront donc etre détruits, se multiplier (leur nombre est variable au cours d'une partie et est théoriquement illimité) et manipuler la mémoire pour arriver à leurs fins. C'est la guerre et tous les coups sont permis. La Machine Virtuelle
Le projet Krikkit War se compose de deux parties distinctes : « qu'est-ce que la machine virtuelle, et que doit-elle faire ? » La machine virtuelle va simuler l'arène dans laquelle se battront les processus. A chaque processus est alloué un processeur qui va exécuter des instructions. La machine virtuelle est chargée de simuler chaque processeur, leurs cycles d'exécution, leurs lectures et leurs écritures mémoire, leurs départs en couille, les cinq feuilles qu'ils se roulent, tout ça. Chaque processeur essaie d'être le dernier vivant, suivant la règle du dernier des mohicans ("S'il n'en reste qu'un, je serai celui-là"). Et la machine virtuelle, c'est le Grand Arbitre, celui à qui on rend les comptes, celui qui accorde et prend la vie (HellMaster Phibrizzo ?), et aussi Big Brother, car elle surveille tout, tout le monde, et tout le temps. Caractéritisques des processeurs : de quoi sont capables nos processus ?Voici les caractéristiques techniques de chaque processeur. Le langage d'assemblage : description du jeu d'instructionsVoici le jeu d'intructions des processeurs. Les instructions sont de taille variable et elles peuvent prendre des paramètres qui peuvent être : Dans les tables qui suivent, la première colonne représente les mnémoniques des instructions. Une mnémonique est un identifiant qui représente une instruction dans le langage de l'assembleur. Les mnémoniques sont suivis des paramètres à fournir, s'il y en a, séparés par des virgules. Notez que chaque caractère est important et qu'il devra apparaître dans le code. Les mnémoniques qui n'affectent pas Z sont signalées, toutes les autres l'affectent. Description des instructionsCette description vous est fournie pour que vous puissiez comprendre ce que fait chaque instruction du langage.
Codage et temps des instructionsCette table fournit le codage bit à bit des instructions. Les X répresentent des bits non significatifs, c'est-à-dire que leur valeur est quelconque et n'influe pas, donc vous pouvez mettre ce que vous voulez. Les registres sont stockés sur 1 quartet et sont juste representés par leur nom depuis la première colonne. Le registre r0 sera codé par 0x0, r1 par 0x1... et r15 par 0xF. 'n' représente une constante d'un quartet. La table fournit aussi les temps des instructions. Il y a trois phases pour chaque instruction et le nombre de cycles pour chacune d'elles.
Fonctionnement des partiesFonctionnement de la VMvm [-help] [-ms n] [-mp n] [-im n] [-cy n] [-nl n] [-cd n] [-t] [file 1 ...[file n]] Les fichiers des champions peuvent être des .cor générés par l'assembleur. Les options numériques devront être non-nulles. Déroulement d'une partieDans chaque partie, plusieurs champions vont s'affronter. L'espace mémoire va être entièrement initialisé à 0x0 et le code des différents champions va être copié en mémoire. Le première sera à l'adresse 0x0000 et les autres tous les taille mémoire / nombre joueurs quartets plus loin. Puis, un numéro de 1 à 15 va être attribué de manière unique (et laissée à votre discrétion) à chacun d'entre eux. Ce numéro va essentiellement servir pour les signes de vie. Au cours de la partie, les champions vont (devoir) émettre des signes de vie. Ceux-ci servent à déterminer le ou les vainqueurs. Comme cela sera expliqué plus loin, du point de vue du fonctionnement, les processeurs n'appartiennent pas aux joueurs, ils évoluent simplement dans la mémoire. Par contre, ils émettent des signaux de vie pour les joueurs, et le vainqueur est le joueur réel (tous les numéros ne sont pas attribués) pour lequel un signal de vie a été émis en dernier (si plusieurs signaux ont eu lieu au même cycle, il y a égalité). Enfin, un processeur est créé pour chaque joueur. Il sont initialisés de la manière suivante : PC contient l'adresse à laquelle le code a été copié, r0 contient le numéro du joueur, r1 à r15 valent 0x0000, P vaut 0x0, Z vaut 1 et les 16 niveaux de la pile contiennent 0x0000. L'arène, la zone de mémoire dans laquelle va se dérouler le combat, est initialisée pleine de 0 avant le chargement des différents champions en mémoire. La partie est ensuite lancée, les processeurs évoluent et appliquent les stratégies mises en place dans les codes des champions en interférant les uns avec les autres dans la mémoire. La partie se termine quand tous les processeurs sont détruits (ce qui arrive toujours) et le ou les gagnants sont annoncés. Fin de la partieEn fin de partie, la machine virtuelle doit annoncer les vainqueurs. Elle le fera en affichant sur la sortie standard le ou les noms (.name) des éventuels champions gagnants, ainsi que leur numéro dans l'ordre sur la ligne de commande (on peut faire jouer un champion contre lui-même et ils auront le même nom). Aucun format particulier n'est imposé si ce n'est que le message doit rester sur une ligne. Cet affichage est obligatoire et ne dépend pas de l'option verbose. Exemples : Le champion Jesus42 (3) a gagné. Les champions Fifi (1), Riri (2) et Loulou (3) sont vainqueurs. Ces champions sont des larves, pas un seul n'a réussi à faire un live : pas de gagnant. Le parallélismePour ne pas favoriser de joueur en fonction de l'ordre de traitement des processeurs par la machine virtuelle, on va évoluer dans un système entièrement parallèle (comme la réalité). A chaque cycle, toutes les opérations vont être executées comme si elles avaient lieu exactement en même temps. Cela a une influence sur les signaux de vie et les écritures en mémoire. Nous en reparlerons au cas par cas dans les sections suivantes. Destruction des processeursAu cours de la partie, les processeurs ne sont pas invulnérables, ils peuvent être détruits de deux façons : En effet, les signaux de vie ne sont pas uniquement utilisés pour déterminer le gagnant, ils sont aussi indispensables à la survie des processeurs. Il y a un compteur dans la machine virtuelle nomme CYCLE_TO_DIE. Si à un cycle quelconque de la partie un processeur n'a pas exécuté un live quelconque (sauf live 0 bien sûr) depuis CYCLE_TO_DIE cycles ou plus, il est instantanément détruit. Les processeurs doivent donc régulierement exécuter live. Mais pour rendre la chose plus intéressante et pour limiter la durée des parties, CYCLE_TO_DIE va décroître au cours du jeu. En fait, plus les processeurs exécutent live, plus CYCLE_TO_DIE décroît. Il existe deux autres compteurs fixés nommés NBR_LIVE et CYCLE_DELTA qui peuvent etre précisés au lancement de la VM. Toutes les NBR_LIVE exécutions d'un live par un processeur quelconque, CYCLE_TO_DIE est décrementé de CYCLE_DELTA. Pour respecter le parallélisme, CYCLE_TO_DIE n'évolue pas juste au moment où un live est exécuté, mais à la fin du cycle, une fois que tout les live à faire ou toutes les lectures des compteurs ont eu lieu. Il est intéressant de noter que CYCLE_TO_DIE est global à la machine virtuelle, c'est-à-dire que tout live d'un processeur diminue la durée de vie de tout le monde... Les live prennent effet à la fin du dernier cycle d'exécution. Ceci a pour conséquence que les évolutions de CYCLE_TO_DIE se font en fin de cycle (sachant que tous les processeurs travaillent en parallèle). Donc toute lecture de cette valeur par stat n'est pas affectée par les live exécutés dans le même cycle. De plus, comme les tests de destruction des processeurs qui n'ont pas fait live depuis trop longtemps sont fait en debut de cycle, avant toute autre action, les die ne peuvent pas tuer les processeurs directement : il faut attendre le cycle suivant. Ces quelques précautions évitent que l'ordre de traitement des processeurs influe sur le déroulement. Le forkComme nous venons de le voir, le nombre de processeurs peut décroître, mais il lui est aussi possible d'augmenter. En fait, cela est possible grâce à l'instruction fork. Cette instruction a pour but de dupliquer le processeur. A l'issue de celle-ci (fin du dernier cycle d'exécution), un nouveau processeur va être introduit dans la machine virtuelle entièrement identique - même le PC - à celui qui a exécuté le fork. La seule différence entre les deux est l'état du registre Z qui vaut 0 dans un processeur et 1 dans l'autre, ce qui permet de les différencier. Evidemment, il n'y a pas de notion de père ou de fils. Si le nombre maximal de processeurs est atteint, le fork échoue. C'est-à-dire qu'aucun nouveau processeur n'est crée. Il renvoie 0 dans Z, et ne prend pas moins de temps que s'il réussit. Le monde du relatifComme cela a été précisé au début, les processeurs travaillent intégralement en relatif. Cela signifie que tous les accès mémoire et les sauts se font relativement au PC courant (donc à la fin de l'instruction qui fait une lecture, une écriture ou un saut). De plus, pour que cela fonctionne correctement, la mémoire est circulaire, c'est à dire que si elle mesure 1000 quartet, qu'un PC vaut 990 et que le processeur saute de 20 en avant, PC vaudra ensuite 10 et pas 1010. Or, comme on peut le constater dans la table instructions, les instructions classiques d'accès à la mémoire ou de saut utilisent dans leur calcul une constante nommée IDX_MOD. Cette valeur qui est précisée au lancement de la VM sert tout simplement à limiter la portée des lectures, écritures ou sauts en mémoire. Pour chacune des intructions classiques, un modulo IDX_MOD va être appliqué à la distance d'accès. Il existe cependant des instructions dites longues qui ne tiennent pas compte de cette limite pour la lecture et le saut en mémoire, mais elles sont plus grosses et plus lentes. Ecritures simultanées en mémoireLe problème est simple : que se passe-t-il lorsque plusieurs processeurs écrivent en mémoire au même endroit au même moment ? Pour respecter le parallélisme, un système de votes a été mis en place. Si un même quartet de la mémoire est affecté plusieurs fois au cours d'un même cycle, il y a un vote indépendant pour chacun des bits du quartet. Si l'on cherche à écrire plus de fois 0 que 1, un 0 est écrit, si l'on cherche à écrire plus de fois 1 que 0, un 1 est écrit, et s'il y a ballotage, la valeur actuelle du bit est conservée. C'est aussi simple que cela. Exécution des instructionsL'exécution de chaque instruction se déroule en trois étapes : Tous les codes ne sont pas définis par des instructions. Si un processeur tente d'exécuter une instruction inconnue, il peut avoir un comportement quelconque. Les quartets autre que ceux de 0 à P ne peuvent être affectés par des instructions qui travaillent en fonction de P. Le bout restant du registre que vous essayez de trafiquer est absent, parti en voyage dans un autre plan, ou s'il a rate sa navette spatiale, un dinosaure en carton pâte est venu monter la garde devant lui. Les quartets au-dessus de P sont sous protection démoniaque, personne ne peut les toucher, meme pas manu. L'affectation de Z dépend du résultat du calcul effectué par l'opération, c'est-à-dire le contenu du registre de destination après affectation du résultat. Les instructions asr, inc, dec, lsl et lsr interprètent les constantes en non signé. rol, live, lp, lc, ll et stat interprètent comme elles souhaitent car il n'y a pas de calculs, seules les valeurs comptent. P est toujours utilisé de manière non signée et dans le cas de lc, le bit de signe est propagé, c'est-à-dire le bit de poids le plus fort. Ordre des actions au cours d'un cycleVoici l'ordre dans lequel ont lieu les principales actions au cours d'un cycle : On remarquera que les lectures ont lieu avant les écritures et que la résolution des live a lieu après lecture des compteurs. Naturellement, le premier cycle qu'exécutera la machine sera le cycle 0, et il sera incrémenté entre deux cycles. L'anti jeuA cause du système de CYCLE_TO_DIE pour limiter le temps des parties, il est possible d'appliquer une technique d'anti-jeu qui consiste à multiplier le nombre de processeurs de manière exponentielle puis de faire des live. La partie se termine alors très rapidement (CYCLE_TO_DIE passe en dessous de 0) et le joueur qui a appliqué cette méthode est quasiment sûr de gagner. Pour empêcher cela et parce que la VM tourne dans un environnement fini, le nombre de processeurs va être limité. Il y a beaucoup de méthodes pour éviter l'anti-jeu, nous avons choisi celle qui semblait la plus simple, la plus efficace et la plus juste. Entre processeurs et joueurs, du point de vue de l'exécution, il y a un lien d'appartenance assez faible (le premier processeur a le numéro du joueur dans r0 et chaque processeur peut demander à qui il appartient), les processeurs ne font qu'exécuter la mémoire. Si on change le code dans la mémoire, le processeur qui passe dessus fait ce qu'on veut. Par contre, en ce qui concerne les fork, le lien va être beaucoup plus fort. Chacun des joueurs va avoir droit à un nombre limite de processeurs. L'appartenance est héréditaire, c'est-à-dire que lorsqu'un processeur fait un fork, les 2 processeurs résultant appartiendront au même joueur que le processeur qui a fait le fork. Si au cours d'un cycle un ou plusieurs processeurs d'un même joueur termine leur fork et que cela fait dépasser le plafond, le ou les fork en question vont simplement échouer. Le vol d'une processeur d'un autre joueur devient alors très intéressant pour empêcher celui-ci de forker... L'accès aux compteurs de la machine virtuelleComme la table des instructions l'indique, il existe une instruction stat qui permet de récuperer la valeur d'un compteur. Elle renvoie la valeur dans le registre que l'on lui passe en argument. Les compteurs suivants existent dans la VM :
Le premier cycle de la partie est le cycle 0. Format des fichiers .corVotre assembleur, à partir d'un fichier source contenant des instructions en langage d'assemblage, devra générer un « binaire », un fichier contenant la traduction de votre code source en langage machine. Ces binaires porteront l'extension .cor. Les données vont y être stockées directement (et pas sous forme de texte). Si on l'édite avec un éditeur de texte, on verra un peu n'importe quoi. Le format de fichier est simple :
point directive arguments D'où le programme d'exemple :
42 63 6F 72 0F 00 00 00 7A 6F 72 6B 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 .. 61 50 51 06 2C F0 0B 20Le fichier doit donc faire 336 octets. N'oubliez pas que votre fichier est écrit par un PC, une Alpha ou une Sun. Le type de base de ces machines est l'octet. On adopte la convention qu'utilisent les humains, c'est-à-dire d'écrire d'abord dans le quartet de poids fort de l'octet, puis dans le quartet de poids faible. Donc, si vous avez a écrire les quartets 0x1 puis 0x2, l'octet qui contient ces deux quartets vaudra 0x12. Modalités de rendu
Documentation Complémentaire
Vous disposez d'un newsgroup dédié au projet :
(oui, le nom du newsgroup est bizarre, mais il est comme ca sur le serveur de news, on ne peut rien y changer, mes plus plates excuses à Douglas Adams. La confusion vient de ce qu'au bocal, il y a des gens qui ne savent pas écrire) Une FAQ sera peut-etre mise en place, nous vous indiquerons dans le newsgroup ad-hoc sa création et son emplacement. Les responsables du suivi et de la correction de ce projet sont vos acus préférés (oui, la vingtaine de gusses qui vous ont appris à nager pendant la piscine).
- But what about the End of the Universe? We'll miss the big moment. |