import pygame
import sys
import math

SCREEN_HEIGHT=480
SCREEN_WIDTH=SCREEN_HEIGHT * 2
MAP_SIZE=8
TILE_SIZE=int((SCREEN_WIDTH / 2) / MAP_SIZE)
FOV=math.pi / 3
HALF_FOV = FOV / 2
CASTED_RAYS=30
STEP_ANGLE = FOV / CASTED_RAYS
MAX_DEPTH = int(MAP_SIZE * TILE_SIZE)
SCALE = (SCREEN_WIDTH / 2) / CASTED_RAYS


player_x = (SCREEN_WIDTH/2)/2
player_y = (SCREEN_WIDTH/2)/2
player_angle = math.pi

MAP = ('########'
       '# #    #'
       '# #  ###'
       '#      #'
       '##     #'
       '#  ### #'
       '#   #  #'
       '########')

pygame.init()
win = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Ray-Casting")
clock = pygame.time.Clock()

def draw_map():
    light_grey = (191, 191, 191)
    dark_grey = (65,65,65)

    for i in range(MAP_SIZE):
        for j in range(MAP_SIZE):
            square = i * MAP_SIZE + j

            pygame.draw.rect(win,
                    light_grey if MAP[square] == '#' else dark_grey,
                    (j * TILE_SIZE, i * TILE_SIZE, TILE_SIZE -1, TILE_SIZE - 1))

def ray_casting():
    # left angle of FOV
    start_angle = player_angle - HALF_FOV

    for ray in range(CASTED_RAYS):
        for depth in range(1,MAX_DEPTH):
            target_x = player_x - math.sin(start_angle) * depth
            target_y = player_y + math.cos(start_angle) * depth
            col = int(target_x / TILE_SIZE)
            row = int(target_y / TILE_SIZE)
            square = row * MAP_SIZE + col

            if MAP[square] == '#':
                pygame.draw.rect(win,
                        (195, 137, 38),
                        (col * TILE_SIZE,
                        row * TILE_SIZE,
                        TILE_SIZE -1, TILE_SIZE-1))

                pygame.draw.line(win, (233, 166, 49),
                        (player_x, player_y),
                        (target_x, target_y))

                # wall shading
                color = 255 / (1 + depth * depth * 0.0001)

                # fix fish eye effect
                depth *= math.cos(player_angle - start_angle)

                # calculate wall height
                wall_height = 21000 / (depth)

                if wall_height > SCREEN_HEIGHT:
                    wall_height = SCREEN_HEIGHT

                pygame.draw.rect(win,
                        (color, color, color),
                        (SCREEN_HEIGHT + ray * SCALE,
                        (SCREEN_HEIGHT / 2) - wall_height/2,
                        SCALE, wall_height))

                break

        start_angle += STEP_ANGLE

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit(0)

    # update 2d background
    pygame.draw.rect(win, (0,0,0), (0, 0, SCREEN_HEIGHT, SCREEN_HEIGHT))

    # update 3d background
    pygame.draw.rect(win, (100, 100, 100), (480, SCREEN_HEIGHT / 2, SCREEN_HEIGHT, SCREEN_HEIGHT))
    pygame.draw.rect(win, (200, 200, 200), (480, -SCREEN_HEIGHT / 2, SCREEN_HEIGHT, SCREEN_HEIGHT))

    draw_map()
    ray_casting()

    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT]:
        # working with radians, not degrees
        player_angle -= 0.1
    elif keys[pygame.K_RIGHT]:
        player_angle += 0.1
    elif keys[pygame.K_UP]:
        forward = True
        player_x += -1 * math.sin(player_angle) * 5
        player_y += math.cos(player_angle) * 5
    elif keys[pygame.K_DOWN]:
        forward = False
        player_x -= -1 * math.sin(player_angle) * 5
        player_y -= math.cos(player_angle) * 5

    # update the display
    pygame.display.flip()

    clock.tick(30)