#  Converted by Jared Stafford from LABDEMO2.BAS by Ken Silverman
#  Uses wxPython for graphics

# Lines that start with #> are QBasic code

import time
import math
from random import random as rnd
import array

# Use Psyco JIT if available.
try:
    import psyco
except:
    psyco = None
    
def sgn(v):
    return cmp(v,0)

#> DEFINT A-Z: CONST XDIM = 320&, YDIM = 200&, BPL = 80, PI = 3.141592653589793#
XDIM, YDIM = 320, 240
# If Psyco is available, turn up the resolution a bit
if psyco:    
    XDIM, YDIM = 512, 384
BPL = XDIM * 3
PI = math.pi


#> SCREEN 13: RANDOMIZE TIMER
#> hval = 128 'Square aspect is actually XDIM*.5, but I think 128 looks nicer
hval = XDIM/2 # ##Since we have square pixels, this should work best.##

#> 
#>    'Code originally from LABDEMO.BAS
#>    'Modified on 10/14/1999 by Ken Silverman for rendering cleanliness
#> 
#> OUT &H3C8, 0  'Make interesting palette

screen = array.array('B','\x00'*(XDIM*YDIM*3))
screenbuf = buffer(screen)
pal = array.array('B')

def addpal(r,g,b):
    pal.append(r)
    pal.append(g)
    pal.append(b)

def getpal(i):
    return pal[i*3:i*3+3]

#> FOR x = 0 TO 63: OUT &H3C9, x: OUT &H3C9, 0: OUT &H3C9, 0: NEXT x
for x in xrange(0,64):
    addpal(x<<2,0,0)
    
#> FOR x = 0 TO 63: OUT &H3C9, 0: OUT &H3C9, x: OUT &H3C9, 0: NEXT x
for x in xrange(0,64):
    addpal(0,x<<2,0)

#> FOR x = 0 TO 63: OUT &H3C9, 0: OUT &H3C9, 0: OUT &H3C9, x: NEXT x
for x in xrange(0,64):
    addpal(0,0,x<<2)

#> FOR x = 0 TO 63: OUT &H3C9, x: OUT &H3C9, x: OUT &H3C9, x: NEXT x
for x in xrange(0,64):
    addpal(x<<2,x<<2,x<<2)

ceil = getpal(176)
floor = getpal(80)

#> 
#> OUT &H3C4, &H4: OUT &H3C5, &H6  'Set Mode X (unchained mode)
#> OUT &H3D4, &H14: OUT &H3D5, &H0
#> OUT &H3D4, &H17: OUT &H3D5, &HE3
#> 
#> DIM pic(64 * 64 * 4), board(64, 64)
pic = [0]*(64 * 64 * 4)
board = [[0]*64 for x in xrange(64)]


#> FOR y = 0 TO 63     'Generate interesting bitmaps (without loading a file)
#>    FOR x = 0 TO 63
#>       pic(x * 64 + y + 0) = (x + y) \ 2 + 0
#>       pic(x * 64 + y + 4096) = ((x XOR y) * .875 + RND * 64 * .125) * .5 + 64
#>       pic(x * 64 + y + 8192) = ((x OR y) * .875 + RND * 64 * .125) * .5 + 128
#>       pic(x * 64 + y + 12288) = (x * x + y * y) \ 128 + 192
#>    NEXT x
#> NEXT y
for y in xrange(0,64):
    for x in xrange(0,64):
        pic[x * 64 + y + 0] = getpal((x + y) / 2 + 0)
        pic[x * 64 + y + 4096] = getpal(int(((x ^ y) * .875 + rnd() * 64 * .125) * .5 + 64))
        pic[x * 64 + y + 8192] = getpal(int(((x | y) * .875 + rnd() * 64 * .125) * .5 + 128))
        pic[x * 64 + y + 12288] = getpal((x * x + y * y) / 128 + 192)
        
#> FOR z = 0 TO 63     'Generate random board (0 is invisible, 1-4 are walls)
#>    board(z, 0) = INT(4 * RND) + 1
#>    board(0, z) = INT(4 * RND) + 1
#>    board(z, 63) = INT(4 * RND) + 1
#>    board(63, z) = INT(4 * RND) + 1
#> NEXT z
for z in xrange(0,64):
    board[z][0] = int(4 * rnd()) + 1
    board[0][z] = int(4 * rnd()) + 1
    board[z][63] = int(4 * rnd()) + 1
    board[63][z] = int(4 * rnd()) + 1

