17 votes

Combien de tours faut-il pour terminer une partie de Hi Ho! Cherry-O?

J'ai récemment acheté ce jeu pour ma fille de 2 ans, qui apprend à compter. Je trouve ce jeu assez frustrant à jouer selon les règles actuelles. Il contient un spinner avec les 7 résultats suivants:

  • Gagner 1 cerise
  • Gagner 2 cerises
  • Gagner 3 cerises
  • Gagner 4 cerises
  • Perdre 2 cerises (x2)
  • Perdre toutes les cerises

Le jeu se joue à 10 cerises.

Question: En moyenne, combien de tours faudra-t-il pour terminer le jeu?

(Edit: J'avais une autre question sur "le nombre attendu de cerises gagnées à chaque tour", mais il s'avère que c'est plutôt difficile à définir en raison de cette fichue option "perdre toutes vos cerises".)

18voto

Jorge Alves Points 400

En examinant le travail qui a été fait jusqu'à présent, il m'a semblé que c'est essentiellement juste une chaîne de Markov, et en cherchant des façons de calculer les chaînes de Markov, j'ai découvert que quelqu'un y est déjà passé par là.

Les données de son travail correspondent aux simulations ici, ce qui ne devrait pas être surprenant étant donné que les règles sont très faciles à coder:

Longueur minimale du jeu: 3
Longueur moyenne attendue du jeu: 15,8
Longueur maximale du jeu: Illimitée
50ème percentile (médiane): 12
95ème percentile: 40
25ème percentile: 7
75ème percentile: 21

Il poste également le code Matlab qu'il a utilisé pour calculer la chaîne... c'est en fait assez simple, relativement parlant.

La matrice de transition de la chaîne de Markov présentée ci-dessous répertorie les probabilités de transition d'un état à un autre (c'est-à-dire du nombre de cerises avant un tour au nombre de cerises après un tour).

          # de cerises après le tour
       0   1   2   3   4   5   6   7   8   9  10
     --------------------------------------------
  0 | 3/7 1/7 1/7 1/7 1/7  0   0   0   0   0   0
  1 | 3/7  0  1/7 1/7 1/7 1/7  0   0   0   0   0
  2 | 3/7  0   0  1/7 1/7 1/7 1/7  0   0   0   0
  3 | 1/7 2/7  0   0  1/7 1/7 1/7 1/7  0   0   0
  4 | 1/7  0  2/7  0   0  1/7 1/7 1/7 1/7  0   0
  5 | 1/7  0   0  2/7  0   0  1/7 1/7 1/7 1/7  0
  6 | 1/7  0   0   0  2/7  0   0  1/7 1/7 1/7 1/7
  7 | 1/7  0   0   0   0  2/7  0   0  1/7 1/7 2/7
  8 | 1/7  0   0   0   0   0  2/7  0   0  1/7 3/7
  9 | 1/7  0   0   0   0   0   0  2/7  0   0  3/7
 10 |  0   0   0   0   0   0   0   0   0   0   0

11voto

intrepion Points 3973

Les mathématiques pour tenir compte du fait qu'il coupe à 0 et à 10 (c'est-à-dire, tourner -2 lorsque vous êtes sur 1 vous laissera sur 0, pas sur -1) et le résultat "tout perdre" sont assez complexes.

J'ai donc opté pour une simulation. Cela a été exécuté avec 10 000 000 de simulations, ce qui devrait être plus que suffisant pour obtenir un bon résultat.

Voici un histogramme du résultat. L'axe des x montre le nombre de spins, tandis que l'axe des y montre le nombre de résultats dans la simulation qui ont pris ce nombre de spins.

Graphique montrant le nombre de spins par rapport à la fréquence, avec une moyenne de 15.8

La moyenne (moyenne), indiquée par la ligne rouge, est de 15.8.

Voici le code C# que j'ai utilisé pour générer les résultats.

using System;
using System.Linq;
using System.Collections.Generic;

public static class CherryO {
    private static Random rng = new Random();

    const int NumRuns = 10000000;
    const int NumSquares = 10;

