<reponame>nezumisama/4D-Blocks<gh_stars>0
# -*- coding: utf-8-*-
"""This module contains the main function of the game."""

import OpenGL, sys, random, numpy, time, pygame, key_num
import logic, input_dev
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GL.ARB.vertex_buffer_object import *
from ctypes import *
import FTGL, settings
from menu import Menu, _menu, _menu_entry, bool2yn

rotx                    =   0.0             #: rotation around x
roty                    =   0.0             #: rotation around y
panx                    =   0.0             #: panning along x
pany                    =   0.0             #: panning along x
shy_vbo                 =   None            #: vbo for y-shadow
sb_vbo                  =   None            #: vbo for static blocks
og_vbo                  =   None            #: vbo for outer grid
boxes_vbo               =   None            #: vbo for boxes
curr_b_vbo              =   None            #: vbo for current block
next_b_vbo              =   None            #: vbo for next block
boxes_nump              =   0               #: number of points in boxes vbo
og_nump                 =   0               #: number of points in outer grid vbo
shy_nump                =   0               #: number of points in y-shadow vbo
sb_nump                 =   0               #: number of points in static box vbo
curr_b_nump             =   0               #: number of points in current block
next_b_nump             =   0               #: number of points in next block
null                    =   c_void_p(0)     #: null pointer
width                   =   5               #: domain width (x)
depth                   =   5               #: domain depth (z)
height                  =   10              #: domain height (y)
w_depth                 =   2               #: domain size in 4th dimension (w) 
w_spacing               =   4               #: spacing between boxes
mouserot                =   False           #: is mouse rotation active
mousepan                =   False           #: is mouse panning active
mouse_last_x            =   0               #: last mouse click x position
mouse_last_y            =   0               #: last mouse clock y position
zoom                    =   -20             #: zoom
log                     =   None            #: logic instance (:py:class:`logic.logic`)
font                    =   None            #: font object for score
menu_font               =   None            #: font object for menu
win_width               =   640             #: window width
win_height              =   480             #: window height
fps_limit               =   60              #: fps limit
fps                     =   0               #: current fps
clock                   =   None            #: clock instance
xmenu                   =   None            #: in-game menu instance
conf                    =   None            #: settings instance
running                 =   True            #: is game running
turbo                   =   False           #: is turno mode active
menu_mode               =   False           #: are we in menu
bind_mode               =   False           #: are we binding 
game_over_mode          =   False           #: are we showing the game over screen
rot_next_b              =   0.0             #: rotation of next block
sh_stipple              =   (c_uint*32)()   #: polygon stipple pattern for shadows

#: data for one cube (24 points, values mean, in order: x, y, z, r, g, b, a, padding
cube_xyzrgba_data       = numpy.array([[ 0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  1.,  1.,  0.,  0.,  0.,  0.,  0.],
       [ 1.,  1.,  1.,  0.,  0.,  0.,  0.,  0.],
       [ 1.,  0.,  1.,  0.,  0.,  0.,  0.,  0.],
       [ 1.,  1.,  1.,  0.,  0.,  0.,  0.,  0.],
       [ 1.,  1.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 1.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 1.,  0.,  1.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  1.,  1.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 1.,  1.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 1.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.],
       [ 1.,  0.,  1.,  0.,  0.,  0.,  0.,  0.],
       [ 1.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 1.,  1.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 1.,  1.,  1.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  1.,  1.,  0.,  0.,  0.,  0.,  0.]], dtype=numpy.float32)                # data for single cube

colors                  =   [[1,0,0],[0,1,0],[1,1,0],[0,0,1],[1,0,1],[0,1,1]] #: colors for blocks: red green yellow blue magenta cyan
#: maping of GL standard function names to ARB extension ones
gl_func_map_            =   [
    ["glBindBuffer", "glBindBufferARB"],
    ["glBufferData", "glBufferDataARB"],
    ["glBufferSubData", "glBufferSubDataARB"],
    ["glGenBuffers", "glGenBuffersARB"],
    ["glDeleteBuffers", "glDeleteBuffersARB"]
                            ]           
#: mapping of rotation function number to parameters for :py:meth:`logic.logic.Rotate`                           
func2rot = [
                (logic.XY, logic.CW), (logic.XY, logic.CCW),
                (logic.XZ, logic.CW), (logic.XZ, logic.CCW),
                (logic.XW, logic.CW), (logic.XW, logic.CCW),
                (logic.YZ, logic.CW), (logic.YZ, logic.CCW),
                (logic.YW, logic.CW), (logic.YW, logic.CCW),
                (logic.ZW, logic.CW), (logic.ZW, logic.CCW)
]
#: mapping of difficulty level to domain size
difficulty2dim = (
                    (4,14,4,2),
                    (5,10,5,2),
                    (5,10,5,3)
)

def reset_settings():
    """Reset some global variables each this is loaded."""
    global rotx, roty, panx, pany, mouserot, mousepan, xmenu, running
    global shy_vbo, sb_vbo, og_vbo, boxes_vbo, cur_b_vbo, next_b_vbo
    global shy_nump, sb_nump, og_nump, boxes_nump, cur_b_nump, next_b_nump
    global mouse_last_x, mouse_last_y, zoom, log, menu_font, fps, clock, turbo
    global menu_mode, bind_mode, game_over_mode, rot_next_b
    rotx, roty, panx, pany = 0.0, 0.0, 0.0, 0.0
    shy_vbo, sb_vbo, og_vbo = None, None, None
    boxes_vbo, cur_b_vbo, next_b_vbo = None, None, None
    shy_nump, sb_nump, og_nump = 0, 0, 0
    boxes_nump, cur_b_nump, next_b_nump = 0, 0, 0
    mouserot, mousepan, mouse_last_x, mouse_last_y = False, False, 0, 0
    zoom, log, font, menu_font, fps = -20, None, None, None, 0
    clock = pygame.time.Clock()
    xmenu, running, turbo = None, True, False
    menu_mode, bind_mode, game_over_mode = False, False, False
    rot_next_b = 0.0

def gen_stipple():
    """Generate stipple pattern for shadows."""
    global sh_stipple
    for i in xrange(8):
        ii=i*4
        sh_stipple[ii] = sh_stipple[ii+1]   = 0x0C0C0C0C
        sh_stipple[ii+2] = sh_stipple[ii+3] = 0xC0C0C0C0

def gl_func_map():
    """Check if some OpenGL functions are present and if not assign their names to names of functions from ARB extension. This is probably needed on some hardware."""
    global gl_func_map_
    for i in gl_func_map_:
        if not bool(eval(i[0])):
            print "Warning: OpenGL function %s not present, using %s instead." % (i[0], i[1])
            exec("global "+i[0]+"\nglobal "+i[1]+"\n"+i[0]+"="+i[1])

def init_shy():
    """Initialize y-shadow object."""
    global null, width, depth, w_depth, shy_vbo
    shy_vbo = glGenBuffers(1)
    glBindBuffer(GL_ARRAY_BUFFER, shy_vbo)
    glBufferData(GL_ARRAY_BUFFER, width*depth*w_depth*128, null, GL_STATIC_DRAW) 
    
def update_shy():
    """Update y-shadow object."""
    global shy_vbo, shy_nump, colors, width, w_spacing, log
    coor = []
    eps = 1e-3
    for i in log.ShadowY():
        col = [j*0.75 for j in colors[i.col]]
        coor.append([i.x+i.w*(width+w_spacing),i.y+eps,i.z,col[0],col[1],col[2],1,0])
        coor.append([i.x+i.w*(width+w_spacing),i.y+eps,i.z+1,col[0],col[1],col[2],1,0])
        coor.append([i.x+i.w*(width+w_spacing)+1,i.y+eps,i.z+1,col[0],col[1],col[2],1,0])
        coor.append([i.x+i.w*(width+w_spacing)+1,i.y+eps,i.z,col[0],col[1],col[2],1,0])
    coords = numpy.array(coor, dtype=numpy.float32)
    glBindBuffer(GL_ARRAY_BUFFER, shy_vbo)
    shy_nump = len(coords)
    glBufferSubData(GL_ARRAY_BUFFER, 0, coords.nbytes, coords)

def draw_shy():
    """Draw y-shadow object."""
    global shy_vbo, shy_nump
    glBindBuffer(GL_ARRAY_BUFFER, shy_vbo)
    glVertexPointer(3, GL_FLOAT, 32, null)
    glColorPointer(4, GL_FLOAT, 32, c_void_p(12))
    glDrawArrays(GL_QUADS, 0, shy_nump)
    
def delete_shy():
    """Destroy y-shadow object."""
    glDeleteBuffers(1,  [shy_vbo])

def init_next_b():
    """Initialize next block object."""
    global null, next_b_vbo, log
    next_b_vbo = glGenBuffers(1)
    glBindBuffer(GL_ARRAY_BUFFER, next_b_vbo)
    maxp = max(map(len, log.blocks))
    glBufferData(GL_ARRAY_BUFFER, maxp*768, null, GL_STATIC_DRAW)

def update_next_b():
    """Update next block object."""
    global next_b_vbo, next_b_nump, cube_xyzrgba_data, colors, width, w_spacing, log
    coords = []
    for i in log.GetNextBlock():
        col = colors[i.col]
        coords.append(cube_xyzrgba_data + numpy.array([i.x+i.w*(width+w_spacing),i.y,i.z,col[0],col[1],col[2],1,0], dtype=numpy.float32))
    coords = numpy.array(coords, dtype=numpy.float32)
    delta = numpy.array([numpy.average(coords[...,0]), numpy.average(coords[...,1]), numpy.average(coords[...,2]), 0, 0, 0, 0, 0], dtype=numpy.float32) 
    coords -= delta
    glBindBuffer(GL_ARRAY_BUFFER, next_b_vbo)
    next_b_nump = len(coords)*24
    glBufferSubData(GL_ARRAY_BUFFER, 0, coords.nbytes, coords)
    
