aboutsummaryrefslogtreecommitdiff
path: root/competition.py
diff options
context:
space:
mode:
Diffstat (limited to 'competition.py')
-rwxr-xr-xcompetition.py353
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)