BESNIER Benjamin MOLION Enzo

Q1 : Simulation sans mémoire

basicSize = 50000 ; # Number of games in classic case 
plotSize = 10000 ; # Number of games in case of ploting for all x and y in [0;1] by 0.1 steps

On construit une fonction buildBounds(probabiltyVector) qui crée une liste de vecteurs bounds de façon à ce que bounds[[i]] donne un intervalle de taille probabilityVector[i] et que pour tout i ≠ j, bounds[[i]] et bounds[[j]] n’ont aucun élément en commun en dehors, éventuellement, de leur bornes.
L’entrée doit être de la forme (a, b, c)a est la probabilité du premier évènement, b la probabilité du deuxième évènement et c la probabilité du troisième évènement. Nécessairement, a + b + c = 1.

buildBounds = function(probabilityVector){
  if (sum(probabilityVector) != 1){
    stop("Make sure that ProbabilityVector is really a probability vector (ie sum = 1)");
  }
  bounds = list();
  bounds[[1]] = c(0, probabilityVector[1]);
  bounds[[2]] = c(probabilityVector[1], probabilityVector[1] + probabilityVector[2]);
  bounds[[3]] = c(probabilityVector[1] + probabilityVector[2], 1); 
  return(bounds)
}

# Correct case
# buildBounds(c(1/4, 1/4, 1/2));
# sum < 1 case
# buildBounds(c(1/4, 1/4, 1/4));
# sum > 1 case 
# buildBounds(c(1/2, 1/2, 1/2));

On construit la fonction fromR01toPFC qui, à partir d’un réel appartenant à [0;1] et d’une liste de vecteurs bounds (créée avec buildBounds), donne le résultat correspondant Pierre, Feuille ou Ciseaux.
Exemple : bounds = [[0 ; 0.5], [0.5 ; 0.75], [0.75 ; 1]]
- x = 0.1 ⇒ fromR01toPFC(x, bounds) = P
- x = 0.63 ⇒ fromR01toPFC(x, bounds) = F
- x = 0.98 ⇒ fromR01toPFC(x, bounds) = C

inInterval = function(x, interval) {
  return(x >= interval[1] && x <= interval[2]);
}

fromR01toPFC = function(x, bounds){
  if(inInterval(x, bounds[[1]])){
    return("P");
  }
  else if (inInterval(x, bounds[[2]])){
    return("F");
  }
  else if (inInterval(x, bounds[[3]])){
    return("C");
  }
  else {
    stop("Decision number not in bounds");
  }
}

On construit la fonction buildPlaysVector qui construit,à partir d’une liste de vecteurs bounds construite avec buildBounds et d’un entier size donnés, le vecteur de taille size correspondant à size choix successifs de jeu.
Exemple buildPlaysVector(3, bounds) = [F, P, C] => le joueur fera 3 parties et jouera successivement Feuille, Pierre puis Ciseaux.
On peut se permettre d’avoir une fonction construisant à priori l’ensemble des choix successifs seulement parce que le choix se fait sans mémoire des choix précédents.

buildPlaysVector = function(size, bounds) {
  runifVector = runif(size);
  return(sapply(runifVector, fromR01toPFC, bounds = bounds));
}

# Test
# print(buildPlaysVector(10, buildBounds(c(1/3, 1/3, 1/3))));

On construit ensuite une fonction qui renvoie le résultat d’une partie :
L’entrée se fait sous la forme getWinner(A, B)A et B sont de la forme respectivement "P", "F" ou "C" si le joueur a joué respectivement Pierre, Feuille ou Ciseaux.
Le résultat est 0 si la partie est nulle, -1 si A a gagné la partie et 1 si B a gagné la partie.

getWinner = function(aChoice, bChoice) {
  if(aChoice == "P"){
    if(bChoice == "P"){return(0);}
    else if(bChoice == "F"){return(1);}
    else if(bChoice == "C"){return(-1);}
  }
  else if(aChoice == "F"){
    if(bChoice == "P"){return(-1);}
    else if(bChoice == "F"){return(0);}
    else if(bChoice == "C"){return(1);}
  }
  else if(aChoice == "C"){
    if(bChoice == "P"){return(1);}
    else if(bChoice == "F"){return(-1);}
    else if(bChoice == "C"){return(0);}
  }
}