def draw_next_b():
    """Draw next block object."""
    global next_b_vbo, next_b_nump
    glBindBuffer(GL_ARRAY_BUFFER, next_b_vbo)
    glVertexPointer(3, GL_FLOAT, 32, null)
    glColorPointer(4, GL_FLOAT, 32, c_void_p(12))
    glDrawArrays(GL_QUADS, 0, next_b_nump)
    
def delete_next_b():
    """Destroy next block object."""
    glDeleteBuffers(1,  [next_b_vbo])

def init_curr_b():
    """Initialize current block object."""
    global null, curr_b_vbo, log
    curr_b_vbo = glGenBuffers(1)
    glBindBuffer(GL_ARRAY_BUFFER, curr_b_vbo)
    maxp = max(map(len, log.blocks))
    glBufferData(GL_ARRAY_BUFFER, maxp*768, null, GL_STATIC_DRAW)

def update_curr_b():
    """Update current block object."""
    global curr_b_vbo, curr_b_nump, cube_xyzrgba_data, colors, width, w_spacing, log
    coords = []
    for i in log.GetCurrentBlock():
        col = colors[i.col]
        coords.append(cube_xyzrgba_data + numpy.array([i.x+i.w*(width+w_spacing),i.y,i.z,col[0],col[1],col[2],1,0], dtype=numpy.float32))
    coords = numpy.array(coords, dtype=numpy.float32)
    glBindBuffer(GL_ARRAY_BUFFER, curr_b_vbo)
    curr_b_nump = len(coords)*24
    glBufferSubData(GL_ARRAY_BUFFER, 0, coords.nbytes, coords)
    
def draw_curr_b():
    """Draw current block object."""
    global curr_b_vbo, curr_b_nump
    glBindBuffer(GL_ARRAY_BUFFER, curr_b_vbo)
    glVertexPointer(3, GL_FLOAT, 32, null)
    glColorPointer(4, GL_FLOAT, 32, c_void_p(12))
    glDrawArrays(GL_QUADS, 0, curr_b_nump)
    
def delete_curr_b():
    """Destroy current block object."""
    glDeleteBuffers(1,  [curr_b_vbo])

def init_sblocks():
    """Initialize static blocks object."""
    global null, sb_vbo, width, height, depth, w_depth
    sb_vbo = glGenBuffers(1)
    glBindBuffer(GL_ARRAY_BUFFER, sb_vbo)
    glBufferData(GL_ARRAY_BUFFER, width*depth*height*w_depth*768, null, GL_STATIC_DRAW) # 6 faces x 4 verts x (xyzrgba_ = 8) floats x 4 bytes

def update_sblocks():
    """Update static blocks object."""
    global sb_vbo, sb_nump, cube_xyzrgba_data, colors, width, w_spacing, log
    coords = []
    for i in log.GetSpace():
    #for i in log.GetSpaceWithBlock():
        col = colors[i.col]
        coords.append(cube_xyzrgba_data + numpy.array([i.x+i.w*(width+w_spacing),i.y,i.z,col[0],col[1],col[2],1,0],dtype=numpy.float32))
    coords = numpy.array(coords, dtype=numpy.float32)
    glBindBuffer(GL_ARRAY_BUFFER, sb_vbo)
    sb_nump = len(coords)*24
    glBufferSubData(GL_ARRAY_BUFFER, 0, coords.nbytes, coords)
    
def draw_sblocks():
    """Draw static blocks object."""
    global sb_vbo, sb_nump
    glBindBuffer(GL_ARRAY_BUFFER, sb_vbo)
    glVertexPointer(3, GL_FLOAT, 32, null)
    glColorPointer(4, GL_FLOAT, 32, c_void_p(12))
    glDrawArrays(GL_QUADS, 0, sb_nump)

def delete_sblocks():
    """Destroy static blocks object."""
    glDeleteBuffers(1,  [sb_vbo])

def init_outer_grid():
    """Initialize outer grid object."""
    global null, og_vbo, width, height, depth, w_depth, w_spacing, og_nump
    # build coords
    coor = []
    for w in xrange(w_depth):
        x_ = w*(width+w_spacing)
        xx = x_+width
        for x in xrange(x_, xx):
            for y in xrange(0, height):
                coor.append([x,y+1,0])
                coor.append([x+1,y+1,0])
                coor.append([x+1,y,0])
                coor.append([x,y,0])
                coor.append([x,y,depth])
                coor.append([x+1,y,depth])
                coor.append([x+1,y+1,depth])
                coor.append([x,y+1,depth])
        for z in xrange(0, depth):
            for y in xrange(0, height):
                coor.append([x_,y,z])
                coor.append([x_,y,z+1])
                coor.append([x_,y+1,z+1])
                coor.append([x_,y+1,z])
                coor.append([xx,y+1,z])
                coor.append([xx,y+1,z+1])
                coor.append([xx,y,z+1])
                coor.append([xx,y,z])
        for z in xrange(0, depth):
            for x in xrange(x_, xx):
                coor.append([x,0,z])
                coor.append([x+1,0,z])
                coor.append([x+1,0,z+1])
                coor.append([x,0,z+1])
                coor.append([x,height,z+1])
                coor.append([x+1,height,z+1])
                coor.append([x+1,height,z])
                coor.append([x,height,z])

    coords = numpy.array(coor, dtype=numpy.float32)
    og_nump = len(coords)
    og_vbo = glGenBuffers(1)
    glBindBuffer(GL_ARRAY_BUFFER, og_vbo)
    glBufferData(GL_ARRAY_BUFFER, og_nump*12, coords, GL_STATIC_DRAW)

def draw_outer_grid():
    """Draw outer grid object."""
    global og_vbo, og_nump
    glBindBuffer(GL_ARRAY_BUFFER, og_vbo)
    glVertexPointer(3, GL_FLOAT, 12, null)
    glDisableClientState(GL_COLOR_ARRAY)
    glDrawArrays(GL_QUADS, 0, og_nump)
    glEnableClientState(GL_COLOR_ARRAY)

def delete_outer_grid():
    """Destroy outer grid object."""
    glDeleteBuffers(1,  [og_vbo])

def set_fonts_sizes():
    """Set sizes of font objects according to current window size."""
    global font, menu_font, win_width, win_height
    font.FaceSize(int(win_height*0.04))
    menu_font.FaceSize(int(win_height*0.04))

def init_font():
    """Initialize fiot objects."""
    global font, menu_font
    font = FTGL.PixmapFont(conf.get("font_filename"))
    menu_font = FTGL.PixmapFont(conf.get("menu_font_filename"))
    set_fonts_sizes()

def set_speed():
    """Set time between block fall steps according to the **speed** setting and **turbo mode**."""
    global conf, turbo
    dt = settings.speed2dt(conf.get("speed"))
    if turbo:
        dt/=4
    pygame.time.set_timer(pygame.USEREVENT, dt)
    
def draw_font():
    """Draw text with score and other info."""
    global font, win_width, win_height, log, null, fps, conf
    line_w = font.line_height*0.5+5
    tmp = font.BBox("Layers cleared: 1000")
    max_w = tmp[3]-tmp[0]
    glColor4f(0.6, 0.6, 1.0, 1.0)
    glWindowPos2f(win_width-max_w, win_height-line_w-10)
    if conf.get("show_fps"):
        font.Render("FPS: %.0f" % fps)
        glBitmap(0, 0, 0, 0, 0, -line_w, null)
    font.Render("Score: %d" % log.score)
    glBitmap(0, 0, 0, 0, 0, -line_w, null)
    font.Render("Layers cleared: %d" % log.layers_cleared)
    glBitmap(0, 0, 0, 0, 0, -line_w, null)
    font.Render("Next: ")

def gl_init():
    """Save OpenGL state (to later restore it) and initialize OpenGL state for this game."""
    global width, height, depth, w_depth
    glPushAttrib(GL_ALL_ATTRIB_BITS)
    glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS)
    glMatrixMode(GL_PROJECTION)
    glPushMatrix()
    glMatrixMode(GL_MODELVIEW)
    glPushMatrix()
    glClearColor(0.0, 0.0, 0.0, 0.0)
    glEnable(GL_CULL_FACE)
    glEnable(GL_DEPTH_TEST)
    glDepthFunc(GL_LESS)
    glEnable(GL_BLEND)
    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
    glClearDepth(1.0)
    glEnableClientState(GL_VERTEX_ARRAY)
    glEnableClientState(GL_COLOR_ARRAY)
    init_outer_grid()
    init_sblocks()
    init_next_b()
    init_curr_b()
    init_shy()
    init_font()
    gen_stipple()
    glPolygonStipple(sh_stipple)

def draw_game_over():
    """Draw game over screen."""
    global menu_font, win_width, win_height, log
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
    glPushMatrix()
    glLoadIdentity()
    glMatrixMode(GL_PROJECTION)
    glPushMatrix()
    glLoadIdentity()
    glColor4f(0.0, 0.0, 0.0, 0.7)
    glBegin(GL_QUADS)
    glVertex2f(-1.0, -1.0)
    glVertex2f(1.0, -1.0)
    glVertex2f(1.0, 1.0)
    glVertex2f(-1.0, 1.0)
    glEnd()
    glPopMatrix()
    glMatrixMode(GL_MODELVIEW)
    glPopMatrix()
    glColor4f(1.0, 1.0, 1.0, 1.0)
    line_w = menu_font.line_height*0.5+5
    x, y = win_width*0.05, win_height*0.5+line_w*2.5
    glWindowPos2f(x, y)
    menu_font.Render("GAME OVER")
    glWindowPos2f(x, y-2*line_w)
    menu_font.Render("Score: %d" % log.score)
    glWindowPos2f(x, y-3*line_w)
    menu_font.Render("Layers cleared: %d" % log.layers_cleared)
    glWindowPos2f(x, y-4*line_w)
    menu_font.Render("Press Esc to continue")