    public static void Main() {
        int total = 0;
        Dictionary totalTurns = new Dictionary();

        for (int i = 0; i < NumRuns; i += 1) {
            int turns = GetNumTurns(NumSquares);
            total += turns;
            if (!totalTurns.ContainsKey(turns)) {
                totalTurns.Add(turns, 1);
            } else {
                totalTurns[turns] += 1;
            }
        }

        int max = totalTurns.Keys.Max();
        for (int i = 0; i <= max; i += 1) {
            if (!totalTurns.ContainsKey(i)) {
                totalTurns.Add(i, 0);
            }
            Console.Write("{0} ", i);
        }

        Console.WriteLine();

        var list = from kvPair in totalTurns
                   orderby kvPair.Value
                   select kvPair.Key;
        foreach (int i in list) {
            Console.Write("{0} ", i);
        }

        Console.WriteLine((double)total / NumRuns);
    }

    public static int GetNumTurns(int target) {
        int position = 0;
        int turns = 0;

        while (true) {
            int spin = GetSpin();
            turns += 1;

            if (spin == 0) {
                position = 0;
            } else {
                position += spin;
            }

            if (position >= target) {
                return turns;
            }

            if (position < 0) {
                position = 0;
            }
        }
    }

    public static int GetSpin() {
        int result = rng.Next(0, 7);

        if (result == 5 || result == 6) {
            return -2;
        }

        return result;
    }
}

7voto

Robin M Points 3307

J'ai écrit quelque chose pour énumérer les états pertinents et ensuite imprimer les probabilités trouvées de cette manière. De toute façon, une simulation semblait être une solution de facilité pour moi :)

Je crois que ce jeu est pour plus d'un joueur? Et cela se termine quand N'IMPORTE QUEL joueur obtient 10 cerises, n'est-ce pas? Le code et les résultats ci-dessous supposent cela.

Probabilité de la fin de Hi-Ho Cherry-O (2 joueurs)

Pour le cas à deux joueurs, j'ai obtenu un nombre de tours de fin moyen d'environ 9,56. Comme je comptais les tours à chaque fois que chaque joueur tournait une fois, cela fait deux tours par tour, donc disons que dans le cas moyen, il faut entre 19 et 20 tours pour que le jeu se termine lorsque deux personnes jouent. Il semble que la médiane soit plus basse: environ 15 tours, donc la moitié des jeux à deux joueurs devraient se terminer au 15ème tour ou avant.

La question originale donne l'impression que le cas à un joueur était envisagé, donc voici les informations pour ce cas.

Probabilité de la fin de Hi-Ho Cherry-O (1 joueur)

Pour un joueur: la longueur moyenne du jeu est de 15,8 tours et la longueur médiane du jeu est entre 11 et 12 tours (la moitié se termine avant le 11ème, la moitié après le 12ème tour).

