# CMSC 471 - Fall 2008
# Author: Don Miner
# Last updated: 10/30

# This program takes one command line arguments:
#		arg1 : The port to bind on. The program will try to bind to this port, or any of the
#				40 ports above it.

# You may report bugs to this checking script by emailing the instructor.
# Any bug reports that result in a change to this script will yield a small amount of extra credit.
# Bugs include logic errors, invalid output or causing the program to exit ungracefully (e.g.,
# unhandled exception).

# Sample run:	python LightcycleServer.py 52485

# adjust this to make the board SIZE x SIZE
SIZE = 30

import socket
import sys

# turn this on if you want to see a lot of information about the connection
VERBOSE = False


# this is the global socket object
sSOCKET = None

# bind to the port we want to bind to, then accept connections from 2 players
def get_players(port):
	global sSOCKET

	HOST = 'localhost'
	s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	
	# start at the desired port. if it is taken, try to next one up
	con_port = port
	while con_port - port < 40:
		try:
			s.bind((HOST, con_port))
			break
		except socket.error, x:
#			print "failed to bind:", x
#			sys.exit(1)
			con_port += 1
		
	print 'Hosting the game on port', con_port


	players = []

	print "Waiting for players to connect"

	s.listen(1)

	while len(players) < 2:
		conn, addr = s.accept()
		addr_str = addr[0] + ':' + str(addr[1])
		if VERBOSE: print 'Connection by', addr_str, 'successful'
		
		data = conn.recv(1024)
		if not data: break
		if VERBOSE: print 'Received team name from', addr_str, ":", data.strip()

		print "Team", data.strip(), "has joined the game."

		# a player is (name, connection object, position)
		players.append([data.strip(), conn, None])

	s.listen(0)

	if len(players) != 2:
		print "not all players are here!"
		sys.exit(1)

	sSOCKET = s

	return players

# send the positions of the players to the players
def tell_positions(players):
	p1, p2 = players
	
	name1, conn1, pos1 = p1
	name2, conn2, pos2 = p2

	if VERBOSE: print "sending position info to", name1
	conn1.send(`pos1[0]` + " " + `pos1[1]` + " " + `pos2[0]` + " " + `pos2[1]` + "\n")
	
	if VERBOSE: print "sending position info to", name2
	conn2.send(`pos2[0]` + " " + `pos2[1]` + " " + `pos1[0]` + " " + `pos1[1]` + "\n")
	
# tell the outcome of the game to the players
def tell_outcome(players, p1_lose, p2_lose):
	p1, p2 = players
	
	name1, conn1, pos1 = p1
	name2, conn2, pos2 = p2

	if p1_lose and p2_lose:
		conn1.send("TIE")		
		conn2.send("TIE")
	elif p1_lose:
		conn1.send("LOSE")
		conn2.send("WIN")
	elif p2_lose:
		conn1.send("WIN")
		conn2.send("LOSE")
	else:
		print "The server was told to end the game, but nobody won or tied?"
		conn1.send("what?")
		conn2.send("what?")

# get the moves from the players	
def get_moves(players):
	global sSOCKET

	p1, p2, = players
	
	name1, conn1, pos1 = p1
	name2, conn2, pos2 = p2
	
	if VERBOSE: print "getting move from", name1
	data1 = conn1.recv(1024).strip()
	if VERBOSE: print name1, "says", data1
	
	if not data1 in ("NORTH", "SOUTH", "WEST", "EAST"):
		print "invalid move from", name1, ":", data1
		sSOCKET.close()
		sys.exit(1)
	
	if VERBOSE: print "getting move from", name2
	data2 = conn2.recv(1024).strip()
	if VERBOSE: print name2, "says", data2
	
	if not data2 in ("NORTH", "SOUTH", "WEST", "EAST"):
		print "invalid move from", name2, ":", data2
		sSOCKET.close()
		sys.exit(1)
	
	return [data1, data2]

# close the connections to everyone, then close the socket.
def close_connections(players):
	for p in players:
		p[1].close()
		if VERBOSE: print "connection closed to", p[0]
		#print p[0], "has left the game."
	
	
	sSOCKET.close()

# print out the board to the server's screen
def print_board():
	global VISITED_SPOTS
	global PLAYERS

	sys.stdout.write(" " * (SIZE - 5) + PLAYERS[1][0] + '\n')
	for y in range(-1, SIZE + 1)[::-1]:
		for x in range(-1, SIZE + 1):
			if (x,y) == PLAYERS[0][2]:
				sys.stdout.write('X')
			elif (x,y) == PLAYERS[1][2]:
				sys.stdout.write('O')
			elif VISITED_SPOTS.has_key((x,y)):
				sys.stdout.write(VISITED_SPOTS[(x,y)])
			else:
				sys.stdout.write(' ')
		sys.stdout.write('\n')
	sys.stdout.write("  " + PLAYERS[0][0] + '\n')



MOVES = { "NORTH": (0, 1), "SOUTH": (0, -1), "EAST": (1, 0), "WEST": (-1, 0) }

# this hash table keeps track of all the visited spots	
VISITED_SPOTS = {}

# this initializes the hash table with the borders
for x in range(SIZE):
	VISITED_SPOTS[(x,-1)] = '*'
	VISITED_SPOTS[(x,SIZE)] = '*'
	VISITED_SPOTS[(-1,x)] = '*'
	VISITED_SPOTS[(SIZE,x)] = '*'

PLAYERS = get_players(int(sys.argv[1]))

#give initial positions
PLAYERS[0][2] = (4, 4)
PLAYERS[1][2] = (SIZE-5, SIZE-5)
VISITED_SPOTS[PLAYERS[0][2]] = 'x'
VISITED_SPOTS[PLAYERS[1][2]] = 'o'

# play until the game reaches an end
while True:
	# tell the players where they are and where their opponent is
	tell_positions(PLAYERS)

	# generate the vectors of which each player moves
	p1, p2 = [ MOVES[m.strip()] for m in get_moves(PLAYERS) ]
	
	#move the players in that direction
	PLAYERS[0][2] = (PLAYERS[0][2][0] + p1[0], PLAYERS[0][2][1] + p1[1])
	PLAYERS[1][2] = (PLAYERS[1][2][0] + p2[0], PLAYERS[1][2][1] + p2[1])
	
	# prints out the board to stdout
	print_board()
	
	# check to see if anyone lost
	p1_lose = p2_lose = False
	if VISITED_SPOTS.has_key(PLAYERS[0][2]):
		p1_lose = True
	if VISITED_SPOTS.has_key(PLAYERS[1][2]):
		p2_lose = True
		
	# if they moved to the same spot, they tie (bug found by Kenny Roethel)
	if PLAYERS[0][2] == PLAYERS[1][2]:
		p1_lose = p2_lose = True
		
	if p1_lose and p2_lose:
		print PLAYERS[0][0], "and", PLAYERS[1][0], "TIE"	
	elif p1_lose:
		print PLAYERS[1][0], "DEFEATS", PLAYERS[0][0]
	elif p2_lose:
		print PLAYERS[0][0], "DEFEATS", PLAYERS[1][0]
	
	if p1_lose or p2_lose:
		tell_outcome(PLAYERS, p1_lose, p2_lose)
		break
	
	# update the visited hash table
	VISITED_SPOTS[PLAYERS[0][2]] = 'x'
	VISITED_SPOTS[PLAYERS[1][2]] = 'o'

	
	# next round of turns...


# clean up the connections
close_connections(PLAYERS)