#> FOR z = 0 TO 1023
#>    board(INT(62 * RND) + 1, INT(62 * RND) + 1) = INT(4 * RND) + 1
#> NEXT z
for z in xrange(0,1024):
    board[int(62 * rnd()) + 1][ int(62 * rnd()) + 1] = int(4 * rnd()) + 1

#> 
#>    'The length of the side of a cube is 1
#>    'Since the 2D board map is 64*64, posx# and posy# range from 0-64
#>    'posz# ranges from -.5 to .5 (0 is the middle)
#> DO  'Make sure starting position isn't inside a cube
#>    x = INT(62 * RND) + 2
#>    y = INT(62 * RND) + 2
#> LOOP WHILE board(x, y) <> 0
x = 0
y = 0
while board[x][y] != 0:
    x = int(62 * rnd()) + 2
    y = int(62 * rnd()) + 2
    

#> posx# = x + .5#: posy# = y + .5#: posz# = 0#: ang = PI * 2 * RND
posx = x + 0.5; posy = y + 0.5; posz = 0; ang = PI * 2 * rnd()

#> 
#> pagoffs = 0: OUT &H3C4, &H2: OUT &H3D4, &HC
svel, fvel, zvel, angvel = 0, 0, 0, 0

#> DO
def drawscreen():
    
    #> cosang# = COS(ang#)
    #> sinang# = SIN(ang#)
    cosang = math.cos(ang)
    sinang = math.sin(ang)
    
    #> vxinc# = sinang# * -2# / XDIM: vx# = cosang# + sinang# + vxinc# * .5#
    #> vyinc# = cosang# * 2# / XDIM: vy# = sinang# - cosang# + vyinc# * .5#
    vxinc = sinang * -2.0 / XDIM; vx = cosang + sinang + vxinc * 0.5
    vyinc = cosang * 2.0 / XDIM; vy = sinang - cosang + vyinc * 0.5
    
    #> FOR sx = 0 TO XDIM - 1
    for sx in xrange(0,XDIM):

        #> 
        #>  'Raytrace in 2D to see what block is hit, and where it gets hit
        #> xscan = INT(posx#): xdir = SGN(vx#): incx# = ABS(vx#)
        #> yscan = INT(posy#): ydir = SGN(vy#): incy# = ABS(vy#)
        xscan = int(posx); xdir = sgn(vx); incx = abs(vx)
        yscan = int(posy); ydir = sgn(vy); incy = abs(vy)
        
        #> xtemp# = posx# - xscan: IF xdir > 0 THEN xtemp# = 1 - xtemp#
        #> ytemp# = posy# - yscan: IF ydir > 0 THEN ytemp# = 1 - ytemp#
        xtemp = posx - xscan
        if xdir > 0:
            xtemp = 1 - xtemp
        ytemp = posy - yscan
        if ydir > 0:
            ytemp = 1 - ytemp
        
        #> d# = xtemp# * incy# - ytemp# * incx#
        d = xtemp * incy - ytemp * incx

        #> DO
        while True:

            #> IF d# < 0 THEN
            if d < 0:
                
                #> xscan = xscan + xdir
                xscan = xscan + xdir
                
                #> IF board(xscan, yscan) THEN
                if board[xscan][yscan]:
                    
                    #> hx# = xscan: IF xdir < 0 THEN hx# = hx# + 1
                    #> hy# = posy# + vy# * (hx# - posx#) / vx#
                    hx = xscan
                    if xdir < 0:
                        hx += 1
                    hy = posy + vy * (hx - posx) / vx
                    
                    #> bx = INT((hy# - INT(hy#)) * 64)
                    bx = int((hy - int(hy)) * 64)
                    
                    #> IF xdir < 0 THEN bx = 63 - bx
                    if xdir < 0:
                        bx = 63 - bx
                        
                    #> EXIT DO
                    break
                #> END IF

                #> d# = d# + incy#
                d += incy
                
            #> ELSE
            else:
                
                #> yscan = yscan + ydir
                yscan = yscan + ydir
                
                #> IF board(xscan, yscan) THEN
                if board[xscan][yscan]:
                    
                    #> hy# = yscan: IF ydir < 0 THEN hy# = hy# + 1
                    #> hx# = posx# + vx# * (hy# - posy#) / vy#
                    hy = yscan
                    if ydir < 0:
                        hy += 1
                    hx = posx + vx * (hy - posy) / vy
                    
                    #> bx = INT((hx# - INT(hx#)) * 64)
                    bx = int((hx - int(hx)) * 64)
                    
                    #> IF ydir > 0 THEN bx = 63 - bx
                    if ydir > 0: bx = 63 - bx
                    
                    #> EXIT DO
                    break
                #> END IF
                
                #> d# = d# - incx#
                d -= incx
                
            #> END IF
        #> LOOP
        
        #> dist# = cosang# * (hx# - posx#) + sinang# * (hy# - posy#)
        dist = cosang * (hx - posx) + sinang * (hy - posy)
        
        #> 
        #>    'Find the ceiling & floor borders, and the texture mapping equation
        #> IF dist# > 1# / 64# THEN
        if dist > (1.0 / 64.0):
            #> sy1 = INT(YDIM \ 2 + (-posz# - .5) * hval / dist#)
            #> sy2 = INT(YDIM \ 2 + (-posz# + .5) * hval / dist#)
            sy1 = int(YDIM / 2 + (-posz - 0.5) * hval / dist)
            sy2 = int(YDIM / 2 + (-posz + 0.5) * hval / dist)
            
            #> IF sy1 < 0 THEN sy1 = 0
            #> IF sy2 > YDIM THEN sy2 = YDIM
            if sy1 < 0:
                sy1 = 0
            if sy2 > YDIM:
                sy2 = YDIM
                
            #> byi& = INT(dist# * 65536# * 64# / hval)
            byi = int(dist * 65536.0 * 64 / hval)
            
            #> by& = (sy1 + 1 - YDIM \ 2) * byi& + (posz# + .5#) * 65536# * 64# - 1
            #> by& = by& + ((board(xscan, yscan) - 1) * 4096& + bx * 64&) * 65536
            
            by = int((sy1 + 1 - YDIM / 2) * byi + (posz + 0.5) * 65536.0 * 64.0 - 1)
            by = by + ((((board[xscan][yscan]) - 1)<<12) + (bx << 6) << 16)
            
        #> ELSE
        else:
            
            #> sy1 = YDIM \ 2: sy2 = sy1
            sy1 = YDIM / 2; sy2 = sy1
            
        #> END IF
        #>   
        #>    'Draw vertical line at column sx:
        #> OUT &H3C5, 2 ^ (sx AND 3): DEF SEG = &HA000 + pagoffs * 16
        #> p = sx \ 4: pe = sy1 * BPL
        p = sx * 3
        pe = sy1 * BPL

        #> DO WHILE p < pe: POKE p, 176: p = p + BPL: LOOP 'Ceilings
        while p < pe:
            screen[p:p+3] = ceil
            p += BPL
            
        #> pe = sy2 * BPL 'Walls
        pe = sy2 * BPL
        
        #> DO WHILE p < pe
        #>    POKE p, pic(by& \ 65536): p = p + BPL: by& = by& + byi&
        #> LOOP
        while p < pe:
            screen[p:p+3] = pic[by >> 16]
            p += BPL
            by += byi
            
        #> pe = YDIM * BPL
        pe = YDIM * BPL
        
        #> DO WHILE p < pe: POKE p, 80: p = p + BPL: LOOP 'Floors
        while p < pe:
            screen[p:p+3] = floor
            p += BPL

        #> 
        #> vx# = vx# + vxinc#
        #> vy# = vy# + vyinc#
        vx += vxinc
        vy += vyinc
        
    #> NEXT sx
    
