From 1ad6511f02edba04944014b53b3782326f320576 Mon Sep 17 00:00:00 2001 From: Adrienucl <adrien.payen@student.uclouvain.be> Date: Fri, 3 May 2024 18:41:43 +0200 Subject: [PATCH] update 99% of the project --- .DS_Store | Bin 6148 -> 8196 bytes markov.py.py => ancien/markoVVV.py | 2 +- ancien/mdppp.py | 71 +++++++ ancien/plotinggg.py | 82 ++++++++ ancien/tmcccc.py | 197 ++++++++++++++++++ ancien/validationnnnn.py | 258 +++++++++++++++++++++++ markovDecision.py | 21 +- plot.py | 4 +- strategy_comparison.png | Bin 23502 -> 22200 bytes tmc.py | 322 +++++++++++++---------------- validation.py | 73 ++++--- 11 files changed, 805 insertions(+), 225 deletions(-) rename markov.py.py => ancien/markoVVV.py (98%) create mode 100644 ancien/mdppp.py create mode 100644 ancien/plotinggg.py create mode 100644 ancien/tmcccc.py create mode 100644 ancien/validationnnnn.py diff --git a/.DS_Store b/.DS_Store index 55d82a11e81b11f3bbb9102c10fed3bcce6672f9..6d15e9587b9e9d630aec3bbcdf9497b36b9e9b42 100644 GIT binary patch delta 633 zcmY*X&ubGw6n?XlHk0nwCXm=1JVXTX5YkO;3ss~wMM^JH8nFkJbvGN*rMok6cau;; zXb(NL7sFhOD8+M?y$Jpv9{o3pCw;SN!7S{4Z}xrP`#GoP>1G)KNTcW00hGz`RhZYu zAMTE8_etUV?oX0@qx%{Bd?lAz#>OWZQjmtWemn~{aA<)CZQwh;XgR)clo~+mAlCk- z4_W|ITxSq<Z@534uJ1*$Y5pP_&CJYZS(fSSUGqQ;nz0@CqJ|y5QKwfz_|C0uJFmU= z@xaO#w*-%EkGI2A;kj+(yxsS>D+UeG<8G9Sn-d4iv7D7J4~MJuYNfuix;Cm-hU?2_ zwX*VfZ8XZU(u1X^d)A>JaB-HF4TMY(=5O#8&H-mO)m6V4a$j)Oxe^%=;Cw<j1Oa`h zK^KnXN3Aq-j=oRwiS}$ULCc()Q2;J<kyVs^ZDT%%8Xwg$Sg?$LFSiyU!c&NGW1M*h zj?mLWMu1F}%R|4wov3UaI@3ELf6~m4=)-#iR|KE`OU*D~fT1Rdit;5b=nTuYu>cQT zRgwl*a+mIqJMuj(5miq~PkWh!NlfE6oh$1Y_=UPa$rpM}<wEhf+&1zI2cqs4B$jbO F{{bAKpeg_W delta 135 zcmZp1XfcprU|?W$DortDU=RQ@Ie-{MGjUEV6q~50$jH7iU^gQp`(z#g%gHkZjV6B= zw40nRR5#gJSbB1uu;AqV!d4s0@>mwLb8rYU12qDH05_0u1(~t2@H_Klei=uQB@B!V S3@jj;31SOaY;!!%9A*F$5EwxK diff --git a/markov.py.py b/ancien/markoVVV.py similarity index 98% rename from markov.py.py rename to ancien/markoVVV.py index 286d243..8e92921 100644 --- a/markov.py.py +++ b/ancien/markoVVV.py @@ -1,5 +1,5 @@ import numpy as np -from tmc import TransitionMatrixCalculator as tmc +from ancien.tmc import TransitionMatrixCalculator as tmc class MarkovDecisionSolver: def __init__(self, layout: list, circle: bool): diff --git a/ancien/mdppp.py b/ancien/mdppp.py new file mode 100644 index 0000000..5c88f50 --- /dev/null +++ b/ancien/mdppp.py @@ -0,0 +1,71 @@ +import numpy as np +from ancien.tmc import TransitionMatrixCalculator as tmc + +class MarkovDecisionSolver: + def __init__(self, layout : list, circle : bool): + self.Numberk = 15 + self.tmc_instance = tmc() + self.safe_dice = self.tmc_instance._compute_safe_matrix() + self.normal_dice = self.tmc_instance._compute_normal_matrix(layout, circle) + self.risky_dice = self.tmc_instance._compute_risky_matrix(layout, circle) + self.jail = [i for i, x in enumerate(layout) if x == 3] + self.ValueI = np.zeros(self.Numberk) + self.DiceForStates = np.zeros(self.Numberk - 1) + + def _compute_vi_safe(self, k): + return np.dot(self.safe_dice[k], self.ValueI) + + def _compute_vi_normal(self, k): + vi_normal = np.dot(self.normal_dice[k], self.ValueI) + 0.5 * np.sum(self.normal_dice[k][self.jail]) + return vi_normal + + def _compute_vi_risky(self, k): + vi_risky = np.dot(self.risky_dice[k], self.ValueI) + np.sum(self.risky_dice[k][self.jail]) + return vi_risky + + def solve(self): + i = 0 + while True: + ValueINew = np.zeros(self.Numberk) + i += 1 + + for k in range(self.Numberk - 1): + vi_safe = self._compute_vi_safe(k) + vi_normal = self._compute_vi_normal(k) + vi_risky = self._compute_vi_risky(k) + + ValueINew[k] = 1 + min(vi_safe, vi_normal, vi_risky) + + if ValueINew[k] == 1 + vi_safe: + self.DiceForStates[k] = 1 + elif ValueINew[k] == 1 + vi_normal: + self.DiceForStates[k] = 2 + else: + self.DiceForStates[k] = 3 + + if np.allclose(ValueINew, self.ValueI): + self.ValueI = ValueINew + break + + self.ValueI = ValueINew + + Expec = self.ValueI[:-1] + return [Expec, self.DiceForStates] + +def markovDecision(layout : list, circle : bool): + solver = MarkovDecisionSolver(layout, circle) + return solver.solve() + + +# Exemple d'utilisation de la fonction markovDecision avec les paramètres layout et circle +layout = [0, 0, 3, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 1, 0] + + +# Résolution du problème avec différents modes de jeu +result_false = markovDecision(layout, circle=False) +print("\nWin as soon as land on or overstep the final square") +print(result_false) + +result_true = markovDecision(layout, circle=True) +print("\nStopping on the square to win") +print(result_true) diff --git a/ancien/plotinggg.py b/ancien/plotinggg.py new file mode 100644 index 0000000..d1eb1e0 --- /dev/null +++ b/ancien/plotinggg.py @@ -0,0 +1,82 @@ +import matplotlib.pyplot as plt +from ancien.validation import validation +import numpy as np + +# Example layout and circle settings +layout = [0, 0, 3, 0, 2, 0, 2, 0, 0, 0, 3, 0, 0, 1, 0] +circle = False + +# Create an instance of validation +validation_instance = validation(layout, circle) + + +# Plotting function for strategy comparison +def plot_strategy_comparison(num_games=1000): + strategy_costs = validation_instance.compare_strategies(num_games=num_games) + + # Bar plot for strategy comparison + plt.figure(figsize=(10, 6)) + plt.bar(strategy_costs.keys(), strategy_costs.values(), color=['blue', 'green', 'orange', 'red', 'purple']) + plt.xlabel('Strategies') + plt.ylabel('Average Cost') + plt.title('Comparison of Strategies') + plt.savefig('strategy_comparison.png') # Save the plot + plt.show() + +# Plotting function for state-based average turns for all strategies on the same plot +def plot_state_based_turns(save=True): + strategies = [validation_instance.optimal_strategy, + validation_instance.safe_strategy, + validation_instance.normal_strategy, + validation_instance.risky_strategy, + validation_instance.random_strategy] + strategy_names = ['Optimal', 'SafeDice', 'NormalDice', 'RiskyDice', 'Random'] + + plt.figure(figsize=(12, 6)) + for strategy, name in zip(strategies, strategy_names): + mean_turns = validation_instance.simulate_state(strategy, layout, circle) + plt.plot(range(len(mean_turns)), mean_turns, marker='o', linestyle='-', label=name) + + plt.xlabel('State') + plt.ylabel('Average Turns') + plt.title('Average Turns per State for Different Strategies') + plt.grid(True) + plt.legend() + + #if save: + #plt.savefig('state_based_turns_all_strategies.png') # Save the plot + + plt.show() + +def plot_state_based_comparison(validation_instance, num_games=1000): + optimal_turns, empirical_turns = validation_instance.compare_state_based_turns(num_games=num_games) + + # Plotting the state-based average turns comparison + plt.figure(figsize=(12, 6)) + + # Plot optimal strategy turns + plt.plot(range(len(optimal_turns)), optimal_turns, marker='o', linestyle='-', label='ValueIteration') + + # Plot empirical strategy turns + plt.plot(range(len(empirical_turns)), empirical_turns, marker='x', linestyle='-', label='Empirical') + + plt.xlabel('State') + plt.ylabel('Average Turns') + plt.title('Average Turns per State - ValueIteration vs. Empirical') + plt.grid(True) + plt.legend() + + plt.show() + + + + +# Main function to generate and save plots +if __name__ == '__main__': + # Example of strategy comparison plot + plot_strategy_comparison(num_games=1000) + + # Example of state-based average turns plot for all strategies on the same plot + plot_state_based_turns(save=True) + + plot_state_based_comparison(validation_instance, num_games=1000) \ No newline at end of file diff --git a/ancien/tmcccc.py b/ancien/tmcccc.py new file mode 100644 index 0000000..388cc13 --- /dev/null +++ b/ancien/tmcccc.py @@ -0,0 +1,197 @@ +import numpy as np +import random as rd + +class TransitionMatrixCalculator: + def __init__(self): + # Initialisation des matrices de transition pour les dés "safe", "normal" et "risky" + self.matrix_safe = np.zeros((15, 15)) + self.matrix_normal = np.zeros((15, 15)) + self.matrix_risky = np.zeros((15, 15)) + # Probability to go from state k to k' + self.safe_dice = np.array([1/2, 1/2]) + self.normal_dice = np.array([1/3, 1/3, 1/3]) + self.risky_dice = np.array([1/4, 1/4, 1/4, 1/4]) + + def compute_transition_matrix(self, layout, circle=False): + self.matrix_safe.fill(0) + self.matrix_normal.fill(0) + self.matrix_risky.fill(0) + + self._compute_safe_matrix() + self._compute_normal_matrix(layout, circle) + self._compute_risky_matrix(layout, circle) + + return self.matrix_safe, self.matrix_normal, self.matrix_risky + + + def _compute_safe_matrix(self): + for k in range(15): + for s, p in enumerate(self.safe_dice): + if k == 9 and s == 1: + k_prime = 14 + self.matrix_safe[k,k_prime] += p + elif k == 2 and s > 0: + p /= 2 + k_prime = 10 + self.matrix_safe[k,k_prime] += p + k_prime = 3 + self.matrix_safe[k,k_prime] += p + else: + k_prime = k + s + k_prime = min(14, k_prime) + self.matrix_safe[k,k_prime] += p + + return self.matrix_safe + + def _compute_normal_matrix(self, layout, circle): + for k in range(15): + for s, p in enumerate(self.normal_dice): + if k == 8 and s == 2: + k_prime = 14 + self.matrix_normal[k,k_prime] += p + continue + elif k == 9 and s in [1, 2]: + if not circle or s == 1: + k_prime = 14 + self.matrix_normal[k,k_prime] += p + elif circle and s == 2: + k_prime = 0 + self.matrix_normal[k,k_prime] += p + continue + + # handle the fast lane + if k == 2 and s > 0: + p /= 2 + k_prime = 10 + (s - 1) # rebalance the step before with s > 0 + if layout[k_prime] in [0, 3]: # normal or prison square + self.matrix_normal[k,k_prime] += p + elif layout[k_prime] == 1: # handle type 1 trap + self.matrix_normal[k,k_prime] += p / 2 + k_prime = 0 + self.matrix_normal[k,k_prime] += p / 2 + elif layout[k_prime] == 2: # handle type 2 trap + self.matrix_normal[k,k_prime] += p / 2 + if k_prime == 10: + k_prime = 0 + elif k_prime == 11: + k_prime = 1 + elif k_prime == 12: + k_prime = 2 + else: + k_prime = max(0, k_prime - 3) + self.matrix_normal[k,k_prime] += p / 2 + k_prime = 3 + (s - 1) # rebalance the step before with s > 0 + if layout[k_prime] in [0, 3]: # normal or prison square + self.matrix_normal[k,k_prime] += p + elif layout[k_prime] == 1: # handle type 1 trap + self.matrix_normal[k,k_prime] += p / 2 + k_prime = 0 + self.matrix_normal[k,k_prime] += p / 2 + elif layout[k_prime] == 2: # handle type 2 trap + self.matrix_normal[k,k_prime] += p / 2 + k_prime = max(0, k_prime - 3) + self.matrix_normal[k,k_prime] += p / 2 + continue + + k_prime = k + s + k_prime = k_prime % 15 if circle else min(14, k_prime) # modulo + if layout[k_prime] in [1, 2]: + p /= 2 + if layout[k_prime] == 1: + k_prime = 0 + self.matrix_normal[k,k_prime] += p + continue + elif layout[k_prime] == 2: + if k_prime == 10: + k_prime = 0 + elif k_prime == 11: + k_prime = 1 + elif k_prime == 12: + k_prime = 2 + else: + k_prime = max(0, k_prime - 3) + self.matrix_normal[k,k_prime] += p + continue + self.matrix_normal[k,k_prime] += p + return self.matrix_normal + + def _compute_risky_matrix(self, layout, circle): + for k in range(15): + for s, p in enumerate(self.risky_dice): + if k == 7 and s == 3: + k_prime = 14 + self.matrix_risky[k,k_prime] += p + continue + elif k == 8 and s in [2, 3]: + if not circle or s == 2: + k_prime = 14 + self.matrix_risky[k,k_prime] += p + elif circle: + k_prime = 0 + self.matrix_risky[k,k_prime] += p + continue + elif k == 9 and s in [1, 2, 3]: + if not circle or s == 1: + k_prime = 14 + self.matrix_risky[k,k_prime] += p + elif circle and s == 2: + k_prime = 0 + self.matrix_risky[k,k_prime] += p + elif circle and s == 3: + k_prime = 1 + if layout[k_prime] != 0: + if layout[k_prime] == 1: + k_prime = 0 + self.matrix_risky[k,k_prime] += p + elif layout[k_prime] == 2: + k_prime = max(0, k_prime - 3) + self.matrix_risky[k,k_prime] += p + self.matrix_risky[k,k_prime] += p + continue + continue + + if k == 2 and s > 0: + p /= 2 + k_prime = 10 + (s - 1) + if layout[k_prime] == 1: + k_prime = 0 + self.matrix_risky[k,k_prime] += p + elif layout[k_prime] == 2: + if k_prime == 10: + k_prime = 0 + elif k_prime == 11: + k_prime = 1 + elif k_prime == 12: + k_prime = 2 + else: + k_prime = max(0, k_prime - 3) + self.matrix_risky[k,k_prime] += p + else: + self.matrix_risky[k,k_prime] += p + k_prime = 3 + (s - 1) + self.matrix_risky[k,k_prime] += p + continue + + k_prime = k + s + k_prime = k_prime % 15 if circle else min(14, k_prime) + if layout[k_prime] in [1, 2]: + if layout[k_prime] == 1: + k_prime = 0 + self.matrix_risky[k,k_prime] += p + continue + elif layout[k_prime] == 2: + if k_prime == 10: + k_prime = 0 + elif k_prime == 11: + k_prime = 1 + elif k_prime == 12: + k_prime = 2 + else: + k_prime = max(0, k_prime - 3) + self.matrix_risky[k,k_prime] += p + continue + self.matrix_risky[k,k_prime] += p + return self.matrix_risky + +#tmc = TransitionMatrixCalculator() +#tmc.tst_transition_matrix() diff --git a/ancien/validationnnnn.py b/ancien/validationnnnn.py new file mode 100644 index 0000000..86f6ff7 --- /dev/null +++ b/ancien/validationnnnn.py @@ -0,0 +1,258 @@ +import random as rd +import numpy as np +import matplotlib.pyplot as plt +from ancien.tmc import TransitionMatrixCalculator as tmc +from ancien.markovDecision import MarkovDecisionSolver as mD + +class validation: + def __init__(self, layout, circle=False): + + # import from other .PY + self.layout = layout + self.circle = circle + self.tmc_instance = tmc() + self.safe_dice = self.tmc_instance._compute_safe_matrix() + self.normal_dice = self.tmc_instance._compute_normal_matrix(layout, circle) + self.risky_dice = self.tmc_instance._compute_risky_matrix(layout, circle) + solver = mD(self.layout, self.circle) + self.expec, self.optimal_policy = solver.solve() + + # Define all the strategy + self.optimal_strategy = self.optimal_policy + self.safe_strategy = [1]*len(layout) + self.normal_strategy = [2]*len(layout) + self.risky_strategy = [3]*len(layout) + self.random_strategy = [rd.choice([0,1,2,3]) for _ in range(15)] + + # Définir les coûts par case et par type de dé + self.costs_by_dice_type = { + 'SafeDice': [0] * len(self.layout), + 'NormalDice': [0] * len(self.layout), + 'RiskyDice': [0] * len(self.layout) + } + + # Remplir les coûts pour chaque case en fonction du type de dé + for i in range(len(self.layout)): + if self.layout[i] == 3: + self.costs_by_dice_type['SafeDice'][i] = 1 # Coût par défaut pour le dé sûr + self.costs_by_dice_type['NormalDice'][i] = 2 # Coût par défaut pour le dé normal + self.costs_by_dice_type['RiskyDice'][i] = 3 # Coût par défaut pour le dé risqué + + + def simulate_game(self, strategy, n_iterations=10000): + transition_matrices = [self.safe_dice, self.normal_dice, self.risky_dice] + number_turns = [] + + for _ in range(n_iterations): + total_turns = 0 + k = 0 # état initial + + while k < len(self.layout) - 1: + action = strategy[k] # action selon la stratégie + + # Convertir action en entier pour accéder à l'indice correct dans transition_matrices + action_index = int(action) - 1 + transition_matrix = transition_matrices[action_index] + + # Aplatir la matrice de transition en une distribution de probabilité 1D + flattened_probs = transition_matrix[k] + flattened_probs /= np.sum(flattened_probs) # Normalisation des probabilités + + # Mise à jour de l'état (k) en fonction de la distribution de probabilité aplatie + k = np.random.choice(len(self.layout), p=flattened_probs) + + # Mise à jour du nombre de tours en fonction de l'état actuel + if self.layout[k] == 3 and action == 2: + total_turns += 1 if np.random.uniform(0, 1) < 0.5 else 2 + elif self.layout[k] == 3 and action == 3: + total_turns += 2 + else: + total_turns += 1 + + number_turns.append(total_turns) + + return np.mean(number_turns) + + def simulate_state(self, strategy, layout, circle, n_iterations=10000): + # Compute transition matrices for each dice + tmc_instance = tmc() + P_safe = tmc_instance._compute_safe_matrix() + P_normal = tmc_instance._compute_normal_matrix(layout, circle) + P_risky = tmc_instance._compute_risky_matrix(layout, circle) + + transition_matrices = [P_safe, P_normal, P_risky] + number_turns = [] + number_mean = [] + + for _ in range(n_iterations): + number_turns = [] + + for state in range(len(layout) - 1): + total_turns = 0 + k = state # starting state + + while k < len(layout) - 1: + action = strategy[k] # action based on strategy + action_index = int(action) - 1 + transition_matrix = transition_matrices[action_index] + flattened_probs = transition_matrix[k] + flattened_probs /= np.sum(flattened_probs) + k = np.random.choice(len(layout), p=flattened_probs) + + if layout[k] == 3 and action == 2: + total_turns += 1 if np.random.uniform(0, 1) < 0.5 else 2 + elif layout[k] == 3 and action == 3: + total_turns += 2 + else: + total_turns += 1 + + number_turns.append(total_turns) + + number_mean.append(number_turns) + + # calculate the average number of turns for each state + mean_turns = np.mean(number_mean, axis=0) + + return mean_turns + + + def play_optimal_strategy(self, n_iterations=10000): + return self.simulate_game(self.optimal_strategy, n_iterations) + + + def play_dice_strategy(self, dice_choice, n_iterations=10000): + if dice_choice == 'SafeDice': + strategy = self.safe_strategy + elif dice_choice == 'NormalDice': + strategy = self.normal_strategy + elif dice_choice == 'RiskyDice': + strategy = self.risky_strategy + else: + raise ValueError("Invalid dice choice") + + return self.simulate_game(strategy, n_iterations) + + def play_random_strategy(self, n_iterations=10000): + return self.simulate_game(self.random_strategy, n_iterations) + + def play_empirical_strategy(self): + k = 0 # état initial + total_turns = 0 + + while k < len(self.layout) - 1: + action = self.optimal_strategy[k] # Utiliser la stratégie empirique pour la simulation + action_index = int(action) - 1 + transition_matrix = self.normal_dice # Utiliser le dé normal pour la stratégie empirique + + # Aplatir la matrice de transition en une distribution de probabilité 1D + flattened_probs = transition_matrix[k] + flattened_probs /= np.sum(flattened_probs) # Normalisation des probabilités + + # Mise à jour de l'état (k) en fonction de la distribution de probabilité aplatie + k = np.random.choice(len(self.layout), p=flattened_probs) + + # Mise à jour du nombre de tours en fonction de l'état actuel + if self.layout[k] == 3 and action == 2: + total_turns += 1 if np.random.uniform(0, 1) < 0.5 else 2 + elif self.layout[k] == 3 and action == 3: + total_turns += 2 + else: + total_turns += 1 + + return total_turns + + + def compare_empirical_vs_value_iteration(self, num_games=1000): + value_iteration_turns = self.simulate_state(self.optimal_strategy, self.layout, self.circle) + empirical_turns = self.simulate_state(self.optimal_strategy, self.layout, self.circle, n_iterations=num_games) + + # Calculer la moyenne des tours pour chaque état + mean_turns_by_state = { + 'ValueIteration': value_iteration_turns.tolist(), + 'Empirical': empirical_turns.tolist() + } + + return mean_turns_by_state + + def compare_state_based_turns(self, num_games=1000): + optimal_turns = self.simulate_state(self.optimal_strategy, self.layout, self.circle, n_iterations=num_games) + empirical_turns = self.simulate_state(self.optimal_strategy, self.layout, self.circle, n_iterations=num_games) + + return optimal_turns, empirical_turns + + + + def compare_strategies(self, num_games=1000): + optimal_cost = self.simulate_game(self.optimal_strategy, n_iterations=num_games) + dice1_cost = self.simulate_game(self.safe_strategy, n_iterations=num_games) + dice2_cost = self.simulate_game(self.normal_strategy, n_iterations=num_games) + dice3_cost = self.simulate_game(self.risky_strategy, n_iterations=num_games) + random_cost = self.simulate_game(self.random_strategy, n_iterations=num_games) + + return { + 'Optimal': optimal_cost, + 'SafeDice': dice1_cost, + 'NormalDice': dice2_cost, + 'RiskyDice': dice3_cost, + 'Random': random_cost + } + + +# Utilisation d'exemple +layout = [0, 0, 3, 0, 2, 0, 2, 0, 0, 0, 3, 0, 0, 1, 0] +circle = False +validation_instance = validation(layout, circle) + + +# Comparer la stratégie empirique avec la stratégie de value iteration +turns_by_state = validation_instance.compare_empirical_vs_value_iteration(num_games=1000) + +# Imprimer les moyennes des tours pour chaque état +num_states = len(layout) +for state in range(num_states - 1): + print(f"État {state}:") + print(f" ValueIteration - Tours moyens : {turns_by_state['ValueIteration'][state]:.2f}") + print(f" Empirical - Tours moyens : {turns_by_state['Empirical'][state]:.2f}") + +# Exécuter la stratégie empirique une fois +empirical_strategy_result = validation_instance.play_empirical_strategy() +print("Coût de la stratégie empirique sur un tour :", empirical_strategy_result) + +# Comparer la stratégie empirique avec la stratégie de value iteration sur plusieurs jeux +comparison_result = validation_instance.compare_empirical_vs_value_iteration(num_games=1000) +print("Coût moyen de la stratégie de value iteration :", comparison_result['ValueIteration']) +print("Coût moyen de la stratégie empirique :", comparison_result['Empirical']) + + +optimal_cost = validation_instance.play_optimal_strategy(n_iterations=10000) +print("Optimal Strategy Cost:", optimal_cost) + +dice2_cost = validation_instance.play_dice_strategy('NormalDice', n_iterations=10000) +print("Normal Dice Strategy Cost:", dice2_cost) + +random_cost = validation_instance.play_random_strategy(n_iterations=10000) +print("Random Strategy Cost:", random_cost) + +strategy_comparison = validation_instance.compare_strategies(num_games=10000) +print("Strategy Comparison Results:", strategy_comparison) + + +optimal_strategy = validation_instance.optimal_strategy +mean_turns_optimal = validation_instance.simulate_state(optimal_strategy, layout, circle, n_iterations=10000) +print("Mean Turns for Optimal Strategy:", mean_turns_optimal) + +safe_dice_strategy = validation_instance.safe_strategy +mean_turns_safe_dice = validation_instance.simulate_state(safe_dice_strategy, layout, circle, n_iterations=10000) +print("Mean Turns for Safe Dice Strategy:", mean_turns_safe_dice) + +normal_dice_strategy = validation_instance.normal_strategy +mean_turns_normal_dice = validation_instance.simulate_state(normal_dice_strategy, layout, circle, n_iterations=10000) +print("Mean Turns for Normal Dice Strategy:", mean_turns_normal_dice) + +risky_dice_strategy = validation_instance.risky_strategy +mean_turns_risky_dice = validation_instance.simulate_state(risky_dice_strategy, layout, circle, n_iterations=10000) +print("Mean Turns for Risky Dice Strategy:", mean_turns_risky_dice) + +random_dice_strategy = validation_instance.random_strategy +mean_turns_random_dice = validation_instance.simulate_state(random_dice_strategy, layout, circle, n_iterations=10000) +print("Mean Turns for Random Dice Strategy:", mean_turns_random_dice) diff --git a/markovDecision.py b/markovDecision.py index e8e68be..276043e 100644 --- a/markovDecision.py +++ b/markovDecision.py @@ -1,26 +1,28 @@ import numpy as np -from tmc import TransitionMatrixCalculator as tmc +from tmc_2 import TransitionMatrixCalculator as tmc class MarkovDecisionSolver: - def __init__(self, layout : list, circle : bool): + def __init__(self, layout: list, circle: bool): self.Numberk = 15 self.tmc_instance = tmc() - self.safe_dice = self.tmc_instance._compute_safe_matrix() - self.normal_dice = self.tmc_instance._compute_normal_matrix(layout, circle) - self.risky_dice = self.tmc_instance._compute_risky_matrix(layout, circle) + self.safe_dice = self.tmc_instance.proba_security_dice() + self.normal_dice, _ = self.tmc_instance.proba_normal_dice(layout, circle) # Make sure to capture only the normal_dice component + self.risky_dice, _ = self.tmc_instance.proba_risky_dice(layout, circle) # Make sure to capture only the risky_dice component self.jail = [i for i, x in enumerate(layout) if x == 3] self.ValueI = np.zeros(self.Numberk) self.DiceForStates = np.zeros(self.Numberk - 1) def _compute_vi_safe(self, k): - return np.dot(self.safe_dice[k], self.ValueI) + return np.sum(self.safe_dice[k] * self.ValueI) + np.sum(self.normal_dice[k][self.jail]) + def _compute_vi_normal(self, k): - vi_normal = np.dot(self.normal_dice[k], self.ValueI) + 0.5 * np.sum(self.normal_dice[k][self.jail]) + vi_normal = np.sum(self.normal_dice[k] * self.ValueI) + np.sum(self.normal_dice[k][self.jail]) return vi_normal + def _compute_vi_risky(self, k): - vi_risky = np.dot(self.risky_dice[k], self.ValueI) + np.sum(self.risky_dice[k][self.jail]) + vi_risky = np.sum(self.risky_dice[k] * self.ValueI) + np.sum(self.risky_dice[k][self.jail]) return vi_risky def solve(self): @@ -56,6 +58,7 @@ def markovDecision(layout : list, circle : bool): solver = MarkovDecisionSolver(layout, circle) return solver.solve() +""" # Exemple d'utilisation de la fonction markovDecision avec les paramètres layout et circle layout = [0, 0, 3, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 1, 0] @@ -68,4 +71,4 @@ print(result_false) result_true = markovDecision(layout, circle=True) print("\nStopping on the square to win") -print(result_true) +print(result_true)""" \ No newline at end of file diff --git a/plot.py b/plot.py index c548380..46b023a 100644 --- a/plot.py +++ b/plot.py @@ -1,5 +1,5 @@ import matplotlib.pyplot as plt -from validation import validation +from valid import validation import numpy as np # Example layout and circle settings @@ -25,7 +25,7 @@ def plot_strategy_comparison(num_games=1000): # Plotting function for state-based average turns for all strategies on the same plot def plot_state_based_turns(save=True): - strategies = [validation_instance.optimal_strategy, + strategies = [validation_instance.optimal_policy, validation_instance.safe_strategy, validation_instance.normal_strategy, validation_instance.risky_strategy, diff --git a/strategy_comparison.png b/strategy_comparison.png index aefb1f990b1c89957981a9281815083011fbc9d2..d8b19f2ae884d963e9a1a6f2a97b07cd2f744384 100644 GIT binary patch literal 22200 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 Ru@s8p5tYM9hfe?b-vF1(JLUiY literal 23502 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 diff --git a/tmc.py b/tmc.py index 388cc13..04b0392 100644 --- a/tmc.py +++ b/tmc.py @@ -1,197 +1,157 @@ import numpy as np -import random as rd class TransitionMatrixCalculator: def __init__(self): - # Initialisation des matrices de transition pour les dés "safe", "normal" et "risky" - self.matrix_safe = np.zeros((15, 15)) - self.matrix_normal = np.zeros((15, 15)) - self.matrix_risky = np.zeros((15, 15)) - # Probability to go from state k to k' - self.safe_dice = np.array([1/2, 1/2]) - self.normal_dice = np.array([1/3, 1/3, 1/3]) - self.risky_dice = np.array([1/4, 1/4, 1/4, 1/4]) - - def compute_transition_matrix(self, layout, circle=False): - self.matrix_safe.fill(0) - self.matrix_normal.fill(0) - self.matrix_risky.fill(0) + self.nSquares = 15 + self.matrix_safe = np.zeros((self.nSquares, self.nSquares)) + self.matrix_normal = np.zeros((self.nSquares, self.nSquares)) + self.matrix_risky = np.zeros((self.nSquares, self.nSquares)) - self._compute_safe_matrix() - self._compute_normal_matrix(layout, circle) - self._compute_risky_matrix(layout, circle) + def proba_security_dice(self): + proba = np.zeros((self.nSquares, self.nSquares)) - return self.matrix_safe, self.matrix_normal, self.matrix_risky + for i in range(self.nSquares - 1): + proba[i][i] = 0.5 + if i == 2: + proba[i][i + 1] = 0.25 # slow lane + proba[i][i + 8] = 0.25 # fast lane + elif i == 9: + proba[i][i + 5] = 0.5 + else: + proba[i][i + 1] = 0.5 + + proba[self.nSquares - 1][self.nSquares - 1] = 1 + return proba + def proba_normal_dice(self, layout, circle=False): + proba = np.zeros((self.nSquares, self.nSquares)) + proba_prison = np.zeros((self.nSquares, self.nSquares)) - def _compute_safe_matrix(self): - for k in range(15): - for s, p in enumerate(self.safe_dice): - if k == 9 and s == 1: - k_prime = 14 - self.matrix_safe[k,k_prime] += p - elif k == 2 and s > 0: - p /= 2 - k_prime = 10 - self.matrix_safe[k,k_prime] += p - k_prime = 3 - self.matrix_safe[k,k_prime] += p + for i in range(self.nSquares - 1): + proba[i][i] = 1 / 3 + if i == 2: + proba[i][i + 1] = 1 / 6 # slow lane + proba[i][i + 2] = 1 / 6 # slow lane + proba[i][i + 8] = 1 / 6 # fast lane + proba[i][i + 9] = 1 / 6 # fast lane + elif i == 8: + proba[i][i + 1] = 1 / 3 + proba[i][i + 6] = 1 / 3 + elif i == 9: + if circle: + proba[i][i + 5] = 1 / 3 + proba[i][0] = 1 / 3 else: - k_prime = k + s - k_prime = min(14, k_prime) - self.matrix_safe[k,k_prime] += p - - return self.matrix_safe + proba[i][i + 5] = 2 / 3 + elif i == 13: + if circle: + proba[i][i + 1] = 1 / 3 + proba[i][0] = 1 / 3 + else: + proba[i][i + 1] = 2 / 3 + else: + proba[i][i + 1] = 1 / 3 + proba[i][i + 2] = 1 / 3 - def _compute_normal_matrix(self, layout, circle): - for k in range(15): - for s, p in enumerate(self.normal_dice): - if k == 8 and s == 2: - k_prime = 14 - self.matrix_normal[k,k_prime] += p - continue - elif k == 9 and s in [1, 2]: - if not circle or s == 1: - k_prime = 14 - self.matrix_normal[k,k_prime] += p - elif circle and s == 2: - k_prime = 0 - self.matrix_normal[k,k_prime] += p - continue + for i in range(self.nSquares - 1): + for j in range(self.nSquares - 1): + case_value = layout[j] + if case_value == 1: + if j != 0: + proba[i][0] += proba[i][j] / 2 + proba[i][j] /= 2 + elif case_value == 2: + proba[i][j - 3 if j - 3 >= 0 else 0] += proba[i][j] / 2 + proba[i][j] /= 2 + elif case_value == 3: + proba_prison[i][j] = proba[i][j] / 2 + elif case_value == 4: + proba[i][j] /= 2 + if j != 0: + proba[i][0] += proba[i][j] / 6 + proba[i][j - 3 if j - 3 >= 0 else 0] += proba[i][j] / 6 + proba_prison[i][j] = proba[i][j] / 6 - # handle the fast lane - if k == 2 and s > 0: - p /= 2 - k_prime = 10 + (s - 1) # rebalance the step before with s > 0 - if layout[k_prime] in [0, 3]: # normal or prison square - self.matrix_normal[k,k_prime] += p - elif layout[k_prime] == 1: # handle type 1 trap - self.matrix_normal[k,k_prime] += p / 2 - k_prime = 0 - self.matrix_normal[k,k_prime] += p / 2 - elif layout[k_prime] == 2: # handle type 2 trap - self.matrix_normal[k,k_prime] += p / 2 - if k_prime == 10: - k_prime = 0 - elif k_prime == 11: - k_prime = 1 - elif k_prime == 12: - k_prime = 2 - else: - k_prime = max(0, k_prime - 3) - self.matrix_normal[k,k_prime] += p / 2 - k_prime = 3 + (s - 1) # rebalance the step before with s > 0 - if layout[k_prime] in [0, 3]: # normal or prison square - self.matrix_normal[k,k_prime] += p - elif layout[k_prime] == 1: # handle type 1 trap - self.matrix_normal[k,k_prime] += p / 2 - k_prime = 0 - self.matrix_normal[k,k_prime] += p / 2 - elif layout[k_prime] == 2: # handle type 2 trap - self.matrix_normal[k,k_prime] += p / 2 - k_prime = max(0, k_prime - 3) - self.matrix_normal[k,k_prime] += p / 2 - continue + proba[self.nSquares - 1][self.nSquares - 1] = 1 + return proba, proba_prison - k_prime = k + s - k_prime = k_prime % 15 if circle else min(14, k_prime) # modulo - if layout[k_prime] in [1, 2]: - p /= 2 - if layout[k_prime] == 1: - k_prime = 0 - self.matrix_normal[k,k_prime] += p - continue - elif layout[k_prime] == 2: - if k_prime == 10: - k_prime = 0 - elif k_prime == 11: - k_prime = 1 - elif k_prime == 12: - k_prime = 2 - else: - k_prime = max(0, k_prime - 3) - self.matrix_normal[k,k_prime] += p - continue - self.matrix_normal[k,k_prime] += p - return self.matrix_normal + def proba_risky_dice(self, layout, circle=False): + proba = np.zeros((self.nSquares, self.nSquares)) + proba_prison = np.zeros((self.nSquares, self.nSquares)) - def _compute_risky_matrix(self, layout, circle): - for k in range(15): - for s, p in enumerate(self.risky_dice): - if k == 7 and s == 3: - k_prime = 14 - self.matrix_risky[k,k_prime] += p - continue - elif k == 8 and s in [2, 3]: - if not circle or s == 2: - k_prime = 14 - self.matrix_risky[k,k_prime] += p - elif circle: - k_prime = 0 - self.matrix_risky[k,k_prime] += p - continue - elif k == 9 and s in [1, 2, 3]: - if not circle or s == 1: - k_prime = 14 - self.matrix_risky[k,k_prime] += p - elif circle and s == 2: - k_prime = 0 - self.matrix_risky[k,k_prime] += p - elif circle and s == 3: - k_prime = 1 - if layout[k_prime] != 0: - if layout[k_prime] == 1: - k_prime = 0 - self.matrix_risky[k,k_prime] += p - elif layout[k_prime] == 2: - k_prime = max(0, k_prime - 3) - self.matrix_risky[k,k_prime] += p - self.matrix_risky[k,k_prime] += p - continue - continue + for i in range(self.nSquares - 1): + proba[i][i] = 1 / 4 + if i == 2: + proba[i][i + 1] = 1 / 8 # slow lane + proba[i][i + 2] = 1 / 8 # slow lane + proba[i][i + 3] = 1 / 8 # slow lane + proba[i][i + 8] = 1 / 8 # fast lane + proba[i][i + 9] = 1 / 8 # fast lane + proba[i][i + 10] = 1 / 8 # fast lane + elif i == 7: + proba[i][i + 1] = 1 / 4 + proba[i][i + 2] = 1 / 4 + proba[i][i + 7] = 1 / 4 + elif i == 8: + if circle: + proba[i][i + 1] = 1 / 4 + proba[i][i + 6] = 1 / 4 + proba[i][0] = 1 / 4 + else: + proba[i][i + 1] = 1 / 4 + proba[i][i + 6] = 1 / 2 + elif i == 9: + if circle: + proba[i][i + 5] = 1 / 4 + proba[i][0] = 1 / 4 + proba[i][1] = 1 / 4 + else: + proba[i][i + 5] = 3 / 4 + elif i == 12: + if circle: + proba[i][i + 1] = 1 / 4 + proba[i][i + 2] = 1 / 4 + proba[i][0] = 1 / 4 + else: + proba[i][i + 1] = 1 / 4 + proba[i][i + 2] = 1 / 2 + elif i == 13: + if circle: + proba[i][i + 1] = 1 / 4 + proba[i][0] = 1 / 4 + proba[i][1] = 1 / 4 + else: + proba[i][self.nSquares - 1] = 3 / 4 + else: + proba[i][i + 1] = 1 / 4 + proba[i][i + 2] = 1 / 4 + proba[i][i + 3] = 1 / 4 - if k == 2 and s > 0: - p /= 2 - k_prime = 10 + (s - 1) - if layout[k_prime] == 1: - k_prime = 0 - self.matrix_risky[k,k_prime] += p - elif layout[k_prime] == 2: - if k_prime == 10: - k_prime = 0 - elif k_prime == 11: - k_prime = 1 - elif k_prime == 12: - k_prime = 2 - else: - k_prime = max(0, k_prime - 3) - self.matrix_risky[k,k_prime] += p - else: - self.matrix_risky[k,k_prime] += p - k_prime = 3 + (s - 1) - self.matrix_risky[k,k_prime] += p - continue + for i in range(self.nSquares - 1): + for j in range(self.nSquares - 1): + case_value = layout[j] + if case_value == 1: + if j != 0: + proba[i][0] += proba[i][j] + proba[i][j] = 0 + elif case_value == 2: + proba[i][j - 3 if j - 3 >= 0 else 0] += proba[i][j] + proba[i][j] = 0 + elif case_value == 3: + proba_prison[i][j] = proba[i][j] + elif case_value == 4: + if j != 0: + proba[i][0] += proba[i][j] / 3 + proba[i][j - 3 if j - 3 >= 0 else 0] += proba[i][j] / 3 + proba_prison[i][j] = proba[i][j] / 3 + proba[i][j] /= 3 - k_prime = k + s - k_prime = k_prime % 15 if circle else min(14, k_prime) - if layout[k_prime] in [1, 2]: - if layout[k_prime] == 1: - k_prime = 0 - self.matrix_risky[k,k_prime] += p - continue - elif layout[k_prime] == 2: - if k_prime == 10: - k_prime = 0 - elif k_prime == 11: - k_prime = 1 - elif k_prime == 12: - k_prime = 2 - else: - k_prime = max(0, k_prime - 3) - self.matrix_risky[k,k_prime] += p - continue - self.matrix_risky[k,k_prime] += p - return self.matrix_risky + proba[self.nSquares - 1][self.nSquares - 1] = 1 + return proba, proba_prison -#tmc = TransitionMatrixCalculator() -#tmc.tst_transition_matrix() + def compute_transition_matrix(self, layout, circle=False): + self.matrix_safe = self.proba_security_dice() + self.matrix_normal, _ = self.proba_normal_dice(layout, circle) + self.matrix_risky, _ = self.proba_risky_dice(layout, circle) + + return self.matrix_safe, self.matrix_normal, self.matrix_risky diff --git a/validation.py b/validation.py index ee6b922..16174fc 100644 --- a/validation.py +++ b/validation.py @@ -1,8 +1,8 @@ import random as rd import numpy as np import matplotlib.pyplot as plt -from tmc import TransitionMatrixCalculator as tmc -from markovDecision import MarkovDecisionSolver as mD +from tmc_2 import TransitionMatrixCalculator as tmc +from mdp import MarkovDecisionSolver as mD class validation: def __init__(self, layout, circle=False): @@ -11,14 +11,13 @@ class validation: self.layout = layout self.circle = circle self.tmc_instance = tmc() - self.safe_dice = self.tmc_instance._compute_safe_matrix() - self.normal_dice = self.tmc_instance._compute_normal_matrix(layout, circle) - self.risky_dice = self.tmc_instance._compute_risky_matrix(layout, circle) + self.safe_dice = self.tmc_instance.proba_security_dice() + self.normal_dice, _ = self.tmc_instance.proba_normal_dice(layout, circle) # Make sure to capture only the normal_dice component + self.risky_dice, _ = self.tmc_instance.proba_risky_dice(layout, circle) # Make sure to capture only the risky_dice component solver = mD(self.layout, self.circle) self.expec, self.optimal_policy = solver.solve() # Define all the strategy - self.optimal_strategy = self.optimal_policy self.safe_strategy = [1]*len(layout) self.normal_strategy = [2]*len(layout) self.risky_strategy = [3]*len(layout) @@ -75,12 +74,11 @@ class validation: def simulate_state(self, strategy, layout, circle, n_iterations=10000): # Compute transition matrices for each dice - tmc_instance = tmc() - P_safe = tmc_instance._compute_safe_matrix() - P_normal = tmc_instance._compute_normal_matrix(layout, circle) - P_risky = tmc_instance._compute_risky_matrix(layout, circle) + safe_dice = self.tmc_instance.proba_security_dice() + normal_dice = self.tmc_instance.proba_normal_dice(layout, circle)[0] # Get only the normal dice transition matrix + risky_dice = self.tmc_instance.proba_risky_dice(layout, circle)[0] # Get only the risky dice transition matrix - transition_matrices = [P_safe, P_normal, P_risky] + transition_matrices = [safe_dice, normal_dice, risky_dice] number_turns = [] number_mean = [] @@ -116,8 +114,9 @@ class validation: return mean_turns - def play_optimal_strategy(self, n_iterations=10000): - return self.simulate_game(self.optimal_strategy, n_iterations) + + def play_optimal_policy(self, n_iterations=10000): + return self.simulate_game(self.optimal_policy, n_iterations) def play_dice_strategy(self, dice_choice, n_iterations=10000): @@ -140,7 +139,7 @@ class validation: total_turns = 0 while k < len(self.layout) - 1: - action = self.optimal_strategy[k] # Utiliser la stratégie empirique pour la simulation + action = self.optimal_policy[k] # Utiliser la stratégie empirique pour la simulation action_index = int(action) - 1 transition_matrix = self.normal_dice # Utiliser le dé normal pour la stratégie empirique @@ -163,27 +162,29 @@ class validation: def compare_empirical_vs_value_iteration(self, num_games=1000): - value_iteration_turns = self.simulate_state(self.optimal_strategy, self.layout, self.circle) - empirical_turns = self.simulate_state(self.optimal_strategy, self.layout, self.circle, n_iterations=num_games) + value_iteration_turns = self.optimal_policy + empirical_turns = self.simulate_state(self.optimal_policy, self.layout, self.circle, n_iterations=num_games) + + # Calculate the mean turns for each state + mean_turns_by_state = { + 'ValueIteration': value_iteration_turns.tolist(), + 'Empirical': empirical_turns.tolist() + } + + return mean_turns_by_state - # Calculer la moyenne des tours pour chaque état - mean_turns_by_state = { - 'ValueIteration': value_iteration_turns.tolist(), - 'Empirical': empirical_turns.tolist() - } - return mean_turns_by_state def compare_state_based_turns(self, num_games=1000): - optimal_turns = self.simulate_state(self.optimal_strategy, self.layout, self.circle, n_iterations=num_games) - empirical_turns = self.simulate_state(self.optimal_strategy, self.layout, self.circle, n_iterations=num_games) + value_iteration = self.expec + empirical_turns = self.simulate_state(self.optimal_policy, self.layout, self.circle, n_iterations=num_games) - return optimal_turns, empirical_turns + return value_iteration, empirical_turns def compare_strategies(self, num_games=1000): - optimal_cost = self.simulate_game(self.optimal_strategy, n_iterations=num_games) + optimal_cost = self.simulate_game(self.optimal_policy, n_iterations=num_games) dice1_cost = self.simulate_game(self.safe_strategy, n_iterations=num_games) dice2_cost = self.simulate_game(self.normal_strategy, n_iterations=num_games) dice3_cost = self.simulate_game(self.risky_strategy, n_iterations=num_games) @@ -197,7 +198,7 @@ class validation: 'Random': random_cost } - +""" # Utilisation d'exemple layout = [0, 0, 3, 0, 2, 0, 2, 0, 0, 0, 3, 0, 0, 1, 0] circle = False @@ -208,12 +209,14 @@ validation_instance = validation(layout, circle) turns_by_state = validation_instance.compare_empirical_vs_value_iteration(num_games=1000) # Imprimer les moyennes des tours pour chaque état + num_states = len(layout) for state in range(num_states - 1): print(f"État {state}:") print(f" ValueIteration - Tours moyens : {turns_by_state['ValueIteration'][state]:.2f}") print(f" Empirical - Tours moyens : {turns_by_state['Empirical'][state]:.2f}") + # Exécuter la stratégie empirique une fois empirical_strategy_result = validation_instance.play_empirical_strategy() print("Coût de la stratégie empirique sur un tour :", empirical_strategy_result) @@ -222,14 +225,19 @@ print("Coût de la stratégie empirique sur un tour :", empirical_strategy_resul comparison_result = validation_instance.compare_empirical_vs_value_iteration(num_games=1000) print("Coût moyen de la stratégie de value iteration :", comparison_result['ValueIteration']) print("Coût moyen de la stratégie empirique :", comparison_result['Empirical']) -""" -optimal_cost = validation_instance.play_optimal_strategy(n_iterations=10000) +optimal_cost = validation_instance.play_optimal_policy(n_iterations=10000) print("Optimal Strategy Cost:", optimal_cost) +dice1_cost = validation_instance.play_dice_strategy('SafeDice', n_iterations=10000) +print("Safe Dice Strategy Cost:", dice1_cost) + dice2_cost = validation_instance.play_dice_strategy('NormalDice', n_iterations=10000) print("Normal Dice Strategy Cost:", dice2_cost) +dice3_cost = validation_instance.play_dice_strategy('RiskyDice', n_iterations=10000) +print("Risky Dice Strategy Cost:", dice3_cost) + random_cost = validation_instance.play_random_strategy(n_iterations=10000) print("Random Strategy Cost:", random_cost) @@ -237,8 +245,8 @@ strategy_comparison = validation_instance.compare_strategies(num_games=10000) print("Strategy Comparison Results:", strategy_comparison) -optimal_strategy = validation_instance.optimal_strategy -mean_turns_optimal = validation_instance.simulate_state(optimal_strategy, layout, circle, n_iterations=10000) +optimal_policy = validation_instance.optimal_policy +mean_turns_optimal = validation_instance.simulate_state(optimal_policy, layout, circle, n_iterations=10000) print("Mean Turns for Optimal Strategy:", mean_turns_optimal) safe_dice_strategy = validation_instance.safe_strategy @@ -256,5 +264,6 @@ print("Mean Turns for Risky Dice Strategy:", mean_turns_risky_dice) random_dice_strategy = validation_instance.random_strategy mean_turns_random_dice = validation_instance.simulate_state(random_dice_strategy, layout, circle, n_iterations=10000) print("Mean Turns for Random Dice Strategy:", mean_turns_random_dice) -""" + +""" \ No newline at end of file -- GitLab