Dés et game-design

Voir le sujet précédent Voir le sujet suivant Aller en bas

Dés et game-design

Message  Apeiron le Lun 22 Déc - 5:27

Introduction

Dans la conception d'Edad j'ai toujours voulu garder le dé, à la fois car il s'agit d'un générateur de hasard intéressant [0] mais aussi car les dés à plein de faces connotent énormément les jeux de rôle papier (DD en tête).
Ce n'est pas le sujet du présent pavé mais le hasard en lui-même apporte beaucoup aux jeux, par exemple ici sans lui on aurait juste des stats fixes et ce serait répétitif, snif.
La clé d'un bon game-design est de permettre de maîtriser ce hasard afin que le joueur se sente acteur de son destin, et ce de différentes manières possibles afin qu'il ait des choix.

Mécanisme du dé

Ces choix doivent correspondre aux profils des joueurs dans la construction du personnage, et en cours de partie doivent offrir des choix ou au moins un déroulement correspondant à ce profil. Voyons donc ce que les dés ont à nous offrir comme mécanismes à nous mettre sous la dent...
On tire généralement un ou plusieurs dés ayant chacun un certain nombre de faces. Si le but est d'optimiser un score (par exemple des dégâts infligés) alors on peut soit augmenter le nombre de dés tirés soit augmenter le nombre de faces.
Dans tous les cas, le but sera de faire évoluer le personnage en lui donnant de nouveaux moyens d'augmenter son impact dans le jeu.

Nombre de faces

Plus on augmente le nombre de faces (passer d'un d6 à un d8 par exemple) plus la moyenne augmente mais aussi la variance (l'écart entre la valeur minimale et maximale). Le moyen naturel de monter en puissance est de contrôler le hasard en pouvant relancer son dé de dégât.
Il y a plusieurs relances possibles, par exemple relancer le dé et prendre le deuxième résultat, ou mieux lancer deux fois et prendre le meilleur. La relance est d'autant meilleure que le dé a beaucoup de faces, contrairement à des mécanismes comme le +1 pour faire un critique.
Il est à noter que cette maîtriser du hasard donne un profil de joueur conservateur (tank/paladin), qui veut faire du lourd sans trop de variance. Les dégâts réguliers sont bons pour éliminer une cible moyenne par tour, contrairement à un boss dont l'armure absorbera les dégâts à tous les tours, ou aux petites cibles sur qui de tels dégâts seraient du gaspillage.

Nombre d'attaques

Pour augmenter le nombre de dés généralement ou bien on augmente le nombre d'attaques ou bien on utilise le mécanisme de critique.
Le critique doit être meilleur que les attaques multiples, car celles-ci peuvent être utilisées sur une même cible (par exemple un boss) mais si nécessaire être partagées entre plusieurs petites cibles contrairement à une attaque critique.
Pour cela le critique pourrait faire davantage de dés, mais il est plus RP (et à mon sens mécaniquement satisfaisant) que chaque attaque subisse l'armure adverse, ce qui rend les attaques multiples moins intéressantes sur les boss.
Cette précaution prise, on peut donner des dés moyens à ce profil (les gros dés allant plutôt avec la relance, alors que les petits vont de pair avec le critique), les dés pouvant être différents si différentes armes sont utilisées (style rôdeur), et sinon correspond juste à un enchainement (style samurai).
Ici le profil du joueur est plutôt "hack and slash" (barbare, rôdeur, archer).

Le critique

