import random

shapeLst = {
	'T':[(0,1),(1,1),(2,1),(1,2)],
	'L':[(1,0),(1,1),(1,2),(2,2)],
	'J':[(2,0),(2,1),(2,2),(1,2)],
	'I':[(1,0),(1,1),(1,2),(1,3)],
	'S':[(1,1),(2,1),(0,2),(1,2)],
	'Z':[(0,1),(1,1),(1,2),(2,2)],
	'O':[(1,1),(1,2),(2,2),(2,1)]
}

def next():
    return random.choice(shapeLst.keys())

def g():
	"""
	game closure 
	will return every block under x,y, 
	if over boundary (10,20) return a list starting with #
	"""
	def closure(x,y):
		if not x in range(10) or not y in range(20):
			return ['#']
		return []
	return closure

def t(context,shape,sym='X'):
	"""
	tetromino closure
	will return list starting with sym if x,y match shape
	will then call context(x,y) and return every thing
	"""
	def closure(x,y):
		if (x,y) in shape:
			return [sym]+context(x,y)
		return context(x,y)
	return closure

def l(context,h):
	"""
	line removal closure
	will return everything under x,y
	but one line is removed and one empty line is stacked on top
	"""
	def closure(x,y):
		if y==0:
			return []
		elif y<=h:
			return context(x,y-1)
		return context(x,y)
	return closure

def lines(context):
	"""
	return list of y coordinate where a complete line is found
	"""
	return [y for y in range(20) if len([x for x in range(10) if len(context(x,y))>0])==10]

def collide(context):
	"""
	return true is one cell have more than one block
	"""
	return True in [True for x in range(-1,11) for y in range(-1,21) if len(context(x,y))>1]

def gameover(context):
	return True in [True for x in range(-1,11) if len(context(x,-1))>1]

def shape(name,x,y,r):
	"""
	return desired shape with translation(x,y) and rotation(r)
	"""
	def translate(x,y,shape):
		return [(a+x,b+y) for (a,b) in shape]
	def rotate(r,shape):
		if r==0:
			return shape
		return rotate(r-1,[(len(shape)-y,x) for x,y in shape])
	return translate(x,y,rotate(r,shapeLst[name]))

def report(context):
	"""
	transform context into human readable text string
	"""
	def build():
		for y in range(20):
			for x in range(10):
				cell = context(x,y)
				if len(cell)>0:
					yield cell[0]
				else:
					yield '.'
			yield "\n"
	return "".join(build())
	
if __name__ == '__main__':
	import unittest,re
	class TestSomething(unittest.TestCase):
		def setUp(self):
			self.line = "".join(('.' for i in range(10)))
			self.empty = "".join((self.line+"\n" for i in range(20)))
			self.game = g()
		
		def testG(self):
			self.assertEqual(self.empty,report(g()))
			self.assertEqual([],g()(0,0))
			self.assertEqual(['#'],g()(0,21))
			self.assertEqual(['#'],g()(-1,0))

		def testShape(self):
			self.assertEqual(set([(0, 1), (1, 1), (2, 1), (1, 2)]),shape('T',0,0,0))
			self.assertEqual([(1, 2), (2, 2), (3, 2), (2, 3)],shape('T',0,1,0))
			self.assertEqual([(1, 1), (2, 1), (3, 1), (2, 2)],shape('T',1,1,0))
			self.assertEqual([(3, 0), (3, 1), (3, 2), (2, 1)],shape('T',1,0,0))
			self.assertEqual([(4, 3), (3, 3), (2, 3), (3, 2)],shape('T',0,0,1))
			self.assertEqual([(3, 1), (3, 2), (3, 3), (2, 2)],shape('T',0,0,2))
			self.assertEqual([(0, 1), (1, 1), (2, 1), (1, 2)],shape('T',0,1,1))

		def testTShape(self):
			self.assertNotEqual(self.empty,report(t(self.game,shape('T',0,0,0),'T')))
			self.assertEqual([],t(self.game,shape('T',0,0,0),'T')(0,0))
			self.assertFalse(collide(t(self.game,shape('T',0,0,0),'T')))
		
		def testLonTShape(self):
			#overlap L on T
			print report(t(t(self.game,shape('L',0,0,0),'L'),shape('T',0,0,0),'T'))
			print t(t(self.game,shape('L',0,0,0),'L'),shape('T',0,0,0),'T')(0,0)
			print collide(t(t(self.game,shape('L',0,0,0),'L'),shape('T',0,0,0),'T'))
	
		def testTShapeYminusOne(self):
			#y+1
			print report(t(self.game,shape('T',0,1,0),'T'))
			print t(self.game,shape('T',0,1,0),'T')(0,0)
			print collide(t(self.game,shape('T',0,1,0),'T'))
		
		def testTShapeRotation(self):
			#one rotation
			print report(t(self.game,shape('T',0,0,1),'T'))
			print t(self.game,shape('T',0,0,1),'T')(0,0)
			print collide(t(self.game,shape('T',0,0,1),'T'))
			
		def testTShapeTowRotation(self):
			#two rotation
			print report(t(self.game,shape('T',0,0,2),'T'))
			print t(self.game,shape('T',0,0,2),'T')(0,0)
			print collide(t(self.game,shape('T',0,0,2),'T'))
		
		def testTShapeCollideEdge(self):
			#edge collide
			print report(t(self.game,shape('T',0,-2,0),'T'))
			print t(self.game,shape('T',0,-2,0),'T')(0,-1)
			print collide(t(self.game,shape('T',0,-2,0),'T'))
		
		def testTShapeFallingCollide(self):
			#falling piece
			y = 0
			while not collide(t(self.game,shape('T',0,y+1,0),'T')):
				y = y+1
			game = t(self.game,shape('T',0,y,0),'T')
			print report(self.game)
		
		def testSShapeFallingCollideWithTShape(self):
			#falling piece
			y = 0
			while not collide(t(self.game,shape('T',0,y+1,0),'T')):
				y = y+1
			game = t(self.game,shape('T',0,y,0),'T')
			print report(self.game)
			#falling second piece
			y=0
			while not collide(t(self.game,shape('S',0,y+1,0),'S')):
				y = y+1
			print report(t(self.game,shape('S',0,y,0),'S'))
		
		def testManyShapeFallingAndLineDetection(self):
			#falling piece
			y = 0
			while not collide(t(self.game,shape('T',0,y+1,0),'T')):
				y = y+1
			game = t(self.game,shape('T',0,y,0),'T')
			print report(self.game)
			#many piece
			game = t(self.game,shape('Z',3,17,0),'Z')
			game = t(self.game,shape('J',5,16,3),'J')
			game = t(self.game,shape('O',7,17,0),'O')
			print report(self.game)
			#line?
			print lines(self.game)
			print report(l(self.game,18))
	
	unittest.main()
