import pygame
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *
import math
import random
import numpy as np

class Ball:
    def __init__(self):
        self.pos = [0, 0.5, 15]
        self.vel = [0, 0, 0]
        self.rot = [0, 0, 0]
        self.rot_vel = [0, 0, 0]
        self.radius = 0.5
        self.mass = 7.0
        self.launched = False
        self.finger_holes = []
        self.texture = None
        self.load_texture()
        
    def load_texture(self):
        # Create a simple procedural texture for the ball
        texture_size = 64
        texture_data = np.zeros((texture_size, texture_size, 3), dtype=np.uint8)
        
        for i in range(texture_size):
            for j in range(texture_size):
                # Create a marble-like pattern
                x = i / texture_size * 2 * math.pi
                y = j / texture_size * 2 * math.pi
                
                # Base color (dark blue/black)
                r = 20 + int(10 * math.sin(x * 5) * math.cos(y * 5))
                g = 20 + int(10 * math.sin(x * 3) * math.cos(y * 7))
                b = 60 + int(20 * math.sin(x * 7) * math.cos(y * 3))
                
                texture_data[i, j] = [r, g, b]
        
        self.texture = glGenTextures(1)
        glBindTexture(GL_TEXTURE_2D, self.texture)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texture_size, texture_size, 0, 
                     GL_RGB, GL_UNSIGNED_BYTE, texture_data)
        
    def reset(self):
        self.pos = [0, 0.5, 15]
        self.vel = [0, 0, 0]
        self.rot = [0, 0, 0]
        self.rot_vel = [0, 0, 0]
        self.launched = False
        
    def launch(self, power, angle):
        self.launched = True
        self.vel[2] = -power * 20  # Increased power
        self.vel[0] = math.sin(math.radians(angle)) * power * 8
        self.rot_vel[0] = -power * 300  # More realistic spin
        
    def update(self, dt, pins):
        if not self.launched:
            return False
            
        # Gravity
        self.vel[1] -= 9.8 * dt
        
        # Floor collision with improved physics
        if self.pos[1] - self.radius < 0:
            self.pos[1] = self.radius
            # Energy loss on bounce
            self.vel[1] = -self.vel[1] * 0.6  # Bounce with energy loss
            
            # Improved friction based on surface contact
            friction = 0.95
            self.vel[0] *= friction
            self.vel[2] *= friction
            
            # Add slight random variation for more realistic movement
            self.vel[0] += random.uniform(-0.1, 0.1)
            self.vel[2] += random.uniform(-0.1, 0.1)
            
        # Air resistance
        air_resistance = 0.99
        self.vel[0] *= air_resistance
        self.vel[2] *= air_resistance
        
        # Update position
        for i in range(3):
            self.pos[i] += self.vel[i] * dt
            self.rot[i] += self.rot_vel[i] * dt
            
        # Check collisions with pins
        for pin in pins:
            if pin.standing:
                dx = self.pos[0] - pin.pos[0]
                dz = self.pos[2] - pin.pos[2]
                dist = math.sqrt(dx*dx + dz*dz)
                
                if dist < self.radius + pin.radius:
                    pin.knock_down(self.vel)
                    # More realistic bounce physics
                    normal = [dx/dist, 0, dz/dist]
                    dot = self.vel[0]*normal[0] + self.vel[2]*normal[2]
                    self.vel[0] -= 1.8 * dot * normal[0]
                    self.vel[2] -= 1.8 * dot * normal[2]
                    
                    # Add some vertical bounce
                    self.vel[1] += 0.5
                    
        # Check if ball is out of bounds
        if self.pos[2] < -5 or abs(self.pos[0]) > 10:
            return True
            
        # Check if ball has stopped
        speed = math.sqrt(self.vel[0]**2 + self.vel[1]**2 + self.vel[2]**2)
        if speed < 0.1 and self.pos[1] <= self.radius + 0.01:
            return True
            
        return False
        
    def draw(self):
        glPushMatrix()
        glTranslatef(*self.pos)
        glRotatef(self.rot[0], 1, 0, 0)
        glRotatef(self.rot[1], 0, 1, 0)
        
        # Enable texturing for the ball
        glEnable(GL_TEXTURE_2D)
        glBindTexture(GL_TEXTURE_2D, self.texture)
        
        # Ball body with texture
        glColor3f(0.8, 0.8, 1.0)  # Lighter base color
        quad = gluNewQuadric()
        gluQuadricTexture(quad, GL_TRUE)
        gluSphere(quad, self.radius, 32, 32)
        
        # Finger holes
        glDisable(GL_TEXTURE_2D)
        glColor3f(0.05, 0.05, 0.05)
        for angle in [0, 120, 240]:
            glPushMatrix()
            glRotatef(angle, 0, 1, 0)
            glTranslatef(0.3, 0, 0)
            gluCylinder(quad, 0.06, 0.06, 0.2, 16, 1)
            glPopMatrix()
            
        glDisable(GL_TEXTURE_2D)
        glPopMatrix()

