Skip to content

Instantly share code, notes, and snippets.

@horstjens
Last active July 20, 2022 07:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save horstjens/95c6b7000ba683a12100d76f30bc40d2 to your computer and use it in GitHub Desktop.
Save horstjens/95c6b7000ba683a12100d76f30bc40d2 to your computer and use it in GitHub Desktop.
emil1
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