Jetris CE

This game’s challenge is
to arrange the falling blocks.
Stay alive forever.

Jetris Console Edition is my second Tetris clone (see Jetris for the first one). It is programmed in Python and uses the curses module to display all its graphics on the shell.
Despite it running purely on the shell, I managed to eliminate any noticeable graphics update bugs. Furthermore the code to clear lines differs immensely from Jetris and actually works properly.
To enhance gameplay I implemented the so-called bag method. Instead of choosing pieces at random, a bag gets filled with all seven possible pieces. Each time you get a new piece, that piece gets randomly chosen out of the bag. If the bag is empty, it gets refilled. That way it is ensured that there will be no more than twelve pieces between two identical pieces (worst-case scenario). Also there will no more than four S or Z pieces in a row which makes the game fairer.
The pieces initially fall at one pps (pixel per second) and the game runs at twelve tps (ticks per seconds). Every ten cleared lines the speed increases by one tick or one twelfth of a second until it stays at the minimum speed, one twelfth of a second.
The game’s clock is handled by a thread and graphics updates are made when they are needed (there are no fps).
To achieve the pieces blocky look I used two spaces and a color pair whose background color is the piece’s color. On the shell two fully filled characters look like a square.
You also have the option to change your key bindings — which get saved on disk –, pause the game and there is a high score list — which also gets saved on disk. The files are located where the python code file resides.

Jetris in action (1) Jetris in action (2) Game over.


# Python 2.7.7 Code
# Jonathan Frech 10th of August, 2016
#         edited 11th of August, 2016
#         edited 12th of August, 2016
#         edited 13th of August, 2016
#         edited 14th of August, 2016
#         edited 16th of August, 2016
#         edited 17th of August, 2016
#         edited 18th of August, 2016
#         edited 19th of August, 2016

#         edited 22nd of August   , 2016
#                bug fix: speed
#         edited  6th of September, 2016
#                bug fix: addstr() error
#         edited  8th of October  , 2016
#                bug fix: addstr(), curs_set() and move() errors

# bash script at /usr/bin/jetris

# import
import time, re, math, random, os
import curses, datetime, threading, sys

# not changeable keys:
#    p  : pause
#    F1 : about
#    F2 : key bindings
#    F12: quit

# path where files will go
PATH = os.path.abspath(os.path.dirname(__file__)) + "/"

""" THREADS """
# ticker thread sends ticks (at 12 fps)
class Ticker(threading.Thread):
	# init
	def __init__(self):
		# inherit
		threading.Thread.__init__(self)

		# tps / ticks
		self.tps = 12
		self.ticks = 0

		# initialize as daemon thread
		self.daemon = True

	# run
	def run(self):
		# while loop
		while True:
			try:
				# tick the game
				m.game.tick()

				# one more tick!
				self.ticks += 1

				# sleep tps times every second
				time.sleep(1./self.tps)

			except:
				pass

""" FUNCTIONS """
# center a given string
def centerstring(string, width, side = "both"):
	n = (width-len(string))/2
	o = (width-len(string))%2
	if side == "both":
		return (" "*n) + string + (" "*n) + (" "*o)
	elif side == "left":
		return (" "*n) + string
	elif side == "right":
		return string + (" "*n) + (" "*o)

# add a string without errors
def addstr(scr, y, x, string, color = 0):
	# screen dimensions
	my, mx = scr.getmaxyx()
	
	# left border
	if x < 0:
		string = string[-x:]
		x = 0
	
	# right border
	if x+len(string) > mx:
		string = string[:mx-x]
		x = mx-len(string)
	
	# bottom right char cannot be accessed
	if y == my-1:
		if x+len(string) > mx-1:
			string = string[:mx-1-x]
			x = mx-1-len(string)
	
	# top and bottom border
	if y >= 0 and y < my:
		scr.addstr(y, x, string, curses.color_pair(color))

# set cursor's visibility without errors
def curs_set(bool):
	try:
		curses.curs_set(bool)
	except:
		pass

# move cursor without errors
def move(scr, y, x):
	my, mx = scr.getmaxyx()
	scr.move(min(max(y, 0), my-1), min(max(x, 0), mx-1))