# Tie case
# getWinner("C", "C");
# A winning case
# getWinner("F", "P");
# B winning case
# getWinner("C", "P");

On construit la fonction buildResultVector(n, Pa, Pb) qui à partir des vecteurs de probabilités Pa et Pb de deux joueurs, donne le vecteur indiquant les résultats de n parties.

# used https://stackoverflow.com/questions/35352647/r-apply-on-a-function-with-two-variables to know how to use mapply in addition to its documentation
buildResultVector = function(size, Pa, Pb){ 
  
  apv = buildPlaysVector(size, buildBounds(Pa));
  bpv = buildPlaysVector(size, buildBounds(Pb));

  return(mapply(getWinner, apv, bpv));
}

# Test
# Pa = c(1/4,1/4,1/2);
# Pb = c(1/4,1/4,1/2);
# cat("Result vector of size 10 given Pa and Pb : [", buildResultVector(10, Pa, Pb), "]");

On construit la fonction expectedValue(n, Pa, Pb) qui renvoie l’espérance de gain de B jouant n parties avec le vecteur de probabilité Pb contre A de vecteur de probabilité Pb.

expectedValue = function(size, Pa, Pb) {
  return(sum(buildResultVector(size, Pa, Pb))/size);
}

# Test
# Pa = c(1/4,1/4,1/2);
# Pb = c(1/4,1/4,1/2);
# print(expectedValue(basicSize, Pa, Pb));

On construit la fonction allPlotsForXYin01(size, Pa) qui affiche tous les graphes ayant pour ordonnée l’espérance de gain estimée sur size parties opposant deux joueurs ayant pour vecteurs de probabilité Pa et (x, y, 1 - x - y) et pour abscisse y, pour x fixé. Elle affiche également (x, y) tels que l’espérance correspondant soit maximale.

allPlotsForXYin01 = function(size, Pa, ymin = -1, ymax = 1){
  expectedValueVector = c();
  abscissa = c();
  
  maxExpectedValue = -Inf;
  maxExpectedValueX = -Inf;
  maxExpectedValueY = -Inf;
  
  for(x in 0.1 * (0:10)){
    
    yindex = 1 ;
    for(y in 0.1 * (0:10)){
      
      # Assert that (x, y, 1 - x - y) is a valid probability vector
      if(x + y <= 1){
        abscissa[yindex] = y ;
        expectedValueVector[yindex] = expectedValue(size, Pa, c(x, y, 1 - x - y));
        # Save x and y s.t. E(X) is maximal
        if(expectedValueVector[yindex] > maxExpectedValue){
          maxExpectedValue = expectedValueVector[yindex] ;
          maxExpectedValueY = y ;
          maxExpectedValueX = x ;
        }
      }
      else{
        expectedValueVector[yindex] = -Inf ;
      }
      yindex = yindex + 1 ;
    }

    plot(x = abscissa, y = expectedValueVector, ylim = c(ymin, ymax), xlab = "y", ylab = "Espérance", main = paste("Esperance en fonction de y pour x = ", x, sep = ""));
  }
  cat(paste("L'espérance est maximale pour (x = ", maxExpectedValueX, ", y = ", maxExpectedValueY, ") et vaut : ", maxExpectedValue, sep = ""));
}

Le joueur biaisé

1.

Pa = c(1/4,1/4,1/2);
Pb = c(1/4,1/4,1/2);
print(expectedValue(basicSize, Pa, Pb));
## [1] 0.00398

2.

Pa = c(1/4,1/4,1/2);
Pb = c(1/3,1/3,1/3);
print(expectedValue(basicSize, Pa, Pb));
## [1] 0.00254

3.

Pa = c(1/4, 1/4, 1/2);
allPlotsForXYin01(plotSize, Pa);

