from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *
import os
import json
import numpy as np
import math
import time
import random

player_pos = [0, 0, 100]
camera_pos = [0, 0, 0]
enemy_distance = 1000
fovY = 120
drawingHUD = True
bombs = []
coins = []
bullets = []
lastbomb = time.time()
lastcoin = time.time()
enemy = None
current_level = 0
score = 0

if not os.path.isfile("db.json"):
    db = [1000, 0, 0, 0]
    with open("db.json", "w") as f:
        json.dump(db, f)

with open("db.json", "r") as f:
    fromdb = json.load(f)

coinscount = fromdb[0]
high_score = fromdb[1]
health_level = fromdb[2]
magazine_level = fromdb[3]
powerup_cost = {"health Boost": 10, "Extra Magazine": 15}
selected_option = 0
menu_options = ["Start Game", "health Boost", "Extra Magazine", "Quit"]
block_size = 100.0
RENDER_DISTANCE = 20
max_health = 0
current_health = 0
ammo = 100

def draw_bomb(x, y, z):
    glPushMatrix()
    glTranslatef(x, y, z)
    glColor3f(0.2, 0.2, 0.2)
    glutSolidSphere(5, 16, 16)
    glPushMatrix()
    glTranslatef(0, 0, 5)
    glColor3f(1.0, 0.0, 0.0)
    gluCylinder(gluNewQuadric(), 1, 1, 3, 10, 10)
    glPopMatrix()
    glPopMatrix()

def draw_coin(x, y, z):
    glPushMatrix()
    glTranslatef(x, y, z)
    glColor3f(1.0, 0.8, 0.0)
    glutSolidSphere(5, 10, 10)
    glPopMatrix()

def draw_bullet(x, y, z):
    glColor3f(1, 1, 1)
    glPointSize(5)
    glBegin(GL_POINTS)
    glVertex3f(x, y, z)
    glEnd()

def draw_block(y, start_x, end_x, color):
    glPushMatrix()
    glTranslatef(0, y, 0)
    glColor3f(*color)
    glBegin(GL_QUADS)
    glVertex3f(start_x, 0, 0)
    glVertex3f(end_x, 0, 0)
    glVertex3f(end_x, block_size, 0)
    glVertex3f(start_x, block_size, 0)
    glEnd()
    glPopMatrix()