if psyco: psyco.bind(drawscreen)
    
ltime=0
def runtmr():
    global ltime, posx, posy, posz, ang
    ctime = time.time()

    dtime = ctime-ltime
    if dtime>1.0: dtime = 1.0
    ltime = ctime

    cosang = math.cos(ang)
    sinang = math.sin(ang)

    #> 
    #>       'Flip page
    #>    OUT &H3D5, pagoffs: pagoffs = (pagoffs + 64) AND 255
    #> 
    #>    'Keyboard control code
    #> DO
    #>    z$ = INKEY$
    #>    IF z$ = CHR$(0) + CHR$(75) THEN ang# = ang# - .1#
    #>    IF z$ = CHR$(0) + CHR$(77) THEN ang# = ang# + .1#
    ang += angvel * dtime * 2

    #>    IF z$ = "<" OR z$ = "," THEN
    #>       posx# = posx# + sinang# * .25#
    #>       posy# = posy# - cosang# * .25#
    #>    END IF
    #>    IF z$ = ">" OR z$ = "." THEN
    #>       posx# = posx# - sinang# * .25#
    #>       posy# = posy# + cosang# * .25#
    #>    END IF
    posx = posx + sinang * dtime * 3.5 * svel
    posy = posy - cosang * dtime * 3.5 * svel
    
    #>    IF z$ = CHR$(0) + CHR$(72) THEN
    #>       posx# = posx# + cosang# * .25#
    #>       posy# = posy# + sinang# * .25#
    #>    END IF
    #>    IF z$ = CHR$(0) + CHR$(80) THEN
    #>       posx# = posx# - cosang# * .25#
    #>       posy# = posy# - sinang# * .25#
    #>    END IF

    posx = posx + cosang * dtime * 3.5 * fvel
    posy = posy + sinang * dtime * 3.5 * fvel

    
    #>    IF UCASE$(z$) = "A" THEN posz# = posz# - .05#
    #>    IF UCASE$(z$) = "Z" THEN posz# = posz# + .05#

    posz += zvel * dtime
    
    #>    IF z$ = CHR$(27) THEN SCREEN 0: END
    #> LOOP WHILE z$ <> ""

    #>   
    #>    'Mouse control code (enable this, unless you're stuck using QBasic!)
    #> 'regs%(0) = 5: CALL int86old(&H33, regs%(), regs%()): bstatus% = regs%(0)
    #> 'regs%(0) = 11: CALL int86old(&H33, regs%(), regs%())
    #> IF (bstatus% AND 2) = 0 THEN
    #>    ang# = ang# + regs%(2) * .01#
    #> ELSE
    #>    posx# = posx# + sinang# * regs%(2) * -.02#
    #>    posy# = posy# - cosang# * regs%(2) * -.02#
    #> END IF
    #> IF (bstatus% AND 1) = 0 THEN
    #>    posx# = posx# + cosang# * regs%(3) * -.02#
    #>    posy# = posy# + sinang# * regs%(3) * -.02#
    #> ELSE
    #>    posz# = posz# + regs%(3) * .02#
    #> END IF
    #>   
    #>    'Don't let your view escape the board
    #> IF posx# < 1.25# THEN posx# = 1.25#
    #> IF posy# < 1.25# THEN posy# = 1.25#
    #> IF posx# > 62.75# THEN posx# = 62.75#
    #> IF posy# > 62.75# THEN posy# = 62.75#
    #> IF posz# < -.5# THEN posz# = -.5#
    #> IF posz# > .5# THEN posz# = .5#
    if posx < 1.25: posx = 1.25
    if posy < 1.25: posy = 1.25
    if posx > 62.75: posx = 62.75
    if posy > 62.75: posy = 62.75
    if posz < -0.5: posz = -0.5
    if posz > 0.5: posz = 0.5