Un critique s'obtient lorsqu'une valeur assez élevée est atteinte sur un dé, et dans ce cas permet d'ajouter un nouveau dé (de même type). Un bonus étendant la zone de critique est meilleur sur de petits dés, et le but est de lancer plein de dés.
Afin de pouvoir battre les boss il vaut mieux pour une moyenne donnée avoir une grande variance, afin de minimiser le nombre de fois où la défense de l'armure s'applique vraiment.
De plus une grande variance correspond bien au profil de joueur attiré par le fait de lancer plein de dés (profil combo disons, c'est à dire en jeu essentiellement les assassins).
Pour cela il serait intéressant d'autoriser que les dés de critique peuvent aussi faire un critique. Ainsi on a bien un enchainement de dé très aléatoire qui peut donner le meilleur comme le pire. [1]

Les combinaisons

Enfin, si le but n'est pas nécessairement d'optimiser le score mais d'obtenir des combinaisons avec les dés (une suite, un carré, etc.), un nouveau profil émerge qui va énormément manipuler les dés pour produire des effets intéressants.
Pour manipuler directement les dés, on peut par exemple faire +1 ou -1 sur une valeur de dé, ou alors retourner un dé (un 2 sur 1d6 devient alors un 4) [2] ce qui peut permettre un échange valeur faible contre valeur forte (et inversement) mais peut d'évolution de score si le dé est sur une valeur moyenne.
Si cette manipulation de dé se fait sur les dés alliés alors cela correspond à un rôle de soutien (prêtre ou général), et un trade-off intéressant apparaît : optimiser le score ou alors éventuellement le baisser en permettant une combinaison.
Il est à noter dans ce cas que les gros dés peu nombreux permettent d'étendre les suites, alors que les petits dés nombreux sont bons pour faire des carrés (ou autre combinaison de même type), ce qui peut orienter dans le choix de conception pour les effets de combinaison.

Conclusion

J'ai réussi à trouver quatre profils naturels avec les dés :
- l'irrégulier mono-cible qui pique
- le régulier pour les cibles moyennes
- le nettoyeur des petites cibles
- le soutien qui essaie de générer des effets globaux
Certes, ce n'est pas une révélation, mais cela permet d'éclaircir la direction à prendre pour Edad.
Il est aussi à noter que ces profils correspondent à plusieurs classes, et on peut imaginer des combinaisons, par exemple avec une autre liste de capacités :
- discrétion / mouvement
- armure / résistance
- endurance / distance
- soin
On peut voir qu'un irrégulier peut très bien être discret (assassin) ou avoir de l'endurance (duelliste), qu'un régulier peut avoir une bonne armure (tank) ou des soins (paladin), que des nettoyeurs peuvent privilégier le déplacement (barbare) ou la distance (archer), et que les soutiens peuvent faire du soin (prêtre) ou se protéger (général).
Beaucoup de pistes à explorer donc... [3]

-------------------------------------

[0] On peut citer aussi les cartes comme autre générateur de hasard intéressant et traditionnel, et ça tombe bien car je travaille justement aussi sur un deck-building. Very Happy

[1] Et bien sûr il faut que la série puisse s'arrêter. Par exemple si le plus petit dé est le d4 alors un bonus au critique de +2 est le maximum : critique sur 2, 3 et 4, toutes les valeurs sauf 1. Donc à chaque lancer de dé la chaîne a 75% de chance de continuer.

[2] Oui, j'ai été inspiré par ce jeu de plateau avec des dés où on essaie de coloniser une planète...
Edit : Inverser les valeurs est plus fort sur les gros dés (pour faire du score ou constituer des suites), alors que faire +1/-1 est meilleur sur les petits dés (pour constituer des carrés). Cependant inverser un dé aide aussi pour le critique (sur un d4 il suffit d'un +1 au critique pour qu'une inversion permette de continuer le chaînage, même si c'est généralement moins bon qu'inverser un gros dé), et même si les deux possibilités peuvent coexister je trouve l'inversion plus fun puisqu'elle correspond davantage à l'objet physique du dé.

[3] Note en passant : il n'y a que le combat qui a vraiment besoin d'être formalisé, mais si on fait ça on ne donne pas de substance au civil. Donc je pense qu'il faut une formalisation pour le civil, mais sous la forme de scores et non de dés (le hasard n'existerait donc que dans le feu de l'action), par exemple pour les talents ou les possessions (richesse, réputation, relations...) des personnages.
avatar
Apeiron
Grand Inquisiteur de la Cohérence
Grand Inquisiteur de la Cohérence

Masculin Nombre de messages : 5471
Age : 29
Date d'inscription : 09/11/2008

Voir le profil de l'utilisateur

Revenir en haut Aller en bas

Re: Dés et game-design

Message  Apeiron le Ven 13 Fév - 6:23

Tests sur les critiques

Après avoir lu un article sur l'implémentation des dés je me suis amusé à jouer un peu avec du C pour déterminer des valeurs par simulation :

Code:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void clearFile(){
    FILE *fp;
    fp=fopen("res.txt", "w");
    if(fp == NULL){
        printf("Cannot open res.txt.\n");
        return(2);
    }
    fprintf(fp,"");
}

float critAverage(int dSize,int critZone){
    float average=0.0;
    if(critZone<0||dSize-1<critZone){
        printf("Critical zone out of bounds.\n");
        return(average);
    }
    int rolls=100000;
    int i,sum,roll;
    srand(time(NULL));
    for(i=0;i<rolls;i++){
        sum=0;
        do {
            roll=1+rand()%dSize;
            sum=sum+roll;
        } while(roll>=dSize-critZone+1);
        average=average+(float)sum/(float)rolls;
    }
    return(average);
}

/*exit status :
    0 : Ok
    1 : Dice size is too small
    2 : Cannot open res.txt
*/
int critPrint(int dSize){
    if(dSize<2){
        printf("Dice size too small.\n");
        return(1);
    }
    FILE *fp;
    fp=fopen("res.txt", "at");
    if(fp == NULL){
        printf("Cannot open res.txt.\n");
        return(2);
    }
    int critZone;
    fprintf(fp,"Critical hits for d%d:\n",dSize);
    for(critZone=0;critZone<dSize;critZone++){
        fprintf(fp,"- zone = %d, average = %f\n",critZone,critAverage(dSize,critZone));
    }
    fprintf(fp,"\n");
    return(0);
}

int main(){
    clearFile();
    critPrint(4);
    critPrint(6);
    critPrint(8);
    critPrint(10);
    critPrint(12);
    return(0);
}

La zone de critique correspond aux valeurs permettant de relancer le dé pour additionner le résultat, et éventuellement recommencer.
Par exemple si la zone est de zéro le dé est lancé tout simplement. Si la zone de critique est de 2 sur un d4 alors 3 et 4 sont des critiques et on peut avoir la séquence suivante : 3 4 3 4 2 = 16.
J'ai obtenu les données suivantes, qui montrent l'allure de la courbe dans le cas général :



Deux choses m'ont intéressées :
1) Les valeurs sont probablement toutes des fractions, ce qui sous-entend qu'une preuve aurait été faisable, mais flemme.
2) C'est la dernière extension de la zone de critique qui fait vraiment le plus mal (comportement exponentiel).

