diff options
Diffstat (limited to 'competition.py')
-rwxr-xr-x | competition.py | 353 |
1 files changed, 353 insertions, 0 deletions
diff --git a/competition.py b/competition.py new file mode 100755 index 0000000..ab72d20 --- /dev/null +++ b/competition.py @@ -0,0 +1,353 @@ +#!/usr/bin/env python3 +""" +Usage: ./competition.py [options] [competitionfile] + +The <competitionfile> 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 Do not write a Competition log + -h Help. What you're looking at + -q Quiet. Don't print so much + -v View the competition afterwards using ./viewcompetition +""" + +import os,sys,subprocess,shlex,re,time + +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-1 else "|")) + print("") + print("+"+"-+"*S) + + +INDEXDELTAS=[(0,-1),(1,-1),(1,0),(1,1),(0,1),(-1,1),(-1,0),(-1,-1)] +def indexAfterSlide(board,idx,dir): + x,y=idx%S,idx//S + dx,dy=INDEXDELTAS[dir] + while True: + x2,y2=x+dx,y+dy + if x2<0 or x2>=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: return 1 + 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=False +complog=True +if len(sys.argv)==1: #no args + fname="competition.txt" +else: + for arg in sys.argv[1:]: #skip script name + if len(arg)>1 and arg[0]=="-": + for c in arg[1:]: #skip "-" + if c=="C": + complog=False + elif c=="h": + print(__doc__) + sys.exit(0) + elif c=="q": quiet=True + elif c=="v": viewcompetition=True + 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 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: + logfname="playerlogs/"+re.sub(r"[^a-zA-Z0-9 ]","",p1fname)+"_white_vs_"+re.sub(r"[^a-zA-Z0-9 ]","",p2fname)+".txt" + p1errlog=open(logfname,mode="w") + logfname="playerlogs/"+re.sub(r"[^a-zA-Z0-9 ]","",p2fname)+"_black_vs_"+re.sub(r"[^a-zA-Z0-9 ]","",p1fname)+".txt" + p2errlog=open(logfname,mode="w") +except: + print("Error: could not open log file '"+logfname+"'.") + 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: + logfname="competitions/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) + timeoutfail=False + if player==1: + p1totaltime+=0.1 + if p1totaltime>=50: + timeoutfail=True + else: + p2totaltime+=0.1 + if p2totaltime>=50: + 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("move =",move,"player =",player) + 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() + + 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 + if complog: + logfile.write("P"+str(player)+": "+movestr(move)+"\n") + + 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: + os.system("."+os.path.sep+"viewcompetition "+logfname) |