def draw_menu():
    """Draw in-game menu."""
    global menu_font, win_width, win_height, xmenu
    highlighted = xmenu.selected
    text = xmenu.get_options_list()
    text_w = [(menu_font.BBox(i[0]), menu_font.BBox(i[1])) for i in text]
    text_w = [(i[0][3]-i[0][0], i[1][3]-i[1][0]) for i in text_w]
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
    glPushMatrix()
    glLoadIdentity()
    glMatrixMode(GL_PROJECTION)
    glPushMatrix()
    glLoadIdentity()
    glColor4f(0.0, 0.0, 0.0, 0.7)
    glBegin(GL_QUADS)
    glVertex2f(-1.0, -1.0)
    glVertex2f(1.0, -1.0)
    glVertex2f(1.0, 1.0)
    glVertex2f(-1.0, 1.0)
    glEnd()
    glPopMatrix()
    glMatrixMode(GL_MODELVIEW)
    glPopMatrix()
    line_w = menu_font.line_height*0.5+5
    x = win_width*0.05
    y = win_height*0.5+line_w*(len(text))*0.5
    for i in xrange(len(text)):
        delta = win_width*0.7-text_w[i][1]
        if i == highlighted:
            glColor4f(1.0, 1.0, 1.0, 1.0)
        else:
            glColor4f(1.0, 1.0, 0.5, 1.0)
        glWindowPos2f(x, y-line_w*i)
        menu_font.Render(text[i][0])
        glWindowPos2f(delta, y-line_w*i)
        menu_font.Render(text[i][1])

def draw_curr_b_():
    """Draw current block at the right position."""
    global log, width, w_spacing
    glPushMatrix()
    off = log.cur_block_offset
    glTranslatef(off[0]+off[3]*(width+w_spacing), off[1], off[2])
    draw_curr_b()
    glPopMatrix()

def draw_next_b_():
    """Draw next block at the right position."""
    global win_width, win_height, zoom, rot_next_b 
    aspect = win_width*1.0/win_height
    #scale = win_height*0.0005
    scale = 0.5
    glPushMatrix()
    glLoadIdentity()
    glTranslatef(10*aspect, 6, zoom)
    glRotatef(rot_next_b, 0.0, 1.0, 0.0)
    glScalef(scale, scale, scale)
    draw_next_b()
    glPopMatrix()
    
def display():
    """Main display call, draws everything needed."""
    global rotz, roty, panx, pany, zoom, log, menu_mode, rot_next_b
    update_shy()
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    glLoadIdentity()
    glTranslatef(0, 0, zoom)
    glRotatef(rotx, 1.0, 0.0, 0.0)
    glRotatef(roty, 0.0, 1.0, 0.0)
    glTranslatef(panx, pany, 0)
    glTranslatef(-0.5*(w_depth*width+(w_depth-1)*w_spacing), height*(-0.5), depth*(-0.5))
    glCullFace(GL_BACK)
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
    draw_sblocks()
    draw_curr_b_()
    if not (menu_mode or game_over_mode):
        draw_next_b_()
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
    glLineWidth(3.0)
    glColor3f(1,1,1)
    glDisableClientState(GL_COLOR_ARRAY)
    draw_sblocks()
    draw_curr_b_()
    if not (menu_mode or game_over_mode):
        glLineWidth(1.5)
        draw_next_b_()
    glEnableClientState(GL_COLOR_ARRAY)
    glLineWidth(1.0)
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
    glEnable(GL_POLYGON_STIPPLE)
    draw_shy()
    glDisable(GL_POLYGON_STIPPLE)
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
    glCullFace(GL_FRONT)
    glColor4f(1.0, 1.0, 1.0, 1.0)
    draw_outer_grid()
    glCullFace(GL_BACK)
    glColor4f(1.0, 1.0, 1.0, 0.2)
    draw_outer_grid()    
    if menu_mode: 
        draw_menu()
    else:
        if game_over_mode:
            draw_game_over()
        else:
            draw_font()
            rot_next_b += 1
    pygame.display.flip()

def reshape (w, h):
    """React to window geometry change, *w* is new width, *h* is new height."""
    aspect = w*1.0 / h
    glViewport(0, 0, w, h)
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluPerspective(60, aspect, 1.0, 1000.0)
    #scale = 10.0
    #glOrtho(-aspect*scale, aspect*scale, -scale, scale, -scale, scale)
    glMatrixMode(GL_MODELVIEW)

def gameover():
    """Callback executed by :py:data:`log` on game over."""
    global game_over_mode
    #pygame.event.post(pygame.event.Event(pygame.QUIT))
    print "game over"
    game_over_mode = True

def block_dropped():
    """Callback executed by :py:data:`log` when block was dropped, updates objects: current block, next block and static blocks."""
    update_curr_b()
    update_next_b()
    update_sblocks()

def block_rotated():
    """Callback executed by :py:data:`log` when block is rotated in 4D, updates current block object."""
    update_curr_b()

def set_mode(w, h):
    """Set display mode with window width *w* and height *h*"""
    pygame.display.set_mode((w, h), pygame.DOUBLEBUF | pygame.OPENGL | pygame.RESIZABLE)

def exit_2_os():
    """Exit to OS."""
    exit(0)

# menu callbacks
def menu_cbck_resume(index, conf):
    """Menu callback executed when menu option **resume** is selected; *index* is index of the current position in current submenu, *conf* is the settings object (instance of :py:class:`settings.Settings`); *conf_* from :py:func:`main` will be given here."""
    global menu_mode
    menu_mode = False
def menu_cbck_exit2os(index, conf):
    """Menu callback executed when menu option **exit to os** is selected. Arguments mean the same as in :py:func:`menu_cbck_resume`."""
    pygame.event.post(pygame.event.Event(pygame.QUIT))
def menu_cbck_exit2menu(index, conf):
    """Menu callback executed when menu option **exit to menu** is selected. Arguments mean the same as in :py:func:`menu_cbck_resume`."""
    global running
    running = False
def menu_cbck_misc_reset(index, conf):
    """Submenu **misc** reset callback. Arguments mean the same as in :py:func:`menu_cbck_resume`."""
    if index == 0:
        return bool2yn(conf.get("show_fps"))
    elif index == 1:
        return str(conf.get("speed"))
def menu_cbck_misc_enter(index, conf):
    """Submenu **misc** enter/selection callback. Arguments mean the same as in :py:func:`menu_cbck_resume`."""
    if index == 0:
        tmp = not conf.get("show_fps")
        conf.set("show_fps", tmp)
        return bool2yn(tmp)
def menu_cbck_misc_inc(index, conf):
    """Submenu **misc** incrementation callback. Arguments mean the same as in :py:func:`menu_cbck_resume`."""
    if index == 0:
        tmp = not conf.get("show_fps")
        conf.set("show_fps", tmp)
        return bool2yn(tmp)
    elif index == 1:
        tmp = conf.get("speed")
        if tmp<9:
            tmp+=1
        conf.set("speed", tmp)
        set_speed()
        return str(tmp)
def menu_cbck_misc_dec(index, conf):
    """Submenu **misc** decrementation callback. Arguments mean the same as in :py:func:`menu_cbck_resume`."""
    if index == 0:
        tmp = not conf.get("show_fps")
        conf.set("show_fps", tmp)
        return bool2yn(tmp)
    elif index == 1:
        tmp = conf.get("speed")
        if tmp>1:
            tmp-=1
        conf.set("speed", tmp)
        set_speed()
        return str(tmp)
def menu_cbck_controls_reset(index, conf):
    """Submenu **controls** reset callback. Arguments mean the same as in :py:func:`menu_cbck_resume`."""
    return conf.get("key_bindings").f_to_str(index)
def menu_cbck_controls_enter(index, conf):
    """Submenu **controls** enter/selection callback. Arguments mean the same as in :py:func:`menu_cbck_resume`."""
    global xmenu
    xmenu.current[index].val = "<press key combo to bind to>"
    bind_mode = True
    conf.get("key_bindings").wait_and_bind(index, timer_evt_id=2, dt=10, timeout=100, loop_callback=display, quit_callback=exit_2_os, neutral_key=pygame.K_ESCAPE)
    return conf.get("key_bindings").f_to_str(index)
def menu_cbck_save(index, conf):
    """Menu callback executed when menu option **save** is selected. Arguments mean the same as in :py:func:`menu_cbck_resume`."""
    conf.save()

def menu_cbck_reset(index, conf):
    """Menu callback executed when menu option **reset** is selected. Arguments mean the same as in :py:func:`menu_cbck_resume`."""
    global xmenu
    conf.reset()
    xmenu.update_values()
    conf.get("key_bindings").input_state.setup_joys()

def execute_triggered(triggered):
    """Execute function numbers from the list *triggered*."""
    global log, menu_mode
    if   key_num.KEY_MENU in triggered: menu_mode = not menu_mode
    elif key_num.KEY_MOV_UP in triggered: log.Translate([0,-1,0])
    elif key_num.KEY_MOV_DOWN in triggered: log.Translate([0,1,0])
    elif key_num.KEY_MOV_LEFT in triggered: log.Translate([-1,0,0])
    elif key_num.KEY_MOV_RIGHT in triggered: log.Translate([1,0,0])
    elif key_num.KEY_MOV_LEFT_W in triggered: log.Translate([0,0,-1])
    elif key_num.KEY_MOV_RIGHT_W in triggered: log.Translate([0,0,1])  
    elif key_num.KEY_FORCE_DROP in triggered:
        log.ForceDrop()
        log.AdvanceFall()
    for i in triggered:
        if i>=key_num.KEY_ROT_XY_CW and i<=key_num.KEY_ROT_ZW_CCW:
            log.Rotate(*func2rot[i-key_num.KEY_ROT_XY_CW])