class Pin:
    def __init__(self, x, z):
        self.pos = [x, 0.75, z]
        self.vel = [0, 0, 0]
        self.rot = [0, 0, 0]
        self.rot_vel = [0, 0, 0]
        self.radius = 0.15
        self.height = 0.75
        self.standing = True
        self.fall_timer = 0
        self.initial_pos = [x, 0.75, z]  # Store initial position for reset
        
    def knock_down(self, impact_vel):
        if self.standing:
            self.standing = False
            # More realistic pin physics based on impact
            self.vel = [
                impact_vel[0] * random.uniform(0.3, 0.7), 
                random.uniform(1.5, 3.0), 
                impact_vel[2] * random.uniform(0.3, 0.7)
            ]
            self.rot_vel = [
                random.uniform(-400, 400), 
                random.uniform(-400, 400), 
                random.uniform(-400, 400)
            ]
            
    def reset(self):
        self.pos = self.initial_pos.copy()
        self.vel = [0, 0, 0]
        self.rot = [0, 0, 0]
        self.rot_vel = [0, 0, 0]
        self.standing = True
        self.fall_timer = 0
            
    def update(self, dt):
        if not self.standing:
            # Gravity
            self.vel[1] -= 9.8 * dt
            
            # Floor collision with improved physics
            if self.pos[1] < 0.1:
                self.pos[1] = 0.1
                self.vel[1] = -self.vel[1] * 0.3  # Bounce with energy loss
                self.vel[0] *= 0.7
                self.vel[2] *= 0.7
                self.rot_vel = [v * 0.7 for v in self.rot_vel]
                
            # Update position
            for i in range(3):
                self.pos[i] += self.vel[i] * dt
                self.rot[i] += self.rot_vel[i] * dt
                
            self.fall_timer += dt
            
    def draw(self):
        glPushMatrix()
        glTranslatef(*self.pos)
        
        if not self.standing:
            glRotatef(self.rot[0], 1, 0, 0)
            glRotatef(self.rot[1], 0, 1, 0)
            glRotatef(self.rot[2], 0, 0, 1)
        
        # Pin body with improved materials
        glColor3f(0.95, 0.95, 0.95)  # Brighter white
        quad = gluNewQuadric()
        gluCylinder(quad, 0.15, 0.08, self.height, 16, 1)
        
        # Red stripes with gloss effect
        glColor3f(0.9, 0.1, 0.1)
        glPushMatrix()
        glTranslatef(0, 0, self.height * 0.6)
        gluCylinder(quad, 0.11, 0.09, self.height * 0.1, 16, 1)
        glPopMatrix()
        
        # Second stripe
        glPushMatrix()
        glTranslatef(0, 0, self.height * 0.3)
        gluCylinder(quad, 0.12, 0.11, self.height * 0.1, 16, 1)
        glPopMatrix()
        
        # Top with rounded cap
        glPushMatrix()
        glTranslatef(0, 0, self.height)
        gluSphere(quad, 0.08, 16, 16)
        glPopMatrix()
        
        glPopMatrix()