Naturellement en prolongeant le critique aussi loin que la taille du dé - 1 c'est les plus gros dés qui ont l'avantage. Mais je rappelle qu'une simple zone de critique à 4 donne une valeur infinie avec un d4. En comparant uniquement pour des zones de 0 à 3 on obtient :



Il faut donc une zone de critique de 3 pour que les d4 deviennent très largement supérieurs aux autres dés, et pour cette valeur les autres dés ont des résultats très proches.
Vu le comportement asymptotique cette stratégie doit être restreinte aux petits dés, donc un 3 en zone critique doit être le maximum atteignable, tout en prémunissant d'une valeur infinie. De plus, cette valeur doit être atteinte pour rendre la stratégie viable.
Enfin la moyenne de 10 dégâts est assez forte, donc il faudra en tenir compte dans l'équilibrage.

L'autre aspect important est la volatilité des dégâts.
Au lieu de calculer l'écart-type théorique je vais aborder directement le cas qui nous intéresse, à savoir la moyenne des dégâts sur un adversaire avec une défense fixée. Plus tard, il faudra la comparer avec la même moyenne pour d'autres stratégies, et à terme calculer les matchups c'est à dire ne pas comparer à une valeur fixe mais comparer stratégie par stratégie.

Code:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void clearFile(){
    FILE *fp;
    fp=fopen("res.txt", "w");
    if(fp == NULL){
        printf("Cannot open res.txt.\n");
        return(2);
    }
    fprintf(fp,"");
}

int max(int n1,int n2){
    if(n1>n2)return(n1);
    else return(n2);
}

float critAverage(int dSize,int critZone,int def){
    float average=0.0;
    if(critZone<0||dSize-1<critZone){
        printf("Critical zone out of bounds.\n");
        return(average);
    }
    int rolls=100000;
    int i,sum,roll,damage;
    srand(time(NULL));
    for(i=0;i<rolls;i++){
        sum=0;
        do {
            roll=1+rand()%dSize;
            sum=sum+roll;
        } while(roll>=dSize-critZone+1);
        damage=max(sum-def,0);
        average=average+(float)damage/(float)rolls;
    }
    return(average);
}

/*  Print the critical hit average
    for a given defense value.
    Parameters : d4, zone=3
*/
/*  Exit status
    0 : Ok
    1 : Cannot open res.txt
*/
int critDefPrint(int def){
    FILE *fp;
    fp=fopen("res.txt", "at");
    if(fp == NULL){
        printf("Cannot open res.txt.\n");
        return(1);
    }
    fprintf(fp,"%f\n",critAverage(4,3,def));
    return(0);
}

int main(){
    clearFile();
    int def,maxDef=20;
    for(def=0;def<=maxDef;def++){
        critDefPrint(def);
    }
    return(0);
}

Dans la mesure où les dégâts moyens sont à 10 j'ai pris les valeurs pour une défense allant de 0 à 20. Toutefois il faut noter que comme les dégâts ne sont pas bornés il est toujours possible (quoi qu’improbable) d'infliger des dégâts même à un adversaire avec une défense colossale :



Au prochain épisode, les relances de dés !

---------------------------------------

[edit] Code corrigé :

Code:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

/*  Exit status
    0 : Ok
    1 : Cannot open res.txt
*/
int clearFile(){
    FILE *fp;
    fp=fopen("res.txt", "w");
    if(fp == NULL){
        printf("Cannot open res.txt.\n");
        return(1);
    }
    fclose(fp);
    return(0);
}

int max(int n1,int n2){
    if(n1>n2)return(n1);
    else return(n2);
}