def draw_terrain():
    colors = [(0.6, 0.65, 0.7), (0.3, 0.5, 0.65), (0.4, 0.4, 0.45), (0.2, 0.6, 0.55),ยท
              (0.65, 0.45, 0.35), (0.5, 0.4, 0.6), (0.35, 0.55, 0.4), (0.7, 0.7, 0.75)]
    player_y = player_pos[1]
    start_y = int(player_y / block_size - RENDER_DISTANCE) * block_size
    end_y = int(player_y / block_size + RENDER_DISTANCE) * block_size
    start_x = player_pos[0] - RENDER_DISTANCE * block_size
    end_x = player_pos[0] + RENDER_DISTANCE * block_size
    for y in np.arange(start_y, end_y, block_size):
        if y < player_y - block_size:
            continue
        color_index = int(y // block_size) % len(colors)
        color = colors[color_index]
        draw_block(y, start_x, end_x, color)

def draw_player():
    glPushMatrix()
    x, y, z = player_pos
    glTranslatef(x, y, z)
    glRotatef(90, 0, 0, 1)
    glRotatef(90, 0, 1, 0)
    glColor3f(0.3, 0.3, 0.3)
    glScalef(0.2, 0.2, 0.2)
    gluCylinder(gluNewQuadric(), 6, 6, 80, 16, 16)
    glPushMatrix()
    glTranslatef(0, 0, 80)
    glColor3f(0.1, 0.1, 0.1)
    gluCylinder(gluNewQuadric(), 6, 0, 20, 16, 16)
    glPopMatrix()
    glPushMatrix()
    glColor3f(0.2, 0.2, 0.2)
    glTranslatef(0, 0, 35)
    glScalef(0.1, 9.0, 1.0)
    glutSolidCube(10)
    glPopMatrix()
    glPushMatrix()
    glTranslatef(0, 0, 5)
    glutSolidSphere(6, 10, 10)
    glScalef(0.1, 3.0, 0.5)
    glutSolidCube(10)
    glPopMatrix()
    glPopMatrix()

def draw_enemy():
    glPushMatrix()
    glTranslatef(*enemy['pos'])
    glColor3f(0.2, 0.2, 0.2)
    glScalef(2.2, 2.2, 2.2)
    glutSolidCube(50)
    glScalef(0.5, 0.5, 0.5)
    glColor3f(0.8, 0.1, 0.1)
    glPushMatrix()
    glRotatef(90, 0, 1, 0)
    gluCylinder(gluNewQuadric(), 30, 0, 100, 16, 16)
    glPopMatrix()
    glPushMatrix()
    glRotatef(-90, 0, 1, 0)
    gluCylinder(gluNewQuadric(), 30, 0, 100, 16, 16)
    glPopMatrix()
    glPushMatrix()
    gluCylinder(gluNewQuadric(), 30, 0, 100, 16, 16)
    glPopMatrix()
    glPushMatrix()
    glRotatef(90, 1, 0, 0)
    gluCylinder(gluNewQuadric(), 30, 0, 100, 16, 16)
    glPopMatrix()
    glPushMatrix()
    glRotatef(180, 1, 0, 0)
    gluCylinder(gluNewQuadric(), 30, 0, 100, 16, 16)
    glPopMatrix()

    glPopMatrix()

def draw_text(x, y, text, font=GLUT_BITMAP_HELVETICA_18, color=(1, 1, 1)):
    glColor3f(*color)
    glMatrixMode(GL_PROJECTION)
    glPushMatrix()
    glLoadIdentity()
    gluOrtho2D(0, 1000, 0, 800)
    glMatrixMode(GL_MODELVIEW)
    glPushMatrix()
    glLoadIdentity()
    glRasterPos2f(x, y)
    for ch in text:
        glutBitmapCharacter(font, ord(ch))
    glPopMatrix()
    glMatrixMode(GL_PROJECTION)
    glPopMatrix()
    glMatrixMode(GL_MODELVIEW)

def draw_hud():
    global coinscount, high_score, selected_option
    verticalheight = 600
    pos_title = 405, verticalheight
    pos_highscore = 440, verticalheight - 50
    pos_coin = 455, verticalheight - 100
    pos_startgame = 450, verticalheight - 180
    pos_healthboost = 380, verticalheight - 210
    pos_magazine = 370, verticalheight - 240
    pos_exit = 480, verticalheight - 270
    draw_text(*pos_title, "GALACTIC GUARDIAN", GLUT_BITMAP_HELVETICA_18, (0.0, 0.8, 1.0))
    draw_text(*pos_highscore, f"HIGH SCORE: {high_score}", GLUT_BITMAP_HELVETICA_18, (0.0, 1.0, 0.0))
    draw_text(*pos_coin, f"COINS: {coinscount}", GLUT_BITMAP_HELVETICA_18, (1.0, 0.5, 0.0))
    for i, option in enumerate(menu_options):
        color = (1, 1, 0) if i == selected_option else (1, 1, 1)
        if i == 0:
            draw_text(*pos_startgame, option, GLUT_BITMAP_HELVETICA_18, color)
        elif i == 1:
            draw_text(*pos_healthboost, f"< {option} ({health_level}) - {powerup_cost['health Boost']} coins >", GLUT_BITMAP_HELVETICA_18, color)
        elif i == 2:
            draw_text(*pos_magazine, f"< {option} ({magazine_level}) - {powerup_cost['Extra Magazine']} coins >", GLUT_BITMAP_HELVETICA_18, color)
        elif i == 3:
            draw_text(*pos_exit, option, GLUT_BITMAP_HELVETICA_18, color)

def draw_game_hud():
    draw_text(10, 770, f"Coins: {coinscount}")
    draw_text(10, 750, f"Ammo: {ammo}")
    draw_text(10, 730, f"Enemy Health: {enemy['health'] if enemy else 'None'}")
    draw_text(10, 710, f"Health: {current_health}/{max_health}")
    draw_text(10, 690, f"Level: {current_level}")
    draw_text(10, 670, f"Score: {score}")

def spawn_enemy(level):
    global enemy
    pos = player_pos.copy()
    pos[1] += enemy_distance
    pos[0] += random.uniform(-300, 300)
    bomb_delay = max(5 - 0.5 * level, 1)
    teleport_chance = min(0.8 + 0.1 * level, 1)
    health = 50 + 50 * level
    enemy = {
        'pos': pos,
        'health': health,
        'bomb_delay': bomb_delay,
        'teleport_chance': teleport_chance,
        'level': level,
        'last_bomb': time.time()
    }

def keyboardListener(key, x, y):
    global coinscount, health_level, magazine_level, drawingHUD, selected_option, ammo, current_health, max_health, bombs, coins, enemy, bullets, current_level, score
    if drawingHUD:
        if key == b'\r':
            current_option = menu_options[selected_option]
            if current_option == "Quit":
                db = [coinscount, high_score, health_level, magazine_level]
                with open("db.json", "w") as f:
                    json.dump(db, f)
                os._exit(0)
            elif current_option == "Start Game":
                drawingHUD = False
                max_health = 100 + 50 * health_level
                current_health = max_health
                ammo = 100 + 20 * magazine_level
                score = 0
                bombs = []
                coins = []
                bullets = []
                enemy = None
                current_level = 0
            elif current_option == "health Boost" and coinscount >= powerup_cost["health Boost"]:
                coinscount -= powerup_cost["health Boost"]
                health_level += 1
            elif current_option == "Extra Magazine" and coinscount >= powerup_cost["Extra Magazine"]:
                coinscount -= powerup_cost["Extra Magazine"]
                magazine_level += 1
        if key == b'w':
            selected_option = (selected_option - 1) % len(menu_options)
        if key == b's':
            selected_option = (selected_option + 1) % len(menu_options)
    else:
        if key == b'w':
            player_pos[1] += 10
        if key == b's':
            player_pos[1] -= 10
        if key == b'a':
            player_pos[0] -= 10
        if key == b'd':
            player_pos[0] += 10
        if key == b' ' and ammo > 0:
            bullets.append(player_pos.copy())
            ammo -= 1

def specialKeyListener(key, x, y):
    pass

def mouseListener(button, state, x, y):
    pass

def setupCamera():
    global camera_pos
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluPerspective(fovY, 1.25, 0.1, 3000)
    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()
    distance_y = -5
    distance_z = 10
    camera_pos = [player_pos[0], player_pos[1] + distance_y, player_pos[2] + distance_z]
    lookat = player_pos.copy()
    lookat[2] += 9
    gluLookAt(*camera_pos, *lookat, 0, 0, 1)

def draw_shapes():
    if drawingHUD:
        draw_hud()
        return
    glEnable(GL_DEPTH_TEST)
    draw_terrain()
    draw_player()
    for i in range(len(bombs)-1, -1, -1):
        if bombs[i][1] < camera_pos[1]:
            bombs.pop(i)
        else:
            draw_bomb(*bombs[i])
    for i in range(len(coins)-1, -1, -1):
        if coins[i][1] < camera_pos[1]:
            coins.pop(i)
        else:
            draw_coin(*coins[i])
    for i in range(len(bullets)-1, -1, -1):
        if enemy and bullets[i][1] > enemy['pos'][1]:
            bullets.pop(i)
        else:
            draw_bullet(*bullets[i])
    if enemy:
        draw_enemy()
    draw_game_hud()

def idle():
    global lastcoin, current_health, drawingHUD, high_score, enemy, bombs, coins, coinscount, bullets, current_level, ammo, score
    if drawingHUD:
        pass
    else:
        now = time.time()
        if enemy is None:
            spawn_enemy(current_level)
        else:
            enemy['pos'][1] = player_pos[1] + enemy_distance
            if now - enemy['last_bomb'] > enemy['bomb_delay']:
                enemy['last_bomb'] = now
                if random.random() < enemy['teleport_chance']:
                    enemy['pos'][0] = player_pos[0]
                bombs.append(enemy['pos'][:])
        player_pos[1] += current_level + 1
        if now - lastcoin > 3:
            lastcoin = now
            if random.random() < 0.3:
                coin_x = player_pos[0] + random.uniform(-400, 400)
                coin_y = player_pos[1] + random.uniform(800, 1200)
                coin_z = player_pos[2]
                coins.append([coin_x, coin_y, coin_z])
        for i in range(len(bombs) - 1, -1, -1):
            dx = player_pos[0] - bombs[i][0]
            dy = player_pos[1] - bombs[i][1]
            dz = player_pos[2] - bombs[i][2]
            dist = math.sqrt(dx*dx + dy*dy + dz*dz)
            if dist < 20:
                current_health -= 20
                del bombs[i]
        for i in range(len(coins) - 1, -1, -1):
            dx = player_pos[0] - coins[i][0]
            dy = player_pos[1] - coins[i][1]
            dz = player_pos[2] - coins[i][2]
            dist = math.sqrt(dx*dx + dy*dy + dz*dz)
            if dist < 20:
                coinscount += 1
                del coins[i]
        for i in range(len(bullets) - 1, -1, -1):
            bullets[i][1] += 15
            if enemy:
                dx = enemy['pos'][0] - bullets[i][0]
                dy = enemy['pos'][1] - bullets[i][1]
                dz = enemy['pos'][2] - bullets[i][2]
                dist = math.sqrt(dx*dx + dy*dy + dz*dz)
                if dist < 50:
                    enemy['health'] -= 10
                    bullets.pop(i)
                    if enemy['health'] <= 0:
                        current_level += 1
                        coinscount += 5*enemy['level']
                        ammo += 5*enemy['level']
                        score += 5*enemy['level']
                        enemy = None

                elif bullets[i][1] > enemy['pos'][1]:
                    bullets.pop(i)
        if current_health <= 0:
            if score > high_score:
                high_score = score
            drawingHUD = True
            enemy = None
            bombs = []
            coins = []
            bullets = []
            current_level = 0
    glutPostRedisplay()

def showScreen():
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    glLoadIdentity()
    glViewport(0, 0, 1000, 800)
    setupCamera()
    draw_shapes()
    glutSwapBuffers()

def main():
    glutInit()
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH)
    glutInitWindowSize(1000, 800)
    glutInitWindowPosition(0, 0)
    glutCreateWindow(b"PROGRAM WINDOW")
    glutDisplayFunc(showScreen)
    glutKeyboardFunc(keyboardListener)
    glutSpecialFunc(specialKeyListener)
    glutMouseFunc(mouseListener)
    glutIdleFunc(idle)
    glClearColor(0.2, 0.2, 0.5, 1.0)
    glutMainLoop()

if __name__ == "__main__":
    main()