#!/usr/bin/env python #RockTest #Programming Competition Tabulation Console, "rt_tabulate", V. 2.0.1 #Last modification: March 11, 2006 #RockTest consists primarily of three GUI programs: RockTest Team Console, # RockTest Judge's Console, and RockTest Tabulation Console. There are also # "rt_update", a file copying program, "setup.sh", an install script, # and various help files. #Copyright 2006 #Michael P. Conlon, Ph.D. #Computer Science Department #Slippery Rock University of Pennsylvania #Slippery Rock, PA 16057 #Email: michael.conlon@sru.edu #724-738-2143 # This program is free software; you may redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # or see http://www.gnu.org/copyleft/gpl.html. #Bugs: #See the comments in RockTest Team Console for more information. from Tkinter import * from string import * import os import commands import sys import fcntl TRUE=1 COLOR="#c5ffc5" COLOR2="#ffffcc" MENU=0 ENTRY=1 root=Tk() root.title("RockTest Tabulation Console") root.iconbitmap("@/usr/local/bin/rocktest/rock_mask.xbm") #This works! root.iconmask("@/usr/local/bin/rocktest/rock_gbg.xbm") class info_box_class: def __init__(self, parent, name, text, but_text, just): self.msg=StringVar() self.tmp_msg=StringVar() self.msg.set(text) self.parent = parent self.name = name self.button_text = but_text self.just = just def close(self): #Close the info box. self.info_box.destroy() def create(self, msg=""): #Create the dialog box. self.info_box=Toplevel(self.parent, bg=COLOR2) self.info_box.title(self.name) #Use the default message if there is no msg parameter. if msg == "": self.tmp_msg.set(self.msg.get()) else: self.tmp_msg.set(msg) self.abt_msg=Message(self.info_box, textvariable=self.tmp_msg, justify=self.just, aspect=300, width=400, bg=COLOR2).pack(side=TOP) self.ok_button=Button(self.info_box, text=self.button_text) self.ok_button.configure(command=lambda obj=self: obj.close()) self.ok_button.pack(side=BOTTOM) #Create "Missing teams.conf file" info box. box_name="Missing teams.conf file" but_text="Close" msg=""" The file "teams.conf" is missing. The program cannot continue. """ no_teams_conf_file_box=info_box_class(root, box_name, msg, but_text, CENTER) #Create "No Tabulation File" info box. box_name="No Tabulation File" but_text="Close" msg=""" There is no tabulation file, or it is empty. Restart rt_tabulate after the judges have graded some problems. """ no_tab_file_box=info_box_class(root, box_name, msg, but_text, CENTER) #Create "About RockTest" info box. box_name="About RockTest" but_text="Close" msg="""RockTest Tabulation Console V. 2.0.1 Copyright 2006 Michael P. Conlon, Ph.D., Computer Science Department, Slippery Rock University Email: michael.conlon@sru.edu No warranty; distribution and modification is permitted under the Gnu General Public License, V2 (or later). See http://www.gnu.org/copyleft/gpl.html for details.""" about_info_box=info_box_class(root, box_name, msg, but_text, CENTER) #Create "Help" info box. box_name="Help" but_text="Close" msg=""" TBA """ help_info_box=info_box_class(root, box_name, msg, but_text, LEFT) class conf_class: def __init__(self): #Get program parameters. #Is there a configuration file? try: #If there is, get parameters from the file. fd = open('/usr/local/etc/rocktest/rocktest.conf') self.data = fd.readlines() #print "Datafile contents:\n", self.data #sys.exit(1) self.total = 0 for self.line in self.data: self.line = self.line[:-1] if self.line[:13] == "problem_count": self.prob_count = atoi(self.line[14:]) self.total += 1 elif self.line[:7]=="printer": self.printer=self.line[8:] self.total += 2 elif self.line[:10]=="team_count": self.team_count = atoi(self.line[11:]) self.total += 4 elif self.line[:16] == "contest_end_time": self.contest_end_time = self.line[17:] self.total += 8 elif self.line[:18] == "contest_start_time": self.contest_start_time = self.line[19:] self.total += 16 elif self.line[:7] == "penalty": self.penalty = atoi(self.line[8:]) self.total += 32 if self.total < 32: self.penalty = 15 else: self.total -= 32 if self.total < 16: self.contest_start_time = "08:30" else: self.total -= 16 if self.total < 8: self.contest_end_time = "12:00" else: self.total -= 8 if self.total < 4: self.team_count = 10 else: self.total -= 4 if self.total < 2: self.printer = "lp0" else: self.total -= 2 if self.total < 1: self.prob_count = 10 fd.close() except: #print "Error processing initialization file." self.prob_count = 10 self.team_count = 10 self.contest_start_time = "08:30" self.contest_end_time = "12:00" self.printer = "lp0" try: fd = open('/usr/local/etc/rocktest/teams.conf') self.teams = fd.readlines() except: #no_teams_conf_file_box.create() #no_teams_conf_file_box.ok_button.configure(command=root.quit) #no_teams_conf_file_box.ok_button.after(60000, root.quit) print "Missing teams.conf file." #self.temp=Toplevel() #self.temp.wait_window(no_teams_conf_file_box) sys.exit(1) return if len(self.teams) != self.team_count: print "Team count disagrees with number of team names." sys.exit(1) self.division=[] for i in range(self.team_count): #print i, self.teams[i] self.division.append(self.teams[i][-2]) #print self.division[i] self.teams[i] = self.teams[i][:-2] #print "problem count = %d\nteam count = %d\nprinter = %s start_time = %s\n"\ # % (self.prob_count, self.team_count, self.printer, \ # self.contest_start_time) conf=conf_class() def sort_ascending(data, field): for i in range(1, len(data)-1): #print "i =", i done = TRUE for j in range(2, len(data)- i + 1): #print "i= ", i, "j =", j, data[min][field], data[j][field] if data[j-1][field] > data[j][field]: temp = data[j] data[j] = data[j-1] data[j-1] = temp done = FALSE if done: return def sort_descending(data, field): for i in range(1, len(data)-1): #print "i =", i done = TRUE for j in range(2, len(data) - i + 1): #print "i= ", i, "j =", j, data[min][field], data[j][field] if data[j-1][field] < data[j][field]: temp = data[j] data[j] = data[j-1] data[j-1] = temp done = FALSE if done: return #Create "No Tabulation File" info box. box_name="No Tabulation File" but_text="Close" msg=""" There is no tabulation file, or it is empty. Restart rt_tabulate after the judges have graded some problems. """ no_tab_file_box=info_box_class(root, box_name, msg, but_text, CENTER) #Create "About RockTest" info box. box_name="About RockTest" but_text="Close" msg="""RockTest Tabulation Console V. 2.0 Copyright 2006 Michael P. Conlon, Ph.D., Computer Science Department, Slippery Rock University Email: michael.conlon@sru.edu No warranty; distribution and modification is permitted under the Gnu General Public License, V2 (or later). See http://www.gnu.org/copyleft/gpl.html for details.""" about_info_box=info_box_class(root, box_name, msg, but_text, CENTER) #Create "Help" info box. box_name="Help" but_text="Close" msg=""" TBA """ help_info_box=info_box_class(root, box_name, msg, but_text, LEFT) class gui_class: def __init__(self, root): #root.minsize(900,35) #root.maxsize(1024, 1000) #Build menubar and drop-down menus. self.menubar = Menu(root, relief=SUNKEN) #Create a pulldown file menu, and add it to the menu bar. self.file_menu=Menu(self.menubar, tearoff=0) self.file_menu.add_command(label="Update", command=self.tabulate) self.file_menu.add_command(label="Print", command=self.print_report) self.file_menu.add_command(label="Quit", command=root.quit) self.menubar.add_cascade(label="File", menu=self.file_menu) #Create a pulldown view menu, and add it to the menu bar. self.view_menu=Menu(self.menubar, tearoff=0) self.view_menu.add_command(label="Font size...") #, #command=self.change_font_size) self.menubar.add_cascade(label="View", menu=self.view_menu) #Create a pulldown help menu, and add it to the menu bar. self.help_menu=Menu(self.menubar, tearoff=0) self.help_menu.add_command(label="Help...", command=help_info_box.create) self.help_menu.add_command(label="About RockTest...", command=about_info_box.create) self.menubar.add_cascade(label="Help", menu=self.help_menu) self.timestamp=StringVar() root.config(menu=self.menubar) root.config(bg=COLOR) #Create the top frame. self.height = 106 + 13 * conf.team_count if self.height > 600: self.height = 600 self.width = 261 + conf.prob_count * 55 + 37 if self.width < 680: self.width = 680 main_frame=Frame(root, bg=COLOR2, width=self.width, height=self.height) #Create the toolbar. self.toolbar=Frame(main_frame, bg=COLOR, borderwidth=3, relief=RIDGE, width=self.width-2, height=47) self.update_button=Button(self.toolbar, text="Update", height=2, command=self.tabulate) self.update_button.place(x=0, y=0, anchor=NW) self.master_sort_button=Button(self.toolbar, text="Master\nSort",\ command=self.master_sort) self.x = 105 self.master_sort_button.place(x=self.x, y=0, anchor=NW) self.sort_team_button=Button(self.toolbar, text="Sort by\nTeam #",\ command=self.sort_by_team) self.x += 75 self.sort_team_button.place(x=self.x, y=0, anchor=NW) self.sort_school_button=Button(self.toolbar, text="Sort by\nName",\ command=self.sort_by_school) self.x += 78 self.sort_school_button.place(x=self.x, y=0, anchor=NW) self.sort_division_button=Button(self.toolbar, text="Sort by\nDivision",\ command=self.sort_by_division) self.x += 78 self.sort_division_button.place(x=self.x, y=0, anchor=NW) self.sort_problem_button=Button(self.toolbar, text="Sort by\nSolutions",\ command=self.sort_by_problems) self.x += 80 self.sort_problem_button.place(x=self.x, y=0, anchor=NW) self.sort_minutes_button=Button(self.toolbar, text="Sort by\nTime",\ command=self.sort_by_minutes) self.x += 88 self.sort_minutes_button.place(x=self.x, y=0, anchor=NW) self.print_button=Button(self.toolbar, text="Print", height=2, command=self.print_report) self.x += 108 self.print_button.place(x=self.x, y=0, anchor=NW) #self.toolbar.place(x=0, y=0, anchor=NW) self.toolbar.pack(side=TOP) self.headingbar=Frame(main_frame, bg=COLOR2, width=self.width-2, height=38,\ relief=FLAT, borderwidth=3) self.relief="sunken" self.x=3 Label(self.headingbar, text="Team", relief=self.relief, justify=CENTER, width=7, height=2).place(anchor=W, \ x=self.x, rely=0.5) self.x += 56 Label(self.headingbar, text="Team\nName", relief=self.relief, height=2, justify=CENTER, width=11).place(\ anchor=W, x=self.x, rely=0.5) self.x += 84 Label(self.headingbar, text="Div", relief=self.relief, height=2).place(\ anchor=W, x=self.x, rely=0.5) self.x += 30 Label(self.headingbar, text="Probs\nSolved", relief=self.relief)\ .place(anchor=W, x=self.x, rely=0.5) self.x += 48 Label(self.headingbar, text="Eff\nMins", relief=self.relief)\ .place(anchor=W, x=self.x, rely=0.5) self.x += 36 self.prob_label = [()] for self.prob_no in range(1, conf.prob_count + 1): self.prob_label.append(Label(self.headingbar, text="Prob #%d\n#Bad Time" % self.prob_no, relief=self.relief, height=2, font=("Helvetica", "8", "bold"))) self.prob_label[self.prob_no].place(anchor=W, x=self.x, rely=0.5) self.x += 56 #self.headingbar.place(anchor=NW, x=0, y=52) self.headingbar.pack(side=TOP) self.textframe=Frame(main_frame, bg=COLOR2,\ borderwidth=3, relief=RIDGE, width=self.width-2) self.boxwidth = 36 + 8 * conf.prob_count if self.boxwidth < 91: self.boxwidth = 91 self.textbox = Text(self.textframe, height=conf.team_count, \ bg=COLOR2, setgrid=TRUE, width=self.boxwidth) #, font=("Courier", "9", "bold")) self.vscroll = Scrollbar(self.textframe, command=self.textbox.yview) self.textbox.configure(yscrollcommand=self.vscroll.set) self.vscroll.pack(side=RIGHT, expand=NO, fill=Y) self.textbox.pack(side=LEFT, expand=YES, fill=BOTH) #self.textframe.place(anchor=NW, x=0, y=91) self.textframe.pack(side=TOP) main_frame.pack() def format_and_display(self): self.text = "" self.header1 = " Probs Eff " self.header2 = " Team School Div Solved Min " self.underline = "-------------------------------------" for i in range(1, conf.prob_count+1): self.header1 += " Prob #%d" % i self.header2 += " #wr Tim" self.underline += "--------" self.header = self.header1 + "\n" + self.header2 + "\n" + \ self.underline + "\n" for self.line in self.tabdata: #print "Line is ", self.line if self.line != {}: self.eff_min = "%4d" % self.line["effective_min"] if self.eff_min == "9999": self.eff_min = " " self.text += "%7s%12s%3s%7d%6s " % (self.line["team"],\ self.line["school"], self.line["division"],\ self.line["solved_ct"], self.eff_min) #Don't bother printing the rest of the line until there are # submissions. if self.line["solved_ct"] > 0 or self.line["tot_bad_subs"] > 0: for i in range(1, conf.prob_count+1): #print i self.text += "%2d%6s" % (self.line["bad_subs"][i], \ self.line["good_time"][i]) self.text += "\n" self.textbox.delete(0.0, END) self.textbox.insert(0.0, self.text) def tabulate(self): #print "Entering update." try: self.tabfile=open('/tmp/rocktest/tabulation', 'r') except: no_tab_file_box.create() #program will terminate when box closes. no_tab_file_box.ok_button.configure(command=root.quit) return self.rawdata=self.tabfile.readlines() self.tabfile.close() #print "length =", len(self.rawdata) if len(self.rawdata) == 0: no_tab_file_box.create() #Program will terminate when box closes. no_tab_file_box.ok_button.configure(command=root.quit) return #print "*****%s*****" % self.rawdata #Remove newline from each element of list for i in range(len(self.rawdata)): self.rawdata[i] = self.rawdata[i][:-1] self.rawdata.sort() if self.rawdata[0] == '': del self.rawdata[0] #print "rawdata = ", self.rawdata for i in range(1, len(self.rawdata)): #Cancel duplicate correct solutions by marking earlier ones as "99" #Self.rule looks like "team04 1 0 08:16". self.rule = self.rawdata[i][7:-6] #Now it looks like "1 0" self.rule = self.rule[find(self.rule, " ") + 1:] #print "self.rule = **%s**" % self.rule if i > 1 and atoi(self.rule) == 0: #print "modifying rawdata[%d]" % (i-1) #Check for consecutive correct same problems from the same team. if self.rawdata[i][:-6] == self.rawdata[i-1][:-6] and \ self.rawdata[-7] == "0": #If so, replace the earlier problem's ruling with "99" self.temp = self.rawdata[i-1][7:-6] self.loc = find(self.temp, " ") self.prob = self.temp[:self.loc] #self.rule = self.temp[self.loc+1:] self.rawdata[i-1] = self.rawdata[i-1][:7] + self.prob +\ " 99" + self.rawdata[i][-6:] #print "\nCorrected to ", self.rawdata[i-1] self.start_min = atoi(conf.contest_start_time[3:]) + 60 * \ atoi(conf.contest_start_time[:2]) self.tabdata=[{}] #Initialize the list of dictionaries. for i in range(1, conf.team_count + 1): self.tabdata.append({}) if i < 10: conf.team_name = "team0%d" % i else: conf.team_name = "team%d" % i self.tabdata[i]["team"] = conf.team_name self.tabdata[i]["division"] = conf.division[i - 1] #print conf.division[i-1] self.tabdata[i]["solved_ct"] = 0 self.tabdata[i]["final_time"] = "00:00" self.tabdata[i]["school"] = conf.teams[i-1][:-1] + " " self.tabdata[i]["school"] = self.tabdata[i]["school"][:10] self.tabdata[i]["good_time"] = [""] self.tabdata[i]["bad_subs"] = [-1] self.tabdata[i]["effective_min"] = 9999 self.tabdata[i]["tot_bad_subs"] = 0 for j in range(1, conf.prob_count+1): self.tabdata[i]["good_time"].append("") self.tabdata[i]["bad_subs"].append(0) #Next, update tabdata according to rawdata. for self.line in self.rawdata: #print "self.line =", self.line if self.line[0] == "#": #Skip comment lines in data. continue conf.team_name = self.line[:6] #i = 1 #print "self.tabdata[i]", self.tabdata[i] for i in range(1, len(self.tabdata)): #while self.tabdata[i]["team"] != conf.team_name: if self.tabdata[i]["team"] == conf.team_name: break i += 1 if self.tabdata[i]["team"] != conf.team_name: print "Loop index out of range. Clear files and check config files." #print "Processing", conf.team_name #Get problem number, ruling, timestamp. self.timestamp = self.line[-5:] self.temp = self.line[7:-6] #print "self.temp = ***%s***" % self.temp self.loc = rfind(self.temp, " ") self.prob_no = atoi(self.temp[:self.loc]) self.ruling = atoi(self.temp[self.loc+1:]) #print "Problem no: %d, ruling: %d, timestamp: %s" % (self.prob_no, self.ruling, self.timestamp) if self.ruling == 0: #Problem is correct. self.tabdata[i]["solved_ct"] += 1 #self.name = "prob%dgood_time" % self.prob_no self.tabdata[i]["good_time"][self.prob_no] = self.timestamp if self.timestamp > self.tabdata[i]["final_time"]: self.tabdata[i]["final_time"] = self.timestamp else: #Count bad submissions self.tabdata[i]["bad_subs"][self.prob_no] += 1 self.tabdata[i]["tot_bad_subs"] += 1 if self.tabdata[i]["solved_ct"] > 0: self.tabdata[i]["effective_min"] = \ (atoi(self.tabdata[i]["final_time"][3:]) + 60 * \ atoi(self.tabdata[i]["final_time"][:2]))- self.start_min + \ conf.penalty * self.tabdata[i]["tot_bad_subs"] self.format_and_display() def print_report(self): os.system("echo \"%s\n%s\" | mpage -da -l -1 | lpr -P%s" % \ (self.header, self.text, conf.printer)) def master_sort(self): sort_ascending(self.tabdata, "effective_min") sort_descending(self.tabdata, "solved_ct") sort_descending(self.tabdata, "division") self.format_and_display() def sort_by_place(self): bubblesort_descending(self.tabdata, "solved_ct") self.format_and_display() def sort_by_team(self): sort_ascending(self.tabdata, "team") self.format_and_display() def sort_by_school(self): sort_ascending(self.tabdata, "school") self.format_and_display() def sort_by_division(self): sort_descending(self.tabdata, "division") self.format_and_display() def sort_by_problems(self): sort_descending(self.tabdata, "solved_ct") self.format_and_display() def sort_by_minutes(self): sort_ascending(self.tabdata, "effective_min") self.format_and_display() gui=gui_class(root) gui.tabulate() root.mainloop()