float critAverage(int dSize,int critZone,int def){
    float average=0.0;
    if(critZone<0||dSize-1<critZone){
        printf("Critical zone out of bounds.\n");
        return(average);
    }
    int rolls=100000;
    int i,sum,roll,damage;
    srand(time(NULL));
    for(i=0;i<rolls;i++){
        sum=0;
        do {
            roll=1+rand()%dSize;
            sum=sum+roll;
        } while(roll>=dSize-critZone+1);
        damage=max(sum-def,0);
        average=average+(float)damage/(float)rolls;
    }
    return(average);
}

/*  Print all the critical hit average
    for all the critical zones of a given dice size.
*/
/*  Exit status
    0 : Ok
    1 : Dice size is too small
    2 : Cannot open res.txt
*/
int critPrint(int dSize){
    if(dSize<2){
        printf("Dice size too small.\n");
        return(1);
    }
    FILE *fp;
    fp=fopen("res.txt", "at");
    if(fp == NULL){
        printf("Cannot open res.txt.\n");
        return(2);
    }
    int critZone;
    fprintf(fp,"Critical hits for d%d:\n",dSize);
    for(critZone=0;critZone<dSize;critZone++){
        fprintf(fp,"%f\n",critAverage(dSize,critZone,0));
    }
    fprintf(fp,"\n");
    fclose(fp);
    return(0);
}

/*  Print the critical hit average
    for a given defense value.
    Parameters : d4, zone=3
*/
/*  Exit status
    0 : Ok
    1 : Cannot open res.txt
*/
int critDefPrint(int def){
    FILE *fp;
    fp=fopen("res.txt", "at");
    if(fp == NULL){
        printf("Cannot open res.txt.\n");
        return(1);
    }
    fprintf(fp,"%f\n",critAverage(4,3,def));
    return(0);
}

int main(){
    clearFile();
    int def,maxDef=20;
    for(def=0;def<=maxDef;def++){
        critDefPrint(def);
    }
    return(0);
}
avatar
Apeiron
Grand Inquisiteur de la Cohérence
Grand Inquisiteur de la Cohérence

Masculin Nombre de messages : 5471
Age : 29
Date d'inscription : 09/11/2008

Voir le profil de l'utilisateur

Revenir en haut Aller en bas

Re: Dés et game-design

Message  Apeiron le Ven 13 Fév - 18:16

Test sur les relances

Comme j'étais bien lancé j'ai décidé d'enchaîner sur les relances [0] :

Code:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

/*  Exit status
    0 : Ok
    1 : Cannot open res.txt
*/
int clearFile(){
    FILE *fp;
    fp=fopen("res.txt", "w");
    if(fp == NULL){
        printf("Cannot open res.txt.\n");
        return(1);
    }
    fclose(fp);
    return(0);
}

int max(int n1,int n2){
    if(n1>n2)return(n1);
    else return(n2);
}

float critAverage(int dSize,int critZone,int def){
    float average=0.0;
    if(critZone<0||dSize-1<critZone){
        printf("Critical zone out of bounds.\n");
        return(average);
    }
    int rolls=100000;
    int i,sum,roll,damage;
    srand(time(NULL));
    for(i=0;i<rolls;i++){
        sum=0;
        do {
            roll=1+rand()%dSize;
            sum=sum+roll;
        } while(roll>=dSize-critZone+1);
        damage=max(sum-def,0);
        average=average+(float)damage/(float)rolls;
    }
    return(average);
}

/*  Print all the critical hit average
    for all the critical zones of a given dice size.
*/
/*  Exit status
    0 : Ok
    1 : Dice size is too small
    2 : Cannot open res.txt
*/
int critPrint(int dSize){
    if(dSize<2){
        printf("Dice size too small.\n");
        return(1);
    }
    FILE *fp;
    fp=fopen("res.txt", "at");
    if(fp == NULL){
        printf("Cannot open res.txt.\n");
        return(2);
    }
    int critZone;
    fprintf(fp,"Critical hits for d%d:\n",dSize);
    for(critZone=0;critZone<dSize;critZone++){
        fprintf(fp,"%f\n",critAverage(dSize,critZone,0));
    }
    fprintf(fp,"\n");
    fclose(fp);
    return(0);
}

/*  Print the critical hit average
    for a given defense value.
    Parameters : d4, zone=3
*/
/*  Exit status
    0 : Ok
    1 : Cannot open res.txt
*/
int critDefPrint(int def){
    FILE *fp;
    fp=fopen("res.txt", "at");
    if(fp == NULL){
        printf("Cannot open res.txt.\n");
        return(1);
    }
    fprintf(fp,"%f\n",critAverage(4,3,def));
    return(0);
}