""" BOXES """
# box class
class Box():
	# init
	def __init__(self, size, frame = True, background = " "):
		self.pos = [0, 0]
		self.size = size
		self.background = background * (self.size[1]/len(background)) + (" "*(self.size[1]%len(background)))
		self.frame = frame

	# set pos
	def setpos(self, pos):
		self.pos = pos

	# center the box
	def center(self):
		self.pos = [(m.my-self.size[0])/2, (m.mx-self.size[1])/2]

	# add a relatively positioned string
	def addstr(self, y, x, string, colorid = 0, sizebound = True):
		# fix relative bounds
		if sizebound:
			if y < 0 or y > self.size[0]:
				return
			
			if x < 0:
				string = string[-x:]
				x = 0
			
			if x+len(string) > self.size[1]:
				string = string[:self.size[1]-x]

		# render string
		addstr(m.stdscr, int(self.pos[0]+y), int(self.pos[1]+x), string, int(colorid))

	# render the box's ascii-styled frame
	def drawbox(self):
		# render frame
		if self.frame:
			self.addstr(-1, -1, "+" + ("-"*self.size[1]) + "+", sizebound = False)
			for _ in range(0, self.size[0]):
				self.addstr(_, -1, "|" + self.background + "|", sizebound = False)
			self.addstr(self.size[0], -1, "+" + ("-"*self.size[1]) + "+", sizebound = False)

		# only clear box
		else:
			for _ in range(0, self.size[0]):
				self.addstr(_, 0, self.background)

# label box (for lists of strings)
class Boxlabel(Box):
	# init
	def __init__(self, size, text, centered = False, frame = True):
		# inherit
		Box.__init__(self, size, frame)

		# text
		self.text = text

		# center text if needed
		if centered:
			for y in range(0, len(self.text)):
				self.text[y] = centerstring(self.text[y], self.size[1])

	# render
	def render(self):
		self.drawbox()
		for y in range(0, len(self.text)):
			self.addstr(y, 0, self.text[y])

# high score box (very specific)
class Boxhighscores(Box):
	# init
	def __init__(self):
		# inherit
		Box.__init__(self, [10+2+2, 8+1+3+6])

	# render
	def render(self):
		# fill a list with the first ten highest scores
		l = m.game.highscores[:]
		while len(l) < 10:
			l.append(["---", 0])
		if len(l) > 10:
			l = l[:10]

		# render text
		self.addstr(0, 0, centerstring("- highscores -", self.size[1]))
		y = 1
		for _ in l:
			y += 1
			name = _[0]
			score = str(_[1])
			while len(name) < 8:
				name += " "
			while len(score) < 3:
				score = " " + score
			self.addstr(y, 0, centerstring(name + ":" + score, self.size[1]))
		self.addstr(12+1, 0, centerstring("Your place: %d." % (m.game.highscoreplace+1), self.size[1]))

	# handle
	def handle(self, char):
		if char == ord("\n"):
			m.game.reset()

# game over box (very specific)
class Boxgameover(Box):
	# init
	def __init__(self, size):
		# inherit
		Box.__init__(self, size)
		self.string1 = centerstring("- game over -", self.size[1])
		self.string2 = centerstring("your name>", self.size[1]-7, "left")
		self.name = ""
		self.cursor = 0
		self.nameinput = False

	# render
	def render(self):
		# activate cursor
		curs_set(True)

		# render
		self.drawbox()
		self.addstr(0, 0, self.string1)
		self.addstr(2, 0, self.string2+self.name)
		move(m.stdscr, self.pos[0]+2, self.pos[1]+len(self.string2)+self.cursor)

	# handle
	def handle(self, char):
		# respond to text inputs
		if self.nameinput:
			# text
			if char >= 32 and char <= 125:
				if len(self.name) < 8:
					self.name = self.name[:self.cursor] + chr(char) + self.name[self.cursor:]
					self.cursor += 1

			# arrow left
			elif char == curses.KEY_LEFT:
				if self.cursor > 0:
					self.cursor -= 1

			# arrow right
			elif char == curses.KEY_RIGHT:
				if self.cursor < len(self.name):
					self.cursor += 1

			# backpace
			elif char == curses.KEY_BACKSPACE:
				if self.cursor > 0:
					self.name = self.name[:self.cursor-1] + self.name[self.cursor:]
					self.cursor -= 1

			# delete
			elif char == 330:
				if self.cursor < len(self.name):
					self.name = self.name[:self.cursor] + self.name[self.cursor+1:]

			# enter finalizes the name and switches to the high score screen
			elif char == ord("\n"):
				self.nameinput = False
				curs_set(False)
				m.game.addhighscore(self.name)
				m.game.active = "highscores"

		# respond to normal inputs (not used)
		else:
			if char == ord("r"):
				m.game.reset()
				curs_set(False)

# key binding box (very specific)
class Boxkeybinding(Box):
	# init
	def __init__(self):
		# inherit
		Box.__init__(self, [0, 0], frame = False)

		# bindings that can be modified
		self.keybindings = [
			["moveleft"         , 0, "Move left "],
			["moveright"        , 0, "Move right"],
			["movedown"         , 0, "Move down "],
			["drop"             , 0, "Quick drop"],

			["turnclockwise"    , 0, "Turn cw   "],
			["turnanticlockwise", 0, "Turn acw  "]
		]

		# set size
		self.size[0] = len(self.keybindings)+2
		for _ in self.keybindings:
			if len(_[2]) > self.size[1]:
				self.size[1] = len(_[2])
		self.size[1] += len("> ") + len(" <") + 8 + len(">")

		self.selected = 0
		self.inputkey = False

		# get bindings from game
		self.getbindings()

	# get bindings from game
	def getbindings(self):
		for _ in range(0, len(self.keybindings)):
			self.keybindings[_][1] = m.game.keys[self.keybindings[_][0]]

	# set bindings to game
	def setbindings(self):
		for _ in range(0, len(self.keybindings)):
			m.game.keys[self.keybindings[_][0]] = self.keybindings[_][1]
		m.game.savekeybindings()

	# render
	def render(self):
		self.drawbox()
		self.addstr(0, 0, centerstring("- keybindings -", self.size[1]))
		for y in range(0, len(self.keybindings)):
			pre = "  "
			if y == self.selected:
				pre = "> "

			if self.inputkey and y == self.selected:
				key = "  " + centerstring("...", 8) + " "
			else:
				key = self.keybindings[y][1]
				if key == -1:
					key = "/"
				else:
					key = curses.keyname(key)
					if "KEY_" in key:
						key = re.sub("KEY_", "", key)
					else:
						key = "'" + key + "'"

				if len(key) > 8:
					key = key[:8]
				else:
					key = centerstring(key, 8)

				key = " <" + key + ">"

			self.addstr(y+2, 0, pre + self.keybindings[y][2] + key)

	# handle
	def handle(self, char):
		if self.inputkey:
			if char == 27 or char in m.game.reservedkeys:
				char = -1

			self.keybindings[self.selected][1] = char
			self.inputkey = False
		else:
			if char == curses.KEY_UP:
				self.selected = (self.selected-1) % len(self.keybindings)

			elif char == curses.KEY_DOWN:
				self.selected = (self.selected+1) % len(self.keybindings)

			elif char == curses.KEY_BACKSPACE:
				self.keybindings[self.selected][1] = -1

			elif char == ord("\n"):
				self.inputkey = True

			elif char == ord("m"):
				self.keybindings[3][1] = -1
				self.keybindings[4][1] = ord(" ")

			elif char == ord("d"):
				m.game.setdefaultkeybindings()
				self.getbindings()

			elif char == m.game.keys["keybinding"]:
				self.setbindings()
				m.game.active = "game"

""" GAME """
class Game():
	# initialization
	def __init__(self):
		# all pieces (with rotations)
		self.pieces = [
			"**;**"  ,
			"-**;**-", "*_;**;-*",
			"**-;-**", "-*;**;*-",
			"*;*;*;*", "****"    ,
			"--*;***", "*-;*-;**", "***;*--", "**;-*;-*",
			"*--;***", "**;*-;*-", "***;--*", "-*;-*;**",
			"-*-;***", "*-;**;*-", "***;-*-", "-*;**;-*"
		]

		# piece id's corresponding color id
		self.piececolor = [1, 2, 2, 3, 3, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7]

		# unique piece ids (no rotations)
		self.uniqueids = [0, 1, 3, 5, 7, 11, 15]

		# piece id's corresponding turned piece id (clockwise and anticlockwise)
		self.turntableclockwise     = [0, 2, 1, 4, 3, 6, 5, 8, 9, 10, 7, 12, 13, 14, 11, 16, 17, 18, 15]
		self.turntableanticlockwise = [0, 2, 1, 4, 3, 6, 5, 10, 7, 8, 9, 14, 11, 12, 13, 18, 15, 16, 17]

		# field
		self.field = []
		self.dimensions = [20, 10]
		self.newfield()

		# score
		self.linescleared = 0
		self.highscores = []
		self.highscoreplace = 0
		self.loadhighscores()

		# piece position and piece
		self.piecepos = [0, 0]
		self.pieceid = 0
		self.nextpieceid = 0

		# bag method
		self.bag = []

		# other variables
		self.dropspeed = 12
		self.timetodrop = self.dropspeed

		# key bindings
		self.keys = {"":0}
		self.reservedkeys = []
		self.setdefaultkeybindings()
		self.loadkeybindings()

		# color scheme
		if curses.has_colors:
			self.defaultcolorscheme = "color"
		else:
			self.defaultcolorscheme = "b/w"
		self.setcolorscheme()

	# reset the game
	def reset(self):
		self.newfield()

		self.bag = []

		self.linescleared = 0
		self.highscoreplace = 0

		self.dropspeed = 12
		self.timetodrop = self.dropspeed

		self.nextpiece()
		self.nextpiece()

		self.active = "game"

		self.render()

	def addhighscore(self, name):
		# [name, score]
		self.score = self.linescleared

		success = False
		for _ in range(0, len(self.highscores)):
			if self.score > self.highscores[_][1]:
				success = True
				self.highscoreplace = _
				self.highscores.insert(self.highscoreplace, [name, self.score])
				break

		if not success:
			self.highscoreplace = len(self.highscores)
			self.highscores.append([name, self.score])
		self.savehighscores()

	# further initialization
	def init(self):
		# init boxes
		self.background = " "
		self.gamebox = Box([self.dimensions[0], self.dimensions[1]*2], True, self.background)
		self.nextpiecebox = Box([4, 8])
		self.infobox = Box([8, 20])

		self.gameoverbox = Boxgameover([3, self.infobox.size[1]])
		self.pausebox = Boxlabel([1, len("- paused -")], ["- paused -"], frame = False)

		self.aboutcontent = [
			"         J E T R I S          ",
			"      - a Tetris clone -      ",
			"                              ",
			"   This game's challenge is   ",
			"to arrange the falling blocks.",
			"     Stay alive forever.      "
		]
		self.aboutbox = Boxlabel([6, 30], self.aboutcontent, frame = False)
		self.keybindingbox = Boxkeybinding()
		self.highscoresbox = Boxhighscores()

		# active section (game / pause / about / keybinding)
		self.active = "game"

		# draw two new pieces (to initiate pieces)
		self.nextpiece()
		self.nextpiece()

		# initialize size (and clear / render)
		self.resize()

	""" keys """
	def savekeybindings(self):
		try:
			doc = open(m.path + "jetris.keybindings", "w")
			for k in self.keys:
				doc.write(k + "=" + str(self.keys[k]) + "\n")
			doc.close()
		except:
			pass

	def loadkeybindings(self):
		try:
			doc = open(m.path + "jetris.keybindings", "r")
			for line in doc.read().split():
				try:
					k, key = re.split("=", line)
					self.keys[k] = int(key)
				except:
					pass
			doc.close()
		except:
			pass

	""" high score """
	def savehighscores(self):
		try:
			doc = open(m.path + "jetris.highscores", "w")
			for h in self.highscores:
				try:
					doc.write(h[0] + "=" + str(h[1]) + "\n")
				except:
					pass
			doc.close()
		except:
			pass

	def loadhighscores(self):
		self.highscores = []
		try:
			doc = open(m.path + "jetris.highscores", "r")
			for line in doc.read().split():
				try:
					name, score = re.split("=", line)
					self.highscores.append([name, int(score)])
				except:
					pass
			doc.close()
		except:
			pass


	""" colors """
	def setcolorscheme(self, scheme = None):
		if scheme is None:
			scheme = self.defaultcolorscheme

		if scheme == "color":
			curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_BLUE   )
			curses.init_pair(2, curses.COLOR_BLACK, curses.COLOR_GREEN  )
			curses.init_pair(3, curses.COLOR_BLACK, curses.COLOR_CYAN   )
			curses.init_pair(4, curses.COLOR_BLACK, curses.COLOR_RED    )
			curses.init_pair(5, curses.COLOR_BLACK, curses.COLOR_MAGENTA)
			curses.init_pair(6, curses.COLOR_BLACK, curses.COLOR_WHITE  )
			curses.init_pair(7, curses.COLOR_BLACK, curses.COLOR_YELLOW )

		elif scheme == "b/w":
			for _ in range(1, 8):
				curses.init_pair(_, curses.COLOR_BLACK, curses.COLOR_WHITE)

	""" keys """
	def setdefaultkeybindings(self):
		self.keys = {
			"moveleft"          :curses.KEY_LEFT ,
			"moveright"         :curses.KEY_RIGHT,
			"movedown"          :curses.KEY_DOWN ,
			"drop"              :ord(" ")        ,

			"turnclockwise"     :curses.KEY_UP   ,
			"turnanticlockwise" :-1              ,

			"pause"             :ord("p")        ,
			"about"             :curses.KEY_F1   ,
			"keybinding"        :curses.KEY_F2
		}
		self.reservedkeys = [self.keys["pause"], self.keys["about"], self.keys["keybinding"]]

	""" reset """
	# set sizes according to screen's size (gets called on screen resizement)
	def resize(self):
		self.gamebox.center()
		self.nextpiecebox.setpos([self.gamebox.pos[0]                   , self.gamebox.pos[1]-12                    ])
		self.infobox     .setpos([self.gamebox.pos[0]                   , self.gamebox.pos[1]+self.dimensions[1]*2+4])
		self.gameoverbox .setpos([self.gamebox.pos[0]+self.infobox.size[0]+3, self.infobox.pos[1]                           ])
		self.pausebox.center()
		self.aboutbox.center()
		self.highscoresbox.center()

		self.keybindingbox.center()

	# generate an empty field
	def newfield(self):
		# clear
		self.field = []

		# fill
		for _ in range(0, self.dimensions[0]):
			self.field.append([""]*self.dimensions[1])

	# randomly choose next piece (bag method needs to be implemented!)
	def nextpiece(self):
		# refill empty bag
		if len(self.bag) <= 0:
			self.bag = self.uniqueids[:]

		# get a random, unique id out of the bag
		id = self.bag[random.randint(0, len(self.bag)-1)]
		self.bag.remove(id)

		# turn id randomly (0, 1, 2 or 3 times)
		for _ in range(0, random.randint(0, 3)):
			id = self.turntableclockwise[id]

		# set ids
		self.pieceid = self.nextpieceid
		self.nextpieceid = id

		# set position
		self.piecepos = [0, self.dimensions[1]/2-2]

		# try to clear lines
		self.clearlines()

		# if new piece is not valid, the game is over!
		if not self.valid(self.pieceid, self.piecepos):
			self.active = "gameover"
			self.gameoverbox.nameinput = True

			m.stdscr.clear()
			self.render()

	""" collision detection / movement """
	# clear full lines
	def clearlines(self):
		# lines cleared
		cleared = 0

		# go through field
		for y in range(0, self.dimensions[0]):
			# line full
			if "" not in self.field[y]:
				# clear line
				self.field[y] = [""]*self.dimensions[1]

				# move other lines down
				for _ in range(0, y)[::-1]:
					self.field[_+1] = self.field[_][:]
					self.field[_] = [""]*self.dimensions[1]

				# one more line cleared!
				cleared += 1

		# add cleared lines to total
		self.linescleared += cleared

		# recalculate speed
		self.dropspeed = 12-(self.linescleared)/10
		if self.dropspeed <= 0:
			self.dropspeed = 1

	# check if piece's position is valid
	def valid(self, id, pos):
		# go through piece
		for p in self.pieceinfo(id):
			# no empty space
			if p[2] == "*":
				# piece part position relative to given pos
				Y = pos[0]+p[0]
				X = pos[1]+p[1]

				# in bounds
				if Y >= 0 and Y < self.dimensions[0] and X >= 0 and X < self.dimensions[1]:
					# field piece in the way, invalid
					if self.field[Y][X] != "":
						return False

				# not in bounds, invalid
				else:
					return False

		# pos and id are valid
		return True

	# move piece (pos is relative)
	def move(self, pos):
		# to later check if piece was moved down
		y = pos[0]

		# create new pos, move, check validity
		pos[0] += self.piecepos[0]
		pos[1] += self.piecepos[1]

		# can move, move
		if self.valid(self.pieceid, pos):
			self.piecepos = pos
			return True

		# piece could not move down, add piece to field
		elif y == 1:
			# go through piece
			for p in self.pieceinfo(self.pieceid):
				if p[2] == "*":
					# pos relative to piece pos
					Y = self.piecepos[0]+p[0]
					X = self.piecepos[1]+p[1]

					# add piece part
					self.field[Y][X] = str(self.piececolor[self.pieceid])

			# draw a next piece
			self.nextpiece()

			# render
			self.render()

		# movement was obstructed
		return False

	# turn piece
	def turn(self, rotation = "clockwise"):
		if rotation == "clockwise":
			# test validity, then turn
			if self.valid(self.turntableclockwise[self.pieceid], self.piecepos):
				self.pieceid = self.turntableclockwise[self.pieceid]
				return True

		elif rotation == "anticlockwise":
			if self.valid(self.turntableanticlockwise[self.pieceid], self.piecepos):
				self.pieceid = self.turntableanticlockwise[self.pieceid]
				return True

		# could not turn
		return False

	""" info """
	# get piece's relative y, x position and solidity at that position ("-" / "*")
	def pieceinfo(self, id):
		info = []

		piece = re.split(";", self.pieces[id])
		for y in range(0, len(piece)):
			for x in range(len(piece[y])):
				info.append([y, x, piece[y][x]])

		return info

	# return the piece's lowest parts
	def lowestparts(self, id):
		parts = []
		for _ in range(0, self.piecedimensions(id)[1]):
			parts.append([0, _])

		for p in self.pieceinfo(id):
			if p[2] == "*":
				if p[0] > parts[p[1]][0]:
					parts[p[1]][0] = p[0]

		return parts

	def airbeneath(self, id, pos):
		air = 0
		for p in self.lowestparts(id):
			Y = int(pos[0]+p[0])
			X = int(pos[1]+p[1])
			air -= 1
			while Y >= 0 and Y < self.dimensions[0] and X >= 0 and X < self.dimensions[1] and self.field[Y][X] == "":
				if self.field[Y][X] == "":
					air += 1
				Y += 1
		return air

	# get piece's dimensions ([y, x])
	def piecedimensions(self, id):
		y, x = 0, 0

		for p in re.split(";", self.pieces[id]):
			if len(p) > x:
				x = len(p)
			y += 1

		return [y, x]

	""" render """
	# render a piece (pos is relative to gamepos)
	def renderpiece(self, id, pos, char = None, colorid = 0, box = None):
		if char is None:
			char = "  "
		if box is None:
			box = self.gamebox

		for p in self.pieceinfo(id):
			if p[2] == "*":
				box.addstr(pos[0]+p[0], (pos[1]+p[1])*2, char, colorid)

	# render piece preview (move piece to bottom)
	def renderpreview(self):
		p = self.piecepos[:]
		while self.valid(self.pieceid, p):
			p[0] += 1
		p[0] -= 1

		self.renderpiece(self.pieceid, p, " *")

	# render self.field
	def renderfield(self):
		self.gamebox.drawbox()

		# render pieces that already have landed
		for y in range(0, len(self.field)):
			for x in range(0, len(self.field[y])):
				if self.field[y][x] != "":
					self.gamebox.addstr(y, x*2, "  ", self.field[y][x])

	# render next piece
	def rendernextpiece(self):
		self.nextpiecebox.drawbox()
		dim = self.piecedimensions(self.nextpieceid)
		self.renderpiece(self.nextpieceid, [(4-dim[0])/2, (4-dim[1])/2.], None, self.piececolor[self.nextpieceid], self.nextpiecebox)

	# render info
	def renderinfo(self):
		content = ["Cleared %d line%s." % (self.linescleared, ["s", ""][self.linescleared == 1]), ""]
		content.extend(self.now())
		# speed in seconds per drop
		content.extend(["", "Speed " + str(round(1.*self.dropspeed/m.ticker.tps, 2)) + "s."])

		content.extend(["", "(c) Jonathan Frech"])
		height = len(content)
		width = 0
		for c in content:
			if len(c) > width:
				width = len(c)

		self.infobox.drawbox()

		for _ in range(0, len(content)):
			self.infobox.addstr(_, 0, content[_])

	def aimove(self):
		id = self.pieceid
		q = []
		for turn in range(0, 4):
			id = self.turntableclockwise[id]
			for x in range(0, self.dimensions[1]):
				y = 0
				while self.valid(id, [y, x]):
					y += 1
				y -= 1
				if self.valid(id, [y, x]):
					q.append([id, [y, x], turn])

		# minimal air beneath
		min = self.dimensions[0]*self.dimensions[1]
		for _ in q:
			if self.airbeneath(_[0], _[1]) < min:
				min = self.airbeneath(_[0], _[1])
		noair = []
		for _ in q:
			if self.airbeneath(_[0], _[1]) <= min:
				noair.append(_)

		# furthest down
		max = 0
		for _ in q:
			if _[1][0] - self.piecedimensions(_[0])[0] > max:
				max = _[1][0] - self.piecedimensions(_[0])[0]
		down = []
		for _ in q:
			if _[1][0] - self.piecedimensions(_[0])[0] >= max:
				down.append(_)

		# flattest
		min = 4
		for _ in q:
			if self.piecedimensions(_[0])[0] < min:
				min = self.piecedimensions(_[0])[0]
		flat = []
		for _ in q:
			if self.piecedimensions(_[0])[0] <= min:
				flat.append(_)

		# best
		best = []
		for _ in q:
			if _ in noair and _ in down and _ in flat:
				best.append(_)

		if len(best) <= 0:
			for _ in q:
				if _ in noair and _ in down:
					best.append(_)

		if len(best) <= 0:
			for _ in q:
				if _ in down and _ in flat:
					best.append(_)

		if len(best) <= 0:
			for _ in q:
				if _ in noair and _ in flat:
					best.append(_)

		if len(best) <= 0:
			best = down[:]
		if len(best) <= 0:
			best = noair[:]
		if len(best) <= 0:
			best = flat[:]
		if len(best) <= 0:
			best = q[:]

		Q = best[0]

		self.pieceid = Q[0]
		if self.piecepos[1] > Q[1][1]:
			self.move([0, -1])
		elif self.piecepos[1] < Q[1][1]:
			self.move([0, 1])
		#self.pieceid = Q[0]
		#self.piecepos = Q[1]
		#self.move([1, 0])



	# render whole game
	def render(self):
		# render the game
		if self.active in ["game", "gameover"]:
			# render
			self.renderfield()
			self.renderinfo()

			if self.active != "gameover":
				self.renderpreview()
				self.renderpiece(self.pieceid, self.piecepos, None, self.piececolor[self.pieceid])
				self.rendernextpiece()
				self.renderinfo()

			# gameover
			else:
				self.gameoverbox.render()


		elif self.active == "about":
			self.aboutbox.render()

		elif self.active == "pause":
			self.pausebox.render()

		elif self.active == "keybinding":
			self.keybindingbox.render()

		elif self.active == "highscores":
			self.highscoresbox.render()

		# update
		m.stdscr.noutrefresh()

	""" tick / handle """
	# tick function (runs at constant tps)
	def tick(self):
		if self.active == "game":
			# move piece
			self.timetodrop -= 1
			if self.timetodrop <= 0:
				self.move([1, 0])
				self.timetodrop = self.dropspeed

		m.stdscr.clear()
		self.render()
		curses.doupdate()

	# handle key event
	def handle(self, char):
		# game handling
		if self.active == "game" or self.active == "gameover":
			if self.active == "gameover":
				self.gameoverbox.handle(char)

			else:
				if char == self.keys["moveleft"]:
					self.move([0, -1])

				elif char == self.keys["moveright"]:
					self.move([0, 1])

				elif char == self.keys["turnclockwise"]:
					self.turn("clockwise")

				elif char == self.keys["turnanticlockwise"]:
					self.turn("anticlockwise")

				elif char == self.keys["movedown"]:
					self.move([1, 0])
					self.timetodrop = self.dropspeed

				elif char == self.keys["drop"]:
					while self.move([1, 0]):
						pass

				#elif char == ord("c"):
				#	self.aimove()

			if not self.gameoverbox.nameinput:
				# move to pause
				if char == self.keys["pause"]:
					self.active = "pause"

				# move to about
				elif char == self.keys["about"]:
					self.active = "about"

				# move to keybinding
				elif char == self.keys["keybinding"]:
					self.active = "keybinding"

		# pause handling
		elif self.active == "pause":
			if char == self.keys["pause"]:
				self.active = "game"

		# about handling
		elif self.active == "about":
			if char == self.keys["about"]:
				self.active = "game"

		# keybinding handling
		elif self.active == "keybinding":
			self.keybindingbox.handle(char)

		elif self.active == "highscores":
			self.highscoresbox.handle(char)

		# other handling, return to game
		else:
			if char != -1:
				self.active = "game"

		# render
		self.render()

	""" other """
	# get current time and date
	def now(self):
		now = datetime.datetime.now()

		day = str(now.day)
		weekday = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"][now.weekday()]
		month = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][now.month-1]
		year = str(now.year)

		hour = str(now.hour)
		if len(hour) == 1:
			hour = " " + hour
		minute = str(now.minute)
		if len(minute) == 1:
			minute = " " + minute
		second = str(now.second)
		if len(second) == 1:
			second = " " + second

		date = weekday + ". " + day + "." + month + "." + year
		time = hour + ":" + minute + ":" + second
		time = centerstring(time, len(date))

		return [date, time]

""" MAIN """
class Main():
	# init
	def __init__(self):
		# init standard screen
		self.stdscr = curses.initscr()
		self.stdscr.keypad(True)

		# init curses
		curses.noecho()
		curses.cbreak()
		curs_set(False)
		curses.start_color()
		curses.use_default_colors()

		# screen size
		self.my, self.mx = self.stdscr.getmaxyx()

		# running variable
		self.running = True

		# path
		self.path = PATH

		# init ticker
		self.ticker = Ticker()

	# quit game
	def quit(self):
		self.running = False

	# further init
	def init(self):
		# start
		self.game = Game()
		self.game.init()
		self.ticker.start()

	# cleanup
	def cleanup(self):
		# cleanup curses
		self.stdscr.keypad(False)
		curses.echo()
		curses.nocbreak()
		curs_set(True)

		curses.endwin()

	# run
	def run(self):
		# while loop
		while self.running:
			# render
			self.game.render()
			curses.doupdate()

			# get char
			char = self.stdscr.getch()

			# clear
			self.stdscr.clear()

			# quit
			if not self.game.gameoverbox.nameinput:
				if char == curses.KEY_F12:
					self.quit()

			# handle resizement
			if curses.is_term_resized(self.my, self.mx):
				self.my, self.mx = self.stdscr.getmaxyx()
				self.game.resize()

			# handle
			self.game.handle(char)

			# render
			self.game.render()

		# cleanup
		self.cleanup()

""" RUN """
m = Main()
m.init()
m.run()
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s