#!/usr/bin/env python3 """ Usage: ./competition.py [options] [competitionfile] The should contain two lines, which are the two commands to execute as players. The script writes a competitionlog to competitions/game_p1_vs_p2.txt (with p1 and p2 replaced by their respective commands). Options: -c Write the Competition log to the specified file instead of the default -C Do not write a Competition log -h Help. What you're looking at -q Quiet. Don't print so much -V Do not view the competition, so don't write an html file """ import os,sys,subprocess,shlex,re,time MAXTIMELIMIT=None #in seconds S=5 PNONE=0 PP1=1 PP2=2 PNEU=3 #Function definitions class Board: def __init__(self): self._neuidx=S*(S//2)+S//2 self._grid=[0]*(S*S) self._grid[self._neuidx]=PNEU for i in range(S): self._grid[i]=PP1 self._grid[S*(S-1)+i]=PP2 def move(self,at,to): if at<0 or at>=S*S: raise Exception("Invalid at") if to<0 or to>=S*S: raise Exception("Invalid to") if self._grid[at]==PNONE: raise Exception("Nothing to move") if self._grid[to]!=PNONE: raise Exception("Place taken") self._grid[to]=self._grid[at] self._grid[at]=PNONE if self._grid[to]==PNEU: self._neuidx=to def undomove(self,at,to): self._grid[at]=self._grid[to] self._grid[to]=PNONE if self._grid[at]==PNEU: self._neuidx=at def get(self,idx): return self._grid[idx] def neu(self): return self._neuidx def pr(self): print("+"+"-+"*S) for y in range(S): print("|",end="") for x in range(S): print(self._grid[S*y+x],end=(" " if x=S or y2<0 or y2>=S or board.get(S*y2+x2)!=PNONE: return S*y+x x,y=x2,y2 def parsemove(line): match=re.match(r"^([0-7]|-1) ([0-9]+) ([0-7])$",line) if not match: return False try: return [int(match.group(i)) for i in range(1,4)] #neudir, from, dir except: return False def isvalid(board,player,move): if move[1]<0 or move[1]>=S*S: return False if board.get(move[1])!=[PP1,PP2][player-1]: return False if move[0]==-1 and not isFirstMove: return False try: if move[0]!=-1: oldneu=board.neu() newneuidx=indexAfterSlide(board,oldneu,move[0]) if newneuidx==oldneu: return False board.move(board.neu(),newneuidx) newidx=indexAfterSlide(board,move[1],move[2]) if newidx==move[1]: if move[0]!=-1: board.undomove(oldneu,newneuidx) return False board.move(move[1],newidx) except Exception as e: return False board.undomove(move[1],newidx) if move[0]!=-1: board.undomove(oldneu,newneuidx) return True def movestr(mv):return " ".join([str(e) for e in mv]) def haswon(board,lastplayer): if board.neu()=S*(S-1): return 2 for dir in range(8): newidx=indexAfterSlide(board,board.neu(),dir) if newidx!=board.neu(): return 0 return lastplayer #Getting entries and flags fname="" quiet=False viewcompetition=True complog=True logfname=None nextIsLogfname=False if len(sys.argv)==1: #no args fname="competition.txt" else: for arg in sys.argv[1:]: #skip script name if nextIsLogfname: logfname=arg nextIsLogfname=False continue if len(arg)>1 and arg[0]=="-": for c in arg[1:]: #skip "-" if c=="c": nextIsLogfname=True elif c=="C": complog=False elif c=="h": print(__doc__) sys.exit(0) elif c=="q": quiet=True elif c=="V": viewcompetition=False else: print("Unrecognised flag '"+c+"'.") print(__doc__) sys.exit(1) elif fname=="": fname=arg else: print("Unrecognised argument '"+arg+"'; the competition file name was already given as '"+fname+"'.") sys.exit(1) if nextIsLogfname: print("Missing argument to -c flag") sys.exit(1) if fname=="-": if not quiet: print("Getting entries from stdin.") p1fname="" p2fname="" while p1fname=="": p1fname=input().strip() while p2fname=="": p2fname=input().strip() else: if fname=="": fname="competition.txt" if not quiet: print("Getting entries from file '"+fname+"'.") try: f=open(fname,mode="r") except: print("Could not open file '"+fname+"'.") sys.exit(1) p1fname=f.readline().strip() if p1fname=="": print("Too few lines in file."); sys.exit(1) p2fname=f.readline().strip() if p2fname=="": print("Too few lines in file."); sys.exit(1) f.close() #Set up player logs if not os.path.exists("playerlogs"): try: os.mkdir("playerlogs") except: print("Error: could not create log directory 'playerlogs'.") sys.exit(1) elif not os.path.isdir("playerlogs"): #Apparently, there's a file named "playerlogs". Bastard. print("Error: an existing file prohibits creation of log directory 'playerlogs'.") sys.exit(1) try: plogfname="playerlogs"+os.path.sep+re.sub(r"[^a-zA-Z0-9 ]","",p1fname)+"_white_vs_"+re.sub(r"[^a-zA-Z0-9 ]","",p2fname)+".txt" p1errlog=open(plogfname,mode="w") plogfname="playerlogs"+os.path.sep+re.sub(r"[^a-zA-Z0-9 ]","",p2fname)+"_black_vs_"+re.sub(r"[^a-zA-Z0-9 ]","",p1fname)+".txt" p2errlog=open(plogfname,mode="w") except: print("Error: could not open log file '"+plogfname+"'.") sys.exit(1) #Start programs if not quiet: print("Running this competition with:") print("P1 (X): '"+p1fname+"'") print("P2 (O): '"+p2fname+"'") try: p1proc=subprocess.Popen(shlex.split(p1fname),stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=p1errlog,bufsize=1,shell=True) #line-buffered except Exception as e: print("Could not execute command '"+p1fname+"'.") raise e (p1in,p1out)=(p1proc.stdin,p1proc.stdout) try: p2proc=subprocess.Popen(shlex.split(p2fname),stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=p2errlog,bufsize=1,shell=True) #line-buffered except Exception as e: print("Could not execute command '"+p2fname+"'.") raise e (p2in,p2out)=(p2proc.stdin,p2proc.stdout) #Set up competition log if not os.path.exists("competitions"): try: os.mkdir("competitions") except: print("Error: could not create log directory 'competitions'.") sys.exit(1) elif not os.path.isdir("competitions"): #Apparently, there's a file named "competitions". Bastard. print("Error: an existing file prohibits creation of log directory 'competitions'.") sys.exit(1) try: if logfname==None: logfname="competitions"+os.path.sep+"game_"+re.sub(r"[^a-zA-Z0-9 ]","",p1fname)+"_vs_"+re.sub(r"[^a-zA-Z0-9 ]","",p2fname)+".txt" logfile=open(logfname,mode="w") logfile.write("P1: "+p1fname+"\nP2: "+p2fname+"\n") except: print("Error: could not open log file '"+logfname+"'.") sys.exit(1) #Start competition board=Board() nummoves=0 endgame=False isFirstMove=True p1totaltime=0 p2totaltime=0 p1in.write(b"go\n"); try: p1in.flush() except IOError as e: if e.errno==os.errno.EINVAL: print("Program P1 quit prematurely.") try: p2proc.terminate() except: pass sys.exit(1) else: raise e p2in.write(b"nogo\n"); try: p2in.flush() except IOError as e: if e.errno==os.errno.EINVAL: print("Program P2 quit prematurely.") try: p1proc.terminate() except: pass sys.exit(1) else: raise e while True: for (pAin,pAout,pBin,pBout,player) in [(p1in,p1out,p2in,p2out,1),(p2in,p2out,p1in,p1out,2)]: while True: line=pAout.readline().decode("ascii").strip() if len(line)>0: break time.sleep(0.1) if MAXTIMELIMIT==None: continue timeoutfail=False if player==1: p1totaltime+=0.1 if p1totaltime>=MAXTIMELIMIT: timeoutfail=True else: p2totaltime+=0.1 if p2totaltime>=MAXTIMELIMIT: timeoutfail=True if timeoutfail: print("P"+str(player)+" timed out!") try: p1proc.terminate() except: pass try: p2proc.terminate() except: pass sys.exit(1) move=parsemove(line) if move==False or not isvalid(board,player,move): print("P"+str(player)+" made an invalid move: '"+line+"'.") endgame=True break if not isFirstMove: newidx=indexAfterSlide(board,board.neu(),move[0]) #print("moving",board.neu(),newidx) board.move(board.neu(),newidx) newidx=indexAfterSlide(board,move[1],move[2]) board.move(move[1],newidx) if not quiet: print("P"+str(player)+": "+movestr(move)+" ",end="") sys.stdout.flush() if complog: logfile.write("P"+str(player)+": "+movestr(move)+"\n") won=haswon(board,player) if not won: #writing the move to the next player if it was a winning move is _quite_ useless. pBin.write((movestr(move)+"\n").encode("ascii")) try: pBin.flush() except IOError as e: if e.errno==os.errno.EINVAL: print("Program P"+str(3-player)+" quit prematurely.") try: if player==1: p2proc.terminate() else: p1proc.terminate() except: pass sys.exit(1) else: raise e nummoves+=1 if won!=0: if not quiet: print("") board.pr() print("") print("P"+str(won)+" has won this game!") if complog: logfile.write("P"+str(won)+" won\n") endgame=True break if not quiet: print("") board.pr() print("") isFirstMove=False if endgame: break #Clean up if complog: logfile.close() p1errlog.close() p2errlog.close() try: p1proc.terminate() except: pass try: p2proc.terminate() except: pass if viewcompetition: if not os.path.exists("gamevisuals"): try: os.mkdir("gamevisuals") except: print("Error: could not create log directory 'gamevisuals'.") sys.exit(1) elif not os.path.isdir("gamevisuals"): #Apparently, there's a file named "gamevisuals". Bastard. print("Error: an existing file prohibits creation of log directory 'gamevisuals'.") sys.exit(1) os.system("."+os.path.sep+"viewcompetition "+logfname+" >gamevisuals"+os.path.sep+"game_"+re.sub(r"[^a-zA-Z0-9 ]","",p1fname)+"_vs_"+re.sub(r"[^a-zA-Z0-9 ]","",p2fname)+".html")