#!/usr/bin/env python #RockTest #Programming Competition Judge's Console V1.0 #Last modification: January 28, 2003 #RockTest consists of two programs: RockTest Team Console, # and RockTest Judge's Console. #Copyright 2003 #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 #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. #Bugs: none known. #Features needed: # SRU "Rock" icon # Ability to kill programs. # Ability to kill user programs. # Log file # Problem queueing # Clarification acceptance and response #See the comments in RockTest Team Console for more information. from Tkinter import * from string import * import os import commands import threading FALSE=0 TRUE=1 COLOR="#c5ffc5" COLOR2="#ffffcc" root=Tk() root.title("RockTest Judge's Console") def strip_cr(text): loc=99999 while loc >= 0: loc = find(text, '\r') if loc >= 0: text = text[:loc] + text[loc+1:] return text class conf_class: def __init__(self): self.team_no = StringVar() self.prob_no = StringVar() self.lang = StringVar() self.dfn = StringVar() self.output = StringVar() self.team_no.set("01") self.prob_no.set("1") self.lang.set("C++") self.dfn.set("program1.dat") self.output.set("") self.ext_map={"BASIC":".bas","C":".c","C++":".cpp","Java":".java", "Pascal":".pas","Python":".py"} self.no_compile = ("BASIC", "Python") self.team_name = "" self.prog_fn = "program1.cpp" #Find judge's home directory. self.home_dir = commands.getoutput("echo $HOME") conf=conf_class() def clear_dirs(): #Clear old program files from teams' directories. #First, find where team home directories are. #Assume there will always be a "team01". team_home = commands.getoutput("cd ~team01; pwd") team_home = team_home[:rfind(team_home, '/')] for i in range(99): #Loop through all teams' names no = i + 1 team = "team" if no < 10: team = "team0" team = team + "%d" % no try: output = os.listdir("%s/%s" % (team_home, team)) for file in output: if file[:7] == "program": print file os.remove("%s/%s/%s" % (team_home, team, file)) except OSError: pass class warning_box_class: def __init__(self, parent, name, text, ok_but_text, cancel_but_text, just): self.msg=StringVar() self.tmp_msg=StringVar() self.msg.set(text) self.parent = parent self.name = name self.ok_button_text = ok_but_text self.cancel_button_text = cancel_but_text self.just = just def close(self): #Close the info box. self.warning_box.destroy() def clear_em(self): clear_dirs() self.warning_box.destroy() def create(self, msg=""): #Create the dialog box. self.warning_box=Toplevel(self.parent, bg=COLOR2) self.warning_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.warning_box, textvariable=self.tmp_msg, justify=self.just, aspect=300, width=400, bg=COLOR2).pack(side=TOP) self.button_frame = Frame(self.warning_box) self.ok_button=Button(self.button_frame, text=self.ok_button_text) self.ok_button.configure(command=lambda obj=self: obj.clear_em()) self.ok_button.pack(side=LEFT) self.cancel_button=Button(self.button_frame, text=self.cancel_button_text) self.cancel_button.configure(command=lambda obj=self: obj.close()) self.cancel_button.pack(side=RIGHT) self.button_frame.pack(side=BOTTOM) #Create "Are you sure?" warning box. box_name="Are you sure?" ok_but_text="Delete Files" cancel_but_text="Cancel" msg="""Warning!!! You are about to delete all users' program files and data files. """ del_warning_box=warning_box_class(root, box_name, msg, ok_but_text, cancel_but_text, CENTER) 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 "About RockTest" info box. box_name="About RockTest" but_text="Close" msg="""RockTest Judge's Console V. 1.0 Copyright 2003, 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=""" 1) Set the team number, problem number, and programming language by clicking on the appropriate widget and selecting the desired setting. 2) Set the datafile name by clicking in the "Data File Name" entry and editing it appropriately. 3) Then click on the "OK" button. This will enable the buttons in the next row. 4) If the programming language requires a separate "compile" step, click on the "Compile" button. The "Compile" button will remain depressed until the compile process is complete. (Expect a delay.) Once the compile is done, the compiler output will appear in the lower pane. 5) To run the program with the data file selected, click on the "Run" button. The output will appear in the lower pane. 6) If you want to see the source code of the program, click on the "View Source" button. The source code of the program will appear in the lower pane. 7) If there is a problem, sometimes it helps to see the team's directory. For this, click on "List Team Files". """ help_info_box=info_box_class(root, box_name, msg, but_text, LEFT) #Create "Cannot copy file" info box. box_name="Cannot copy program file" but_text="Close" msg="""Unable to copy program file. Check that the team number, problem number, and programming language options are all properly set. If so, check that team directories are group-owned by 'rocktest', with read and write permissions for the group. Also check that the file for this program is group-owned by 'rocktest' and sgid rocktest.""" ccpf_info_box=info_box_class(root, box_name, msg, but_text, CENTER) #Create "No Data file" info box. box_name="No Data File Found" but_text="Close" msg=""" Cannot find data file. Be sure you have entered its correct name in the "Data File Name" entry. Data files should be in the "~/judge/data" directory. """ no_data_file_box=info_box_class(root, box_name, msg, but_text, CENTER) #Create "No Source file" info box. box_name="No Source Code File" but_text="Close" msg=""" Cannot find %s program file. """ % conf.lang.get() no_source_file_box=info_box_class(root, box_name, msg, but_text, CENTER) #Create "No ML file" info box. box_name="No Machine Code File" but_text="Close" msg=""" Cannot find machine code file. You need to compile the program first. It is also possible that the compile step failed. """ no_ml_file_box=info_box_class(root, box_name, msg, but_text, CENTER) #Create "No judge dir" info box. box_name="No Judge Directory" but_text="Close" msg="""You need a 'judge' directory within your home directory to operate the judge's console. Close this box, close the program, create a ~/judge directory, then restart the judge's console. """ no_judge_dir_box=info_box_class(root, box_name, msg, but_text, CENTER) #Create "Press the OK button" info box. box_name="Notice!" but_text="Close" msg=""" After changing the team, problem number, programming language, or data file name, you must press "OK" before proceeding. """ pressOK_box=info_box_class(root, box_name, msg, but_text, CENTER) class gui_class: def __init__(self, root): #root.minsize(623, 300) root.minsize(640,300) h=45 #Height of setup frame and control frame. #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="Clear Directories", command=del_warning_box.create) #self.file_menu.add_separator() self.file_menu.add_command(label="Quit", command=root.quit) self.menubar.add_cascade(label="File", menu=self.file_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) self.setup_frame = Frame(root, bg=COLOR, borderwidth=3, relief=RIDGE, width=700) Label(self.setup_frame, text="Team:",bg=COLOR).place(anchor=W, x=0, rely=0.5) OptionMenu(self.setup_frame, conf.team_no, "00","01","02","03","04", "05","06","07","08","09","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").place(anchor=W, x=45, rely=0.5) Label(self.setup_frame, text="Problem Number:", bg=COLOR, wraplength=80)\ .place(anchor=W, x=120, rely=0.5) OptionMenu(self.setup_frame, conf.prob_no, "1","2","3","4","5","6","7", "8","9").place(anchor=W, x=180, rely=0.5) Label(self.setup_frame, text="Programming Language:", bg=COLOR, \ wraplength=100).place(anchor=W, x=245, rely=0.5) OptionMenu(self.setup_frame, conf.lang, "BASIC","C","C++","Java","Pascal", "Python").place(anchor=W, x=330, rely=0.5) Label(self.setup_frame, text="Data File Name:", wraplength=90, bg=COLOR)\ .place(anchor=W, x=430, rely=0.5) Entry(self.setup_frame, textvariable=conf.dfn, width=11 ,bg=COLOR2)\ .place(anchor=W, x=490, rely=0.5) Button(self.setup_frame, text="OK", command=self.prep_problem)\ .place(anchor=E, relx=1.0, rely=0.5) self.setup_frame.place(x=0, y=0, height=h, relwidth=1.0) self.control_frame=Frame(root, bg=COLOR, borderwidth=3, relief=RIDGE) Label(self.control_frame, text="Timestamp:", width=11, bg=COLOR)\ .grid(row=0, column=0) self.ts=Label(self.control_frame, textvariable=self.timestamp, relief=\ SUNKEN, width=6, bg=COLOR2).grid(row=0, column=1) self.cb=Button(self.control_frame, text="Compile", width=11, state=DISABLED, command=self.compile) self.cb.grid(row=0, column=2) self.rb=Button(self.control_frame, text="Run", width=11, state=DISABLED, command=self.run) self.rb.grid(row=0, column=3) self.vb=Button(self.control_frame, text="View source", width=11, state=DISABLED, command=self.view_source) self.vb.grid(row=0, column=4) self.lb=Button(self.control_frame, text="List Team Files", width=11, state=DISABLED, command=self.list_team_dir) self.lb.grid(row=0, column=5) self.control_frame.place(x=0, y=h+2, height=h, relwidth=1.0) self.action_frame=Frame(root, bg=COLOR2, borderwidth=3) self.textbox = Text(self.action_frame, wrap=WORD, width=133, font=("Courier", 10), bg=COLOR2, relief=RIDGE) self.vscroll = Scrollbar(self.action_frame, command=self.textbox.yview) self.textbox.configure(yscrollcommand=self.vscroll.set) self.vscroll.pack(side=RIGHT, fill=Y) self.textbox.pack(side=RIGHT, fill=BOTH) self.action_frame.place(x=0, y=2*h+4, relheight=0.65, relwidth=1.0) def enable_action_buttons(self): self.cb.configure(state=NORMAL) self.rb.configure(state=NORMAL) self.vb.configure(state=NORMAL) self.lb.configure(state=NORMAL) def disable_action_buttons(self): self.cb.configure(state=DISABLED) self.rb.configure(state=DISABLED) self.vb.configure(state=DISABLED) self.lb.configure(state=DISABLED) def prep_problem(self): #"~/judge" directory must already exist. if not ("judge" in os.listdir("%s" % conf.home_dir)): no_judge_dir_box.create() return #Set parameters from control frame option buttons. conf.team_name = "team" + conf.team_no.get() conf.prog_fn = "program" + conf.prob_no.get() + \ conf.ext_map[conf.lang.get()] #Copy program file into ~/judge. #print "Copy command: " + "cp -f ~%s/%s ~/judge" % \ # (conf.team_name, conf.prog_fn) status, output = commands.getstatusoutput("ls -l ~%s/%s" % \ (conf.team_name, conf.prog_fn)) if status == 0: self.timestamp.set(output[50:56]) status, output = commands.getstatusoutput("cp -f ~%s/%s ~/judge" % \ (conf.team_name, conf.prog_fn)) #The above two lines are commented out and the following two lines #are inserted for testing. #status, output = commands.getstatusoutput("cp -f ~/python/%s ~/judge"\ # % conf.prog_fn) if status != 0: ccpf_info_box.create() return try: os.remove("%s/judge/%s" % (conf.home_dir, conf.prog_fn[:find(conf.prog_fn,'.')])) except: pass self.enable_action_buttons() #Disable "compile" button for interpreted languages. if conf.lang.get() in conf.no_compile: self.cb.configure(state=DISABLED) def control_frame_consistent(self): if conf.team_name[4:] != conf.team_no.get() or \ conf.prog_fn[7] != conf.prob_no.get() or \ not (conf.prog_fn in os.listdir("%s/judge" % conf.home_dir)): return FALSE return TRUE def compile(self): if not self.control_frame_consistent(): self.disable_action_buttons() pressOK_box.create() return #Does source program file exist? if not (conf.prog_fn in os.listdir("%s/judge" % conf.home_dir)): no_source_file_box.create() return self.textbox.delete(1.0, END) if conf.lang.get() in conf.no_compile: #No compiler for these languages. output = "No compilation needed for this programming language." return self.textbox.insert(END, "Compiling. Please wait.") if conf.lang.get()=="C": output = commands.getoutput("gcc -lm -o ~/judge/%s ~/judge/%s" %\ (conf.prog_fn[:find(conf.prog_fn,'.')], conf.prog_fn)) if output == "": output = "Compile successful.\n" elif conf.lang.get()=="C++": #print "Compiling." output = commands.getoutput("g++ -lm -o ~/judge/%s ~/judge/%s" %\ (conf.prog_fn[:find(conf.prog_fn,'.')], conf.prog_fn)) if output == "": output = "Compile successful.\n" elif conf.lang.get()=="Java": output = commands.getoutput("gcj --main=%s -o ~/judge/%s ~/judge/%s" \ % (conf.prog_fn[:find(conf.prog_fn,'.')], \ conf.prog_fn[:find(conf.prog_fn,'.')], \ conf.prog_fn)) if output == "": output = "Compile successful.\n" #output = "No Java compiler available." elif conf.lang.get() == "Pascal": status, output = commands.getstatusoutput( "fpc -Co -Cr -o~/judge/%s -Se10 -vew ~/judge/%s" % \ (conf.prog_fn[:find(conf.prog_fn,'.')], conf.prog_fn)) ##Using P2C to preprocess. #status, output = commands.getstatusoutput( # "p2c -a ~/judge/%s 2> /dev/null" % conf.prog_fn) ##If the status is zero and the filename does not appear in the ##preprocessor output, there are no errors or warnings. #stat = find(output, conf.prog_fn) #if (status == 0 and stat < 0): # commands.getoutput("p2cc -o ~/judge/%s ~/judge/%s" % \ # (conf.prog_fn[:find(conf.prog_fn,'.')], conf.prog_fn)) #else: # status=1 #output=commands.getoutput('echo "%s" | grep -v Translation' % output) #output=output + "\nTranslation aborted.\n" if (output == "" or status == 0): output = output + "\nCompile successful.\n" self.textbox.delete(1.0, END) self.textbox.insert(END, output) def run(self): if not self.control_frame_consistent(): self.disable_action_buttons() press_OK_box.create() return if conf.lang.get() in conf.no_compile: #Does source program file exist? if not (conf.prog_fn in os.listdir("%s/judge" % conf.home_dir)): no_source_file_box.create() return else: #Does machine language program file exist? if not (conf.prog_fn[:find(conf.prog_fn,'.')] in os.listdir("%s/judge" % conf.home_dir)): no_ml_file_box.create() return #Does data file exist? if not (conf.dfn.get() in os.listdir("%s/judge/data" % conf.home_dir)): no_data_file_box.create() return pn = conf.prog_fn if conf.lang.get() == "BASIC": output = commands.getoutput("bwbasic %s/judge/%s < %s/judge/data/%s" \ % (conf.home_dir, pn, conf.home_dir, conf.dfn.get())) #Remove carriage returns from Marble's BwBASIC output. output = strip_cr(output) #Remove BWBASIC: prompt from last line of output. loc = rfind(output, '\n') output = output[:loc+1] #Remove "? " prompt from input statement's output. loc = 99999 while loc >=0: loc = find(output, '\n? ') if loc >=0: output = output[:loc] + output[loc+3:] elif conf.lang.get() in ("C", "C++", "Java", "Pascal"): output = commands.getoutput("%s/judge/%s < %s/judge/data/%s" % \ (conf.home_dir, pn[:find(pn, '.')], conf.home_dir, conf.dfn.get())) elif conf.lang.get() == "Python": output = commands.getoutput("python %s/judge/%s < %s/judge/data/%s" % \ (conf.home_dir, pn, conf.home_dir, conf.dfn.get())) else: print "Error. Undefined programming language." sys.exit(1) self.textbox.delete(1.0, END) self.textbox.insert(END, output) def list_team_dir(self): if not self.control_frame_consistent(): self.disable_action_buttons() press_OK_box.create() return dir = commands.getoutput("ls -l ~%s" % conf.team_name) self.textbox.delete(1.0, END) self.textbox.insert(END, dir) def view_source(self): if not self.control_frame_consistent(): self.disable_action_buttons() press_OK_box.create() return self.textbox.delete(1.0, END) source=commands.getoutput("cat ~/judge/%s" % conf.prog_fn) self.textbox.insert(END, source) conf=conf_class() gui=gui_class(root) root.mainloop()