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) | 