## L'espérance est maximale pour (x = 1, y = 0) et vaut : 0.245

4.

On en déduit que la meilleure stratégie pour le joueur B lorsque \(P^{(A)} = (\frac{1}{4}, \frac{1}{4}, \frac{1}{2})\) est d’adopter le vecteur probabilité \(P^{(A)} = (1, 0, 0)\), soit de jouer systématiquement Pierre.

5.

Soient \(x_i\) avec \(i \in [\![1 ; 3]\!]\) tels que
\(x_1\) l’évènement “B gagne la partie”,
\(x_2\) l’évènement “la partie se solde par une égalité”,
\(x_3\) l’évènement “B perd la partie”.

Soit \(C\) = {\(Pierre, Feuille, Ciseau\)} l’ensemble des choix possibles et \(J\) = {\(A, B\)} l’ensemble des joueurs. Alors on note “\(c_j\), \((c \in C, j \in J)\) le joueur \(j\) joue le choix \(c\)”.

On a alors pour \(P^{(B)} = (\frac{1}{4}, \frac{1}{4}, \frac{1}{2})\) :
\(P(X = x_1) = Pierre_{B} \times Ciseau_{A} + Feuille_{B} \times Pierre_{A} + Ciseau_{B} \times Feuille_{A} = \frac{1}{4} \times \frac{1}{2} + \frac{1}{4} \times \frac{1}{4} + \frac{1}{2} \times \frac{1}{4} = 0.3125\)
De même,
\(P(X = x_2) = \frac{1}{4} \times \frac{1}{4} + \frac{1}{4} \times \frac{1}{4} + \frac{1}{2} \times \frac{1}{2} = 0.375\)
\(P(X = x_3) = \frac{1}{4} \times \frac{1}{4} + \frac{1}{4} \times \frac{1}{2} + \frac{1}{2} \times \frac{1}{4} = 0.3125\)

Comme on joue en version à somme nulle, on a \(x_{1} = 1\), \(x_{2} = 0\), \(x_{3} = -1\).

On rappelle la formule de l’espérance : \[\mathbb{E}(X) = \sum\limits_{i=1}^3 x_{i} \times P(X=x_{i})\]

On obtient \(\mathbb{E}(X) = \sum\limits_{i=1}^3 x_{i} \times P(X=x_{i}) = 1 \times 0.3125 + 0 \times 0.375 + (-1) \times 0.3125 = 0\).

De même pour \(P^{(B)} = (\frac{1}{3}, \frac{1}{3}, \frac{1}{3})\), on obtient \(\mathbb{E}(X) = 0\).

Pour la formule générale \(P^{(B)} = (x, y, 1-x-y)\), on obtient \(\mathbb{E}(X) = 1 \times (x \times \frac{1}{2} + y \times \frac{1}{4} + (1 - x - y) \times \frac{1}{4}) - 1 \times (x \times \frac{1}{4} + y \times \frac{1}{2} + (1 - x -y) \times \frac{1}{4}) = \frac{1}{4} \times x - \frac{1}{4} \times y\).

Afin de maximiser l’espérance \(\mathbb{E}(X)\), il convient de maximiser \(x\) et de minimiser \(y\).
La valeur extrême pouvant être prise par \((x, y)\) est \((1, 0)\), ce qui correspond à \(P^{(B)} = (1, 0, 0)\).

On peut déduire que pour maximiser ses victoires, le joueur B doit systématiquement jouer Pierre (ce qui est assez intuitif car le joueur A joue la plupart du temps Ciseau, qui est battu par Pierre).

Le joueur non biaisé

1.

Pa = c(1/3,1/3,1/3);
Pb = c(1/4,1/4,1/2);
print(expectedValue(basicSize, Pa, Pb));
## [1] -0.0013

2.

Pa = c(1/3,1/3,1/3);
Pb = c(1/3,1/3,1/3);
print(expectedValue(basicSize, Pa, Pb));
## [1] -0.0021

3.

Pa = c(1/3, 1/3, 1/3);
allPlotsForXYin01(plotSize, Pa, ymin = -0.2, ymax = 0.2);