def remove_list(l1, l2):
    """Remove from list *l1* elements which are in list *l2*."""
    return list(set(l1)-set(l2))

def unload():
    """Uninitialize all objects and restore OpenGL state saved in :py:func:`gl_init`."""
    delete_curr_b()
    delete_next_b()
    delete_outer_grid()
    delete_sblocks()
    delete_shy()
    glPopAttrib(GL_ALL_ATTRIB_BITS)
    glPopClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS)
    glMatrixMode(GL_PROJECTION)
    glPopMatrix()
    glMatrixMode(GL_MODELVIEW)
    glPopMatrix()
    print "UNLOADED"

def main(conf_, difficulty):
    """Main function. This should be called to start the game. Arguments: *conf_* is settings, instance of :py:class:`settings.Settings`; *difficulty* - int between 0 and 2 (inclusive) - difficulty level (0 easy, 1 normal, 2 hard)."""
    global win_width, win_height, width, height, depth, w_depth, fps, fps_limit, log, conf, panx, pany, turbo, running
    global mouserot, mousepan, mouse_last_x, mouse_last_y, rotx, roty, zoom, menu_mode, xmenu, colors
    conf = conf_
    reset_settings()
    # reset input state
    conf.get("key_bindings").input_state.reset()
    width, height, depth, w_depth = difficulty2dim[difficulty]
    gl_func_map()
    #pygame.init()
    pygame.joystick.init()
    conf.get("key_bindings").input_state.setup_joys()
    #set_mode(win_width, win_height)
    #pygame.display.set_caption("Blockout4D test")
    surf = pygame.display.get_surface()
    win_width, win_height = surf.get_size()
    pygame.event.post(pygame.event.Event(pygame.VIDEORESIZE, size=(win_width, win_height), w=win_width, h=win_height))
    log = logic.logic(width, height, depth, w_depth, num_colors=len(colors))
    log.SetGameOverCallback(gameover)
    log.SetBlockDroppedCallback(block_dropped)
    log.SetBlockRotatedCallback(block_rotated)
    gl_init()
    block_dropped()
    set_speed()
    pygame.time.set_timer(pygame.USEREVENT+1, 200)
    menu_layout = _menu("*", [
        _menu_entry("resume", None, menu_cbck_resume, None, None),
        _menu_entry("exit to os", None, menu_cbck_exit2os, None, None),
        _menu_entry("exit to menu", None, menu_cbck_exit2menu, None, None),
        _menu("settings", [
            _menu("misc", [
                _menu_entry("show fps", menu_cbck_misc_reset, menu_cbck_misc_enter, menu_cbck_misc_inc, menu_cbck_misc_dec),
                _menu_entry("speed", menu_cbck_misc_reset, None, menu_cbck_misc_inc, menu_cbck_misc_dec),
            ]),
            _menu("controls", [_menu_entry(i, menu_cbck_controls_reset, menu_cbck_controls_enter, None, None) for i in key_num.labels]),
            _menu_entry("save", None, menu_cbck_save, None, None),
            _menu_entry("reset", None, menu_cbck_reset, None, None)
        ])
    ])
    xmenu = Menu(menu_layout, conf)
    triggered_hold = []
    triggered_once = []
    triggered_timestamp = {}
    triggered_backoff = 300
    triggered_norepeat = [20]
    while running:
        for ev in pygame.event.get():
            if ev.type == pygame.QUIT: 
                exit_2_os()
            elif ev.type == pygame.VIDEORESIZE:
                win_width, win_height = ev.w, ev.h
                print "Resizing to %d x %d" % (win_width, win_height)
                set_mode(win_width, win_height)                
                reshape(win_width, win_height)
                set_fonts_sizes()
            else:
                if game_over_mode:
                    if ev.type == pygame.KEYDOWN:
                        if ev.key == pygame.K_ESCAPE:
                            running = False
                else:
                    if menu_mode: # we're in menu
                        if ev.type == pygame.KEYDOWN:
                            if ev.key == pygame.K_ESCAPE:
                                if xmenu.path:  
                                    xmenu.escape()
                                else:
                                    menu_mode = False 
                                    conf.get("key_bindings").input_state.reset()                  
                            elif ev.key == pygame.K_UP:
                                xmenu.up()
                            elif ev.key == pygame.K_DOWN:
                                xmenu.down()
                            elif ev.key == pygame.K_LEFT:
                                xmenu.left()
                            elif ev.key == pygame.K_RIGHT:
                                xmenu.right()
                            elif ev.key == pygame.K_RETURN:
                                xmenu.enter()
                    else: # we're not in menu
                        conf.get("key_bindings").input_state.process_event(ev)
                        triggered = conf.get("key_bindings").get_triggered()
                        tmp = turbo
                        turbo = (key_num.KEY_TURBO in triggered)
                        if turbo!=tmp: 
                            set_speed()
                            log.AdvanceFall()
                        triggered_new = remove_list(triggered, triggered_once)
                        for i in triggered_new: triggered_timestamp[i] = pygame.time.get_ticks()
                        execute_triggered(triggered_new)
                        if ev.type == pygame.USEREVENT+1:
                            t=pygame.time.get_ticks()
                            execute_triggered(filter(lambda x: (t-triggered_timestamp[x])>triggered_backoff, triggered_once))
                        triggered_once = remove_list(triggered, triggered_norepeat)
                        if ev.type == pygame.USEREVENT:
                            log.AdvanceFall()
                        """
                        elif ev.type == pygame.KEYDOWN:
                            print "Key press: ", ev
                            if ev.key == pygame.K_ESCAPE:                     
                                menu_mode = True
                        """
                        if ev.type == pygame.MOUSEBUTTONDOWN:
                            if ev.button == 1:
                                mouserot = True
                                mouse_last_x = ev.pos[0]
                                mouse_last_y = ev.pos[1]
                            elif ev.button == 3:
                                mousepan = True
                                mouse_last_x = ev.pos[0]
                                mouse_last_y = ev.pos[1]
                            elif ev.button == 4: # scroll up
                                zoom += 0.5
                            elif ev.button == 5: # scroll down
                                zoom -= 0.5 
                        elif ev.type == pygame.MOUSEBUTTONUP:
                            if ev.button == 1:
                                mouserot = False
                            elif ev.button == 3:
                                mousepan = False
                        elif ev.type == pygame.MOUSEMOTION:
                                x, y = ev.pos
                                if mouserot:
                                    roty += (x-mouse_last_x)*0.3
                                    rotx += (y-mouse_last_y)*0.3
                                    mouse_last_x = x
                                    mouse_last_y = y
                                elif mousepan:
                                    panx -= (x-mouse_last_x)*0.005*zoom
                                    pany += (y-mouse_last_y)*0.005*zoom
                                    mouse_last_x = x
                                    mouse_last_y = y
        display()
        clock.tick(fps_limit)
        fps = clock.get_fps()
    unload()
    return

# -*- coding: utf-8-*-
"""This module contains the main function of the game."""

import OpenGL, sys, random, numpy, time, pygame, key_num
import logic, input_dev
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GL.ARB.vertex_buffer_object import *
from ctypes import *
import FTGL, settings
from menu import Menu, _menu, _menu_entry, bool2yn

rotx                    =   0.0             #: rotation around x
roty                    =   0.0             #: rotation around y
panx                    =   0.0             #: panning along x
pany                    =   0.0             #: panning along x
shy_vbo                 =   None            #: vbo for y-shadow
sb_vbo                  =   None            #: vbo for static blocks
og_vbo                  =   None            #: vbo for outer grid
boxes_vbo               =   None            #: vbo for boxes
curr_b_vbo              =   None            #: vbo for current block
next_b_vbo              =   None            #: vbo for next block
boxes_nump              =   0               #: number of points in boxes vbo
og_nump                 =   0               #: number of points in outer grid vbo
shy_nump                =   0               #: number of points in y-shadow vbo
sb_nump                 =   0               #: number of points in static box vbo
curr_b_nump             =   0               #: number of points in current block
next_b_nump             =   0               #: number of points in next block
null                    =   c_void_p(0)     #: null pointer
width                   =   5               #: domain width (x)
depth                   =   5               #: domain depth (z)
height                  =   10              #: domain height (y)
w_depth                 =   2               #: domain size in 4th dimension (w) 
w_spacing               =   4               #: spacing between boxes
mouserot                =   False           #: is mouse rotation active
mousepan                =   False           #: is mouse panning active
mouse_last_x            =   0               #: last mouse click x position
mouse_last_y            =   0               #: last mouse clock y position
zoom                    =   -20             #: zoom
log                     =   None            #: logic instance (:py:class:`logic.logic`)
font                    =   None            #: font object for score
menu_font               =   None            #: font object for menu
win_width               =   640             #: window width
win_height              =   480             #: window height
fps_limit               =   60              #: fps limit
fps                     =   0               #: current fps
clock                   =   None            #: clock instance
xmenu                   =   None            #: in-game menu instance
conf                    =   None            #: settings instance
running                 =   True            #: is game running
turbo                   =   False           #: is turno mode active
menu_mode               =   False           #: are we in menu
bind_mode               =   False           #: are we binding 
game_over_mode          =   False           #: are we showing the game over screen
rot_next_b              =   0.0             #: rotation of next block
sh_stipple              =   (c_uint*32)()   #: polygon stipple pattern for shadows