/*  reroll is
    0 : no reroll
    1 : can choose to reroll
    2 : better of two rolls
*/
float rerollAverage(int dSize,int reroll,int def){
    float average=0.0;
    if(dSize%2!=0){
        printf("Dice size must be even!\n");
        return(average);
    }
    if(reroll<0||2<reroll){
        printf("Wrong argument for reroll.\n");
        return(average);
    }
    int rolls=100000;
    int i,roll,damage;
    srand(time(NULL));
    for(i=0;i<rolls;i++){
        roll=1+rand()%dSize;
        if(reroll==1){
            if(roll<=dSize/2){
                roll=1+rand()%dSize;
            }
        }
        if(reroll==2){
            roll=max(roll,1+rand()%dSize);
        }
        damage=max(roll-def,0);
        average=average+(float)damage/(float)rolls;
    }
    return(average);
}

/*  Print all the reroll hit average
    for all the available reroll options
*/
/*  Exit status
    0 : Ok
    1 : Dice size is too small
    2 : Cannot open res.txt
*/
int rerollPrint(int dSize){
    if(dSize<2){
        printf("Dice size too small.\n");
        return(1);
    }
    FILE *fp;
    fp=fopen("res.txt", "at");
    if(fp == NULL){
        printf("Cannot open res.txt.\n");
        return(2);
    }
    int reroll;
    fprintf(fp,"Reroll hits for d%d:\n",dSize);
    for(reroll=0;reroll<=2;reroll++){
        fprintf(fp,"%f\n",rerollAverage(dSize,reroll,0));
    }
    fprintf(fp,"\n");
    fclose(fp);
    return(0);
}

int main(){
    clearFile();
    int dSize;
    for(dSize=4;dSize<=12;dSize=dSize+2){
        rerollPrint(dSize);
    }
    return(0);
}

Le principe est simple :
- pas de relance : on lance une fois le dé
- relance faible : on peut choisir de relancer le dé [1]
- relance forte : on prend le meilleur de deux lancers [2]

J'ai obtenu les valeurs suivantes :



Il est à noter que la relance forte n'est pas tellement plus forte que la relance faible, sans compter que la relance faible laisse faussement du choix.
Plus grave encore : avoir la capacité de relance forte ne fait qu'une augmentation d'environ 30% des dégâts, c'est à dire une augmentation proportionnellement équivalent à passer de 0 à 1 sur la zone de critique d'un d4.
Enfin, les dégâts moyens des deux stratégies devraient être équivalents, pour que les deux profils se distinguent sur la volatilité.
J'ai donc modifié mon code pour que le résultat soit choisi parmi n dés au lieu de juste 2 :

Code:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

/*  Exit status
    0 : Ok
    1 : Cannot open res.txt
*/
int clearFile(){
    FILE *fp;
    fp=fopen("res.txt", "w");
    if(fp == NULL){
        printf("Cannot open res.txt.\n");
        return(1);
    }
    fclose(fp);
    return(0);
}

int max(int n1,int n2){
    if(n1>n2)return(n1);
    else return(n2);
}

float critAverage(int dSize,int critZone,int def){
    float average=0.0;
    if(critZone<0||dSize-1<critZone){
        printf("Critical zone out of bounds.\n");
        return(average);
    }
    int rolls=100000;
    int i,sum,roll,damage;
    srand(time(NULL));
    for(i=0;i<rolls;i++){
        sum=0;
        do {
            roll=1+rand()%dSize;
            sum=sum+roll;
        } while(roll>=dSize-critZone+1);
        damage=max(sum-def,0);
        average=average+(float)damage/(float)rolls;
    }
    return(average);
}

/*  Print all the critical hit average
    for all the critical zones of a given dice size.
*/
/*  Exit status
    0 : Ok
    1 : Dice size is too small
    2 : Cannot open res.txt
*/
int critPrint(int dSize){
    if(dSize<2){
        printf("Dice size too small.\n");
        return(1);
    }
    FILE *fp;
    fp=fopen("res.txt", "at");
    if(fp == NULL){
        printf("Cannot open res.txt.\n");
        return(2);
    }
    int critZone;
    fprintf(fp,"Critical hits for d%d:\n",dSize);
    for(critZone=0;critZone<dSize;critZone++){
        fprintf(fp,"%f\n",critAverage(dSize,critZone,0));
    }
    fprintf(fp,"\n");
    fclose(fp);
    return(0);
}

/*  Print the critical hit average
    for a given defense value.
    Parameters : d4, zone=3
*/
/*  Exit status
    0 : Ok
    1 : Cannot open res.txt
*/
int critDefPrint(int def){
    FILE *fp;
    fp=fopen("res.txt", "at");
    if(fp == NULL){
        printf("Cannot open res.txt.\n");
        return(1);
    }
    fprintf(fp,"%f\n",critAverage(4,3,def));
    return(0);
}

