nuum.py

Created by numworks-nl

Created on December 30, 2020

10.2 KB

Speciaal Halloween-spel! Durf jij onze 4 labyrinten in 3D te verkennen om de 4 sleutels te vinden en te ontsnappen uit onze sinistere crypte? Gebruik richtingspijlen en haakjes om te draaien. Om te beginnen tussen nuum().


from ion import *
from kandinsky import *
from math import *
from random import randint
from time import monotonic

### PROGRAM CONSTANTS
# Display
SCREEN_WIDTH = 320
SCREEN_HEIGHT = 240
VIDEO_HALF_HEIGHT = SCREEN_HEIGHT // 2

COLUMN_WIDTH = 16
NUMBER_OF_COLUMNS = SCREEN_WIDTH // COLUMN_WIDTH
FOV = pi / 3
WALL_HEIGHT = SCREEN_HEIGHT // 4

# Movement
ROTATION_SPEED = 2*pi / 8
RUNNING_SPEED = 1

# Surfaces IDs
CEILING = 0
FLOOR = 1
WALL_EMPTY = 2
WALL_STANDARD = 3
WALL_FANCY = 4
WALL_SPECIAL_A = 5
WALL_SPECIAL_B = 6
WALL_SPECIAL_C = 7

MAZE_SIZE = 17
MAZES = [
    994639892451692017993627844655427188346119489096700102527510313302320457573868616417279,
    994639136329297165277925056994599494901591635504316814914476715640179960969549973159935,
    994638899191857351225063837897269868434453723710226411561032419934171038551468501237759,
    994638903127657620198142832056217335350998827328484167051085993695318850524015157706751,
        ]
HIGHLIGHTS = [WALL_FANCY, WALL_SPECIAL_A, WALL_SPECIAL_B, WALL_SPECIAL_C]
### IMAGES DEFINITION
def draw_sprite(rects, x, depth, xCenter, yCenter, scale, offset):
    invDepth = 1/depth
    ratio = int(scale * invDepth)
    y = VIDEO_HALF_HEIGHT + offset * invDepth
    for (px, py, dx, dy, color) in rects:
        fill_rect(int(x + ratio * (px - xCenter)), int(y + ratio * (py - yCenter)), int(dx * ratio), int(dy * ratio), color)

def sprite_key(x, depth):
    rects = [
            (2,0,1,9,(255,181,49)),
            (0,9,1,3,(255,181,49)),
            (4,9,1,3,(255,181,49)),
            (1,9,3,1,(255,181,49)),
            (1,11,3,1,(255,181,49)),
            (2,1,3,3,(255,181,49)),
            ]
    draw_sprite(rects, x, depth, 2.5, 6, 5, 0)
KEY = (sprite_key, 13)

def sprite_pumpkin(x, depth):
    rects = [
            (2,0,6,1,(204,102,51)),
            (2,7,6,1,(204,102,51)),
            (0,1,10,6,(204,102,51)),
            (8,3,1,1,(51,51,51)),
            (3,3,1,1,(51,51,51)),
            (4,1,1,2,(51,51,51)),
            (4,5,1,1,(51,51,51)),
            (5,6,1,1,(51,51,51)),
            (3,6,1,1,(51,51,51)),
            (2,5,1,1,(51,51,51)),
            (6,5,1,1,(51,51,51)),
            (7,6,1,1,(51,51,51)),
            (8,5,1,1,(51,51,51)),
            (7,2,1,2,(51,51,51)),
            ]
    draw_sprite(rects, x, depth, 5, 4, 5, 50)
PUMPKIN = (sprite_pumpkin, 25)

SPRITES = [((MAZE_SIZE - 0.5, MAZE_SIZE - 1.5), KEY)]
### HELPERS
def unit(x, y):
    n = sqrt(x**2 + y**2)
    return x/n, y/n

def rotate(x, y, angle):
    cosa, sina = cos(angle), sin(angle)
    return cosa * x - sina * y, sina * x + cosa * y

def buildLens(x, y, fov):
    ratio = tan(fov / 2.0)
    return (ratio * y, - ratio * x)

def blend(c1, c2, a = 0.5):
    b = 1 - a
    return (a * c1[0] + b * c2[0], a * c1[1] + b * c2[1], a * c1[2] + b * c2[2])

def angle(x, y, x2, y2):
    res = atan2(y, x) - atan2(y2, x2)
    while res < pi: res += 2*pi
    while res > pi: res -= 2*pi
    return res

def nThBit(x, n):
    return (x >> n) & 1

def numberOfSprites(): return len(SPRITES)
def spritePosition(index): return SPRITES[index][0]
def spriteImage(index): return SPRITES[index][1][0]
def spriteHalfWidth(index): return SPRITES[index][1][1]