class BowlingGame:
    def __init__(self):
        pygame.init()
        self.width, self.height = 1200, 800
        self.screen = pygame.display.set_mode((self.width, self.height), DOUBLEBUF | OPENGL)
        pygame.display.set_caption("Enhanced 3D Bowling Game")
        
        self.setup_gl()
        
        self.ball = Ball()
        self.pins = self.setup_pins()
        
        self.power = 0
        self.angle = 0
        self.charging = False
        self.max_power = 1.0
        
        self.score = 0
        self.frame = 1
        self.roll = 1
        self.game_over = False
        
        self.camera_pos = [0, 5, 20]
        self.camera_target = [0, 1, 0]
        self.camera_angle = 0
        self.camera_distance = 20
        
        self.lane_texture = self.create_lane_texture()
        self.background_color = [0.1, 0.2, 0.4]  # Dark blue background
        
        # Game state
        self.waiting_for_roll = True
        self.ball_done = False
        self.knocked_this_roll = 0
        
        # Lighting
        self.light_pos = [5, 10, 10]
        self.ambient_light = [0.4, 0.4, 0.4, 1.0]
        self.diffuse_light = [0.9, 0.9, 0.9, 1.0]
        self.specular_light = [1.0, 1.0, 1.0, 1.0]
        
    def create_lane_texture(self):
        texture_size = 256
        texture_data = np.zeros((texture_size, texture_size, 3), dtype=np.uint8)
        
        for i in range(texture_size):
            for j in range(texture_size):
                # Create wood grain pattern
                wood_intensity = 150 + int(50 * math.sin(i/20) * math.cos(j/30))
                texture_data[i, j] = [wood_intensity, wood_intensity-20, wood_intensity-40]
        
        texture = glGenTextures(1)
        glBindTexture(GL_TEXTURE_2D, texture)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texture_size, texture_size, 0, 
                     GL_RGB, GL_UNSIGNED_BYTE, texture_data)
        return texture
        
    def setup_gl(self):
        glEnable(GL_DEPTH_TEST)
        glEnable(GL_LIGHTING)
        glEnable(GL_LIGHT0)
        glEnable(GL_LIGHT1)  # Additional light
        glEnable(GL_COLOR_MATERIAL)
        glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)
        
        # Improved lighting
        glLightfv(GL_LIGHT0, GL_POSITION, (5, 10, 10, 1))
        glLightfv(GL_LIGHT0, GL_AMBIENT, (0.4, 0.4, 0.4, 1))
        glLightfv(GL_LIGHT0, GL_DIFFUSE, (0.9, 0.9, 0.9, 1))
        glLightfv(GL_LIGHT0, GL_SPECULAR, (1.0, 1.0, 1.0, 1))
        
        # Second light for better illumination
        glLightfv(GL_LIGHT1, GL_POSITION, (-5, 8, 5, 1))
        glLightfv(GL_LIGHT1, GL_AMBIENT, (0.2, 0.2, 0.2, 1))
        glLightfv(GL_LIGHT1, GL_DIFFUSE, (0.5, 0.5, 0.5, 1))
        glLightfv(GL_LIGHT1, GL_SPECULAR, (0.5, 0.5, 0.5, 1))
        
        # Material properties for better reflections
        glMaterialfv(GL_FRONT, GL_SPECULAR, [1.0, 1.0, 1.0, 1.0])
        glMaterialf(GL_FRONT, GL_SHININESS, 50.0)
        
        glMatrixMode(GL_PROJECTION)
        gluPerspective(45, self.width/self.height, 0.1, 100.0)
        glMatrixMode(GL_MODELVIEW)
        
    def setup_pins(self):
        pins = []
        start_z = -10
        spacing = 0.5
        
        # Standard 10-pin triangle formation
        for row in range(4):
            for col in range(row + 1):
                x = (col - row/2) * spacing
                z = start_z - row * spacing * 0.866
                pins.append(Pin(x, z))
                
        return pins
        
    def reset_pins(self):
        for pin in self.pins:
            pin.reset()
            
    def reset_ball(self):
        self.ball.reset()
        self.power = 0
        self.angle = 0
        self.charging = False
        self.ball_done = False
        self.waiting_for_roll = True
        
    def count_knocked_pins(self):
        return sum(1 for pin in self.pins if not pin.standing)
        
    def draw_lane(self):
        # Enable texturing for the lane
        glEnable(GL_TEXTURE_2D)
        glBindTexture(GL_TEXTURE_2D, self.lane_texture)
        
        # Lane with texture
        glColor3f(0.9, 0.8, 0.6)  # Wood color
        glBegin(GL_QUADS)
        glTexCoord2f(0, 0)
        glVertex3f(-2, 0, 20)
        glTexCoord2f(1, 0)
        glVertex3f(2, 0, 20)
        glTexCoord2f(1, 1)
        glVertex3f(2, 0, -15)
        glTexCoord2f(0, 1)
        glVertex3f(-2, 0, -15)
        glEnd()
        
        glDisable(GL_TEXTURE_2D)
        
        # Gutters with improved appearance
        glColor3f(0.3, 0.3, 0.3)
        glBegin(GL_QUADS)
        # Left gutter
        glVertex3f(-2.5, 0, 20)
        glVertex3f(-2, 0, 20)
        glVertex3f(-2, 0, -15)
        glVertex3f(-2.5, 0, -15)
        # Right gutter
        glVertex3f(2, 0, 20)
        glVertex3f(2.5, 0, 20)
        glVertex3f(2.5, 0, -15)
        glVertex3f(2, 0, -15)
        glEnd()
        
        # Gutter edges
        glColor3f(0.5, 0.5, 0.5)
        glLineWidth(2.0)
        glBegin(GL_LINES)
        # Left edge
        glVertex3f(-2.5, 0.1, 20)
        glVertex3f(-2.5, 0.1, -15)
        # Right edge
        glVertex3f(2.5, 0.1, 20)
        glVertex3f(2.5, 0.1, -15)
        glEnd()
        
        # Lane boundaries
        glColor3f(0.9, 0.9, 0.1)  # Bright yellow
        glLineWidth(3.0)
        glBegin(GL_LINES)
        # Left boundary
        glVertex3f(-2, 0.01, 20)
        glVertex3f(-2, 0.01, -15)
        # Right boundary
        glVertex3f(2, 0.01, 20)
        glVertex3f(2, 0.01, -15)
        glEnd()
        
        # Arrows with improved visibility
        glColor3f(0.7, 0.7, 0.2)
        for z in range(5, 15, 3):
            glBegin(GL_TRIANGLES)
            glVertex3f(0, 0.01, z)
            glVertex3f(-0.2, 0.01, z + 0.5)
            glVertex3f(0.2, 0.01, z + 0.5)
            glEnd()
            
        # Foul line
        glColor3f(1.0, 0.0, 0.0)  # Red
        glLineWidth(2.0)
        glBegin(GL_LINES)
        glVertex3f(-2, 0.02, 0)
        glVertex3f(2, 0.02, 0)
        glEnd()
            
        glEnable(GL_LIGHTING)
        
    def draw_hud(self):
        glDisable(GL_LIGHTING)
        glMatrixMode(GL_PROJECTION)
        glPushMatrix()
        glLoadIdentity()
        glOrtho(0, self.width, self.height, 0, -1, 1)
        glMatrixMode(GL_MODELVIEW)
        glPushMatrix()
        glLoadIdentity()
        
        glDisable(GL_DEPTH_TEST)
        
        # Semi-transparent HUD background
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        glColor4f(0.1, 0.1, 0.2, 0.7)
        glBegin(GL_QUADS)
        glVertex2f(0, 0)
        glVertex2f(self.width, 0)
        glVertex2f(self.width, 80)
        glVertex2f(0, 80)
        glEnd()
        glDisable(GL_BLEND)
        
        # Score display
        font = pygame.font.SysFont('Arial', 24)
        score_text = font.render(f"Score: {self.score} | Frame: {self.frame} | Roll: {self.roll}", True, (255, 255, 255))
        text_surface = pygame.image.tostring(score_text, 'RGBA', True)
        glRasterPos2f(20, 20)
        glDrawPixels(score_text.get_width(), score_text.get_height(), GL_RGBA, GL_UNSIGNED_BYTE, text_surface)
        
        # Power bar with improved visuals
        if not self.ball.launched and self.waiting_for_roll:
            bar_width = 200
            bar_height = 25
            x, y = 20, self.height - 60
            
            # Background
            glColor3f(0.3, 0.3, 0.3)
            glBegin(GL_QUADS)
            glVertex2f(x, y)
            glVertex2f(x + bar_width, y)
            glVertex2f(x + bar_width, y + bar_height)
            glVertex2f(x, y + bar_height)
            glEnd()
            
            # Power fill with gradient
            fill = self.power * bar_width
            if self.power < 0.3:
                glColor3f(0.0, 1.0, 0.0)  # Green
            elif self.power < 0.7:
                glColor3f(1.0, 1.0, 0.0)  # Yellow
            else:
                glColor3f(1.0, 0.0, 0.0)  # Red
                
            glBegin(GL_QUADS)
            glVertex2f(x, y)
            glVertex2f(x + fill, y)
            glVertex2f(x + fill, y + bar_height)
            glVertex2f(x, y + bar_height)
            glEnd()
            
            # Border
            glColor3f(1.0, 1.0, 1.0)
            glLineWidth(2.0)
            glBegin(GL_LINE_LOOP)
            glVertex2f(x, y)
            glVertex2f(x + bar_width, y)
            glVertex2f(x + bar_width, y + bar_height)
            glVertex2f(x, y + bar_height)
            glEnd()
            
            # Power label
            power_text = font.render("Power", True, (255, 255, 255))
            text_surface = pygame.image.tostring(power_text, 'RGBA', True)
            glRasterPos2f(x, y - 25)
            glDrawPixels(power_text.get_width(), power_text.get_height(), GL_RGBA, GL_UNSIGNED_BYTE, text_surface)
            
            # Angle indicator
            angle_y = y - 50
            glColor3f(0.5, 0.5, 1.0)
            glLineWidth(3.0)
            glBegin(GL_LINES)
            glVertex2f(x + bar_width/2, angle_y)
            glVertex2f(x + bar_width/2 + self.angle * 2, angle_y)
            glEnd()
            
            # Angle label
            angle_text = font.render("Angle", True, (255, 255, 255))
            text_surface = pygame.image.tostring(angle_text, 'RGBA', True)
            glRasterPos2f(x, angle_y - 25)
            glDrawPixels(angle_text.get_width(), angle_text.get_height(), GL_RGBA, GL_UNSIGNED_BYTE, text_surface)
            
            # Instructions
            instructions = font.render("Use LEFT/RIGHT to aim, SPACE to charge power", True, (200, 200, 100))
            text_surface = pygame.image.tostring(instructions, 'RGBA', True)
            glRasterPos2f(self.width - instructions.get_width() - 20, 20)
            glDrawPixels(instructions.get_width(), instructions.get_height(), GL_RGBA, GL_UNSIGNED_BYTE, text_surface)
        
        # Display knocked pins for current roll
        if self.ball_done:
            knocked_text = font.render(f"Knocked down: {self.knocked_this_roll} pins", True, (255, 255, 255))
            text_surface = pygame.image.tostring(knocked_text, 'RGBA', True)
            glRasterPos2f(self.width/2 - knocked_text.get_width()/2, 50)
            glDrawPixels(knocked_text.get_width(), knocked_text.get_height(), GL_RGBA, GL_UNSIGNED_BYTE, text_surface)
            
            continue_text = font.render("Press SPACE to continue", True, (200, 200, 100))
            text_surface = pygame.image.tostring(continue_text, 'RGBA', True)
            glRasterPos2f(self.width/2 - continue_text.get_width()/2, 80)
            glDrawPixels(continue_text.get_width(), continue_text.get_height(), GL_RGBA, GL_UNSIGNED_BYTE, text_surface)
        
        glEnable(GL_DEPTH_TEST)
        
        glPopMatrix()
        glMatrixMode(GL_PROJECTION)
        glPopMatrix()
        glMatrixMode(GL_MODELVIEW)
        glEnable(GL_LIGHTING)
        
    def handle_input(self):
        keys = pygame.key.get_pressed()
        
        # Camera controls
        if keys[K_a]:
            self.camera_angle -= 1
        if keys[K_d]:
            self.camera_angle += 1
        if keys[K_w] and self.camera_distance > 10:
            self.camera_distance -= 0.5
        if keys[K_s] and self.camera_distance < 30:
            self.camera_distance += 0.5
            
        # Update camera position based on angle
        rad_angle = math.radians(self.camera_angle)
        self.camera_pos[0] = math.sin(rad_angle) * self.camera_distance
        self.camera_pos[2] = math.cos(rad_angle) * self.camera_distance + 5
        
        if self.waiting_for_roll and not self.ball.launched:
            # Adjust angle
            if keys[K_LEFT]:
                self.angle -= 1
                self.angle = max(-30, self.angle)
            if keys[K_RIGHT]:
                self.angle += 1
                self.angle = min(30, self.angle)
                
            # Charge power
            if keys[K_SPACE]:
                if not self.charging:
                    self.charging = True
                    self.power = 0
                else:
                    self.power += 0.02
                    if self.power > self.max_power:
                        self.power = self.max_power
            else:
                if self.charging and self.power > 0.1:  # Minimum power threshold
                    self.ball.launch(self.power, self.angle)
                    self.charging = False
                    self.waiting_for_roll = False
                    
        # Continue to next roll
        if self.ball_done and keys[K_SPACE]:
            self.next_roll()
                    
    def next_roll(self):
        self.knocked_this_roll = self.count_knocked_pins()
        self.score += self.knocked_this_roll
        
        # Game progression logic
        if self.roll == 1:
            if self.knocked_this_roll == 10:  # Strike
                self.roll = 3  # Skip second roll
            else:
                self.roll = 2
        elif self.roll == 2:
            self.roll = 3
            
        if self.roll == 3:
            self.frame += 1
            self.roll = 1
            self.reset_pins()
            
        if self.frame > 10:  # End of game
            self.game_over = True
            
        self.reset_ball()
        
    def run(self):
        clock = pygame.time.Clock()
        running = True
        
        while running:
            dt = clock.tick(60) / 1000.0
            
            for event in pygame.event.get():
                if event.type == QUIT:
                    running = False
                elif event.type == KEYDOWN:
                    if event.key == K_ESCAPE:
                        running = False
                    elif event.key == K_r:
                        self.reset_ball()
                        self.reset_pins()
                        self.score = 0
                        self.frame = 1
                        self.roll = 1
                        self.game_over = False
                        
            self.handle_input()
            
            # Update game objects
            if not self.ball_done:
                self.ball_done = self.ball.update(dt, self.pins)
                
            for pin in self.pins:
                pin.update(dt)
                
            # Render
            glClearColor(*self.background_color, 1.0)
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
            glLoadIdentity()
            
            # Update camera target to follow ball
            if self.ball.launched and not self.ball_done:
                self.camera_target[0] = self.ball.pos[0] * 0.1
                self.camera_target[2] = self.ball.pos[2] * 0.1
            else:
                self.camera_target = [0, 1, 0]
            
            gluLookAt(self.camera_pos[0], self.camera_pos[1], self.camera_pos[2],
                     self.camera_target[0], self.camera_target[1], self.camera_target[2],
                     0, 1, 0)
            
            self.draw_lane()
            self.ball.draw()
            
            for pin in self.pins:
                pin.draw()
                
            self.draw_hud()
            
            # Game over screen
            if self.game_over:
                glDisable(GL_LIGHTING)
                glMatrixMode(GL_PROJECTION)
                glPushMatrix()
                glLoadIdentity()
                glOrtho(0, self.width, self.height, 0, -1, 1)
                glMatrixMode(GL_MODELVIEW)
                glPushMatrix()
                glLoadIdentity()
                
                glDisable(GL_DEPTH_TEST)
                
                # Semi-transparent overlay
                glEnable(GL_BLEND)
                glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
                glColor4f(0.0, 0.0, 0.0, 0.7)
                glBegin(GL_QUADS)
                glVertex2f(0, 0)
                glVertex2f(self.width, 0)
                glVertex2f(self.width, self.height)
                glVertex2f(0, self.height)
                glEnd()
                glDisable(GL_BLEND)
                
                # Game over text
                font = pygame.font.SysFont('Arial', 48)
                game_over_text = font.render("GAME OVER", True, (255, 255, 255))
                text_surface = pygame.image.tostring(game_over_text, 'RGBA', True)
                glRasterPos2f(self.width/2 - game_over_text.get_width()/2, self.height/2 - 50)
                glDrawPixels(game_over_text.get_width(), game_over_text.get_height(), GL_RGBA, GL_UNSIGNED_BYTE, text_surface)
                
                score_text = font.render(f"Final Score: {self.score}", True, (255, 255, 255))
                text_surface = pygame.image.tostring(score_text, 'RGBA', True)
                glRasterPos2f(self.width/2 - score_text.get_width()/2, self.height/2 + 20)
                glDrawPixels(score_text.get_width(), score_text.get_height(), GL_RGBA, GL_UNSIGNED_BYTE, text_surface)
                
                restart_text = pygame.font.SysFont('Arial', 24).render("Press R to restart", True, (200, 200, 100))
                text_surface = pygame.image.tostring(restart_text, 'RGBA', True)
                glRasterPos2f(self.width/2 - restart_text.get_width()/2, self.height/2 + 80)
                glDrawPixels(restart_text.get_width(), restart_text.get_height(), GL_RGBA, GL_UNSIGNED_BYTE, text_surface)
                
                glEnable(GL_DEPTH_TEST)
                glPopMatrix()
                glMatrixMode(GL_PROJECTION)
                glPopMatrix()
                glMatrixMode(GL_MODELVIEW)
                glEnable(GL_LIGHTING)
            
            pygame.display.flip()
            
        pygame.quit()

if __name__ == "__main__":
    game = BowlingGame()
    game.run()