## L'espérance est maximale pour (x = 0.3, y = 0.5) et vaut : 0.0164

4.

Il semble que le choix du vecteur de probabilité n’influe pas sur l’espérance de gain et que celle-ci se trouve systématiquement réduite à 0.

5.

On obtient, en tenant un raisonnement analogue au cas où A était biaisé, pour \(P^{(B)} = (\frac{1}{4}, \frac{1}{4}, \frac{1}{2})\) :
\(P(X = x_1) = \frac{1}{4} \times \frac{1}{3} + \frac{1}{4} \times \frac{1}{3} + \frac{1}{2} \times \frac{1}{3} = \frac{1}{3}\)
\(P(X = x_2) = \frac{1}{4} \times \frac{1}{3} + \frac{1}{4} \times \frac{1}{3} + \frac{1}{2} \times \frac{1}{3} = \frac{1}{3}\)
\(P(X = x_3) = \frac{1}{4} \times \frac{1}{3} + \frac{1}{4} \times \frac{1}{3} + \frac{1}{2} \times \frac{1}{3} = \frac{1}{3}\)

Et ainsi, \(\mathbb{E}(X) = 1 \times \frac{1}{3} + 0 \times \frac{1}{3} + (-1) \times \frac{1}{3} = 0\)

De même, avec \(P^{(B)} = (\frac{1}{3}, \frac{1}{3}, \frac{1}{3})\), on obtient :
\(P(X = x_1) = \frac{1}{3} \times \frac{1}{3} + \frac{1}{3} \times \frac{1}{3} + \frac{1}{2} \times \frac{1}{3} = \frac{1}{3}\)
\(P(X = x_2) = \frac{1}{3} \times \frac{1}{3} + \frac{1}{3} \times \frac{1}{3} + \frac{1}{3} \times \frac{1}{3} = \frac{1}{3}\)
\(P(X = x_3) = \frac{1}{3} \times \frac{1}{3} + \frac{1}{3} \times \frac{1}{3} + \frac{1}{3} \times \frac{1}{3} = \frac{1}{3}\)

Et ainsi, \(\mathbb{E}(X) = 1 \times \frac{1}{3} + 0 \times \frac{1}{3} + (-1) \times \frac{1}{3} = 0\)

Pour la formule générale \(P^{(B)} = (x, y, 1-x-y)\), on obtient \(\mathbb{E}(X) = 1 \times (x \times \frac{1}{3} + y \times \frac{1}{3} + (1 - x - y) \times \frac{1}{3}) - 1 \times (x \times \frac{1}{3} + y \times \frac{1}{3} + (1 - x -y) \times \frac{1}{3}) = 0\).

Ainsi, on vérifie mathématiquement les hypothèses de la réponse à la question 4. : l’espérance de gain d’un joueur non biaisé est de 0 quel que soit son adversaire. Un joueur non biaisé ne peut donc espérer gagner ou perdre, à long terme celui-ci verra ses gains réduits à 0.

Q2 : Apprentissage

On construit d’abord quelques fonctions “outils” (assez triviales) utiles plus tard.

learningExpectedValue = function(resultVector){
  return(sum(resultVector)/length(resultVector));
}

buildLearningResultVector = function(apv, bpv) {
    return(mapply(getWinner, apv, bpv));
}

buildLearningCumulativeVector = function(resultVector){
  cumulativeVector = c(); 
  for(i in 1:length(resultVector)){
    cumulativeVector[i] = learningExpectedValue(resultVector[1:i]);
  }
  return(cumulativeVector) ;
}

# gives one game choice given a probabilityVector
nextPlay = function(probabilityVector) {
  buildPlaysVector(1, buildBounds(probabilityVector));
}

1. Machine apprenante optimale

On fait la supposition qu’un humain est incapable d’équilibrer ses fréquences de jeu et donc qu’à long terme son vecteur de probabilité tendra vers \(P \neq (\frac{1}{3}, \frac{1}{3}, \frac{1}{3})\). Ainsi, il convient d’apprendre ce vecteur (en l’affinant au cours du temps) puis de jouer systématiquement le coup battant le coup le plus utilisé.