#: data for one cube (24 points, values mean, in order: x, y, z, r, g, b, a, padding
cube_xyzrgba_data       = numpy.array([[ 0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  1.,  1.,  0.,  0.,  0.,  0.,  0.],
       [ 1.,  1.,  1.,  0.,  0.,  0.,  0.,  0.],
       [ 1.,  0.,  1.,  0.,  0.,  0.,  0.,  0.],
       [ 1.,  1.,  1.,  0.,  0.,  0.,  0.,  0.],
       [ 1.,  1.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 1.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 1.,  0.,  1.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  1.,  1.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 1.,  1.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 1.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.],
       [ 1.,  0.,  1.,  0.,  0.,  0.,  0.,  0.],
       [ 1.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 1.,  1.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 1.,  1.,  1.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  1.,  1.,  0.,  0.,  0.,  0.,  0.]], dtype=numpy.float32)                # data for single cube

colors                  =   [[1,0,0],[0,1,0],[1,1,0],[0,0,1],[1,0,1],[0,1,1]] #: colors for blocks: red green yellow blue magenta cyan
#: maping of GL standard function names to ARB extension ones
gl_func_map_            =   [
    ["glBindBuffer", "glBindBufferARB"],
    ["glBufferData", "glBufferDataARB"],
    ["glBufferSubData", "glBufferSubDataARB"],
    ["glGenBuffers", "glGenBuffersARB"],
    ["glDeleteBuffers", "glDeleteBuffersARB"]
                            ]           
#: mapping of rotation function number to parameters for :py:meth:`logic.logic.Rotate`                           
func2rot = [
                (logic.XY, logic.CW), (logic.XY, logic.CCW),
                (logic.XZ, logic.CW), (logic.XZ, logic.CCW),
                (logic.XW, logic.CW), (logic.XW, logic.CCW),
                (logic.YZ, logic.CW), (logic.YZ, logic.CCW),
                (logic.YW, logic.CW), (logic.YW, logic.CCW),
                (logic.ZW, logic.CW), (logic.ZW, logic.CCW)
]
#: mapping of difficulty level to domain size
difficulty2dim = (
                    (4,14,4,2),
                    (5,10,5,2),
                    (5,10,5,3)
)

def reset_settings():
    """Reset some global variables each this is loaded."""
    global rotx, roty, panx, pany, mouserot, mousepan, xmenu, running
    global shy_vbo, sb_vbo, og_vbo, boxes_vbo, cur_b_vbo, next_b_vbo
    global shy_nump, sb_nump, og_nump, boxes_nump, cur_b_nump, next_b_nump
    global mouse_last_x, mouse_last_y, zoom, log, menu_font, fps, clock, turbo
    global menu_mode, bind_mode, game_over_mode, rot_next_b
    rotx, roty, panx, pany = 0.0, 0.0, 0.0, 0.0
    shy_vbo, sb_vbo, og_vbo = None, None, None
    boxes_vbo, cur_b_vbo, next_b_vbo = None, None, None
    shy_nump, sb_nump, og_nump = 0, 0, 0
    boxes_nump, cur_b_nump, next_b_nump = 0, 0, 0
    mouserot, mousepan, mouse_last_x, mouse_last_y = False, False, 0, 0
    zoom, log, font, menu_font, fps = -20, None, None, None, 0
    clock = pygame.time.Clock()
    xmenu, running, turbo = None, True, False
    menu_mode, bind_mode, game_over_mode = False, False, False
    rot_next_b = 0.0

def gen_stipple():
    """Generate stipple pattern for shadows."""
    global sh_stipple
    for i in xrange(8):
        ii=i*4
        sh_stipple[ii] = sh_stipple[ii+1]   = 0x0C0C0C0C
        sh_stipple[ii+2] = sh_stipple[ii+3] = 0xC0C0C0C0

def gl_func_map():
    """Check if some OpenGL functions are present and if not assign their names to names of functions from ARB extension. This is probably needed on some hardware."""
    global gl_func_map_
    for i in gl_func_map_:
        if not bool(eval(i[0])):
            print "Warning: OpenGL function %s not present, using %s instead." % (i[0], i[1])
            exec("global "+i[0]+"\nglobal "+i[1]+"\n"+i[0]+"="+i[1])

def init_shy():
    """Initialize y-shadow object."""
    global null, width, depth, w_depth, shy_vbo
    shy_vbo = glGenBuffers(1)
    glBindBuffer(GL_ARRAY_BUFFER, shy_vbo)
    glBufferData(GL_ARRAY_BUFFER, width*depth*w_depth*128, null, GL_STATIC_DRAW) 
    
def update_shy():
    """Update y-shadow object."""
    global shy_vbo, shy_nump, colors, width, w_spacing, log
    coor = []
    eps = 1e-3
    for i in log.ShadowY():
        col = [j*0.75 for j in colors[i.col]]
        coor.append([i.x+i.w*(width+w_spacing),i.y+eps,i.z,col[0],col[1],col[2],1,0])
        coor.append([i.x+i.w*(width+w_spacing),i.y+eps,i.z+1,col[0],col[1],col[2],1,0])
        coor.append([i.x+i.w*(width+w_spacing)+1,i.y+eps,i.z+1,col[0],col[1],col[2],1,0])
        coor.append([i.x+i.w*(width+w_spacing)+1,i.y+eps,i.z,col[0],col[1],col[2],1,0])
    coords = numpy.array(coor, dtype=numpy.float32)
    glBindBuffer(GL_ARRAY_BUFFER, shy_vbo)
    shy_nump = len(coords)
    glBufferSubData(GL_ARRAY_BUFFER, 0, coords.nbytes, coords)

def draw_shy():
    """Draw y-shadow object."""
    global shy_vbo, shy_nump
    glBindBuffer(GL_ARRAY_BUFFER, shy_vbo)
    glVertexPointer(3, GL_FLOAT, 32, null)
    glColorPointer(4, GL_FLOAT, 32, c_void_p(12))
    glDrawArrays(GL_QUADS, 0, shy_nump)
    
def delete_shy():
    """Destroy y-shadow object."""
    glDeleteBuffers(1,  [shy_vbo])

def init_next_b():
    """Initialize next block object."""
    global null, next_b_vbo, log
    next_b_vbo = glGenBuffers(1)
    glBindBuffer(GL_ARRAY_BUFFER, next_b_vbo)
    maxp = max(map(len, log.blocks))
    glBufferData(GL_ARRAY_BUFFER, maxp*768, null, GL_STATIC_DRAW)

def update_next_b():
    """Update next block object."""
    global next_b_vbo, next_b_nump, cube_xyzrgba_data, colors, width, w_spacing, log
    coords = []
    for i in log.GetNextBlock():
        col = colors[i.col]
        coords.append(cube_xyzrgba_data + numpy.array([i.x+i.w*(width+w_spacing),i.y,i.z,col[0],col[1],col[2],1,0], dtype=numpy.float32))
    coords = numpy.array(coords, dtype=numpy.float32)
    delta = numpy.array([numpy.average(coords[...,0]), numpy.average(coords[...,1]), numpy.average(coords[...,2]), 0, 0, 0, 0, 0], dtype=numpy.float32) 
    coords -= delta
    glBindBuffer(GL_ARRAY_BUFFER, next_b_vbo)
    next_b_nump = len(coords)*24
    glBufferSubData(GL_ARRAY_BUFFER, 0, coords.nbytes, coords)
    
def draw_next_b():
    """Draw next block object."""
    global next_b_vbo, next_b_nump
    glBindBuffer(GL_ARRAY_BUFFER, next_b_vbo)
    glVertexPointer(3, GL_FLOAT, 32, null)
    glColorPointer(4, GL_FLOAT, 32, c_void_p(12))
    glDrawArrays(GL_QUADS, 0, next_b_nump)
    
def delete_next_b():
    """Destroy next block object."""
    glDeleteBuffers(1,  [next_b_vbo])

def init_curr_b():
    """Initialize current block object."""
    global null, curr_b_vbo, log
    curr_b_vbo = glGenBuffers(1)
    glBindBuffer(GL_ARRAY_BUFFER, curr_b_vbo)
    maxp = max(map(len, log.blocks))
    glBufferData(GL_ARRAY_BUFFER, maxp*768, null, GL_STATIC_DRAW)

def update_curr_b():
    """Update current block object."""
    global curr_b_vbo, curr_b_nump, cube_xyzrgba_data, colors, width, w_spacing, log
    coords = []
    for i in log.GetCurrentBlock():
        col = colors[i.col]
        coords.append(cube_xyzrgba_data + numpy.array([i.x+i.w*(width+w_spacing),i.y,i.z,col[0],col[1],col[2],1,0], dtype=numpy.float32))
    coords = numpy.array(coords, dtype=numpy.float32)
    glBindBuffer(GL_ARRAY_BUFFER, curr_b_vbo)
    curr_b_nump = len(coords)*24
    glBufferSubData(GL_ARRAY_BUFFER, 0, coords.nbytes, coords)
    
def draw_curr_b():
    """Draw current block object."""
    global curr_b_vbo, curr_b_nump
    glBindBuffer(GL_ARRAY_BUFFER, curr_b_vbo)
    glVertexPointer(3, GL_FLOAT, 32, null)
    glColorPointer(4, GL_FLOAT, 32, c_void_p(12))
    glDrawArrays(GL_QUADS, 0, curr_b_nump)
    
def delete_curr_b():
    """Destroy current block object."""
    glDeleteBuffers(1,  [curr_b_vbo])

