-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDND Dungeon Master Tools.py
406 lines (347 loc) · 22.2 KB
/
DND Dungeon Master Tools.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
"""
Dungeon Masters (DM) Tools
Has three main functions: Encounters, Player Information, Initiative Track
- The Encounters section enables a DM to put in details of monsters and characters and it works out what type of encounter it is out of
deadly, hard, medium or easy for the players
- The Player information takes note of the players name, their character name and their max health and stores this in a file for the
DM to refer to in future
- The Initiative Track enables the DM to track what is happening during combat. It allows entry of updated player information including
what the player has rolled for their initiative. It then places the players in order for the DM. At the end of each turn the End Turn
button pressed will update the list and place the player at the top to the bottom and move each other one up. It also enables the DM
to edit details such as health of a player when they take damage.
"""
from tkinter import *#include everything from the tkinter module
charInfo=[]
initInfo=[]
newinitInfo=[]
class App(Tk):#constructor for the frames
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
#setup menu so we can access the exit option from a File menu
MainMenu(self)
#setup frame for each of the 'pages'
container = Frame(self,bg="Red",width = 500, height = 500)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}#creates a list of frames
for f in (StartPage, encounters, players,initiativePage):#for each frame f in the list of pages
frame = f(container, self)
self.frames[f]=frame#will make an entry of the frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(StartPage)#show the frame for the start page as this initialises it. looks to the show frame method below
def show_frame(self, context):# passes in the name of the frame which is the context name here
frame = self.frames[context]#uses the frame name that is passed into it, so to begin it passes StartPage
frame.tkraise()#shows the frame
class StartPage(Frame):#the main starting page to access the other functionality.
def __init__(self, parent, controller):
Frame.__init__(self, parent)
f = Frame(self, width = 500, height = 500)#creates the frame on the page
f.pack(side=LEFT, expand = 1)
label = Label(f, text="Dungeon Masters Tools",font=("Verdana",16))#adds the label for the title of the program.the f determines it is placed in the frame
label.grid(row=2,column=2, columnspan=5)#determines where inside the frame the label is placed
page_two = Button(f, text="Character Info", command=lambda:controller.show_frame(players))#creates the button to the Character Info page
page_two.grid(row=4,column=2)
page_one = Button(f, text="Encounters", command=lambda:controller.show_frame(encounters))#creates the button to go to the encounters page
page_one.grid(row=4,column=3)
page_three = Button(f, text="Initiative", command=lambda:controller.show_frame(initiativePage))#creates the button to go to the encounters page
page_three.grid(row=4,column=4)
class encounters(Frame):#the encounters page
def __init__(self, parent, controller):
Frame.__init__(self, parent)
def partyXPThreshold(level_ans2,char_ans2):#works out the party XP threshold to determine the encounter level
party=[]
level=level_ans2
characters=char_ans2
partyFile=open("charXPthreshold.txt","r")#opens the file in read mode to look at the values from the D&D Players Handbook
for arec in partyFile:
values = arec.split()
party.append([int(values[0]),int(values[1]),int(values[2]),int(values[3]),int(values[4])])#stores the items in a 2D array
#0 = level, 1=easy, 2=medium, 3=hard, 4=deadly
lengthParty=len(party)
for i in range(0,lengthParty):
if party[i][0]==level:#performs the calculations for each of the levels as obtained from the D&D Players Handbook
easy=party[i][1]*characters
medium=party[i][2]*characters
hard=party[i][3]*characters
deadly=party[i][4]*characters
easyLabel = Label(f, text="Easy: "+str(easy))#displays the levels and each of the value thresholds
easyLabel.grid(row=6,column=2, columnspan=3, sticky=E)
mediumLabel = Label(f, text="Medium: "+str(medium))
mediumLabel.grid(row=7,column=2, columnspan=3,sticky=E)
hardLabel = Label(f, text="Hard: "+str(hard))
hardLabel.grid(row=8,column=2, columnspan=3,sticky=E)
deadlyLabel = Label(f, text="Deadly: "+str(deadly))
deadlyLabel.grid(row=9,column=2, columnspan=3,sticky=E)
return easy, medium, hard, deadly
partyFile.close()
def readValues():#reads in the values from the boxes for the character number and the level number
char_ans2=int(char_ans.get())#gets the answer from the entry box
level_ans2=int(level_ans.get())
level=partyXPThreshold(level_ans2,char_ans2)
return level
def monsterReading():
mons_ans2=int(mons_ans.get())#gets the answer from the entry box
monXP_ans2=int(monXP_ans.get())
total = mons_ans2 * monXP_ans2#works out the monster encounter value by taking the number of monsters and the XP and multiplying
easyLabel = Label(f, text="Monster XP: "+str(total))#displays the encounter response
easyLabel.grid(row=6,column=7, columnspan=3, sticky=E)
return total
def encounterType():#works out the encounter by comparing the total monster value and the party XP threshold
level = readValues()
monsTotal=monsterReading()
titleLabel = Label(f, text="Encounter type",font=("Verdana",14))
titleLabel.grid(row=13,column=2, columnspan=3, sticky=E)
encLabel = Label(f, text=" ",font=("Verdana",14))
encLabel.grid(row=14,column=2, columnspan=3, sticky=E)
if monsTotal>=level[3]:#compares the answer and displays the appropriate response.
encLabel = Label(f, text="DEADLY",bg="red",fg="White",font=("Verdana",14))
encLabel.grid(row=14,column=2, columnspan=3, sticky=E)
elif monsTotal>=level[2]:
encLabel = Label(f, text="Hard",font=("Verdana",14))
encLabel.grid(row=14,column=2, columnspan=3, sticky=E)
elif monsTotal>=level[1]:
encLabel = Label(f, text="Medium",font=("Verdana",14))
encLabel.grid(row=14,column=2, columnspan=3, sticky=E)
else:
encLabel = Label(f, text="Easy",font=("Verdana",14))
encLabel.grid(row=14,column=2, columnspan=3, sticky=E)
f=Frame(self, width=500, height=500)#sets the frame up on the encounters page
f.pack(side=LEFT, expand=1)
welcome = Label(f, text="Encounter Check", bg="red",fg="white",font=("Verdana",14))#displays the title
welcome.grid(row=1,column=2,columnspan=10)
characters = Label(f, text="Number of Characters",font=("Verdana",8))#asks for the number of characters
characters.grid(row=2,columnspan=4, sticky=E)
char_ans=Entry(f)
char_ans.grid(row=2,column=4)
level = Label(f, text="Level",font=("Verdana",8))#asks for the level of the characters
level.grid(row=4,columnspan=4, sticky=E)
level_ans=Entry(f)
level_ans.grid(row=4,column=4)
monsters = Label(f, text="Number of Monsters",font=("Verdana",8))#asks for the number of monsters
monsters.grid(row=2,column=7, columnspan=4, sticky=E)
mons_ans=Entry(f)
mons_ans.grid(row=2,column=12)
monXP= Label(f, text="Monster XP",font=("Verdana",8))#asks for the monster XP value
monXP.grid(row=4,column=7, columnspan=4, sticky=E)
monXP_ans=Entry(f)
monXP_ans.grid(row=4,column=12)
Button(f,text="Calculate Character",command=readValues).grid(row=10, column=3)#provides the button to calculate the character values
Button(f,text="Calculate Monsters",command=monsterReading).grid(row=10, column=7)#provides the button to calculate the monster values
note = Label(f, text="(All boxes must be complete) for the type of encounter to be calculated",font=("Verdana, 10"))
note.grid(row=11,column=4, columnspan=4)
Button(f,text="Type of Encounter",command=encounterType).grid(row=12, column = 5)#provides the button to calculate the final encounter type
start_page = Button(f, text="Back to start page", command=lambda:controller.show_frame(StartPage))#provides the button back to the main page
start_page.grid(row=1,column=9)
page_two = Button(f, text="Characters", command=lambda:controller.show_frame(players))#provides the button to the characters or players page
page_two.grid(row=1,column=11)
page_three = Button(f, text="Initiative Tracker", command=lambda: controller.show_frame(initiativePage)) # creates the button to the initiative tracker page
page_three.grid(row=1, column=12)
class players(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
def showCharBoxes():#this will show the boxes to enter the character information for character name, player name and the max health
numChar=int(numCharBox_ans.get())
for i in range(0,numChar):
#labels
charName = Label(f, text="Character Name",font=("Verdana",8))
charName.grid(row=i+5,columnspan=4, sticky=E)
#entry
charName_ans = Entry(f)
charName_ans.grid(row=i+5,column=4)
#labels
playerName = Label(f, text="Player Name",font=("Verdana",8))
playerName.grid(row=i+5,column=4,columnspan=4, sticky=E)
#entry
playerName_ans = Entry(f)
playerName_ans.grid(row=i+5,column=9)
#labels
health = Label(f, text="Max Health",font=("Verdana",8))
health.grid(row=i+5,column=9,columnspan=4, sticky=E)
#entry
health_ans = Entry(f)
health_ans.grid(row=i+5,column=15)
charInfo.append([charName_ans,playerName_ans,health_ans])
def saveCharacterInfo():#this will save the information entered for the characters into a file
fName=fileName.get()
t=""
n=open(fName+".txt","w")
for i in charInfo:
t="Character: "+i[0].get()+", is played by "+i[1].get()+". Their max health is:"+i[2].get()
n.write(t+"\n")
fname= Label(f, text="Complete!",font=("Verdana",14))
fname.grid(row=18,column=1, columnspan=20)
n.close()
f = Frame(self, width = 500, height = 500)#setup of the frame for the characters page
f.pack(side=LEFT, expand = 1)
characters= Label(f, text="Characters",font=("Verdana",14))#displays the heading
characters.grid(row=1, column=3)
numCharBox= Label(f, text="Number of Characters",font=("Verdana",8))#asks for the number of characters
numCharBox.grid(row=2,column=3, columnspan=8, sticky=E)
numCharBox_ans=Entry(f)
numCharBox_ans.grid(row=2,column=12)
button=Button(f,text="Enter Character Details",command=showCharBoxes).grid(row=3, column=1,columnspan=10)#button to allow the user to enter the correct number of character details
button=Button(f,text="Save Details",bg="light blue",command=saveCharacterInfo).grid(row=3, column=14,columnspan=4)#ensures the information is saved into a file
fname= Label(f, text="Enter file name below to save these details to...(any existing file name will be overwritten)",font=("Verdana",10))
fname.grid(row=15,column=1, columnspan=20)
fname= Label(f, text="Once you have entered the file name press Save Details above",font=("Verdana",10))#allows the user to enter a file name
fname.grid(row=16,column=1, columnspan=20)
fileName = Entry(f)
fileName.grid(row=17,column=1)
start_page = Button(f, text="Back to start page", command=lambda:controller.show_frame(StartPage))#button to go back to the main page
start_page.grid(row=1,column=6)
page_one = Button(f, text="Encounters", command=lambda:controller.show_frame(encounters))#button to go to the encounters page
page_one.grid(row=1,column=8)
page_three = Button(f, text="Initiative Tracker", command=lambda: controller.show_frame(initiativePage)) # creates the button to the initiative tracker page
page_three.grid(row=1, column=10)
class initiativePage(Frame):#the initiative page to allow a DM to track an encounter.
def __init__(self, parent, controller):
Frame.__init__(self, parent)
f = Frame(self, width = 500, height = 500)#creates the frame on the page
f.pack(side=LEFT, expand = 1)
label = Label(f, text="Initiative tracker",font=("Verdana",16))#adds the label for the title of the program.the f determines it is placed in the frame
label.grid(row=1,column=1, columnspan=4)#determines where inside the frame the label is placed
def endTurn():
#at the end of a turn when the turn complete button is pressed the array is reordered to show the next
# player at the top and the previous player moves to the bottom of the list
temp = newinitInfo[0]
newinitInfo.pop(0)
#move position 0 of array to last in list
newinitInfo.append(temp)
#display
disInit()
def editinfo():#the user can update information like the health once a player has taken damage
item = int(listItem.get())-1#gets the person in the list
aspect = aspectItem.get()#gets the aspect such as health, name or AC
newVal = newValue.get()#gets the new value to replace the old one
if aspect=="Name": #updates the existing array of data
newinitInfo[item][0]=newVal
elif aspect=="AC":
newinitInfo[item][1]=newVal
elif aspect=="Health":
newinitInfo[item][2]=newVal
#newinitInfo[item]=
disInit()
def updateOrder():
#if the DM needs to update the order and swap people over due to rules within encounters this is done here
#first pos is gained from the entry box
position1=int(pos1.get())-1
#second pos is gained from the entry box
position2 = int(pos2.get())-1
#swap in array to new positions
newinitInfo[position1][4] = position2
newinitInfo[position2][4] = position1
temp = newinitInfo[position1]
newinitInfo[position1] = newinitInfo[position2]
newinitInfo[position2] = temp
#call to display
disInit()
def disInit():
#displays the initiative order to the screen with the key data
i = 11
initOrderShow = Label(f, text="Initiative Order:", font=("Verdana", 8))
initOrderShow.grid(row=11, column=1, columnspan=2, sticky=E)
for d in newinitInfo:
charName = Label(f, text="Name: " + d[0] + "\t AC: " + str(d[1]) + "\t Max Health: " + str(d[2])+ "\tInitiative Roll: "+str(d[3]), font=("Verdana", 8))
charName.grid(row=i + 1, column=1,columnspan=2, sticky=W)
i += 1
def initOrder():#gets the information from the data entry to add to the new array.
z=len(initInfo)
newinitInfo.clear()
for x in range(z):
newinitInfo.append([initInfo[x][0].get(),int(initInfo[x][1].get()),int(initInfo[x][2].get()),int(initInfo[x][3].get()),x])
n = len(newinitInfo) - 1
for j in range(n):#reorders the array to place the players in their initative order.
for i in range(n):
if newinitInfo[i][3] == newinitInfo[i + 1][3]:#informs the DM of players who have rolled the same number
sameNo = Label(f, text=newinitInfo[i][0] + " has rolled the same as " + newinitInfo[i + 1][0],fg="Red", font=("Verdana", 12))
sameNo.grid(row=20, column=1,columnspan=3)
elif newinitInfo[i][3] < newinitInfo[i + 1][3]:
newinitInfo[i + 1][4] -= 1
newinitInfo[i][4] += 1
temp = newinitInfo[i]
newinitInfo[i] = newinitInfo[i + 1]
newinitInfo[i + 1] = temp
#now to display the information
disInit()
turnEnd = Button(f, text="Turn complete",command=lambda: endTurn()) # creates the button to go to the encounters page
turnEnd.grid(row=11, column=4)#button to call the turn complete function
def initiativeBoxes():#this will show the boxes to enter the character information for name, Character AC, the max health, initiativeRoll
#labels
charName = Label(f, text="Name",font=("Verdana",8))
charName.grid(row=5,column=1, sticky=E)
#entry
nameInit = Entry(f)
nameInit.grid(row=5,column=2)
#labels
playerName = Label(f, text="AC",font=("Verdana",8))
playerName.grid(row=5,column=3, sticky=E)
#entry
charAC = Entry(f)
charAC.grid(row=5,column=4)
#labels
health = Label(f, text="Health",font=("Verdana",8))
health.grid(row=5,column=5, sticky=E)
#entry
maxhealth = Entry(f)
maxhealth.grid(row=5,column=6)
#labels
initiativeRoll = Label(f, text="Roll",font=("Verdana",8))
initiativeRoll.grid(row=5,column=7, sticky=E)
#entry
initRoll = Entry(f)
initRoll.grid(row=5,column=8)
initInfo.append([nameInit,charAC,maxhealth,initRoll])#adds the boxes to the array
bttInitiative = Button(f, text="Put into order", command=initOrder)#creates the button to the Character Info page
bttInitiative.grid(row=7,column=1)
bttInitiative = Button(f, text="Enter Info", command=initiativeBoxes)#creates the button enter Info
bttInitiative.grid(row=3,column=1)#each time the button is pressed a new player can be added to the encounter
instructions = Label(f, text="Select Enter Info. Add each character/monster individually and press Enter Info again to add more. ")
instructions.grid(row=2, columnspan=20, sticky=W)
instructions2 = Label(f, text="If multiple players rolled the same a message will show. Change the order, using numbers, in the box below")
instructions2.grid(row=6,columnspan=6, sticky=W)
#shows the entry boxes should the DM wish to change order of the players i.e. if two rolled the same, or player held their initiative order
updateInitOrder = Button(f, text="Swap these positions", command=updateOrder)
updateInitOrder.grid(row=7, column=3, columnspan=2)
pos1 = Entry(f)
pos1.grid(row=7, column=5)
pos2 = Entry(f)
pos2.grid(row=7, column=6)
# master = Tk()
#shows the data entry boxes for editing an item in the list should a player health need to be updated
editLabel = Label(f, text="Enter player number in list to the left to edit.")
editLabel.grid(row=12, column=6)
listItem = Entry(f)
listItem.grid(row=12, column=7)
#ASPECT
aspects = ["Name","AC","Health"]#these are the options for the drop down menu
aspLabel = Label(f, text="Aspect")# label for the drop down menu
aspLabel.grid(row=13, column=6, sticky=E)#position of label
aspectItem = StringVar(f)
aspectItem.set(aspects[2])#sets default to health as this is likely to be most common
w = OptionMenu(f,aspectItem, *aspects)#setup the drop down menu with the aspects list and assigning to aspectItem
w.grid(row=13,column=7)#positions the drop down menu
#THE NEW VALUE
newValueLabel = Label(f, text="New Value")
newValueLabel.grid(row=14, column=6, sticky=E)
newValue = Entry(f)
newValue.grid(row=14, column=7)
#adds the button to update the data calling the editinfo function
upData = Button(f, text="UPDATE DATA", command=lambda: editinfo())
upData.grid(row=15, column=6)
#displays buttons to the other pages and to get back to the home page
page_two = Button(f, text="Character Info", command=lambda:controller.show_frame(players))#creates the button to the Character Info page
page_two.grid(row=1,column=7)
page_one = Button(f, text="Encounters", command=lambda:controller.show_frame(encounters))#creates the button to go to the encounters page
page_one.grid(row=1,column=6)
start_page = Button(f, text="Back to Start", command=lambda:controller.show_frame(StartPage))#creates the button to go to the encounters page
start_page.grid(row=1,column=5)
class MainMenu:
def __init__(self, master):#the main menu to allow the user to quit by going to File and Exit
menubar= Menu(master)
filemenu = Menu(menubar, tearoff=0)
filemenu.add_command(label="Exit", command=master.destroy)
menubar.add_cascade(label="File", menu=filemenu)
master.config(menu=menubar)
app = App()#creates an instance of itself
app.mainloop()