On construit la fonction buildSimpleLearningDynamicPlaysVector(opponentPlaysVector, currentIndex) qui construit le vecteur de probabilité \(P = (1, 0, 0)\) ou \(P = (0, 1, 0)\) ou \(P = (0, 0, 1)\) dont le choix à \(1\) est celui battant le coup de plus grande fréquence dans les currentIndex - 1 premiers jeux de opponentPlaysVector.

buildSimpleLearningDynamicPlaysVector = function(opponentPlaysVector, currentIndex){
    
  freqP = sum(opponentPlaysVector[1:(currentIndex - 1)] == "P") / (currentIndex - 1) ;
  freqF = sum(opponentPlaysVector[1:(currentIndex - 1)] == "F") / (currentIndex - 1) ;
  freqC = sum(opponentPlaysVector[1:(currentIndex - 1)] == "C") / (currentIndex - 1) ;
  
  if(freqP >= freqF && freqP >= freqC){
    # P is most used --> use F
    resultVector = c(0, 1, 0) ;
  } 
  else{
    if(freqF >= freqC && freqF >= freqP){
      # F is most used --> use C
      resultVector = c(0, 0, 1) ;
    }
    else{
      # C is most used --> use P
      resultVector = c(1, 0, 0) ;
    }
  }
  return(resultVector);
}

On construit la fonction buildSimpleLearningPlaysVector(learningGameSize, opponentPlaysVector, startingProbabilityVector, firstRelevantIndex) qui construit le vecteur de jeu avec apprentissage du jeu de opponentPlaysVector, de taille learningGameSize. Les firstRelevantIndex premiers coups sont choisis grâce au vecteur de probabilité startingProbabilityVector, les suivants selon la méthode décrite précédemment.

buildSimpleLearningPlaysVector = function(learningGameSize, opponentPlaysVector, startingProbabilityVector, firstRelevantIndex){
  
  playsVector = c() ;
  P = startingProbabilityVector ;
  playsVector[1] = nextPlay(P);
  
  for(i in 2:learningGameSize){
    P = buildSimpleLearningDynamicPlaysVector(opponentPlaysVector, i)
    if(i >= firstRelevantIndex){
      playsVector[i] = nextPlay(P);
    }
    else{
      playsVector[i] = nextPlay(startingProbabilityVector);
    }
  }
  
  return(playsVector);

}

On construit la fonction simpleLearningGame(learningGameSize, probabilityVector) construisant le vecteur de résultat d’une partie de taille learningGameSize entre un joueur de vecteur de probabilité probabilityVector et une machine apprenante décrite précédemment.

simpleLearningGame = function(learningGameSize, probabilityVector){
  
  startingProbabilityVector = c(1/3, 1/3, 1/3) ;
  firstRelevantIndex = 10 ; # Number of learnings before use of learnt playsVector
  
  opponentPlaysVector = buildPlaysVector(learningGameSize, bounds = buildBounds(probabilityVector)) ;
  playsVector = buildSimpleLearningPlaysVector(learningGameSize, opponentPlaysVector, startingProbabilityVector, firstRelevantIndex) ;
  return(buildLearningResultVector(opponentPlaysVector, playsVector));

} 

2. Test de la machine apprenante

Enfin, on teste cette machine en faisant learningGameSize parties contre un joueur de vecteur de probabilité probabilityVector. On affiche son espérance sur l’ensemble des parties puis l’évolution de cette espérance au cours des parties.

learningGameSize = 2000 ;
probabilityVector = c(1/4, 1/4, 1/2) ;

resultVector = simpleLearningGame(learningGameSize, probabilityVector);

lev = learningExpectedValue(resultVector);
print(lev);
## [1] 0.274
learningCumulativeVector = buildLearningCumulativeVector(resultVector);
plot(learningCumulativeVector, ylim = c(-1, 1), type = "l");

3. Évaluation de la machine apprenante et amélioration