def init_sblocks():
    """Initialize static blocks object."""
    global null, sb_vbo, width, height, depth, w_depth
    sb_vbo = glGenBuffers(1)
    glBindBuffer(GL_ARRAY_BUFFER, sb_vbo)
    glBufferData(GL_ARRAY_BUFFER, width*depth*height*w_depth*768, null, GL_STATIC_DRAW) # 6 faces x 4 verts x (xyzrgba_ = 8) floats x 4 bytes

def update_sblocks():
    """Update static blocks object."""
    global sb_vbo, sb_nump, cube_xyzrgba_data, colors, width, w_spacing, log
    coords = []
    for i in log.GetSpace():
    #for i in log.GetSpaceWithBlock():
        col = colors[i.col]
        coords.append(cube_xyzrgba_data + numpy.array([i.x+i.w*(width+w_spacing),i.y,i.z,col[0],col[1],col[2],1,0],dtype=numpy.float32))
    coords = numpy.array(coords, dtype=numpy.float32)
    glBindBuffer(GL_ARRAY_BUFFER, sb_vbo)
    sb_nump = len(coords)*24
    glBufferSubData(GL_ARRAY_BUFFER, 0, coords.nbytes, coords)
    
def draw_sblocks():
    """Draw static blocks object."""
    global sb_vbo, sb_nump
    glBindBuffer(GL_ARRAY_BUFFER, sb_vbo)
    glVertexPointer(3, GL_FLOAT, 32, null)
    glColorPointer(4, GL_FLOAT, 32, c_void_p(12))
    glDrawArrays(GL_QUADS, 0, sb_nump)

def delete_sblocks():
    """Destroy static blocks object."""
    glDeleteBuffers(1,  [sb_vbo])

def init_outer_grid():
    """Initialize outer grid object."""
    global null, og_vbo, width, height, depth, w_depth, w_spacing, og_nump
    # build coords
    coor = []
    for w in xrange(w_depth):
        x_ = w*(width+w_spacing)
        xx = x_+width
        for x in xrange(x_, xx):
            for y in xrange(0, height):
                coor.append([x,y+1,0])
                coor.append([x+1,y+1,0])
                coor.append([x+1,y,0])
                coor.append([x,y,0])
                coor.append([x,y,depth])
                coor.append([x+1,y,depth])
                coor.append([x+1,y+1,depth])
                coor.append([x,y+1,depth])
        for z in xrange(0, depth):
            for y in xrange(0, height):
                coor.append([x_,y,z])
                coor.append([x_,y,z+1])
                coor.append([x_,y+1,z+1])
                coor.append([x_,y+1,z])
                coor.append([xx,y+1,z])
                coor.append([xx,y+1,z+1])
                coor.append([xx,y,z+1])
                coor.append([xx,y,z])
        for z in xrange(0, depth):
            for x in xrange(x_, xx):
                coor.append([x,0,z])
                coor.append([x+1,0,z])
                coor.append([x+1,0,z+1])
                coor.append([x,0,z+1])
                coor.append([x,height,z+1])
                coor.append([x+1,height,z+1])
                coor.append([x+1,height,z])
                coor.append([x,height,z])

    coords = numpy.array(coor, dtype=numpy.float32)
    og_nump = len(coords)
    og_vbo = glGenBuffers(1)
    glBindBuffer(GL_ARRAY_BUFFER, og_vbo)
    glBufferData(GL_ARRAY_BUFFER, og_nump*12, coords, GL_STATIC_DRAW)

def draw_outer_grid():
    """Draw outer grid object."""
    global og_vbo, og_nump
    glBindBuffer(GL_ARRAY_BUFFER, og_vbo)
    glVertexPointer(3, GL_FLOAT, 12, null)
    glDisableClientState(GL_COLOR_ARRAY)
    glDrawArrays(GL_QUADS, 0, og_nump)
    glEnableClientState(GL_COLOR_ARRAY)

def delete_outer_grid():
    """Destroy outer grid object."""
    glDeleteBuffers(1,  [og_vbo])

def set_fonts_sizes():
    """Set sizes of font objects according to current window size."""
    global font, menu_font, win_width, win_height
    font.FaceSize(int(win_height*0.04))
    menu_font.FaceSize(int(win_height*0.04))

def init_font():
    """Initialize fiot objects."""
    global font, menu_font
    font = FTGL.PixmapFont(conf.get("font_filename"))
    menu_font = FTGL.PixmapFont(conf.get("menu_font_filename"))
    set_fonts_sizes()

def set_speed():
    """Set time between block fall steps according to the **speed** setting and **turbo mode**."""
    global conf, turbo
    dt = settings.speed2dt(conf.get("speed"))
    if turbo:
        dt/=4
    pygame.time.set_timer(pygame.USEREVENT, dt)
    
def draw_font():
    """Draw text with score and other info."""
    global font, win_width, win_height, log, null, fps, conf
    line_w = font.line_height*0.5+5
    tmp = font.BBox("Layers cleared: 1000")
    max_w = tmp[3]-tmp[0]
    glColor4f(0.6, 0.6, 1.0, 1.0)
    glWindowPos2f(win_width-max_w, win_height-line_w-10)
    if conf.get("show_fps"):
        font.Render("FPS: %.0f" % fps)
        glBitmap(0, 0, 0, 0, 0, -line_w, null)
    font.Render("Score: %d" % log.score)
    glBitmap(0, 0, 0, 0, 0, -line_w, null)
    font.Render("Layers cleared: %d" % log.layers_cleared)
    glBitmap(0, 0, 0, 0, 0, -line_w, null)
    font.Render("Next: ")

def gl_init():
    """Save OpenGL state (to later restore it) and initialize OpenGL state for this game."""
    global width, height, depth, w_depth
    glPushAttrib(GL_ALL_ATTRIB_BITS)
    glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS)
    glMatrixMode(GL_PROJECTION)
    glPushMatrix()
    glMatrixMode(GL_MODELVIEW)
    glPushMatrix()
    glClearColor(0.0, 0.0, 0.0, 0.0)
    glEnable(GL_CULL_FACE)
    glEnable(GL_DEPTH_TEST)
    glDepthFunc(GL_LESS)
    glEnable(GL_BLEND)
    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
    glClearDepth(1.0)
    glEnableClientState(GL_VERTEX_ARRAY)
    glEnableClientState(GL_COLOR_ARRAY)
    init_outer_grid()
    init_sblocks()
    init_next_b()
    init_curr_b()
    init_shy()
    init_font()
    gen_stipple()
    glPolygonStipple(sh_stipple)

def draw_game_over():
    """Draw game over screen."""
    global menu_font, win_width, win_height, log
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
    glPushMatrix()
    glLoadIdentity()
    glMatrixMode(GL_PROJECTION)
    glPushMatrix()
    glLoadIdentity()
    glColor4f(0.0, 0.0, 0.0, 0.7)
    glBegin(GL_QUADS)
    glVertex2f(-1.0, -1.0)
    glVertex2f(1.0, -1.0)
    glVertex2f(1.0, 1.0)
    glVertex2f(-1.0, 1.0)
    glEnd()
    glPopMatrix()
    glMatrixMode(GL_MODELVIEW)
    glPopMatrix()
    glColor4f(1.0, 1.0, 1.0, 1.0)
    line_w = menu_font.line_height*0.5+5
    x, y = win_width*0.05, win_height*0.5+line_w*2.5
    glWindowPos2f(x, y)
    menu_font.Render("GAME OVER")
    glWindowPos2f(x, y-2*line_w)
    menu_font.Render("Score: %d" % log.score)
    glWindowPos2f(x, y-3*line_w)
    menu_font.Render("Layers cleared: %d" % log.layers_cleared)
    glWindowPos2f(x, y-4*line_w)
    menu_font.Render("Press Esc to continue")

def draw_menu():
    """Draw in-game menu."""
    global menu_font, win_width, win_height, xmenu
    highlighted = xmenu.selected
    text = xmenu.get_options_list()
    text_w = [(menu_font.BBox(i[0]), menu_font.BBox(i[1])) for i in text]
    text_w = [(i[0][3]-i[0][0], i[1][3]-i[1][0]) for i in text_w]
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
    glPushMatrix()
    glLoadIdentity()
    glMatrixMode(GL_PROJECTION)
    glPushMatrix()
    glLoadIdentity()
    glColor4f(0.0, 0.0, 0.0, 0.7)
    glBegin(GL_QUADS)
    glVertex2f(-1.0, -1.0)
    glVertex2f(1.0, -1.0)
    glVertex2f(1.0, 1.0)
    glVertex2f(-1.0, 1.0)
    glEnd()
    glPopMatrix()
    glMatrixMode(GL_MODELVIEW)
    glPopMatrix()
    line_w = menu_font.line_height*0.5+5
    x = win_width*0.05
    y = win_height*0.5+line_w*(len(text))*0.5
    for i in xrange(len(text)):
        delta = win_width*0.7-text_w[i][1]
        if i == highlighted:
            glColor4f(1.0, 1.0, 1.0, 1.0)
        else:
            glColor4f(1.0, 1.0, 0.5, 1.0)
        glWindowPos2f(x, y-line_w*i)
        menu_font.Render(text[i][0])
        glWindowPos2f(delta, y-line_w*i)
        menu_font.Render(text[i][1])

def draw_curr_b_():
    """Draw current block at the right position."""
    global log, width, w_spacing
    glPushMatrix()
    off = log.cur_block_offset
    glTranslatef(off[0]+off[3]*(width+w_spacing), off[1], off[2])
    draw_curr_b()
    glPopMatrix()

def draw_next_b_():
    """Draw next block at the right position."""
    global win_width, win_height, zoom, rot_next_b 
    aspect = win_width*1.0/win_height
    #scale = win_height*0.0005
    scale = 0.5
    glPushMatrix()
    glLoadIdentity()
    glTranslatef(10*aspect, 6, zoom)
    glRotatef(rot_next_b, 0.0, 1.0, 0.0)
    glScalef(scale, scale, scale)
    draw_next_b()
    glPopMatrix()
    