#> 
#> LOOP
#> 

# WX-specific code begins here

import wx
screenimg = wx.EmptyImage(XDIM,YDIM)
screenimg.SetDataBuffer(screenbuf)

class DemoWindow(wx.Window):
    def __init__(self, parent, ID):
        wx.Window.__init__(self, parent, ID, size=(XDIM,YDIM))
        self.Bind(wx.EVT_PAINT, self.paint)

        self.Bind(wx.EVT_KEY_DOWN, lambda evt: self.key(evt.m_keyCode,1))
        self.Bind(wx.EVT_KEY_UP, lambda evt: self.key(evt.m_keyCode,0))
        self.Bind(wx.EVT_IDLE, self.idle)
        self.SetFocus()
        self.drawing = False

    def idle(self,evt):
        runtmr();
        drawscreen()
        self.Refresh(False)

    def key(self,key,press):
        global svel, fvel, zvel, angvel
        if key == ord(','):
            svel = press
        elif key == ord('.'):
            svel = -press
        elif key == ord('Z'):
            zvel = press
        elif key == ord('A'):
            zvel = -press
        elif key == wx.WXK_UP:
            fvel = press
        elif key == wx.WXK_DOWN:
            fvel = -press
        elif key == wx.WXK_RIGHT:
            angvel = press
        elif key == wx.WXK_LEFT:
            angvel = -press
        elif key == wx.WXK_ESCAPE:
            if press:
                app.ExitMainLoop()

    def paint(self,evt):
        dc = wx.PaintDC(self)
        dc.DrawBitmap(wx.BitmapFromImage(screenimg),0,0)

class DemoFrame(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent, -1, "LABDEMO2", size=(XDIM,YDIM),
                          style=wx.DEFAULT_FRAME_STYLE)
        self.SetSizeHints(XDIM,YDIM,XDIM,YDIM,1,1)


app = wx.PySimpleApp()
frame = DemoFrame(None)
win = DemoWindow(frame, -1)
frame.Show(1)
app.MainLoop()