La machine apprenante semble obtenir de bons résultats. Cependant, un humain assez perspicace pourrait aisément déduire au bout de quelques parties le schéma de jeu de celle-ci.
Ainsi, il convient d’utiliser un algorithme qui ne s’adapte pas optimalement de manière déterministe mais conserve une partie de hasard dans son comportement.
On propose le schéma suivant.
On fait la supposition qu’un humain est difficilement capable d’équilibrer les fréquences de jeu à long terme mais qu’il tentera de le faire sur quelques coups successifs. Ainsi, de courtes portion de son historique devraient être équilibrées.
Le principe de l’algorithme est donc d’avoir une fenêtre glissantes de quelques parties sur l’historique du joueur, d’établir grâce à celle-ci son vecteur de probabilité puis d’utiliser celui-ci comme notre vecteur de probabilité.
En effet, pour équilibrer ses choix, le joueur jouera à l’inverse de ses choix précédents donc il inversera son vecteur de probabilité. Or, un vecteur menant à la victoire contre celui-ci son inverse. On suppose qu’avant la phase d’apprentissage, on joue avec un vecteur de probabilité \(P = (\frac{1}{3}, \frac{1}{3}, \frac{1}{3})\).

On construit la fonction buildDynamicProbabilityVector(playsVector, currentIndex, windowSize) qui construit le vecteur de probabilité à utiliser (déterminé selon la méthode évoquée précédemment) d’après les currentIndex - 1 premiers jeux de playsVector et selon une taille de fenêtre d’apprentissage de taille windowSize.

buildDynamicProbabilityVector = function(playsVector, currentIndex, windowSize) {
  freqP = 0 ;
  freqF = 0 ;

  for(j in (currentIndex - windowSize):(currentIndex - 1)){
    if(playsVector[j] == "P"){
      freqP = freqP + 1 ; 
    }
    else{
      if(playsVector[j] == "F"){
        freqF = freqF + 1 ;
      }
    }
  }
  freqP = freqP / windowSize ;
  freqF = freqF / windowSize ;
  return(c(freqP, freqF, 1 - (freqP + freqF)));
}

On construit la fonction buildLearningPlaysVector(learningGameSize, learningWindowSize, opponentPlaysVector, probabilityVector) qui construit le vecteur de jeu de la nouvelle machine apprenante de taille learningGameSize d’après le vecteur de jeu opponentPlaysVector de l’adversaire, une taille de fenêtre d’apprentissage learningWindowSize et un vecteur de probabilité initial probabilityVector.

buildLearningPlaysVector = function(learningGameSize, learningWindowSize, opponentPlaysVector, probabilityVector) {
  
  playsVector = c() ;
  playsVector[1] = nextPlay(probabilityVector) ;
  
  for(i in 2:learningGameSize){
    if(i > learningWindowSize){
      # probabilityVector becomes the vector used in the `learningWindowSize` last plays
      probabilityVector = buildDynamicProbabilityVector(opponentPlaysVector, i, learningWindowSize);
    }
    playsVector[i] = nextPlay(probabilityVector) ;
  }
  return(playsVector)
}

On construit la fonction learningGame(learningGameSize, learningWindowSize, probabilityVector) construisant le vecteur de résultat d’une partie de taille learningGameSize entre un joueur de vecteur de probabilité probabilityVector et une machine apprenante décrite précédemment, de taille de fenêtre d’apprentissage learningWindowSize.

learningGame = function(learningGameSize, learningWindowSize, probabilityVector){
  startingProbabilityVector = c(1/3, 1/3, 1/3);

  opponentPlaysVector = buildPlaysVector(learningGameSize, bounds = buildBounds(probabilityVector)) ;
  playsVector = buildLearningPlaysVector(learningGameSize, learningWindowSize, opponentPlaysVector, startingProbabilityVector) ;
  return(buildLearningResultVector(opponentPlaysVector, playsVector));
}

4. Test de la nouvelle machine apprenante