def display():
    """Main display call, draws everything needed."""
    global rotz, roty, panx, pany, zoom, log, menu_mode, rot_next_b
    update_shy()
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    glLoadIdentity()
    glTranslatef(0, 0, zoom)
    glRotatef(rotx, 1.0, 0.0, 0.0)
    glRotatef(roty, 0.0, 1.0, 0.0)
    glTranslatef(panx, pany, 0)
    glTranslatef(-0.5*(w_depth*width+(w_depth-1)*w_spacing), height*(-0.5), depth*(-0.5))
    glCullFace(GL_BACK)
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
    draw_sblocks()
    draw_curr_b_()
    if not (menu_mode or game_over_mode):
        draw_next_b_()
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
    glLineWidth(3.0)
    glColor3f(1,1,1)
    glDisableClientState(GL_COLOR_ARRAY)
    draw_sblocks()
    draw_curr_b_()
    if not (menu_mode or game_over_mode):
        glLineWidth(1.5)
        draw_next_b_()
    glEnableClientState(GL_COLOR_ARRAY)
    glLineWidth(1.0)
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
    glEnable(GL_POLYGON_STIPPLE)
    draw_shy()
    glDisable(GL_POLYGON_STIPPLE)
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
    glCullFace(GL_FRONT)
    glColor4f(1.0, 1.0, 1.0, 1.0)
    draw_outer_grid()
    glCullFace(GL_BACK)
    glColor4f(1.0, 1.0, 1.0, 0.2)
    draw_outer_grid()    
    if menu_mode: 
        draw_menu()
    else:
        if game_over_mode:
            draw_game_over()
        else:
            draw_font()
            rot_next_b += 1
    pygame.display.flip()

def reshape (w, h):
    """React to window geometry change, *w* is new width, *h* is new height."""
    aspect = w*1.0 / h
    glViewport(0, 0, w, h)
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluPerspective(60, aspect, 1.0, 1000.0)
    #scale = 10.0
    #glOrtho(-aspect*scale, aspect*scale, -scale, scale, -scale, scale)
    glMatrixMode(GL_MODELVIEW)

def gameover():
    """Callback executed by :py:data:`log` on game over."""
    global game_over_mode
    #pygame.event.post(pygame.event.Event(pygame.QUIT))
    print "game over"
    game_over_mode = True

def block_dropped():
    """Callback executed by :py:data:`log` when block was dropped, updates objects: current block, next block and static blocks."""
    update_curr_b()
    update_next_b()
    update_sblocks()

def block_rotated():
    """Callback executed by :py:data:`log` when block is rotated in 4D, updates current block object."""
    update_curr_b()

def set_mode(w, h):
    """Set display mode with window width *w* and height *h*"""
    pygame.display.set_mode((w, h), pygame.DOUBLEBUF | pygame.OPENGL | pygame.RESIZABLE)

def exit_2_os():
    """Exit to OS."""
    exit(0)

# menu callbacks
def menu_cbck_resume(index, conf):
    """Menu callback executed when menu option **resume** is selected; *index* is index of the current position in current submenu, *conf* is the settings object (instance of :py:class:`settings.Settings`); *conf_* from :py:func:`main` will be given here."""
    global menu_mode
    menu_mode = False
def menu_cbck_exit2os(index, conf):
    """Menu callback executed when menu option **exit to os** is selected. Arguments mean the same as in :py:func:`menu_cbck_resume`."""
    pygame.event.post(pygame.event.Event(pygame.QUIT))
def menu_cbck_exit2menu(index, conf):
    """Menu callback executed when menu option **exit to menu** is selected. Arguments mean the same as in :py:func:`menu_cbck_resume`."""
    global running
    running = False
def menu_cbck_misc_reset(index, conf):
    """Submenu **misc** reset callback. Arguments mean the same as in :py:func:`menu_cbck_resume`."""
    if index == 0:
        return bool2yn(conf.get("show_fps"))
    elif index == 1:
        return str(conf.get("speed"))
def menu_cbck_misc_enter(index, conf):
    """Submenu **misc** enter/selection callback. Arguments mean the same as in :py:func:`menu_cbck_resume`."""
    if index == 0:
        tmp = not conf.get("show_fps")
        conf.set("show_fps", tmp)
        return bool2yn(tmp)
def menu_cbck_misc_inc(index, conf):
    """Submenu **misc** incrementation callback. Arguments mean the same as in :py:func:`menu_cbck_resume`."""
    if index == 0:
        tmp = not conf.get("show_fps")
        conf.set("show_fps", tmp)
        return bool2yn(tmp)
    elif index == 1:
        tmp = conf.get("speed")
        if tmp<9:
            tmp+=1
        conf.set("speed", tmp)
        set_speed()
        return str(tmp)
def menu_cbck_misc_dec(index, conf):
    """Submenu **misc** decrementation callback. Arguments mean the same as in :py:func:`menu_cbck_resume`."""
    if index == 0:
        tmp = not conf.get("show_fps")
        conf.set("show_fps", tmp)
        return bool2yn(tmp)
    elif index == 1:
        tmp = conf.get("speed")
        if tmp>1:
            tmp-=1
        conf.set("speed", tmp)
        set_speed()
        return str(tmp)
def menu_cbck_controls_reset(index, conf):
    """Submenu **controls** reset callback. Arguments mean the same as in :py:func:`menu_cbck_resume`."""
    return conf.get("key_bindings").f_to_str(index)
def menu_cbck_controls_enter(index, conf):
    """Submenu **controls** enter/selection callback. Arguments mean the same as in :py:func:`menu_cbck_resume`."""
    global xmenu
    xmenu.current[index].val = "<press key combo to bind to>"
    bind_mode = True
    conf.get("key_bindings").wait_and_bind(index, timer_evt_id=2, dt=10, timeout=100, loop_callback=display, quit_callback=exit_2_os, neutral_key=pygame.K_ESCAPE)
    return conf.get("key_bindings").f_to_str(index)
def menu_cbck_save(index, conf):
    """Menu callback executed when menu option **save** is selected. Arguments mean the same as in :py:func:`menu_cbck_resume`."""
    conf.save()

def menu_cbck_reset(index, conf):
    """Menu callback executed when menu option **reset** is selected. Arguments mean the same as in :py:func:`menu_cbck_resume`."""
    global xmenu
    conf.reset()
    xmenu.update_values()
    conf.get("key_bindings").input_state.setup_joys()

def execute_triggered(triggered):
    """Execute function numbers from the list *triggered*."""
    global log, menu_mode
    if   key_num.KEY_MENU in triggered: menu_mode = not menu_mode
    elif key_num.KEY_MOV_UP in triggered: log.Translate([0,-1,0])
    elif key_num.KEY_MOV_DOWN in triggered: log.Translate([0,1,0])
    elif key_num.KEY_MOV_LEFT in triggered: log.Translate([-1,0,0])
    elif key_num.KEY_MOV_RIGHT in triggered: log.Translate([1,0,0])
    elif key_num.KEY_MOV_LEFT_W in triggered: log.Translate([0,0,-1])
    elif key_num.KEY_MOV_RIGHT_W in triggered: log.Translate([0,0,1])  
    elif key_num.KEY_FORCE_DROP in triggered:
        log.ForceDrop()
        log.AdvanceFall()
    for i in triggered:
        if i>=key_num.KEY_ROT_XY_CW and i<=key_num.KEY_ROT_ZW_CCW:
            log.Rotate(*func2rot[i-key_num.KEY_ROT_XY_CW])

def remove_list(l1, l2):
    """Remove from list *l1* elements which are in list *l2*."""
    return list(set(l1)-set(l2))

def unload():
    """Uninitialize all objects and restore OpenGL state saved in :py:func:`gl_init`."""
    delete_curr_b()
    delete_next_b()
    delete_outer_grid()
    delete_sblocks()
    delete_shy()
    glPopAttrib(GL_ALL_ATTRIB_BITS)
    glPopClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS)
    glMatrixMode(GL_PROJECTION)
    glPopMatrix()
    glMatrixMode(GL_MODELVIEW)
    glPopMatrix()
    print "UNLOADED"