float rerollAverage(int dSize,int reroll,int def){
    float average=0.0;
    if(dSize%2!=0){
        printf("Dice size must be even!\n");
        return(average);
    }
    int rolls=100000;
    int i,j,roll,damage;
    srand(time(NULL));
    for(i=0;i<rolls;i++){
        roll=1+rand()%dSize;
        for(j=0;j<reroll;j++){
            roll=max(roll,1+rand()%dSize);
        }
        damage=max(roll-def,0);
        average=average+(float)damage/(float)rolls;
    }
    return(average);
}

/*  Print all the reroll hit average
    for all the available reroll options
*/
/*  Exit status
    0 : Ok
    1 : Dice size is too small
    2 : Cannot open res.txt
*/
int rerollPrint(int dSize){
    if(dSize<2){
        printf("Dice size too small.\n");
        return(1);
    }
    FILE *fp;
    fp=fopen("res.txt", "at");
    if(fp == NULL){
        printf("Cannot open res.txt.\n");
        return(2);
    }
    int reroll;
    fprintf(fp,"Reroll hits for d%d:\n",dSize);
    for(reroll=0;reroll<=3;reroll++){
        fprintf(fp,"%f\n",rerollAverage(dSize,reroll,0));
    }
    fprintf(fp,"\n");
    fclose(fp);
    return(0);
}

int main(){
    clearFile();
    int dSize;
    for(dSize=4;dSize<=12;dSize=dSize+2){
        rerollPrint(dSize);
    }
    return(0);
}

L'inconvénient de cette optique, de même que pour les chaînes de critique, est bien sûr d'augmenter le nombre de lancers de dés.
Mais dans la mesure où cela ajoute un effet dramatique (combo pour les critiques et contrôle pour les relances) conforme à la stratégie choisie par le joueur, je pense que cela est bénéfique à l'amusement général.
Bien sûr il faut garantir des tours assez courts, mais le fait que le système soit en différentiel fait qu'on a tendance à davantage se préoccuper du lancer des autres.
J'ai donc obtenu les valeurs suivantes :



Avec trois relances on obtient les 10 dégâts moyens.
Il faut toutefois remarquer que contrairement aux critiques ici le rendement d'une amélioration est décroissant, ce qui peut donner un sentiment d'injustice [3]. En contrepartie même à bas niveau ou avec des armes plus petites les dégâts restent conséquents, contrairement aux critiques qui ne fonctionnent bien qu'à haut niveau et avec uniquement les d4.

Il ne reste plus qu'à montrer les dégâts pour un d12 avec trois relances sur des défenses allant de 0 à 20 :



Bien sûr cela est problématique car à l'heure actuelle la courbe du barbare serait toujours en dessous de celle de l'assassin. Mais l'équilibrage est pour plus tard [4] !
En attendant on a bien le comportement voulu, à savoir que l'assassin continue à faire des dégâts même contre les boss (possédant beaucoup de défense) contrairement au barbare qui lui devrait être meilleur pour les créatures moyennes.
Avant de passer à l'équilibrage il faut le comparer avec le ranger qui lui devra briller avec des attaques multiples sur de petites créatures.

En attendant, le code final est :

Code:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

/*  Exit status
    0 : Ok
    1 : Cannot open res.txt
*/
int clearFile(){
    FILE *fp;
    fp=fopen("res.txt", "w");
    if(fp == NULL){
        printf("Cannot open res.txt.\n");
        return(1);
    }
    fclose(fp);
    return(0);
}

int max(int n1,int n2){
    if(n1>n2)return(n1);
    else return(n2);
}

float critAverage(int dSize,int critZone,int def){
    float average=0.0;
    if(critZone<0||dSize-1<critZone){
        printf("Critical zone out of bounds.\n");
        return(average);
    }
    int rolls=100000;
    int i,sum,roll,damage;
    srand(time(NULL));
    for(i=0;i<rolls;i++){
        sum=0;
        do {
            roll=1+rand()%dSize;
            sum=sum+roll;
        } while(roll>=dSize-critZone+1);
        damage=max(sum-def,0);
        average=average+(float)damage/(float)rolls;
    }
    return(average);
}

/*  Print all the critical hit average
    for all the critical zones of a given dice size.
*/
/*  Exit status
    0 : Ok
    1 : Dice size is too small
    2 : Cannot open res.txt
*/
int critPrint(int dSize){
    if(dSize<2){
        printf("Dice size too small.\n");
        return(1);
    }
    FILE *fp;
    fp=fopen("res.txt", "at");
    if(fp == NULL){
        printf("Cannot open res.txt.\n");
        return(2);
    }
    int critZone;
    fprintf(fp,"Critical hits for d%d:\n",dSize);
    for(critZone=0;critZone<dSize;critZone++){
        fprintf(fp,"%f\n",critAverage(dSize,critZone,0));
    }
    fprintf(fp,"\n");
    fclose(fp);
    return(0);
}

