#!/usr/bin/env python #RockTest #Programming Competition Team Console V1.0 #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, Pennsylvania 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. #Design Objectives # 1) Make something more dependable than PC^2 (Programming Contest # Control) software in Java from CSUS. # 2) Automate the most common, error-prone, and time-consuming # functions of a computer programming contest. # Thus, file names and working directories are determined by RockTest. # Starting an editor, compiling, and running are reduced to clicks # of command buttons. # 3) Code for portability. # This goal has not been adequately met. However, the programs in the # suite are programmed in Python, and no GUI libraries other than TkInter, # which is a standard part of Python, have been used. Except for the # "commands" module, all code is portable to other OS's. However, until # network communication between contestants and judges is implemented, this # program suite cannot run under a single-user OS such as Windows anyway. # # In its current configuration, it is intended that contestants and judges # will be running a Linux/Unix GUI window under an X-server, typically under # Windows. All users will be accessing the same X-client. Therefore, for # large numbers of teams, a powerful Linux/Unix box is required. # There must be some! Untested under contest conditions. #To do: #Too much hardcoded data. Number of contestants, number of problems, # choices of programming languages, and perhaps other things # ought to be read from the environment or an initialization file. #Needs automated program submission, ideally via Internet using # strong encryption. #Needs automated notification of judges decisions. #Needs automated method of submitting problem-clarification requests # to the judges, and an automated means of receiving the responses. from Tkinter import * from sys import * from string import * import os import commands root = Tk() COLOR="#c5ffc5" COLOR2="#ffffcc" FALSE=0 TRUE=1 def truncate(text, lines): #Truncate all but "lines" lines from the text. count = 0 i = 0 while i= 0: loc = find(text, '\r') if loc >= 0: text = text[:loc] + text[loc+1:] return text class setup_class: #Creates setup window and initializes variables. def close(self): #Close the "Setup" dialog box. self.set_fns() main_win.enable() main_win.set_compile_button() self.win.destroy() def set_fns(self): #Set names of program and data files. self.data_fn.set("program"+self.prob_no.get()+".dat") if self.lang.get()=="BASIC": self.ext = "bas" elif self.lang.get()=="C": self.ext = "c" elif self.lang.get()=="C++": self.ext = "cpp" elif self.lang.get()=="Java": self.ext = "java" elif self.lang.get()=="Pascal": self.ext = "pas" elif self.lang.get()=="Python": self.ext = "py" else: print "Unknown language. This shouldn't happen." sys.exit(1) self.prog_fn.set("program"+self.prob_no.get()+"."+self.ext) return def setup(self): #Create dialog box. self.win=Toplevel(root, bg=COLOR) self.win.title("RockTest Client Setup") self.main_frame=Frame(self.win, bg=COLOR, width=500) self.ed_frame = Frame(self.main_frame, relief=RIDGE, borderwidth=3, \ bg=COLOR2) wid=5 Label(self.ed_frame, text="Choose your editor:", bg=COLOR2)\ .grid(row=0, column=0, columnspan=2, sticky=W) self.b1=Radiobutton(self.ed_frame, text="KWrite", variable=self.editor, \ bg=COLOR2, value="KWrite").grid(row=1, column=0, sticky=W) self.l1=Label(self.ed_frame, text="Powerful easy-to-use GUI editor", \ bg=COLOR2).grid(row=1, column=1, sticky=W) self.b2=Radiobutton(self.ed_frame, text="KEdit", variable=self.editor, \ bg=COLOR2, value="KEdit").grid(row=2, column=0, sticky=W) self.l2=Label(self.ed_frame, text="Easy-to-use GUI editor", bg=COLOR2)\ .grid(row=2, column=1, sticky=W) self.b3=Radiobutton(self.ed_frame, text="Pico", variable=self.editor, \ bg=COLOR2, value="Pico").grid(row=3, column=0, sticky=W) self.l3=Label(self.ed_frame, text="Easy-to-use text-based editor", \ bg=COLOR2).grid(row=3, column=1, sticky=W) self.b4=Radiobutton(self.ed_frame, text="Vi", variable=self.editor, \ bg=COLOR2, value="Vi").grid(row=4, column=0, sticky=W) self.l4=Label(self.ed_frame, text="Warning: not for newbies", bg=COLOR2)\ .grid(row=4, column=1, sticky=W) self.b5=Radiobutton(self.ed_frame, text="Emacs", variable=self.editor, \ bg=COLOR2, value="Emacs").grid(row=5, column=0, sticky=W) self.l5=Label(self.ed_frame, text="Warning: not for newbies", bg=COLOR2)\ .grid(row=5, column=1, sticky=W) self.ed_frame.pack(side=TOP) #self.db_frame=Frame(self.main_frame, relief=RIDGE, borderwidth=3, # bg=COLOR) #Label(self.db_frame, text="Choose your debugger:", bg=COLOR)\ # .grid(row=0, column=0, columnspan=2, sticky=W) #self.b5=Radiobutton(self.db_frame, text="Kdbg", variable=self.debugger, \ # bg=COLOR2, value="Kdbg").grid(row=1, column=0, sticky=W) #self.l5=Label(self.db_frame, text="Easy-to-use GUI debugger", bg=COLOR)\ # .grid(row=1, column=1, sticky=W) #self.b6=Radiobutton(self.db_frame, text="Gdbg", variable=self.debugger, \ # bg=COLOR2, value="Gdbg").grid(row=2, column=0, sticky=W) #self.l6=Label(self.db_frame, text="Warning: not for newbies", bg=COLOR)\ # .grid(row=2, column=1, sticky=W) #self.b7=Radiobutton(self.db_frame, text="none", variable=self.debugger, \ # bg=COLOR2, value="").grid(row=3, column=0, sticky=W) #self.l7=Label(self.db_frame, text="I don't use debuggers.", bg=COLOR)\ # .grid(row=3, column=1, sticky=W) #self.db_frame.grid(row=0, column=2, sticky=W+N) self.l_frame = Frame(self.main_frame, relief=RIDGE, borderwidth=3, \ bg=COLOR2) Label(self.l_frame, text="Choose your programming language:", bg=COLOR2)\ .grid(row=0, column=0, columnspan=2, sticky=W) self.b8=Radiobutton(self.l_frame, text="BASIC", variable=self.lang, \ bg=COLOR2, value="BASIC").grid(row=1, column=0, sticky=W) self.b9=Radiobutton(self.l_frame, text="C", variable=self.lang, bg=COLOR2, value="C").grid(row=2, column=0, sticky=W) self.b10=Radiobutton(self.l_frame, text="C++", variable=self.lang, \ bg=COLOR2, value="C++").grid(row=3, column=0, sticky=W) self.b11=Radiobutton(self.l_frame, text="Java", variable=self.lang, \ bg=COLOR2, value="Java").grid(row=4, column=0, sticky=W) self.b12=Radiobutton(self.l_frame, text="Pascal", variable=self.lang, \ bg=COLOR2, value="Pascal").grid(row=5, column=0, sticky=W) self.b13=Radiobutton(self.l_frame, text="Python", variable=self.lang, \ bg=COLOR2, value="Python").grid(row=6, column=0, \ sticky=W) Frame(self.l_frame, width=196).grid(row=1, column=1) self.l_frame.pack(side=TOP, expand=YES, fill=X) self.ok_button=Button(self.main_frame, text="OK", command=self.close) \ .pack(side=TOP, expand=YES) self.main_frame.pack(padx=20, pady=20) return def __init__(self): self.prob_count = 6 #Maximum is 15, because of next statement. self.prob_list = "1","2","3","4","5","6" #prob_list = (prob_list[0:self.prob_count]) #Get team name from $USER environment variable. self.team = commands.getoutput('echo $USER') self.team = self.team[4:] #Or get team number from ".team" file. #try: # init_f = open(".team", "r") #except IOError: # print "Unable to open initialization file." # exit(1) #self.team = init_f.readline()[0:-1] root.title("RockTest: Team #" + self.team) self.editor = StringVar() self.debugger = StringVar() self.lang = StringVar() self.prog_fn = StringVar() self.data_fn = StringVar() self.prob_no = StringVar() self.editor.set("KWrite") self.debugger.set("Kdbg") self.lang.set("C++") self.prog_fn.set("program1.cpp") self.prob_no.set("1") self.data_fn.set("program1.dat") self.has_debugger = ("C", "C++", "Pascal") self.avail_langs = "BASIC", "C", "C++", "Java", "Pascal", "Python" self.lang_dict = {"BASIC":"bas", "C":"c", "C++":"cpp", "Java":"java",\ "Pascal":"pas", "Python":"py"} return class info_box_class: def __init__(self, parent, name, text, but_text, just): self.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): #Create the dialog box. self.info_box=Toplevel(self.parent, bg=COLOR2) self.info_box.title(self.name) self.abt_msg=Message(self.info_box, textvariable=self.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 Team 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 "Press the OK button" info box. box_name="Notice!" but_text="Close" msg=""" After changing the value in the "Problem #" widget or the "Language" widget you must press "OK" before proceeding. """ pressOK_box=info_box_class(root, box_name, msg, but_text, CENTER) #Create "No program file" info box. box_name="No program file" but_text="Close" msg=""" The program file that you have attempted to process does not exist. Click on "Edit" in the "Program File" pane to create the file. It is also possible that the program file exists, but you have the incorrect "Language" setting. """ no_prog_file_box=info_box_class(root, box_name, msg, but_text, CENTER) #Create "No data file" info box. box_name="No data file" but_text="Close" msg=""" The data file that you have attempted to process does not exist. Click on "Edit" in the "Data File" pane to create the file. If the current program does not require data, create an empty data file. This version of RockTest always expects a data file. """ no_data_file_box=info_box_class(root, box_name, msg, but_text, CENTER) #Create "No machine language file" info box. box_name="No machine language file" but_text="Close" msg=""" The machine language program file that you have attempted to run does not exist. Assuming the source code file for this problem exists, click on "Compile" in the "Program File" pane to compile your source code file. """ no_ml_file_box=info_box_class(root, box_name, msg, but_text, CENTER) #Create "Help" info box. box_name="Help" but_text="Close" msg=""" 1) If you have not already done so, choose an editor and programming language by clicking on File-->Setup. Click on the "OK" button when done. The "Setup" dialog box will close. 2) Choose the number of the problem on which you wish to work. Change the programming language if you wish. Click "OK". (You must click on "OK" after changing either the "Problem #" or "Language" at any time.) 3) In the "Program File" pane, click on "Edit" to create or edit your program. In the "Data File" pane, click on "Edit" to create or edit your test data file. This version of RockTest requires that every program have a data file. If a problem doesn't need data, create an empty data file. 4) In the "Program File" pane, click on "Compile" or "Run" to compile or run your program. The "Compile" button is unavailable (and unnecessary) when you use an interpreted language such as BASIC or Python. When your program runs, it will automatically use the data file indicated in the "Data File" pane as standard input. 5) Repeat the steps above as appropriate. Java Programmers: the class enclosing the "main" method must have the same name as the program file, minus the ".java", for each program! E.g., for file "program3.java" the class must be called "program3", or the program will not compile. """ help_box=info_box_class(root, box_name, msg, but_text, LEFT) def edit_data(): if not main_win.consistent(): main_win.disable() pressOK_box.create() return #Some editors won't save an empty file (e.g., KWrite). try: if not (config.data_fn.get() in os.listdir("~")): os.system("touch %s" % config.data_fn.get()) except OSError: os.system("touch %s" % config.data_fn.get()) #Launch editor, inside window if necessary. #Should also check to see whether the editor is already running. if config.editor.get()=="KWrite": command = "kwrite " + config.data_fn.get() + "&" elif config.editor.get()=="KEdit": command = "kedit " + config.data_fn.get() + "&" elif config.editor.get()=="Pico": command = "konsole -e pico " + config.data_fn.get() + "&" elif config.editor.get()=="Vi": command = "konsole -e vi " + config.data_fn.get() + "&" elif config.editor.get()=="Emacs": command = "emacs " + config.data_fn.get() + "&" else: print "Unknown editor. This shouldn't happen." sys.exit(1) os.system(command) def edit_program(): if not main_win.consistent(): main_win.disable() pressOK_box.create() return #Create file so ownerships are correct. try: if not (config.prog_fn.get() in os.listdir("~")): os.system("touch %s" % config.prog_fn.get()) except OSError: os.system("touch %s" % config.prog_fn.get()) os.system("whoami") #Launch editor, inside window if necessary. #Should also check to see whether the editor is already running. if config.editor.get()=="KWrite": command = "kwrite " + config.prog_fn.get() + "&" elif config.editor.get()=="KEdit": command = "kedit " + config.prog_fn.get() + "&" elif config.editor.get()=="Pico": command = "konsole -e pico " + config.prog_fn.get() + "&" elif config.editor.get()=="Vi": command = "konsole -e vi " + config.prog_fn.get() + "&" elif config.editor.get()=="Emacs": command = "emacs " + config.prog_fn.get() + "&" else: print "Unknown editor. This shouldn't happen." sys.exit(1) os.system(command) def compile_program(): if not main_win.consistent(): main_win.disable() pressOK_box.create() return lang = config.lang.get() pn = config.prob_no.get() fn = config.prog_fn.get() #Does program file exist? if fn not in os.listdir("."): no_prog_file_box.create() return #Set group ownership and permissions. #Eventually, this should move to a "submit" section. os.system("chgrp rocktest %s" % fn) #This may not be necessary. os.system("chmod 660 %s" % fn) #This may not be necessary. main_win.textbox.delete(1.0, END) try: os.remove(fn[:find(fn,'.')]) #Delete old executable. except: pass if lang=="C": status, output = commands.getstatusoutput("gcc -lm -o %s %s" % \ (fn[:find(fn,'.')], fn)) if status == 0 and output == "": output = output + "Compile successful.\n" elif lang=="C++": status, output = commands.getstatusoutput("g++ -lm -o %s %s" % \ (fn[:find(fn,'.')], fn)) if status == 0 and output == "": output = output + "Compile successful.\n" elif lang=="Java": status, output = commands.getstatusoutput("gcj --main=%s -o %s %s" % \ (fn[:find(fn,'.')], fn[:find(fn,'.')], fn)) if status == 0 and output == "": output = output + "Compile successful.\n" elif lang == "Pascal": status, output = commands.getstatusoutput( "fpc -Co -Cr -o%s -Se10 -vew %s" % (fn[:find(fn,'.')], fn)) if status == 0 or output == "": output = output + "Compile successful.\n" #Using fpc compiler. ##Using P2C to preprocess. #status, output = commands.getstatusoutput( # "p2c -a %s 2> /dev/null" % fn) #print "Status=", status, "\nOutput:\n", output ##If the status is zero and the filename does not appear in the ##preprocessor output, there are no errors or warnings. #stat = find(output, fn) #print "Filename found at line ", stat, "in compiler output." #if (status == 0 and stat < 0): # commands.getoutput("p2cc -o %s %s" % (fn[:find(fn,'.')], 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 + "\n\nCompile successful.\n" #output = commands.getoutput("fpc -Co -Cr %s %s" % # (fn[:find(fn,'.')],fn)) main_win.textbox.delete(1.0, END) main_win.textbox.insert(END, output) def run_program(): if not main_win.consistent(): main_win.disable() pressOK_box.create() return lang = config.lang.get() dfn = config.data_fn.get() pfn = config.prog_fn.get() #Does program file exist? if pfn not in os.listdir("."): no_prog_file_box.create() return #Does data file exist? if dfn not in os.listdir("."): no_data_file_box.create() return #Does machine code file exist? if not (lang in ("BASIC", "Python")): if not pfn[:find(pfn,".")] in os.listdir("."): no_ml_file_box.create() return if lang == "BASIC": output = commands.getoutput("bwbasic %s < %s" % (pfn, dfn)) #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 lang in ("C", "C++", "Java", "Pascal"): #print "File name: ", pfn, " Data file name:", dfn output = commands.getoutput("./%s < %s" % (pfn[:find(pfn, '.')], dfn)) #elif lang == "Java": # output = commands.getoutput("kaffe %s.class < %s" % # (pfn[:find(pfn, '.')], dfn)) elif lang == "Python": output = commands.getoutput("python %s < %s" % (pfn, dfn)) else: print "Error. Undefined programming language." sys.exit(1) main_win.textbox.delete(1.0, END) main_win.textbox.insert(END, output) class main_win_class: def __init__(self, root): root.minsize(455, 350) w=760 h=50 #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="Setup...", command=config.setup) 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_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) root.config(menu=self.menubar) self.setup_frame = Frame(root, bg=COLOR, borderwidth=3, relief=RIDGE, height=h, width=w) #self.text1_1= Label(self.setup_frame, text="Problem #", bg=COLOR)\ # .pack(side=LEFT) self.text1_1= Label(self.setup_frame, text="Problem #", bg=COLOR)\ .place(anchor=W, x=0, rely=0.5) self.prob_opt = OptionMenu(self.setup_frame, config.prob_no, \ "1","2","3","4","5","6") self.prob_opt.place(anchor=W, x=70, rely=0.5) self.text1_2 = Label(self.setup_frame, text="Language", bg=COLOR)\ .place(anchor=W, x=130, rely=0.5) OptionMenu(self.setup_frame, config.lang, "BASIC", "C", "C++", "Java", "Pascal", "Python").place(anchor=W, x=200, rely=0.5) self.okay_but = Button(self.setup_frame, text="OK", command=self.okay) self.okay_but.place(anchor=E, relx=1.0, rely=0.5) self.file_frame = Frame(root, bg=COLOR, height=h, width=w) self.prog_frame = Frame(self.file_frame, borderwidth=3, relief=RIDGE, \ bg=COLOR) self.prog_fn_label = Label(self.prog_frame, text = "Program File", \ bg=COLOR).grid(row=0, column=0) self.prog_fn_entry = Label(self.prog_frame, width=12, relief=SUNKEN, bg=COLOR2) self.prog_fn_entry.grid(row=1, column=0) self.pedit_but = Button(self.prog_frame, text="Edit", width=4, \ command=edit_program) self.pedit_but.grid(row=0, column=1, rowspan=2) self.compile_but = Button(self.prog_frame, text="Compile", width=4, \ command=compile_program) self.compile_but.grid(row=0, column=2, rowspan=2) self.run_but = Button(self.prog_frame, text="Run", width=4, \ command=run_program) self.run_but.grid(row=0, column=3, rowspan=2) self.prog_frame.place(anchor=W, x=0, rely=0.5) #Frame(self.file_frame, bg=COLOR, width=100).pack(side=LEFT) self.data_frame = Frame(self.file_frame, borderwidth=3, relief=RIDGE, bg=COLOR) self.data_fn_label = Label(self.data_frame, text="Data File", bg=COLOR)\ .grid(row=0, column=0) self.data_fn_entry = Label(self.data_frame, width=11, relief=SUNKEN, bg=COLOR2) self.data_fn_entry.grid(row=1, column=0) self.dedit_but = Button(self.data_frame, text="Edit", width=4, \ command=edit_data) self.dedit_but.grid(row=0, column=2, rowspan=2) self.data_frame.place(anchor=W, x=300, rely=0.5) self.action_frame=Frame(root, bg=COLOR2, borderwidth=3, relief=RIDGE, height=300) self.textbox = Text(self.action_frame, wrap=WORD, width=81, height=20, font=("Courier", 10), bg=COLOR2) self.vscroll = Scrollbar(self.action_frame, command=self.textbox.yview) self.textbox.configure(yscrollcommand=self.vscroll.set) self.vscroll.place(anchor=NE, relx=1.0, y=0, width=17, relheight=0.68) self.textbox.place(x=0, y=0, relwidth=0.96, relheight=0.68) self.setup_frame.place(x=0, y=0, relwidth=1.0, height=h+2) self.file_frame.place(x=0, y=h+2, relwidth=1.0, height=h+2) self.action_frame.place(x=0, y=2*h+4, relwidth=1.0, relheight=1.0) self.disable() def disable(self): self.pedit_but.configure(state = DISABLED) self.compile_but.configure(state = DISABLED) self.run_but.configure(state = DISABLED) self.dedit_but.configure(state = DISABLED) def enable(self): self.pedit_but.configure(state = NORMAL) self.compile_but.configure(state = NORMAL) self.run_but.configure(state = NORMAL) self.dedit_but.configure(state = NORMAL) def set_compile_button(self): if config.lang.get() in ("BASIC", "Python"): self.compile_but.configure(state = DISABLED) else: self.compile_but.configure(state = NORMAL) def okay(self): config.set_fns() self.enable() self.set_compile_button() def consistent(self): #Do file names match current selections? prob_no=config.prob_no.get() prog_fn=config.prog_fn.get() data_fn=config.data_fn.get() #This "if" statement depends on single-digit problem numbers. #The problem number is just before the "." in the file name. if prob_no != prog_fn[find(prog_fn,".")-1] or \ prob_no != data_fn[find(data_fn,".")-1]: return FALSE ext = prog_fn[find(prog_fn, ".")+1:] if config.lang_dict[config.lang.get()] != ext: return FALSE return TRUE #self.prog_fn = StringVar() #self.data_fn = StringVar() #self.prob_no = StringVar() config = setup_class() main_win = main_win_class(root) #Configure widgets with dependencies on other objects. main_win.prog_fn_entry.configure(textvariable = config.prog_fn) main_win.data_fn_entry.configure(textvariable = config.data_fn) config.setup() root.mainloop()