Enfin, on teste cette machine en faisant learningGameSize parties contre un joueur de vecteur de probabilité probabilityVector, avec une fenêtre d’apprentissage learningWindowSize. On affiche son espérance sur l’ensemble des parties puis l’évolution de cette espérance au cours des parties.

learningGameSize = 2000 ;
learningWindowSize = 10 ;
probabilityVector = c(1/4, 1/4, 1/2) ;

learningResultVector = learningGame(learningGameSize, learningWindowSize, probabilityVector);

lev = learningExpectedValue(learningResultVector);
print(lev);
## [1] -0.006
learningCumulativeVector = buildLearningCumulativeVector(learningResultVector);
plot(learningCumulativeVector, ylim = c(-0.3, 0.3), type = "l");

On observe que cette machine obtient de moins bons résultats.
Cependant, ce résultat s’explique aisément par la nature de cette machine et de ce test : pour élaborer celle-ci, des suppositions sur la nature humaine ont été formulées et le test est conduit sur une machine de vecteur de probabilité statique et non sur un humain.

Q3 (Bonus)

Stratégie non aléatoire

En se basant sur la publication Social cycling and conditional responses in the Rock-Paper-Scissors game de Zhijian Wang, Bin Xu et Hai-Jun Zhou, illustrée dans la vidéo Winning at Rock Paper Scissors de la chaine YouTube Numberphile, on établit la stratégie non aléatoire suivante :
Si l’on pert une partie, le coup suivant sera celui n’ayant pas été joué durant la partie précédente,
Si l’on gagne une partie, le coup suivant sera celui de l’adversaire durant la partie précédente,
Si une partie se solde par un match nul, le coup suivant sera choisi aléatoirement avec un vecteur de probabilité \(P = (\frac{1}{3}, \frac{1}{3}, \frac{1}{3})\).

Il s’agit d’une version simplifiée de la première machine apprenante, on reprend donc le code de cette dernière, en dehors de la fonction buildSimpleLearningDynamicPlaysVector.

notPlayed = function(a, b) {
  if(a == "P"){
    if(b == "F"){
      # (P, F) --> C
      result = c(0, 0, 1);
    }
    else{
      # (P, C) --> F
      result = c(0, 1, 0);      
    }
  }else{
    if(a == "F"){
      if(b == "P"){
        # (F, P) --> C
        result = c(0, 0, 1);
      }
      else{
        # (F, C) --> P
        result = c(1, 0, 0);
      } 
    }
    else{
      if(b == "P"){
        # (C, P) --> F
        result = c(0, 1, 0);
      }
      else{
        # (C, F) --> P
        result = c(1, 0, 0);
      }      
    }
  }
  return(result);
}

played = function(c) {
  if(c == "P"){
    result = c(1, 0, 0);
  }
  else{
    if(c == "F"){
      result = c(0, 1, 0);      
    }
    else{
      result = c(0, 0, 1);
    }
  }
}

buildDeterministicLearningDynamicPlaysVector = function(opponentPlaysVector, playsVector, currentIndex){
  
  lastPlay = playsVector[currentIndex - 1] ;
  lastOpponentPlay = opponentPlaysVector[currentIndex - 1]  ;
  result = getWinner(lastPlay, lastOpponentPlay) ; 
    
  # Tie case
  if(result == 0){
    resultVector = c(1/3, 1/3, 1/3) ;
  }
  else {
    # Lose case
    if(result == 1){
      resultVector = notPlayed(lastPlay, lastOpponentPlay);
    }
    # Win case
    else{
      resultVector = played(lastOpponentPlay) ;
    }
  }
  return(resultVector);
}
buildDeterministicLearningPlaysVector = function(learningGameSize, opponentPlaysVector){
  
  playsVector = c() ;
  P = c() ;
  playsVector[1] = nextPlay(c(1/3, 1/3, 1/3));
  
  for(i in 2:learningGameSize){
    P = buildDeterministicLearningDynamicPlaysVector(opponentPlaysVector, playsVector, i)
    playsVector[i] = nextPlay(P);
  }
  
  return(playsVector);

}
deterministicLearningGame = function(learningGameSize, probabilityVector){
  
  startingProbabilityVector = c(1/3, 1/3, 1/3) ;
  firstRelevantIndex = 10 ; # Number of learnings before use of learnt playsVector
  
  opponentPlaysVector = buildPlaysVector(learningGameSize, bounds = buildBounds(probabilityVector)) ;
  playsVector = buildDeterministicLearningPlaysVector(learningGameSize, opponentPlaysVector) ;
  return(buildLearningResultVector(opponentPlaysVector, playsVector));

} 
learningGameSize = 2000 ;
probabilityVector = c(1/4, 1/4, 1/2) ;