def startingPosition(): return (0.5, 1.5), (1, 0)

def wall(mapId, x, y):
    if nThBit(MAZES[mapId], x + MAZE_SIZE * y) == 0: return WALL_EMPTY
    if x % 3 == 0 and y % 3 == 0: return HIGHLIGHTS[mapId]
    return WALL_STANDARD

def prompt(mapId, x, y):
    if (floor(x), floor(y)) == (MAZE_SIZE - 1, MAZE_SIZE - 2): return "Grab Key"
    return str(mapId) + "/4"

def interact(x, y):
    if (floor(x), floor(y)) == (MAZE_SIZE - 1, MAZE_SIZE - 2): return True
    return False

def randomSprites(mapId, n = 5):
    res = []
    for i in range(n):
        x, y = 0, 0
        while nThBit(MAZES[mapId], x + MAZE_SIZE * y) == 1:
            x, y = randint(0, MAZE_SIZE - 1), randint(0, MAZE_SIZE - 1)
        res.append(((x + 0.5, y + 0.5), PUMPKIN))
    return res

### DISPLAY MODULE
def transparent(wallId):
    return wallId == WALL_EMPTY

def color(wallId):
    if wallId == CEILING: return (33, 33, 33)
    elif wallId == FLOOR: return (48, 48, 48)
    elif wallId == WALL_STANDARD: return (62, 62, 62)
    elif wallId == WALL_FANCY: return (190, 110, 50)
    elif wallId == WALL_SPECIAL_A: return (50, 0, 80)
    elif wallId == WALL_SPECIAL_B: return (90, 255, 0)
    elif wallId == WALL_SPECIAL_C: return (255, 0, 0)
    return (247, 16, 247)

def castingParameters(x, y, rayX, rayY):
    stepX, stepY = None, None
    if rayX == 0: stepX, stepY = 1, 0
    elif rayY == 0: stepX, stepY = 0, 1
    else: stepX, stepY = abs(1/rayX), abs(1/rayY)

    travelX, tileStepX = ((x % 1) * stepX, -1) if rayX < 0 else ((1 - x % 1) * stepX, 1)
    travelY, tileStepY = ((y % 1) * stepY, -1) if rayY < 0 else ((1 - y % 1) * stepY, 1)

    return (stepX, stepY), (tileStepX, tileStepY), (travelX, travelY)

def castRay(x, y, camX, camY, lensX, lensY, angle, mapId):
    rayX, rayY = camX + angle * lensX, camY + angle * lensY
    tileX, tileY = floor(x), floor(y)

    (stepX, stepY), (tileStepX, tileStepY), (travelX, travelY) = castingParameters(x, y, rayX, rayY)

    hit = False
    hitVertical = False
    while not hit:
        if travelX < travelY:
            travelX += stepX
            tileX += tileStepX
            hitVertical = True
        else:
            travelY += stepY
            tileY += tileStepY
            hitVertical = False
        hit = not(transparent(wall(mapId, tileX, tileY)))

    dist = ((tileX - x + (1 - tileStepX) / 2) * stepX) if hitVertical else ((tileY - y + (1 - tileStepY) / 2) * stepY)
    dist *= sqrt(camX**2 + camY**2)

    return (tileX, tileY), abs(dist), hitVertical

def fetchSprites(x, y, camX, camY, lensX, lensY, mapId):
    number = numberOfSprites()
    res = []
    for index in range(number):
        spriteX, spriteY = spritePosition(index)
        spriteX, spriteY = spriteX - x, spriteY - y
        invDet = 1 / (lensX * camY - lensY * camX)
        transformX, transformY = invDet * (camY * spriteX - camX * spriteY), invDet * (lensX * spriteY - lensY * spriteX)
        if transformY > 0:
            spriteOnscreenX = int(SCREEN_WIDTH / 2 * (1 + transformX / transformY))
            column = spriteOnscreenX // COLUMN_WIDTH
            if column >= 0 and column < NUMBER_OF_COLUMNS:
                lastColumnOfSprite = (spriteOnscreenX + spriteHalfWidth(index) / transformY) // COLUMN_WIDTH + 1
                if lastColumnOfSprite >= NUMBER_OF_COLUMNS: lastColumnOfSprite = NUMBER_OF_COLUMNS - 1
                res.append((lastColumnOfSprite, transformY, spriteOnscreenX, column, index))
    res.sort()
    return res