Voici le code pour générer les données utilisées ci-dessus. Attention : cela devient vraiment lent lorsque vous ajoutez des joueurs (une durée d'exécution exponentielle en fonction du nombre de joueurs, mais seulement quadratique en termes de nombre de tours). Vous pouvez modifier les valeurs de turn_cap et num_players en haut du fichier pour ce que vous voulez découvrir. Il n'énumère que jusqu'à turn_cap, car vous ne vous souciez probablement pas au-delà. Les jeux qui se terminent plus tard sont toujours pris en compte, il n'affiche simplement pas de données pour eux.

(Python 2.5):

from copy import deepcopy
from sys import stdout
num_players = 2
turn_cap = 50 # Je suppose que tout le monde en a marre à ce stade
print_merges = False
print_num_states = False
print_turn_progress = True
ignore_score_of_finished_games = True

def flatten(list_of_lists):
    r"""aide pour aplatir une liste de listes en une seule liste"""
    return [item for sub_list in list_of_lists for item in sub_list]
def count(iterable,p=bool):
    r"""retourne pour combien d'éléments dans l'itérable le prédicat p évalue à vrai"""
    n = 0
    for item in iterable:
        if p(item):
            n += 1
    return n
def lists_the_same(l0,l1):
    return len(l0)==len(l1) and \
           all(l0[i]==l1[i] for i in range(len(l0))) 
def split_list(items,p):
    r"""divise la liste "items" en deux listes en fonction d'un prédicat "p"

    retourne un tuple. (éléments pour lesquels le prédicat est vrai, éléments pour lesquels le prédicat est faux) """
    good, bad = [], []
    for i in items:
        if p(i):
            good.append(i)
        else:
            bad.append(i)
    return good, bad
class World:
    def __init__(self, num_players=2, probability=1.0):
        self.player_scores = [0 for x in range(num_players)]
        self.probability = probability
        self.turn_number = 0
        self.ended_on_turn = None
        self.num_players = num_players
    def check_win(self):
        if any(( (score>=10) for score in self.player_scores)):
            self.ended_on_turn = self.turn_number   
    def gain_cherries(self, player_index, num_cherries):
        self.player_scores[player_index] += num_cherries
        self.check_win()
    def lose_cherries(self, player_index, num_cherries):
        self.player_scores[player_index] = max(0, self.player_scores[player_index] - num_cherries)
    def lose_all_cherries(self, player_index):
        self.player_scores[player_index] = 0
    def run_turn(self):
        r"""si ce n'est pas terminé, cela divise le monde en 7 nouveaux mondes basés sur le lancer de chaque joueur"""
        if None==self.ended_on_turn:
            self.turn_number += 1
            worlds = [self]
            for i in range(self.num_players):
                worlds = flatten([w.run_player_turn(i) for w in worlds])
            return worlds
        else:
            return [self]
    def run_player_turn(self, player_index):
        new_worlds = [deepcopy(self) for i in range(7)]
        for w in new_worlds:
            w.probability = self.probability / 7.0
        new_worlds[0].gain_cherries(player_index,1)
        new_worlds[1].gain_cherries(player_index,2)
        new_worlds[2].gain_cherries(player_index,3)
        new_worlds[3].gain_cherries(player_index,4)
        new_worlds[4].lose_cherries(player_index,2)
        new_worlds[5].lose_cherries(player_index,2)
        new_worlds[6].lose_all_cherries(player_index)
        return new_worlds
    def mergable(self, other):
        if world_ended(self) and ignore_score_of_finished_games:
            return ( (self.ended_on_turn == other.ended_on_turn) and
                     (self.num_players == other.num_players) )
        else:
            return ( lists_the_same(self.player_scores, other.player_scores) and
                     (self.turn_number == other.turn_number) and
                     (self.ended_on_turn == other.ended_on_turn) and
                     (self.num_players == other.num_players) )
    def merge(self, other):
        w = deepcopy(self)
        w.probability += other.probability
        return w
    def __str__(self):
        return ( str(self.player_scores) + ", " +
                 "p:" + str(self.probability) + ", " +
                 "terminé le tour:" + str(self.ended_on_turn) + ", " )
def collapse_duplicate_worlds(world_list):
    r"""parcourt la liste des mondes, trouve ceux qui peuvent être fusionnés
    et les fusionne, modifiant la liste sur place"""
    i0 = 0
    while i0 < len(world_list):
        i1 = len(world_list) - 1
        while i1 > i0+1:
            w0 = world_list[i0]
            w1 = world_list[i1]
            if w0.mergable(w1):
                world_list[i0] = w0.merge(w1)
                if print_merges:
                    print "fusion de",w0
                    print "avec",w1
                del world_list[i1]
            i1 -=1
        i0 += 1
def dump_stats(worlds):
    print 'tour\tFINI\tPAR'
    p_on = [0.0 for i in range(turn_cap+1)]
    p_by = [0.0 for i in range(turn_cap+1)]
    for w in worlds:
        if world_ended(w):
            turn = w.ended_on_turn
            p_on[turn] += w.probability
            for i in range(turn, turn_cap+1):
                p_by[i] += w.probability
    for i in range(1,turn_cap+1):
        print str(i)+'\t'+str(p_on[i])+'\t'+str(p_by[i])
def world_ended(w):
    return None!=w.ended_on_turn
def main():
    worlds = [World(num_players)]
    finished_worlds = []
    for i in range(turn_cap):
        if print_turn_progress:
            print "tour#:",i+1
        worlds = flatten([w.run_turn() for w in worlds])
        collapse_duplicate_worlds(worlds)
        done, worlds = split_list(worlds,world_ended)
        finished_worlds.extend(done)
        if print_num_states:
            print len(worlds),"états de monde inachevés"
            print len(finished_worlds),"états de monde terminés"
    finished_worlds.extend(worlds)
    dump_stats(finished_worlds)

if "__main__"==__name__:
    main()

6voto

Raibaz Points 1860

C'est en fait une question assez intéressante. J'ai aussi décidé d'écrire une simulation. Les résultats sont assez intéressants et tendent à montrer un peu à quel point il faut être prudent pour obtenir une bonne simulation. Pour ceux que ça intéresse, voici un peu de code :

[Edit : remplacement du code en raison d'une erreur stupide]
[Edit2: ajout du code pour calculer le gain moyen par rotation]

#include 
#include 
#include 
#include 
#include 
#include "bounded.h"

static const int win = 10;
static const int games = 20000000;
static const int init[] = { 1, 2, 3, 4, -2, -2, -10};

int rand_lim(int limit) {
    int divisor = RAND_MAX/(limit+1);
    int retval;

    do { 
        retval = rand() / divisor;
    } while (retval > limit);

    return retval;
}

int main(){ 
    srand(unsigned(time(NULL)));
    std::vector spin_vals(init, init+7);
    std::vector gains;

    std::vector spins;
    unsigned total_spins = 0;

    for (unsigned game=0; game current_game_val;
        while (current_game_val != win) {
            ++total_spins;
            ++current_spins;
            int current_spin = spin_vals[rand_lim(spin_vals.size()-1)];
            int old_val = current_game_val;
            current_game_val = current_game_val + current_spin;
            gains.push_back(current_game_val - old_val);
        }
        spins.push_back(current_spins);
    }

    double mean_spins = (double)total_spins / games;

    std::cout << "Moyenne des rotations/jeu : " << mean_spins << "\n";

    std::vector::iterator median = spins.begin()+games/2;

    std::nth_element(spins.begin(), median, spins.end());

    std::cout << "Médiane des rotations/jeu : " << (int)*median << "\n";

    double total_gains = std::accumulate(gains.begin(), gains.end(), 0.0);

    double mean_gain = total_gains / gains.size();

    std::cout << "Gain moyen par rotation : " << mean_gain << "\n";

    return 0;
}

Pour la sortie, j'obtiens :

Moyenne des rotations/jeu : 15.7967
Médiane des rotations/jeu : 12
Gain moyen par rotation : 0.633044

Le gain par rotation semble correspondre au nombre de rotations par jeu -- s'il faut ~16 rotations pour arriver à 10, alors le gain moyen par rotation devrait être d'environ 10/16.

-3voto

Chris Marasti-Georg Points 17023

Les calculs suivants sont basés sur une probabilité égale d'être à n'importe quel nombre particulier (0-9), lorsque la roue est lancée.

Le nombre attendu de cerises par rotation peut être calculé comme suit :

Contributions de la rotation (probabilité de 1/7 chacune) :

   1 : (1*10)/10 = 1
   2 : (2*9+1)/10 = 1.9
   3 : (3*8+2+1)/10 = 2.7
   4 : (4*7+3+2+1)/10 = 3.4
  -2 : -(2*8+1+0)/10 = -1.7
  -2 : -(2*8+1+0)/10 = -1.7
-tous : -(0+1+2+3+4+5+6+7+8+9)/10 = -4.5

                            Total = 1.1

Total/7 = 0.157143 <-- valeur attendue

Théoriquement, le jeu moyen devrait prendre 10/0.157143 = 63.5 rotations.

À noter qu'avec beaucoup plus de précision, on obtient 63.636363.... rotations.


J'ai maintenant effectué une simulation pour déterminer les probabilités de votre score lorsque vous tournez la roue et obtenez le résultat Perdre Tout. J'ai effectué 3 simulations de 10 000 000 et j'ai calculé la moyenne des résultats pour TOUS, et une simulation de 10 000 000 pour les autres, donc voici les nouveaux calculs :

Contributions de la rotation (probabilité de 1/7 chacune) :

   1 : (1*10)/10 = 1
   2 : (2*9*0.967145 +
        1*0.032855)/10 = 1.74415
   3 : (3*8*0.923705 +
        2*0.043412 +
        1*0.032884)/10 = 2.22886
   4 : (4*7*0.865895 +
        3*0.058240 +
        2*0.042263 +
        1*0.033602)/10 = 2.45379
  -2 : -(2*8*0.563137 +
        1*0.079143 +
        0*0.357720)/10 = -0.90893
  -2 : -(2*8*0.563137 +
        1*0.079143 +
        0*0.357720)/10 = -0.90893
-tous : -(0 * .358065 + 
         1 * .078969 +
         2 * .092976 +
         3 * .094948 +
         4 * .107013 +
         5 * .069808 +
         6 * .064553 +
         7 * .057246 +
         8 * .042941 +
         9 * .033480) = -2.75975

                            Total = 2.84919

Total/7 = 0.40703 <-- valeur attendue

Théoriquement, le jeu moyen devrait prendre 10/0.40703 = 24.56839 rotations.

AlleGamers.com

AlleGamers est une communauté de gamers qui cherche à élargir la connaissance des jeux vidéo.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X