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

# This program takes two command line arguments:
#		arg1 : the pieces file. This file contains the pieces, in order. This file may look like:
#					7
#					ILOSTJZ
#
#		arg2 : the move file. This file contains how many times to rotate and where to drop each piece
#				This file may look like:
#					0 0
#					0 5
#					0 5
#					1 3
#					0 0
#					0 0
#					0 0
#
#		Note that the number of moves must equal the number of pieces.

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

# Note that my code is horrible here. It is inefficient and doesn't adhere to good coding standards.
# Therefore, you should probably try to figure out how to work the tetris game mechanics in your
# own program your own way, instead of using this as an example.

# sample run: python tetris_checker.py pieces1.txt moves1.txt

import sys


# The pieces
I = [['I', 'I', 'I', 'I']]
J = [['J', ' ', ' '], ['J', 'J', 'J']]
L = [[' ', ' ', 'L'], ['L', 'L', 'L']]
O = [['O', 'O'], ['O', 'O']]
S = [[' ', 'S', 'S'], ['S', 'S', ' ']]
Z = [['Z', 'Z', ' '], [' ', 'Z', 'Z']]
T = [[' ', 'T', ' '], ['T', 'T', 'T']]

# pieces hash table
tetris_pieces = {'I': I, 'J': J, 'L': L, 'O' : O, 'S' : S, 'Z' : Z, 'T' : T}



def print_piece(p):
	for row in p:
		print "".join(row)

# rotates piece p 90 degrees N times.
def rotate(p, N):
	if N == 0:
		return p
		
	new_p = []
	for item in p[0]:
		new_p.append([])
	

	for row_idx in range(len(p)):
		for col_idx in range(len(p[row_idx])):
			new_p[col_idx].append(p[row_idx][col_idx])
	
	
	#my algorithm above creates the mirror image of what I want. Instead of fixing this bug directly,
	# I just mirror it back:
	for row_idx in range(len(new_p)):
		new_p[row_idx] = new_p[row_idx][::-1]
	
	return rotate(new_p, N - 1)


# gets the pieces from the pieces file
def get_problem():
	try:
		pieces_file = open(sys.argv[1])
	except IndexError:
		print "No piece file given"
		sys.exit(1)
	except IOError:
		print "Problem opening file", sys.argv[1]
		sys.exit(1)

	lines = [ line.strip() for line in pieces_file.readlines() if len(line.strip()) > 0 ]
	if len(lines) != 2:
		print "Invalid problem file", lines
		sys.exit(1)

	if int(lines[0]) != len(lines[1]):
		print "Invalid problem file, number of moves given does not equal number of pieces given:", lines[0], ',' , lines[1]
		sys.exit(1)


	ps = [ c for c in lines[1] ]
	
	for c in ps:
		if not c in tetris_pieces.keys():
			print c, "is an invalid piece"
			sys.exit(1)
			
			
	return ps

# gets the moves from the moves file.
def get_input():
	def check_input(inp):
		bad = False
	
		for move in inp:
			if len(move) != 2:
				print "Invalid line (not length of 2):", move
				bad = True
			if len(move) >= 1 and not (move[0] in (0, 1, 2, 3)):
				print "A rotation other than 0, 1, 2, 3:", move
				bad = True
			if len(move) >= 2 and not (move[1] in range(8)):
				print "Invalid column:", move
				bad = True
				
		return bad
	
	try:
		move_file = open(sys.argv[2])
	except IndexError:
		print "No move file given"
		sys.exit(1)
	except IOError:
		print "Problem opening file", sys.argv[2]
		sys.exit(1)
	
	try:
		# generate the 2 column / N row 2D list.
		moves = [ [ int(token) for token in line.strip().split() ] for line in move_file.readlines() if len(line.strip()) > 0 ]
	except ValueError:
		print "Non-int value given, exiting"
		sys.exit(1)
	
	if check_input(moves):
		print "Exiting because of bad input"
		sys.exit(1)


	return moves

pieces = get_problem()
moves = get_input()
num_moves = len(moves)

if num_moves != len(pieces):
	print "Number of moves does not equal number of pieces", pieces, moves


print
print


EMPTY_LINE = [' ', ] * 8
BOARD = [['-'] * 8]
LINES = 0

# prints the current state of the game to stdout
def print_board():
	global BOARD

	print
	print "|" + " "*8 + "|"

	for row in BOARD:
		if sum([c == ' ' for c in row]) == 8:
			continue
		print "|" + "".join(row) + "|"
		

# checks to see if the top N rows of the game are empty
def top_N_empty(N):
	global EMPTY_LINE
	global BOARD
	
	if len(BOARD) < N:
		return False
	
	for i in range(N):
		if BOARD[i] != EMPTY_LINE:
			return False

	return True

# drops the piece p in the column specified by col.
def drop_piece(p, col):
	global BOARD
	global EMPTY_LINE
		
	while not top_N_empty(len(p)):
		BOARD = [EMPTY_LINE[:]] + BOARD
	
	down = 0
	
	while True:
		stop = False
		for row_idx in range(len(p)):
			for col_idx in range(len(p[0])):
				if p[row_idx][col_idx] != ' ':
					try:
						if BOARD[row_idx + down][col + col_idx] != ' ':
							# we hit something so the piece shouldn't drop this far down
							stop = True
							down -= 1
							break
					except IndexError:
						print "piece",p, "hitting right wall when dropped in col", col
						sys.exit(1)
			if stop:
				break
		if stop:
			break
	
		down += 1
	
	# copy the piece into the game board
	for row_idx in range(len(p)):
		for col_idx in range(len(p[0])):
			if p[row_idx][col_idx] != ' ':
				BOARD[row_idx + down][col + col_idx] = p[row_idx][col_idx]

# check to see if there are any lines that are full (and thus need to be cleared)
def check_lines():
	global LINES
	global BOARD

	new_BOARD = []
	for line in BOARD:
		if line[0] != '-' and sum([c == ' ' for c in line]) == 0:
			LINES += 1
			print "LINE!"
		else:
			new_BOARD.append(line)
			
	BOARD = new_BOARD

# strip the blank lines in the board
def strip_blank_lines():
	global BOARD
	
	new_BOARD = []
	
	for line in BOARD:
		if  sum([c == ' ' for c in line]) < 8:
			new_BOARD.append(line)
			
	BOARD = new_BOARD
		
# for each piece, rotate it as specified then drop it in the board, as specified.
for idx in range(num_moves):
	p = tetris_pieces[pieces[idx]]
	p = rotate(p, moves[idx][0])
	

	drop_piece(p, moves[idx][1])
	check_lines()
	
	print '\n\n\n===== move', idx, '====='
	print_board()	

strip_blank_lines()

print "FINAL LINES:", LINES
print "FINAL HEIGHT:", len(BOARD) - 1