def drawSurfacesAndSprites(x, y, camX, camY, mapId):
    zBuffer = [None] * NUMBER_OF_COLUMNS
    lensX, lensY = buildLens(camX, camY, FOV)
    sprites = fetchSprites(x, y, camX, camY, lensX, lensY, mapId)
    nextSprite = 0
    for column in range(NUMBER_OF_COLUMNS):
        (wallX, wallY), distance, hasHitNS = castRay(x, y, camX, camY, lensX, lensY, 2 * column / (NUMBER_OF_COLUMNS - 1) - 1, mapId)
        height = min(int(WALL_HEIGHT / distance), VIDEO_HALF_HEIGHT) if distance > 0 else VIDEO_HALF_HEIGHT
        fill_rect(column * COLUMN_WIDTH, 0, COLUMN_WIDTH, VIDEO_HALF_HEIGHT - height, color(CEILING))
        fill_rect(column * COLUMN_WIDTH, VIDEO_HALF_HEIGHT + height, COLUMN_WIDTH, VIDEO_HALF_HEIGHT - height, color(FLOOR))
        wallColor = color(wall(mapId, wallX, wallY))
        if (hasHitNS):
            wallColor = blend(wallColor, (0, 0, 0), 0.9)
        fill_rect(column * COLUMN_WIDTH, VIDEO_HALF_HEIGHT - height, COLUMN_WIDTH, 2 * height, wallColor)
        zBuffer[column] = distance
        feed = True
        while nextSprite < len(sprites) and feed:
            lastCol, depth, screenX, midCol, index = sprites[nextSprite]
            if lastCol == column:
                if depth < zBuffer[midCol]:
                    spriteImage(index)(screenX, depth)
                nextSprite += 1
            else:
                feed = False


def drawPrompt(x, y, camX, camY, mapId):
    message = prompt(mapId, x, y)
    draw_string(message, SCREEN_WIDTH - (len(message) + 1) * 10, SCREEN_HEIGHT - 46, 'white', 'black')

def drawFrame(x, y, camX, camY, mapId):
    drawSurfacesAndSprites(x, y, camX, camY, mapId)
    drawPrompt(x, y, camX, camY, mapId)

### GAMEPLAY MODULE
def move(x, y, dirX, dirY, speed, mapId):
    dx, dy = unit(dirX, dirY)
    xNew, yNew = x + speed * dx, y + speed * dy
    if transparent(wall(mapId, floor(xNew), floor(yNew))):
        return xNew, yNew
    return x, y

def handleKeys(dt, mapId, x, y, camX, camY):
    redraw = False
    if keydown(KEY_LEFTPARENTHESIS):
        camX, camY = rotate(camX, camY, dt * ROTATION_SPEED)
        redraw = True
    elif keydown(KEY_RIGHTPARENTHESIS):
        camX, camY = rotate(camX, camY, - dt * ROTATION_SPEED)
        redraw = True

    if keydown(KEY_UP):
        x, y = move(x, y, camX, camY, dt * RUNNING_SPEED, mapId)
        redraw = True
    elif keydown(KEY_DOWN):
        x, y = move(x, y, camX, camY, - dt * RUNNING_SPEED, mapId)
        redraw = True
    if keydown(KEY_LEFT):
        x, y = move(x, y, camY, - camX, - dt * RUNNING_SPEED, mapId)
        redraw = True
    elif keydown(KEY_RIGHT):
        x, y = move(x, y, camY, - camX, dt * RUNNING_SPEED, mapId)
        redraw = True

    if keydown(KEY_OK):
        if interact(x, y):
            redraw, mapId, ((x, y), (camX, camY)) = True, (mapId + 1), startingPosition()
            del SPRITES[1:]
            if mapId < len(MAZES):
                SPRITES.extend(randomSprites(mapId))

    return (redraw, x, y, camX, camY, mapId)

### MAIN
def nuum():
    # Init
    game_map = 0
    (game_x, game_y), (game_camX, game_camY) = startingPosition()
    SPRITES.extend(randomSprites(game_map))

    redraw = True
    time, dt = monotonic(), 0

    # Run loop
    while game_map < len(MAZES):
        if redraw:
            drawFrame(game_x, game_y, game_camX, game_camY, game_map)
            redraw = False

        dt = monotonic() - time
        time += dt
        redraw, game_x, game_y, game_camX, game_camY, game_map = handleKeys(dt, game_map, game_x, game_y, game_camX, game_camY)

    draw_string("You escaped the labyrinth.", 30, 111, 'white', 'black')

print("Launch with 'nuum()'.\nArrow keys to move.\nParentheses to look around.\nFind the four keys to escape.")

During your visit to our site, NumWorks needs to install "cookies" or use other technologies to collect data about you in order to:

With the exception of Cookies essential to the operation of the site, NumWorks leaves you the choice: you can accept Cookies for audience measurement by clicking on the "Accept and continue" button, or refuse these Cookies by clicking on the "Continue without accepting" button or by continuing your browsing. You can update your choice at any time by clicking on the link "Manage my cookies" at the bottom of the page. For more information, please consult our <a href="https://www.numworks.com/legal/cookies-policy/">cookies policy</a>.