def main(conf_, difficulty):
    """Main function. This should be called to start the game. Arguments: *conf_* is settings, instance of :py:class:`settings.Settings`; *difficulty* - int between 0 and 2 (inclusive) - difficulty level (0 easy, 1 normal, 2 hard)."""
    global win_width, win_height, width, height, depth, w_depth, fps, fps_limit, log, conf, panx, pany, turbo, running
    global mouserot, mousepan, mouse_last_x, mouse_last_y, rotx, roty, zoom, menu_mode, xmenu, colors
    conf = conf_
    reset_settings()
    # reset input state
    conf.get("key_bindings").input_state.reset()
    width, height, depth, w_depth = difficulty2dim[difficulty]
    gl_func_map()
    #pygame.init()
    pygame.joystick.init()
    conf.get("key_bindings").input_state.setup_joys()
    #set_mode(win_width, win_height)
    #pygame.display.set_caption("Blockout4D test")
    surf = pygame.display.get_surface()
    win_width, win_height = surf.get_size()
    pygame.event.post(pygame.event.Event(pygame.VIDEORESIZE, size=(win_width, win_height), w=win_width, h=win_height))
    log = logic.logic(width, height, depth, w_depth, num_colors=len(colors))
    log.SetGameOverCallback(gameover)
    log.SetBlockDroppedCallback(block_dropped)
    log.SetBlockRotatedCallback(block_rotated)
    gl_init()
    block_dropped()
    set_speed()
    pygame.time.set_timer(pygame.USEREVENT+1, 200)
    menu_layout = _menu("*", [
        _menu_entry("resume", None, menu_cbck_resume, None, None),
        _menu_entry("exit to os", None, menu_cbck_exit2os, None, None),
        _menu_entry("exit to menu", None, menu_cbck_exit2menu, None, None),
        _menu("settings", [
            _menu("misc", [
                _menu_entry("show fps", menu_cbck_misc_reset, menu_cbck_misc_enter, menu_cbck_misc_inc, menu_cbck_misc_dec),
                _menu_entry("speed", menu_cbck_misc_reset, None, menu_cbck_misc_inc, menu_cbck_misc_dec),
            ]),
            _menu("controls", [_menu_entry(i, menu_cbck_controls_reset, menu_cbck_controls_enter, None, None) for i in key_num.labels]),
            _menu_entry("save", None, menu_cbck_save, None, None),
            _menu_entry("reset", None, menu_cbck_reset, None, None)
        ])
    ])
    xmenu = Menu(menu_layout, conf)
    triggered_hold = []
    triggered_once = []
    triggered_timestamp = {}
    triggered_backoff = 300
    triggered_norepeat = [20]
    while running:
        for ev in pygame.event.get():
            if ev.type == pygame.QUIT: 
                exit_2_os()
            elif ev.type == pygame.VIDEORESIZE:
                win_width, win_height = ev.w, ev.h
                print "Resizing to %d x %d" % (win_width, win_height)
                set_mode(win_width, win_height)                
                reshape(win_width, win_height)
                set_fonts_sizes()
            else:
                if game_over_mode:
                    if ev.type == pygame.KEYDOWN:
                        if ev.key == pygame.K_ESCAPE:
                            running = False
                else:
                    if menu_mode: # we're in menu
                        if ev.type == pygame.KEYDOWN:
                            if ev.key == pygame.K_ESCAPE:
                                if xmenu.path:  
                                    xmenu.escape()
                                else:
                                    menu_mode = False 
                                    conf.get("key_bindings").input_state.reset()                  
                            elif ev.key == pygame.K_UP:
                                xmenu.up()
                            elif ev.key == pygame.K_DOWN:
                                xmenu.down()
                            elif ev.key == pygame.K_LEFT:
                                xmenu.left()
                            elif ev.key == pygame.K_RIGHT:
                                xmenu.right()
                            elif ev.key == pygame.K_RETURN:
                                xmenu.enter()
                    else: # we're not in menu
                        conf.get("key_bindings").input_state.process_event(ev)
                        triggered = conf.get("key_bindings").get_triggered()
                        tmp = turbo
                        turbo = (key_num.KEY_TURBO in triggered)
                        if turbo!=tmp: 
                            set_speed()
                            log.AdvanceFall()
                        triggered_new = remove_list(triggered, triggered_once)
                        for i in triggered_new: triggered_timestamp[i] = pygame.time.get_ticks()
                        execute_triggered(triggered_new)
                        if ev.type == pygame.USEREVENT+1:
                            t=pygame.time.get_ticks()
                            execute_triggered(filter(lambda x: (t-triggered_timestamp[x])>triggered_backoff, triggered_once))
                        triggered_once = remove_list(triggered, triggered_norepeat)
                        if ev.type == pygame.USEREVENT:
                            log.AdvanceFall()
                        """
                        elif ev.type == pygame.KEYDOWN:
                            print "Key press: ", ev
                            if ev.key == pygame.K_ESCAPE:                     
                                menu_mode = True
                        """
                        if ev.type == pygame.MOUSEBUTTONDOWN:
                            if ev.button == 1:
                                mouserot = True
                                mouse_last_x = ev.pos[0]
                                mouse_last_y = ev.pos[1]
                            elif ev.button == 3:
                                mousepan = True
                                mouse_last_x = ev.pos[0]
                                mouse_last_y = ev.pos[1]
                            elif ev.button == 4: # scroll up
                                zoom += 0.5
                            elif ev.button == 5: # scroll down
                                zoom -= 0.5 
                        elif ev.type == pygame.MOUSEBUTTONUP:
                            if ev.button == 1:
                                mouserot = False
                            elif ev.button == 3:
                                mousepan = False
                        elif ev.type == pygame.MOUSEMOTION:
                                x, y = ev.pos
                                if mouserot:
                                    roty += (x-mouse_last_x)*0.3
                                    rotx += (y-mouse_last_y)*0.3
                                    mouse_last_x = x
                                    mouse_last_y = y
                                elif mousepan:
                                    panx -= (x-mouse_last_x)*0.005*zoom
                                    pany += (y-mouse_last_y)*0.005*zoom
                                    mouse_last_x = x
                                    mouse_last_y = y
        display()
        clock.tick(fps_limit)
        fps = clock.get_fps()
    unload()
    return
# -*- coding: utf-8-*- This module contains the main function of the game. #: rotation around x #: rotation around y #: panning along x #: panning along x #: vbo for y-shadow #: vbo for static blocks #: vbo for outer grid #: vbo for boxes #: vbo for current block #: vbo for next block #: number of points in boxes vbo #: number of points in outer grid vbo #: number of points in y-shadow vbo #: number of points in static box vbo #: number of points in current block #: number of points in next block #: null pointer #: domain width (x) #: domain depth (z) #: domain height (y) #: domain size in 4th dimension (w) #: spacing between boxes #: is mouse rotation active #: is mouse panning active #: last mouse click x position #: last mouse clock y position #: zoom #: logic instance (:py:class:`logic.logic`) #: font object for score #: font object for menu #: window width #: window height #: fps limit #: current fps #: clock instance #: in-game menu instance #: settings instance #: is game running #: is turno mode active #: are we in menu #: are we binding #: are we showing the game over screen #: rotation of next block #: polygon stipple pattern for shadows #: data for one cube (24 points, values mean, in order: x, y, z, r, g, b, a, padding # data for single cube #: colors for blocks: red green yellow blue magenta cyan #: maping of GL standard function names to ARB extension ones #: mapping of rotation function number to parameters for :py:meth:`logic.logic.Rotate` #: mapping of difficulty level to domain size Reset some global variables each this is loaded. Generate stipple pattern for shadows. Check if some OpenGL functions are present and if not assign their names to names of functions from ARB extension. This is probably needed on some hardware. Initialize y-shadow object. Update y-shadow object. Draw y-shadow object. Destroy y-shadow object. Initialize next block object. Update next block object. Draw next block object. Destroy next block object. Initialize current block object. Update current block object. Draw current block object. Destroy current block object. Initialize static blocks object. # 6 faces x 4 verts x (xyzrgba_ = 8) floats x 4 bytes Update static blocks object. #for i in log.GetSpaceWithBlock(): Draw static blocks object. Destroy static blocks object. Initialize outer grid object. # build coords Draw outer grid object. Destroy outer grid object. Set sizes of font objects according to current window size. Initialize fiot objects. Set time between block fall steps according to the **speed** setting and **turbo mode**. Draw text with score and other info. Save OpenGL state (to later restore it) and initialize OpenGL state for this game. Draw game over screen. Draw in-game menu. Draw current block at the right position. Draw next block at the right position. #scale = win_height*0.0005 Main display call, draws everything needed. React to window geometry change, *w* is new width, *h* is new height. #scale = 10.0 #glOrtho(-aspect*scale, aspect*scale, -scale, scale, -scale, scale) Callback executed by :py:data:`log` on game over. #pygame.event.post(pygame.event.Event(pygame.QUIT)) Callback executed by :py:data:`log` when block was dropped, updates objects: current block, next block and static blocks. Callback executed by :py:data:`log` when block is rotated in 4D, updates current block object. Set display mode with window width *w* and height *h* Exit to OS. # menu callbacks Menu callback executed when menu option **resume** is selected; *index* is index of the current position in current submenu, *conf* is the settings object (instance of :py:class:`settings.Settings`); *conf_* from :py:func:`main` will be given here. Menu callback executed when menu option **exit to os** is selected. Arguments mean the same as in :py:func:`menu_cbck_resume`. Menu callback executed when menu option **exit to menu** is selected. Arguments mean the same as in :py:func:`menu_cbck_resume`. Submenu **misc** reset callback. Arguments mean the same as in :py:func:`menu_cbck_resume`. Submenu **misc** enter/selection callback. Arguments mean the same as in :py:func:`menu_cbck_resume`. Submenu **misc** incrementation callback. Arguments mean the same as in :py:func:`menu_cbck_resume`. Submenu **misc** decrementation callback. Arguments mean the same as in :py:func:`menu_cbck_resume`. Submenu **controls** reset callback. Arguments mean the same as in :py:func:`menu_cbck_resume`. Submenu **controls** enter/selection callback. Arguments mean the same as in :py:func:`menu_cbck_resume`. Menu callback executed when menu option **save** is selected. Arguments mean the same as in :py:func:`menu_cbck_resume`. Menu callback executed when menu option **reset** is selected. Arguments mean the same as in :py:func:`menu_cbck_resume`. Execute function numbers from the list *triggered*. Remove from list *l1* elements which are in list *l2*. Uninitialize all objects and restore OpenGL state saved in :py:func:`gl_init`. Main function. This should be called to start the game. Arguments: *conf_* is settings, instance of :py:class:`settings.Settings`; *difficulty* - int between 0 and 2 (inclusive) - difficulty level (0 easy, 1 normal, 2 hard). # reset input state #pygame.init() #set_mode(win_width, win_height) #pygame.display.set_caption("Blockout4D test") # we're in menu # we're not in menu elif ev.type == pygame.KEYDOWN:                             print "Key press: ", ev                             if ev.key == pygame.K_ESCAPE:                                                      menu_mode = True # scroll up # scroll down