diff --git a/tankwars-game/tankwars-game-win.py b/tankwars-game/tankwars-game-win.py new file mode 100644 index 0000000..58f1b9f --- /dev/null +++ b/tankwars-game/tankwars-game-win.py @@ -0,0 +1,650 @@ +#!/usr/bin/python3 +# Description: tankwars - A Pygame written in Python. +# Usage: python3 tankwars-game.py +# Author: Justin Oros +# Source: https://github.com/JustinOros + +import pygame +from pygame.locals import * +from OpenGL.GL import * +from OpenGL.GLU import * +import random, math, sys, os +from OpenGL.GLUT import glutInit, glutSolidSphere, glutSolidCube, glutBitmapCharacter, GLUT_BITMAP_HELVETICA_18 +import time +import numpy as np + +pygame.init() +pygame.mixer.init() +glutInit() + +sound_cannon = pygame.mixer.Sound("sound-tank-cannon.mp3") +sound_reload = pygame.mixer.Sound("sound-tank-reload.mp3") +sound_move = pygame.mixer.Sound("sound-tank-movement.mp3") +sound_turret = pygame.mixer.Sound("sound-tank-turret.mp3") +sound_explosion = pygame.mixer.Sound("sound-tank-explosion.mp3") +sound_alarm = pygame.mixer.Sound("sound-tank-alarm.mp3") +sound_warning = pygame.mixer.Sound("sound-tank-warning.mp3") + +pygame.mixer.music.load("music-track01.mp3") +pygame.mixer.music.set_volume(0.5) +pygame.mixer.music.play(-1) + +pygame.joystick.init() +joysticks = [] +for i in range(pygame.joystick.get_count()): + j = pygame.joystick.Joystick(i) + j.init() + joysticks.append(j) + +info = pygame.display.Info() +WIDTH, HEIGHT = info.current_w, info.current_h +screen = pygame.display.set_mode((WIDTH, HEIGHT), DOUBLEBUF | OPENGL | FULLSCREEN) +pygame.mouse.set_visible(False) +pygame.event.set_grab(True) + +glEnable(GL_DEPTH_TEST) +glEnable(GL_COLOR_MATERIAL) +glEnable(GL_LIGHTING) +glEnable(GL_LIGHT0) +glLightfv(GL_LIGHT0, GL_DIFFUSE, (1,1,1,1)) +glEnable(GL_TEXTURE_2D) +glMatrixMode(GL_PROJECTION) +glLoadIdentity() +gluPerspective(60, WIDTH/HEIGHT, 0.1, 500) +glMatrixMode(GL_MODELVIEW) + +player_pos = [0,0,0] +player_angle = 0 +pitch = 0 +crosshair_offset = 0 +level = 1 +bullets = [] +enemy_bullets = [] +enemies = [] +enemy_speed = 2 +last_fire_time = 0 +FIRE_COOLDOWN = 1.5 +ENEMY_FIRE_COOLDOWN = 2.0 +total_enemies = 0 +TANK_COLLISION_RADIUS = 4 +moving_sound_playing = False +player_life = 100 +player_tank_visible = True +game_over = False +game_over_time = 0 +alarm_playing = False + +NPY_AVAILABLE = False +tank_vertices = None +tank_display_list = None +TANK_SCALE = 0.003 + +if os.path.isfile("object-tank.npy"): + try: + tank_data = np.load("object-tank.npy", allow_pickle=True).item() + NPY_AVAILABLE = True + + tank_display_list = glGenLists(1) + glNewList(tank_display_list, GL_COMPILE) + glBegin(GL_TRIANGLES) + for face in tank_data['faces']: + for vertex_idx in face: + v = tank_data['vertices'][vertex_idx] + glVertex3f(v[0]*TANK_SCALE, v[1]*TANK_SCALE, v[2]*TANK_SCALE) + glEnd() + glEndList() + + print(f"Tank model loaded successfully!") + print(f"Vertices: {len(tank_data['vertices'])}") + print(f"Faces: {len(tank_data['faces'])}") + + except Exception as e: + print(f"Error loading tank model: {e}") + import traceback + traceback.print_exc() + NPY_AVAILABLE = False +else: + print("object-tank.npy file not found!") + +def spawn_enemies(level): + global total_enemies + enemies.clear() + count = level*5 + total_enemies = count + min_dist = 80 + for _ in range(count): + while True: + x = random.randint(-200,200) + z = random.randint(-200,200) + collision = math.hypot(x-player_pos[0], z-player_pos[2])0 else 0 + glPushMatrix() + glRotatef(turret_yaw - direction, 0, 1, 0) + glTranslatef(0, 1, 1.5) + glRotatef(-turret_pitch, 1, 0, 0) + glColor3f(0.8,0.8,0) + if not NPY_AVAILABLE: + glScalef(0.2,0.2,2) + draw_cube(1) + glPopMatrix() + glPopMatrix() + +def draw_enemy_tank(x, z, body_direction=0, turret_target=None): + distance = math.hypot(x - player_pos[0], z - player_pos[2]) + if distance >= 100: + return + alpha = 1.0 + if distance > 90: + alpha = max(0, 1 - (distance-90)/10) + glEnable(GL_BLEND) + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) + glColor4f(1,0,0,alpha) + glPushMatrix() + glTranslatef(x, 0, z) + glRotatef(body_direction + 90, 0, 1, 0) + if NPY_AVAILABLE and tank_display_list: + glCallList(tank_display_list) + else: + draw_cube(2) + if turret_target: + dx = turret_target[0]-x + dz = turret_target[2]-z + turret_angle = math.degrees(math.atan2(dx,-dz)) + glPushMatrix() + glRotatef(turret_angle - (body_direction + 90), 0,1,0) + glTranslatef(0,1,1.5) + glColor4f(1,1,0,alpha) + if not NPY_AVAILABLE: + glScalef(0.2,0.2,2) + draw_cube(1) + glPopMatrix() + glPopMatrix() + glDisable(GL_BLEND) + +def check_tank_collision(new_pos, ignore_enemy=None, is_player=False): + for t in enemies: + if t is ignore_enemy: + continue + if math.hypot(new_pos[0]-t['pos'][0], new_pos[2]-t['pos'][2]) < TANK_COLLISION_RADIUS: + return True + if not is_player: + if math.hypot(new_pos[0]-player_pos[0], new_pos[2]-player_pos[2]) < TANK_COLLISION_RADIUS: + return True + return False + +def move_enemies(dt): + if player_life<=0: return + current_time = time.time() + for e in enemies: + distance_to_player = math.hypot(e['pos'][0]-player_pos[0], e['pos'][2]-player_pos[2]) + previously_detected = e['player_detected'] + e['player_detected'] = distance_to_player <= 100 + + if e['player_detected'] and not previously_detected and not e['warning_played']: + sound_warning.play() + e['warning_played'] = True + + if e['player_detected']: + e['state'] = 'hunting' + target_dx = player_pos[0] - e['pos'][0] + target_dz = player_pos[2] - e['pos'][2] + e['target_angle'] = math.degrees(math.atan2(target_dx, -target_dz)) + if e['state']=='rotating': + angle_diff = (e['target_angle'] - e['angle'] + 540)%360 - 180 + max_rot = 120*dt + if abs(angle_diff)<5: + e['angle']=e['target_angle'] + if not e['player_detected']: + e['state']='moving' + e['move_timer']=30 + else: + e['state']='hunting' + else: + e['angle'] += max_rot if angle_diff>0 else -max_rot + e['angle']%=360 + elif e['state']=='moving': + dx = math.sin(math.radians(e['angle'])) + dz = -math.cos(math.radians(e['angle'])) + new_pos = [e['pos'][0]+dx*enemy_speed*dt,0,e['pos'][2]+dz*enemy_speed*dt] + if not check_tank_collision(new_pos,ignore_enemy=e): + e['pos'][0]=new_pos[0] + e['pos'][2]=new_pos[2] + else: + e['target_angle']=random.uniform(0,360) + e['state']='rotating' + e['move_timer']-=dt + if e['move_timer']<=0: + if random.random()<0.3: + e['target_angle']=random.uniform(0,360) + e['state']='rotating' + else: + e['move_timer']=30 + elif e['state']=='hunting': + target_dx = player_pos[0]-e['pos'][0] + target_dz = player_pos[2]-e['pos'][2] + target_angle = math.degrees(math.atan2(target_dx, -target_dz)) + angle_diff = (target_angle - e['angle'] + 540)%360 - 180 + max_rot = 90*dt + if abs(angle_diff)0 else -max_rot + e['angle']%=360 + if abs(angle_diff)<45: + dx = math.sin(math.radians(e['angle'])) + dz = -math.cos(math.radians(e['angle'])) + new_pos = [e['pos'][0]+dx*enemy_speed*dt,0,e['pos'][2]+dz*enemy_speed*dt] + if not check_tank_collision(new_pos,ignore_enemy=e): + e['pos'][0]=new_pos[0] + e['pos'][2]=new_pos[2] + if abs(angle_diff)<15 and distance_to_player<=50: + if current_time - e.get('last_fire',0) > ENEMY_FIRE_COOLDOWN: + e['last_fire']=current_time + bx,by,bz = e['pos'][0],1,e['pos'][2] + dx = math.sin(math.radians(e['angle'])) + dz = -math.cos(math.radians(e['angle'])) + dy=0 + enemy_bullets.append([bx,by,bz,dx,dy,dz,current_time]) + sound_cannon.play() + if not e['player_detected']: + e['target_angle']=random.uniform(0,360) + e['state']='rotating' + +spawn_enemies(level) +clock = pygame.time.Clock() +running=True +radar_sweep_angle=0.0 + +while running: + dt=clock.tick(60)/1000 + keys=pygame.key.get_pressed() + move_vector=[0,0,0] + fire_pressed=False + speed_multiplier = 2 if keys[K_LSHIFT] or keys[K_RSHIFT] else 1 + + for joystick in joysticks: + lx=joystick.get_axis(0) + ly=joystick.get_axis(1) + if abs(lx)>0.2: move_vector[0]+=math.cos(math.radians(player_angle))*lx*dt*5*speed_multiplier; move_vector[2]+=math.sin(math.radians(player_angle))*lx*dt*5*speed_multiplier + if abs(ly)>0.2: move_vector[0]+=math.sin(math.radians(player_angle))*(-ly)*dt*5*speed_multiplier; move_vector[2]-=math.cos(math.radians(player_angle))*(-ly)*dt*5*speed_multiplier + if joystick.get_button(0) or joystick.get_button(4) or joystick.get_button(5): fire_pressed=True + if joystick.get_button(6): crosshair_offset-=5 + if joystick.get_button(7): crosshair_offset+=5 + + if keys[K_w]: move_vector[0]+=math.sin(math.radians(player_angle))*5*dt*speed_multiplier; move_vector[2]-=math.cos(math.radians(player_angle))*5*dt*speed_multiplier + if keys[K_s]: move_vector[0]-=math.sin(math.radians(player_angle))*5*dt*speed_multiplier; move_vector[2]+=math.cos(math.radians(player_angle))*5*dt*speed_multiplier + if keys[K_a]: move_vector[0]-=math.cos(math.radians(player_angle))*5*dt*speed_multiplier; move_vector[2]-=math.sin(math.radians(player_angle))*5*dt*speed_multiplier + if keys[K_d]: move_vector[0]+=math.cos(math.radians(player_angle))*5*dt*speed_multiplier; move_vector[2]+=math.sin(math.radians(player_angle))*5*dt*speed_multiplier + + moving=any(move_vector) + if moving and not moving_sound_playing: sound_move.play(-1); moving_sound_playing=True + elif not moving and moving_sound_playing: sound_move.stop(); moving_sound_playing=False + + proposed_pos=[player_pos[0]+move_vector[0],0,player_pos[2]+move_vector[2]] + if not check_tank_collision(proposed_pos,is_player=True): + player_pos[0]=proposed_pos[0]; player_pos[2]=proposed_pos[2] + + for event in pygame.event.get(): + if event.type==QUIT: running=False + if event.type==KEYDOWN: + if event.key==K_ESCAPE: running=False + if event.key==K_SPACE: fire_pressed=True + if event.key==K_v: player_tank_visible = not player_tank_visible + if event.type==MOUSEBUTTONDOWN and event.button==1: fire_pressed=True + if event.type==pygame.USEREVENT+1: + sound_reload.play() + pygame.time.set_timer(pygame.USEREVENT+1,0) + + current_time=time.time() + if fire_pressed and (current_time - last_fire_time>=FIRE_COOLDOWN): + last_fire_time=current_time + bx=player_pos[0]; by=1; bz=player_pos[2] + dx=math.sin(math.radians(player_angle)) + dy=math.sin(math.radians(pitch)) + dz=-math.cos(math.radians(player_angle)) + bullets.append([bx,by,bz,dx,dy,dz,current_time]) + sound_cannon.play() + pygame.time.set_timer(pygame.USEREVENT+1,500) + + mx,my=pygame.mouse.get_rel() + player_angle+=mx*0.1 + pitch-=my*0.1 + pitch=max(-10,min(10,pitch)) + + for b in bullets: + b[0]+=b[3]*60*dt + b[1]+=b[4]*30*dt - 9.8*dt*dt + b[2]+=b[5]*60*dt + + for b in enemy_bullets: + b[0]+=b[3]*60*dt + b[1]+=b[4]*30*dt + b[2]+=b[5]*60*dt + + current_time=time.time() + new_bullets=[] + for b in bullets[:]: + keep=True + if b[1]<0 or (current_time-b[6])>10.0: + keep=False + else: + for e in enemies[:]: + if abs(b[0]-e['pos'][0])<2 and abs(b[2]-e['pos'][2])<2: + enemies.remove(e) + total_enemies-=1 + sound_explosion.set_volume(0.5) + sound_explosion.play() + keep=False + break + if keep: new_bullets.append(b) + bullets=new_bullets + + new_enemy_bullets=[] + for b in enemy_bullets[:]: + keep=True + if b[1]<0 or (current_time-b[6])>10.0: + keep=False + else: + if player_life>0 and abs(b[0]-player_pos[0])<2 and abs(b[2]-player_pos[2])<2: + player_life-=25 + sound_explosion.play() + keep=False + if player_life<=0: + game_over=True + game_over_time=time.time() + pygame.mixer.music.stop() + if keep: new_enemy_bullets.append(b) + enemy_bullets=new_enemy_bullets + + if player_life <= 25 and not alarm_playing: + sound_alarm.play(-1) + alarm_playing = True + elif player_life > 25 and alarm_playing: + sound_alarm.stop() + alarm_playing = False + + move_enemies(dt) + if len(enemies)==0 and not game_over: + level+=1 + player_life=100 + spawn_enemies(level) + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + glLoadIdentity() + cam_height=4 + rad_yaw=math.radians(player_angle) + rad_pitch=math.radians(pitch) + dx=math.sin(rad_yaw)*math.cos(rad_pitch) + dy=math.sin(rad_pitch) + dz=-math.cos(rad_yaw)*math.cos(rad_pitch) + eye_x=player_pos[0]; eye_y=cam_height; eye_z=player_pos[2] + center_x=eye_x+dx+crosshair_offset*0.05; center_y=eye_y+dy; center_z=eye_z+dz + gluLookAt(eye_x,eye_y,eye_z,center_x,center_y,center_z,0,1,0) + glLightfv(GL_LIGHT0, GL_POSITION, (eye_x,eye_y,eye_z,1)) + + turret_target=[center_x,center_y,center_z] + + draw_sky() + draw_grid() + if player_tank_visible: + draw_player_tank(player_pos[0],player_pos[2],player_angle,turret_target) + for e in enemies: + draw_enemy_tank(e['pos'][0], e['pos'][2], body_direction=e['angle'], turret_target=player_pos) + for b in bullets: + draw_bullet(b[0],b[1],b[2],(0,1,1)) + for b in enemy_bullets: + draw_bullet(b[0],b[1],b[2],(1,0,0)) + draw_crosshair() + draw_text(WIDTH-100, HEIGHT-10,f"Enemies: {total_enemies}",color=(0,1,0)) + draw_text(10,HEIGHT-10,f"Life: {player_life}%",color=(0,1,0)) + if game_over: + draw_text(WIDTH//2-70, HEIGHT//2-20, "GAME OVER", color=(1,0,0)) + if time.time()-game_over_time>1: + running=False + + glDisable(GL_LIGHTING) + glMatrixMode(GL_PROJECTION) + glPushMatrix() + glLoadIdentity() + gluOrtho2D(0, WIDTH, 0, HEIGHT) + glMatrixMode(GL_MODELVIEW) + glPushMatrix() + glLoadIdentity() + radar_radius=80 + radar_x=WIDTH-radar_radius-20 + radar_y=HEIGHT-radar_radius-20 + glColor3f(0,0.5,0) + glBegin(GL_LINE_LOOP) + for i in range(360): + theta=math.radians(i) + x=radar_x+radar_radius*math.cos(theta) + y=radar_y+radar_radius*math.sin(theta) + glVertex2f(x,y) + glEnd() + sweep_length=radar_radius + sweep_rad=math.radians(radar_sweep_angle) + ex=radar_x+math.sin(sweep_rad)*sweep_length + ey=radar_y+math.cos(sweep_rad)*sweep_length + glColor3f(0,1,0) + glLineWidth(2) + glBegin(GL_LINES) + glVertex2f(radar_x,radar_y) + glVertex2f(ex,ey) + glEnd() + radar_sweep_angle=(radar_sweep_angle+120*dt)%360 + glColor3f(0,1,1) + glPointSize(6) + glBegin(GL_POINTS) + glVertex2f(radar_x,radar_y) + glEnd() + for idx,e in enumerate(enemies): + dx=e['pos'][0]-player_pos[0] + dz=e['pos'][2]-player_pos[2] + distance=math.hypot(dx,dz) + max_display_distance=100 + display_distance=min(distance,max_display_distance)/max_display_distance*radar_radius + angle=math.atan2(dx,-dz)-math.radians(player_angle) + ex=radar_x+math.sin(angle)*display_distance + ey=radar_y+math.cos(angle)*display_distance + glColor3f(1,0,0) + glPointSize(4) + glBegin(GL_POINTS) + glVertex2f(ex,ey) + glEnd() + glPopMatrix() + glMatrixMode(GL_PROJECTION) + glPopMatrix() + glMatrixMode(GL_MODELVIEW) + glEnable(GL_LIGHTING) + + pygame.display.flip() + +pygame.quit()