resultVector = deterministicLearningGame(learningGameSize, probabilityVector);

lev = learningExpectedValue(resultVector);
print(lev);
## [1] 0.0025
learningCumulativeVector = buildLearningCumulativeVector(resultVector);
plot(learningCumulativeVector, ylim = c(-1, 1), type = "l");

Variante

On remarque qu’il suffit d’adapter les fonctions buildBounds, fromR01toPFC (rebaptisée fromR01toPFCLS) et getWinner pour obtenir la version Pierre-Feuille-Ciseaux-Lézard-Spock :

buildBounds = function(probabilityVector){
  if (sum(probabilityVector) != 1){
    stop("Make sure that ProbabilityVector is really a probability vector (ie sum = 1)");
  }
  bounds = list();
  bounds[[1]] = c(0, probabilityVector[1]);
  bounds[[2]] = c(probabilityVector[1], probabilityVector[1] + probabilityVector[2]);
  bounds[[3]] = c(probabilityVector[1] + probabilityVector[2], probabilityVector[1] + probabilityVector[2] + probabilityVector[3]);
  bounds[[4]] = c(probabilityVector[1] + probabilityVector[2] + probabilityVector[3], probabilityVector[1] + probabilityVector[2] + probabilityVector[3] + probabilityVector[4]);
  
  bounds[[5]] = c(probabilityVector[1] + probabilityVector[2] + probabilityVector[3] + probabilityVector[4], 1); 
  return(bounds)
}
inInterval = function(x, interval) {
  return(x >= interval[1] && x <= interval[2]);
}

fromR01toPFCLS = function(x, bounds){
  if(inInterval(x, bounds[[1]])){
    return("P");
  }
  else if (inInterval(x, bounds[[2]])){
    return("F");
  }
  else if (inInterval(x, bounds[[3]])){
    return("C");
  }
  else if (inInterval(x, bounds[[4]])){
    return("L");
  }
  else if (inInterval(x, bounds[[5]])){
    return("S");
  }
  else {
    stop("Decision number not in bounds");
  }
}
buildPlaysVector = function(size, bounds) {
  runifVector = runif(size);
  return(sapply(runifVector, fromR01toPFCLS, bounds = bounds));
}
getWinner = function(aChoice, bChoice) {
  if(aChoice == "P"){
    if(bChoice == "P"){return(0);}
    else if(bChoice == "F" || bChoice == "S"){return(1);}
    else if(bChoice == "C" || bChoice == "L"){return(-1);}
  }
  else if(aChoice == "F"){
    if(bChoice == "P" || bChoice == "S"){return(-1);}
    else if(bChoice == "F"){return(0);}
    else if(bChoice == "C" || bChoice == "L"){return(1);}
  }
  else if(aChoice == "C"){
    if(bChoice == "P" || bChoice == "S"){return(1);}
    else if(bChoice == "F" || bChoice == "L"){return(-1);}
    else if(bChoice == "C"){return(0);}
  }
  else if(aChoice == "L"){
    if(bChoice == "P" || bChoice == "C"){return(1);}
    else if(bChoice == "F" || bChoice == "S"){return(-1);}
    else if(bChoice == "L"){return(0);}
  }
  else if(aChoice == "S"){
    if(bChoice == "F" || bChoice == "L"){return(1);}
    else if(bChoice == "P" || bChoice == "C"){return(-1);}
    else if(bChoice == "S"){return(0);}
  }
  
}