/*  Print the critical hit average
    for a given defense value.
    Parameters : d4, zone=3
*/
/*  Exit status
    0 : Ok
    1 : Cannot open res.txt
*/
int critDefPrint(int def){
    FILE *fp;
    fp=fopen("res.txt", "at");
    if(fp == NULL){
        printf("Cannot open res.txt.\n");
        return(1);
    }
    fprintf(fp,"%f\n",critAverage(4,3,def));
    fclose(fp);
    return(0);
}

float rerollAverage(int dSize,int reroll,int def){
    float average=0.0;
    if(dSize%2!=0){
        printf("Dice size must be even!\n");
        return(average);
    }
    int rolls=100000;
    int i,j,roll,damage;
    srand(time(NULL));
    for(i=0;i<rolls;i++){
        roll=1+rand()%dSize;
        for(j=0;j<reroll;j++){
            roll=max(roll,1+rand()%dSize);
        }
        damage=max(roll-def,0);
        average=average+(float)damage/(float)rolls;
    }
    return(average);
}

/*  Print all the reroll hit average
    for all the available reroll options
*/
/*  Exit status
    0 : Ok
    1 : Dice size is too small
    2 : Cannot open res.txt
*/
int rerollPrint(int dSize){
    if(dSize<2){
        printf("Dice size too small.\n");
        return(1);
    }
    FILE *fp;
    fp=fopen("res.txt", "at");
    if(fp == NULL){
        printf("Cannot open res.txt.\n");
        return(2);
    }
    int reroll;
    fprintf(fp,"Reroll hits for d%d:\n",dSize);
    for(reroll=0;reroll<=3;reroll++){
        fprintf(fp,"%f\n",rerollAverage(dSize,reroll,0));
    }
    fprintf(fp,"\n");
    fclose(fp);
    return(0);
}

/*  Print the reroll hit average
    for a given defense value.
    Parameters : d12, reroll=3
*/
/*  Exit status
    0 : Ok
    1 : Cannot open res.txt
*/
int rerollDefPrint(int def){
    FILE *fp;
    fp=fopen("res.txt", "at");
    if(fp == NULL){
        printf("Cannot open res.txt.\n");
        return(1);
    }
    fprintf(fp,"%f\n",rerollAverage(12,3,def));
    fclose(fp);
    return(0);
}

int main(){
    clearFile();
    int def,maxDef=20;
    for(def=0;def<=maxDef;def++){
        rerollDefPrint(def);
    }
    return(0);
}

--------------------------------------------------

[0] Mon code est probablement optimisable mais flemme.

[1] Rationnellement cela arrive lorsqu'on a fait un résultat sous la moyenne, ce que j'ai implémenté ici. Il est à noter que j'ai supposé dans le code que le nombre de faces était pair.

[2] Entretemps DD5 l'a implémenté dans son système d'avantage, avec la contrepartie de parfois prendre le pire des deux (désavantage), possibilité que je n'utilise pas ici.

[3] Qu'on peut corriger par les coûts des compétences : ainsi pour un assassin la montée en niveau sera de plus en plus chère alors que pour un barbare elle le sera de moins en moins, ceci afin d'équilibrer pour un même niveau les classes entre elles.

[4] Je pense passer par des bonus de caractéristiques. Ainsi à haut niveau le barbare jouera sur une grande force/endurance alors que l'assassin devra investir dans les compétences, ce qui me semble le plus correct au niveau du flavor.
avatar
Apeiron
Grand Inquisiteur de la Cohérence
Grand Inquisiteur de la Cohérence

Masculin Nombre de messages : 5471
Age : 29
Date d'inscription : 09/11/2008

Voir le profil de l'utilisateur

Revenir en haut Aller en bas

Re: Dés et game-design

Message  Apeiron le Dim 29 Mar - 3:31

Note de lecture

Après avoir lu cet article, je me suis rendu de plusieurs points...

Tout d'abord je l'ai lu suite à une discussion avec Nimeroni qui me soutenait qu'une relance sur 1d20 équivalait à un +5 ou +6, alors que j'estimais plutôt cela à un +3 ou +4. La moyenne de prendre le max sur 2d20 est de 13.825, donc le gain était effectivement de +3.325 en moyenne.

Toutefois, et c'est là où Nimé voulait en venir, avec juste un d20 il y a 50% de chance de dépasser 10, alors que pour une relance il y a 50% de chance de dépasser 15. Ainsi, pour les valeurs intermédiaires une relance semble effectivement correspondre à un +5.

Évidemment les deux points de vue sont justes dans la mesure où le +3.325 n'est qu'en moyenne, et donc qu'il peut y avoir +4 ou +5 pour les valeurs intermédiaires, mais seulement un +1 ou un +2 pour les valeurs extrêmes. Cela pouvait d'ailleurs se deviner au fait que les deux courbes n'ont pas la même forme.

