Last active
July 20, 2022 07:02
-
-
Save horstjens/95c6b7000ba683a12100d76f30bc40d2 to your computer and use it in GitHub Desktop.
emil1
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import random | |
import PySimpleGUI as sg | |
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg | |
import matplotlib.pyplot as plt | |
import matplotlib.ticker as mtick | |
import numpy as np | |
from collections import defaultdict | |
# with re-roll | |
def roll(dice_string="2D6+1", details=False): | |
"""random generator for d&d values, with re-reoll | |
examples: | |
2d6+1 : roll 2 6-sides dice, add 1 to the end result | |
1D6 : roll 1 6-sided die, if it rolls a 6 it counts as 5 but roll again | |
(exploding dice, has very low probability of very high number) | |
2D6-1 : roll 2 6-sided exploding dice, subtract 1 from the end result | |
returns: | |
if details is False: end_result | |
if details is True: (end_result, number of dice, number of eyes, delta) | |
""" | |
if not "D" in dice_string: | |
if details: | |
return dd(dice_string) | |
return dd(dice_string)[0] | |
# check for errors | |
result, number, eyes, delta = dd(dice_string.replace("D","d")) | |
# no errors | |
end_result = 0 | |
for _ in range(number): | |
while True: | |
roll = random.randint(1, eyes) | |
if roll != eyes: | |
end_result += roll | |
break | |
end_result += eyes-1 | |
end_result += delta | |
if details: | |
return end_result, number, eyes, delta | |
return end_result | |
def dd(dice_string="2d6"): | |
""" | |
accept an dungeons and dragons dice string | |
like '2d6' or '2d6+1' or '2d6-1' | |
dice string must have a 'd' | |
number before d is number of dice | |
number after d is number of eyes per dice | |
optional: | |
dicestring can have a suffix, starting with + or - | |
suffix must be an integer | |
integer is added / substracted from end result | |
returns sum of eyes | |
""" | |
# exist suffix? | |
delta = 0 | |
if ("+" in dice_string) or ("-" in dice_string): | |
if (dice_string.count("+") == 1) and (dice_string.count("-") == 0): | |
sign = "+" | |
elif (dice_string.count("+") == 0) and (dice_string.count("-") == 1): | |
sign = "-" | |
else: | |
raise ValueError("dice string can only contain one + or one -", dice_string) | |
prefix, suffix = dice_string.split(sign) | |
try: | |
delta = int(suffix) | |
except: | |
raise ValueError(f"part after {sign} must be an integer", dice_string) | |
if sign == "-": | |
delta *= -1 | |
dice_string = prefix | |
if not 'd' in dice_string: | |
raise ValueError("dice_string must contain a d:", dice_string) | |
if dice_string.count("d") != 1: | |
raise ValueError("dice string must have exacly one d", dice_string) | |
before, after = dice_string.split("d") | |
try: | |
number = int(before) | |
except: | |
raise ValueError("part before d must be integer", before) | |
if number < 0: | |
raise ValueError("part before d must be a positive integer:", before) | |
try: | |
eyes = int(after) | |
except: | |
raise ValueError("part after d must be integer", after) | |
if eyes < 0: | |
raise ValueError("part after d must be a positive integer:", after) | |
#print("all ok", before, after) | |
if (eyes == 0) or (number==0): | |
return 0 | |
result = 0 | |
for _ in range(number): | |
result += random.randint(1, eyes) | |
return result + delta, number, eyes, delta | |
def draw_figure(canvas, figure): | |
figure_canvas_agg = FigureCanvasTkAgg(figure, canvas) | |
figure_canvas_agg.draw() | |
figure_canvas_agg.get_tk_widget().pack(side='top', fill='both', expand=1) | |
return figure_canvas_agg | |
def main(): | |
sg.theme('DarkGreen') # Add a touch of color | |
# All the stuff inside your window. | |
layout = [ | |
[sg.Text('how many throws'), | |
sg.Slider( | |
range=(1,1000), | |
key="howmany", | |
default_value=10, | |
orientation="h", | |
size=(70,10), | |
)], | |
[sg.Column(layout = [ | |
[sg.Text("Alice: min: 2 max: 12", text_color="blue", font=("System",15), | |
key="alice")], | |
[sg.Text('D&D dice_string:'), | |
sg.Input("2d6", key="dicestring1", font=("System", 15), | |
enable_events=True)], | |
[sg.Text("Bob: min: 2 max: 12", text_color = "orange", font=("System",15), | |
key="bob")], | |
[sg.Text('D&D dice_string:'), | |
sg.Input("2d6", key="dicestring2", font=("System", 15), | |
enable_events=True)], | |
], size=(300,150) ), | |
sg.Multiline("result", key="display", autoscroll=True, size=(60,8)) ], | |
#[sg.Multiline("result", key="display", autoscroll=True, expand_x=True, expand_y=True)], | |
#[sg.Graph(canvas_size=(600,600), | |
# graph_bottom_left=(0,0), | |
# graph_top_right=(1,1), | |
# background_color = "#FFFFFF", | |
# key="canvas")], | |
[sg.Canvas(size=(800, 640), key='canvas')], | |
[sg.Button('Ok'), sg.Button('Cancel')] | |
] | |
# Create the Window | |
window = sg.Window('Alice vs. Bob D&D battle simulator', layout, finalize=True) | |
#window.finalize() | |
canvas_elem = window['canvas'] | |
canvas = canvas_elem.TKCanvas | |
fig, ax = plt.subplots(nrows=2, ncols=1, gridspec_kw={'height_ratios': [2, 1.2]}) | |
fig_agg = draw_figure(canvas, fig) | |
# Event Loop to process "events" and get the "values" of the inputs | |
while True: | |
event, values = window.read() | |
if event == sg.WIN_CLOSED or event == 'Cancel': # if user closes window or clicks cancel | |
break | |
#print('You entered ', values[0]) | |
if event == "dicestring1": | |
try: | |
result, number, eyes, delta = roll(values["dicestring1"], True) | |
except Exception as e: | |
window["alice"].update(e) | |
continue | |
if "D" in values["dicestring1"]: | |
star = "*" | |
else: | |
star = "" | |
window["alice"].update(f"Alice: min: {number+delta} max: {number*eyes+delta}{star}") | |
if event == "dicestring2": | |
try: | |
result, number, eyes, delta = roll(values["dicestring2"], True) | |
except Exception as e: | |
window["bob"].update(e) | |
continue | |
if "D" in values["dicestring2"]: | |
star = "*" | |
else: | |
star = "" | |
window["bob"].update(f"Bob: min: {number+delta} max: {number*eyes+delta}{star}") | |
if event == "Ok": | |
result1 = [] | |
result2 = [] | |
rolls_alice = defaultdict(lambda:0) | |
rolls_bob = defaultdict(lambda:0) | |
# sanity check | |
try: | |
roll(values["dicestring1"]) | |
roll(values["dicestring2"]) | |
except Exception as e: | |
sg.PopupError(e) | |
continue | |
n = int(values["howmany"]) | |
for _ in range(n): | |
a = roll(values["dicestring1"]) | |
b = roll(values["dicestring2"]) | |
result1.append(a) | |
result2.append(b) | |
rolls_alice[a] += 1 | |
rolls_bob[b] += 1 | |
#result=f"Alice:{result1}\nBob: {result2}" | |
result ="" | |
alice_wins = 0 | |
bob_wins = 0 | |
#draws = 0 | |
for i in range(n): | |
if result1[i] > result2[i]: | |
alice_wins += 1 | |
elif result1[i] < result2[i]: | |
bob_wins += 1 | |
draws = n - (alice_wins + bob_wins) | |
if alice_wins > bob_wins: | |
winner = "Alice" | |
elif alice_wins < bob_wins: | |
winner = "Bob" | |
else: | |
winner = "Nobody" | |
result += f"Victories: Alice {alice_wins} Bob: {bob_wins}" | |
#result += f"\n{winner} wins" | |
result += f"\nAlice:{result1}\nBob: {result2}" | |
result += f"\n{winner} wins " | |
# print(result) | |
window["display"].update(result) | |
# ============ graph using matplotlib ============= | |
ax[0].clear() | |
ax[1].clear() | |
min_roll= min(min(rolls_alice.keys()), min(rolls_bob.keys())) | |
max_roll = max(max(rolls_alice.keys()), max(rolls_bob.keys())) | |
#print("min-max", min_roll,max_roll) | |
ind = np.arange(min_roll,max_roll+1) | |
# equalize shapes: every result must be in every rolls | |
for i in ind: | |
if rolls_alice[i] == 0: | |
pass # defaultdict already updates this value | |
if rolls_bob[i] == 0: | |
pass | |
print(result1, result2, rolls_alice, rolls_bob) | |
width = 0.35 # the width of the bars | |
ax[0].grid(True) | |
#print("rolls alice values", rolls_alice.values()) | |
y_alice = [] | |
ak = list(rolls_alice.keys()) | |
ak.sort() | |
for k in ak: | |
y_alice.append(rolls_alice[k]) | |
#y_alice.sort() | |
y_bob = [] | |
bk = list(rolls_bob.keys()) | |
bk.sort() | |
for k in bk: | |
y_bob.append(rolls_bob[k]) | |
#y_bob.sort() | |
print(y_alice, y_bob) | |
ax[0].bar(ind, y_alice, width, label='Alice', color="blue") | |
ax[0].bar(ind+width, y_bob, width, label='Bob', color="orange") | |
ax[0].set_xticks(ind + width / 2, labels=[str(x) for x in range(min_roll, max_roll+1)]) | |
#ax[0].legend() | |
ax[0].set_title("Alice {} vs. Bob {}".format(values["dicestring1"], values["dicestring2"])) | |
#ax[0].set_xlabel("result") | |
ax[0].set_ylabel(f"frequency (n={n})") | |
# ---- victories ---- | |
ax[1].set_title("Victories") | |
#percent = np.linspace(0,100, n) | |
fmt = '%.0f%%' | |
#xticks = mtick.FormatStrFormatter(fmt) | |
ax[1].barh(y=[f"Alice {alice_wins/n*100:.1f}%", | |
f"Bob {bob_wins/n*100:.1f}%", | |
f"draw {draws/n*100:.1f}%"], | |
width=[alice_wins,bob_wins,draws], left=1, height=0.5, | |
color=["blue","orange","grey"] | |
) | |
#ax[1].xaxis.set_major_formatter(xticks) | |
ax[1].grid(True) | |
if alice_wins > bob_wins: | |
c = "blue" | |
t = f"Alice wins vs. Bob {alice_wins}:{bob_wins} (draws: {draws})" | |
elif bob_wins > alice_wins: | |
c = "orange" | |
t = f"Bob wins vs. Alice {bob_wins}:{alice_wins} (draws: {draws})" | |
else: | |
c = "black" | |
t = f"Nobody wins. Alice: {alice_wins} Bob: {bob_wins} (draws: {draws}" | |
fig.suptitle(t, color=c) | |
#fig.suptitle("Alice {} vs. Bob {}".format(values["dicestring1"], values["dicestring2"])) | |
fig.legend(["Alice","Bob"]) | |
fig_agg.draw() | |
window.close() | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment