Welcome, guest | Sign In | My Account | Store | Cart
#!/usr/bin/env python
"tetris -- a brand new game written in python by Alfe"

import sys, random, time, select, os, termios

width
= 10
height
= 22

blocks
= [ [ (0,0), (0,1),  (0,-1),  (1,0)  ],  # T
           
[ (0,0), (0,1),  (0,2),   (0,-1) ],  # I
           
[ (0,0), (0,1),  (1,1),  (-1,0)  ],  # S
           
[ (0,0), (0,-1), (1,-1), (-1,0)  ],  # Z
           
[ (0,0), (0,1),  (1,1),   (1,0)  ],  # O
           
[ (0,0), (-1,1), (-1,0),  (1,0)  ],  # L
           
[ (0,0), (1,1),  (-1,0),  (1,0)  ],  # J
           
]

inverted    
= '\033[7;1m'
blue        
= '\033[7;34m'
normal      
= '\033[0m'
clear_screen
= '\033[2J'  # clear the screen
home        
= '\033[H'   # goto top left corner of the screen
# (the latter two were found using 'clear | od -c')

empty
= '  '
black
= inverted + '  ' + normal  # two inverted spaces
blue  
= blue     + '  ' + normal  # two inverted spaces
floor
= '=='

left  
= 'left'
right
= 'right'
turn  
= 'turn'
down  
= 'down'
quit  
= 'quit'

shaft
= None

def play_tetris():  
    initialize_shaft
()
   
while True:  # until game is lost
        block
= get_random_block()
        coordinates
= (width/2-1, 1)  # in the middle at the top
       
if not place_block(block, coordinates, blue):  # collision already?
           
return  # game is lost!
        next_fall_time
= time.time() + fall_delay()
       
# ^^^ this is the time when the block will fall automatically
       
#     one line down
       
while True:  # until block is placed fixedly
            print_shaft
()
            remove_block
(block, coordinates)
            x
, y = coordinates
           
try:
               
try:
                    command
= get_command(next_fall_time)
               
except Timeout:  # no command given
                   
raise Fall()
               
else:  # no exception, so process command:
                   
if   command == left:
                        new_coordinates
= (x-1, y)
                        new_block
= block
                   
elif command == right:
                        new_coordinates
= (x+1, y)
                        new_block
= block
                   
elif command == turn:
                        new_coordinates
= (x, y)
                        new_block
= turn_block(block)
                   
elif command == down:
                       
raise Fall()
                   
elif command == quit:
                       
return
                   
else:
                       
raise Exception("internal error: %r" % command)
                   
if place_block(new_block, new_coordinates,
                                   blue
):  # command ok?
                       
# execute the command:
                        block      
= new_block
                        coordinates
= new_coordinates
                   
else:
                        place_block
(block, coordinates, blue)
                       
# ignore the command which could not be executed
                       
# maybe beep here or something ;->
           
except Fall:  
               
# make the block fall automatically:
                new_coordinates
= (x, y+1)
                next_fall_time
= time.time() + fall_delay()
               
if place_block(block, new_coordinates, blue):  # can be placed?
                    coordinates
= new_coordinates
               
else:
                    place_block
(block, coordinates,
                                black
)  # place block there again
                   
break               # and bail out
        remove_full_lines
()

class Timeout(Exception):  pass
class    Fall(Exception):  pass

def remove_full_lines():              
   
global shaft, width, height
   
def line_full(line):  
       
global width
       
for x in range(width):
           
if line[x] == empty:
               
return False
       
return True
   
   
def remove_line(y):  
       
global shaft, width
       
del shaft[y]  # cut out line
        shaft
.insert(0, [ empty ] * width)  # fill up with an empty line
   
   
for y in range(height):  
       
if line_full(shaft[y]):
            remove_line
(y)

def fall_delay():                      
   
return 1.3  # cheap version; implement raising difficulty here

def turn_block(block):                
   
"return a turned copy(!) of the given block"
    result
= []
   
for x, y in block:
        result
.append((y, -x))
   
return result

def get_command(next_fall_time):      
   
"if a command is entered, return it; otherwise raise the exception Timeout"
   
while True:  # until a timeout occurs or a command is found:
        timeout
= next_fall_time - time.time()
       
if timeout > 0.0:
           
(r, w, e) = select.select([ sys.stdin ], [], [], timeout)
       
else:
           
raise Timeout()
       
if sys.stdin not in r:  # not input on stdin?
           
raise Timeout()
        key
= os.read(sys.stdin.fileno(), 1)
       
if   key == 'j':
           
return left
       
elif key == 'l':
           
return right
       
elif key == 'k':
           
return turn
       
elif key == ' ':
           
return down
       
elif key == 'q':
           
return quit
       
else:  # any other key:  ignore
           
pass

def place_block(block, coordinates, color):  
   