J'ai justifié cette différence de point de vue en disant que D&D avait un système de toucher, contrairement à Edad qui ne s'appuie que sur un différentiel de dégâts, mais je pense que c'était une erreur. Le meilleur témoignage en est que j'ai moi-même travaillé plus haut sur les valeurs à valeur de défense donnée.
C'est juste que les deux systèmes ont intégré une notion de variance, mais que l'approche n'est pas tout à fait la même :

1) Dans D&D les tours devraient être rapides puisque dans l'attaque il n'y a que le toucher et les dés de dégâts, lancés généralement en même temps. Il peut y avoir un trade-off toucher/dégâts qui pour une même moyenne de dégâts permettrait de toucher plus souvent ou de faire plus mal, ce qui importe essentiellement selon la CA en face.

2) Dans Edad la notion de toucher disparaît au profit d'un différentiel de dégâts, et la CA adverse n'est que sa valeur d'attaque. La variance est intégrée sous forme de critique ou de relance de dés, ce qui de fait rallonge la durée des tours.

Je trouve frustrant dans D&D qu'il y ait des tours où on lance les dés mais qu'il ne se passe rien, et je préfère que les tours soient plus longs que d'avoir de temps à autre des tours vides. Je ne suis pas sûr que ce soit le cas de tous les joueurs néanmoins. De plus, le fait d'avoir un système séquentiel pour D&D et simultané pour Edad influence énormément le ressenti.

Tout cela pour conclure qu'en réalité les deux systèmes ont le même type de variance [0], mais que la différence réside en réalité dans la notion de pas de temps.

Sinon, pour rester dans le flavor, selon moi l'aspect simultané et le différentiel sont davantage conforme au sentiment de combat au corps à corps que réussir son test de toucher et donner directement les dégâts. En fait, je trouve que le flavor de D&D correspond davantage au combat à distance.

Dans la mesure où j'ai déjà supposé que les attaques (sauf bouclier) ne comptaient pas pour contrer les flèches [1] cela revient de facto à ce que la défense fixe (utilisable même en étant surpris) soit de fait une CA contre les projectiles.

Peut-être qu'il faudrait envisager une solution mixte [2] pour le combat à distance ?
Restent bien sûr :
1) le problème de rendre simultané la visée (par un système de tuiles portant le numéro d'un personnage, au lieu des directions d'attaque ?)
2) ainsi que la gestion des lignes de vue quand le tir a lieu pendant un déplacement adverse (comme pour les autres attaques ? ou alors simplement ignoré ?)

Ignorer la défense adverse est fort, donc ne pas toucher à tous les coups peut être un prix acceptable.
De plus, cela permettrait d'introduire un gameplay vraiment différent des autres, qui pourrait justifier les arrangements nécessaires à tenter de maintenir un système simultané.
Le système sera plus complexe, naturellement, mais dan la mesure où j'ai déjà enlevé l'inutile et que les mécanismes sont intéressants et correspondent au flavor, il n'y a pas lieu de se priver.

[edit] Les magiciens eux peuvent avoir un temps d'incantation au lieu d'un système de toucher ? Enfin, sauf mages de bataille... Horlogers : synchro des temps d'incantation (Tzolk'in).

------------------------------------------------

[0] Je néglige ici le cumul des mécanismes de variances à la fois dans le toucher et dans les dégâts.

[1] Ou les balles... Kenshin, sors de ce corps !

[2] Une réhabilitation du d20, que je n'utilisais pas dans le système jusqu'à maintenant ? Pas sûr... la défense passive est assez basse...
En passant faire intervenir la distance est une mauvaise idée car :
1) un malus lié à la distance inciterait les archers à tirer toujours à bout portant... mais dans la mesure où ils ne peuvent se défendre ils restent très vulnérables, donc en fait le trade-off peut être stratégiquement intéressant.
2) la notion de longue distance peut être juste inutilement pénible.
Je rappelle que pour les archers je vois trois types de dons possibles : tir de précision, tir à bout portant, tirs multiples (version distance du ranger), sans compter des effets de la forme tir en mouvement (incluant la monte).
avatar
Apeiron
Grand Inquisiteur de la Cohérence
Grand Inquisiteur de la Cohérence

Masculin Nombre de messages : 5471
Age : 29
Date d'inscription : 09/11/2008

Voir le profil de l'utilisateur

Revenir en haut Aller en bas

Re: Dés et game-design

Message  Contenu sponsorisé


Contenu sponsorisé


Revenir en haut Aller en bas

Voir le sujet précédent Voir le sujet suivant Revenir en haut

- Sujets similaires

 
Permission de ce forum:
Vous ne pouvez pas répondre aux sujets dans ce forum