"if the given block can be placed in the shaft at the given coordinates"\
   
" then place it there and return True; return False otherwise and do not"\
   
" place anything"
   
global shaft, width, height
    block_x
, block_y = coordinates
   
for stone_x, stone_y in block:
        x
= block_x + stone_x
        y
= block_y + stone_y
       
if (x < 0 or x >= width or
            y
< 0 or y >= height or  # border collision?
            shaft
[y][x] != empty):   # block collision?
           
return False  # cannot be placed there
   
# reached here?  ==> can be placed there
   
# now really place it:
   
for stone_x, stone_y in block:
        x
= block_x + stone_x
        y
= block_y + stone_y
        shaft
[y][x] = color
   
return True

def remove_block(block, coordinates):  
   
global shaft
    block_x
, block_y = coordinates
   
for stone_x, stone_y in block:
        x
= block_x + stone_x
        y
= block_y + stone_y
        shaft
[y][x] = empty

def get_random_block():                
   
if random.randint(1, 10) == 1:
       
return perfect_block() or random.choice(blocks)
   
return random.choice(blocks)

def perfect_block():
    result
= []
   
for y in range(height):
       
if filter(lambda b: b != empty, shaft[y]):  # found summit
            random_order
= range(width)
            random
.shuffle(random_order)
           
for x in random_order:
               
if shaft[y][x] == empty:  # found space besides summit
                   
for x_ in range(width-x):  # fill to the right
                       
if shaft[y][x+x_] != empty:
                           
break
                       
for y_ in range(height-y):
                           
if shaft[y+y_][x+x_] == empty:
                                result
.append((x_, y_))
                           
else:
                               
break
                   
for x_ in range(-1, -x-1, -1):  # fill to the left
                       
if shaft[y][x+x_] != empty:
                           
break
                       
for y_ in range(height-y):
                           
if shaft[y+y_][x+x_] == empty:
                                result
.append((x_, y_))
                           
else:
                               
break
                   
# shift block in x direction to center it:
                    xmin
= min(map(lambda v: v[0], result))
                    xmax
= max(map(lambda v: v[0], result))
                   
return map(lambda v: (v[0]-(xmax+xmin)/2, v[1]), result)
   
return None

def initialize_shaft():                
   
global width, height, shaft, empty
    shaft
= [ None ] * height
   
for y in range(height):
        shaft
[y] = [ empty ] * width

def print_shaft():                    
   
# cursor-goto top left corner:
    sys
.stdout.write(home)
   
for y in range(height):  
       
if y > 3:  # does this line have a border?  (the topmost ones do not)
            sys
.stdout.write(']')
       
else:
            sys
.stdout.write(' ')
       
for x in range(width):
            sys
.stdout.write(shaft[y][x])
       
if y > 3:  # does this line have a border?  (the topmost ones do not)
            sys
.stdout.write('[\n')
       
else:
            sys
.stdout.write('\n')
   
   
# print bottom:
    sys
.stdout.write(']' + floor * width + '[\n')

def prepare_tty():                      
   
"set the terminal in char mode (return each keyboard press at once) and"\
   
" switch off echoing of this input; return the original settings"
    stdin_fd
= sys.stdin.fileno()  # will most likely be 0  ;->
    old_stdin_config
= termios.tcgetattr(stdin_fd)
   
[ iflag, oflag, cflag, lflag, ispeed, ospeed, cc ] = \
        termios
.tcgetattr(stdin_fd)
    cc
[termios.VTIME] = 1
    cc
[termios.VMIN] = 1
    iflag
= iflag & ~(termios.IGNBRK |
                      termios
.BRKINT |
                      termios
.PARMRK |
                      termios
.ISTRIP |
                      termios
.INLCR |
                      termios
.IGNCR |
                     
#termios.ICRNL |
                      termios
.IXON)
   
#  oflag = oflag & ~termios.OPOST
    cflag
= cflag | termios.CS8
    lflag
= lflag & ~(termios.ECHO |
                      termios
.ECHONL |
                      termios
.ICANON |
                     
# termios.ISIG |
                      termios
.IEXTEN)
    termios
.tcsetattr(stdin_fd, termios.TCSANOW,
                     
[ iflag, oflag, cflag, lflag, ispeed, ospeed, cc ])
   
return (stdin_fd, old_stdin_config)

def cleanup_tty(original_tty_settings):  
   
"restore the original terminal settings"
    stdin_fd
, old_stdin_config = original_tty_settings
    termios
.tcsetattr(stdin_fd, termios.TCSADRAIN, old_stdin_config)

original_tty_settings
= prepare_tty()  # switch off line buffering etc.
sys
.stdout.write(clear_screen)
try:  # ensure that tty will be reset in the end
    play_tetris
()
finally:
    cleanup_tty
(original_tty_settings)

History