# ################ #
# ballistix.py #
# ################ #
# by Meithan West #
# 22 / Jan / 2009 #
# ################ #
# Game instructions:
# Shoot the squares that appear on the screen
# LEFT/RIGHT: change turrent angle
# UP/DOWN: increase/decrease power
# SPACE: fire!
# ESCAPE: quit
# If the game runs too slow/fast, tweak the variable 'speed'
# in the 'Settings' section below
from __future__ import division
from math import *
import pygame
import random
pygame.init()
# Definitions
black = (0, 0, 0)
white = (255, 255, 255)
blue = (0, 0, 255)
green = (0, 255, 0)
red = (255, 0, 0)
yellow = (255, 255, 0)
purple = (255, 0, 255)
pi = 3.14159
g = 9.8
# Settings
width = 800
height = 550
bgcolor = blue
speed = 1000
# ###################### #
# Cannon class definition #
# ###################### #
class Cannon:
# Constructor
def __init__(self, surface):
self.surface = surface
self.x = 15
self.y = 65
self.alpha = 30
self.size = 30
self.power = 70
self.fired = False
self.score = 0
self.shots = 0
self.charge = 1000
self.draw(1)
# Cannon shoots
def shoot(self):
self.fired = True
self.shots += 1
self.charge = 0
# Keyboard input handler
def key_event(self,event):
if event.key == pygame.K_LEFT:
if self.alpha < 90:
self.draw(0)
self.alpha += 1
self.draw(1)
elif event.key == pygame.K_RIGHT:
if self.alpha > 0:
self.draw(0)
self.alpha -= 1
self.draw(1)
elif event.key == pygame.K_DOWN:
if self.power > 40:
self.power -= 1
elif event.key == pygame.K_UP:
if self.power < 120:
self.power += 1
# Drawing function"
def draw(self,mode):
size = self.size
# base
x1 = scrx(self.x-size/2)
y1 = scry(self.y+size/2)
x2 = scrx(self.x-size/2)
y2 = scry(self.y)
if mode == 0: color = bgcolor
else: color = white
pygame.draw.arc(self.surface, color, (x1,y1,size,size),0,3.2,1)
pygame.draw.rect(self.surface, color, (x2,y2,size+1,size/2),1)
# barrel
x1 = scrx(self.x)
y1 = scry(self.y)
x2 = scrx(self.x + size*cos(self.alpha*pi/180))
y2 = scry(self.y + size*sin(self.alpha*pi/180))
if mode == 0: color = bgcolor
else: color = yellow
pygame.draw.line(self.surface, color, (x1,y1),(x2,y2),1)
# ###################### #
# Shell class definition #
# ###################### #
class Shell:
# Constructor
def __init__(self,surface,alpha,power):
self.surface = surface
self.size = 3
# kinematics
self.x0 = cannon.x + cannon.size*cos(alpha*pi/180)*1.1
self.y0 = cannon.y + cannon.size*sin(alpha*pi/180)*1.1
self.vx0 = power*cos(alpha*pi/180)
self.vy0 = power*sin(alpha*pi/180)
self.t0 = t
self.x = self.x0
self.y = self.y0
self.vx = self.vx0
self.vy = self.vy0
# Function to move the shell
# Uses exact solutions to the kinematic equations
def move(self):
self.undraw()
self.x = self.x0 + self.vx0*(t-self.t0)
self.y = self.y0 + self.vy0*(t-self.t0) - g/2*(t-self.t0)**2
self.vx = self.vx0
self.vy = self.vy0 - g*(t-self.t0)
if not self.destroyed(): self.draw()
# Check if shell is out of the playing area
def destroyed(self):
sx = self.x
sy = height-self.y
if sx > width or sy > height-55-self.size:
return True
else:
return False
# Drawing function
def draw(self):
# shell
sx = scrx(self.x)
sy = scry(self.y)
pygame.draw.circle(self.surface,red,(sx,sy),self.size,0)
# trail
alpha = atan(self.vy/self.vx)
cosa = cos(alpha)
sina = sin(alpha)
sx = scrx(self.x-self.size*cosa*1.3)
sy = scry(self.y-self.size*sina*1.3)
self.surface.set_at((sx,sy),red)
# Erasing function
def undraw(self):
sx = scrx(self.x)
sy = scry(self.y)
pygame.draw.circle(self.surface,bgcolor,(sx,sy),self.size,0)
# Utility function to return the shell position
def pos(self):
return self.x, self.y
# ####################### #
# Target class definition #
# ####################### #
class Target:
# Constructor
def __init__(self,surface):
self.x = random.randint(50, width-10)
self.y = random.randint(10, height-50)
self.size = 10
self.surface = surface
self.draw()
# Drawing function
def draw(self):
sx = scrx(self.x)
sy = scry(self.y)
pygame.draw.rect(self.surface,yellow,(sx,sy,self.size,self.size),0)
# Erasing function
def undraw(self):
sx = scrx(self.x)
sy = scry(self.y)
pygame.draw.rect(self.surface,bgcolor,(sx,sy,self.size,self.size),0)
# Checks if the target was hit by a shell
def hit(self,(x,y),rad):
if x > (self.x-rad) and \
x < (self.x+self.size+rad) and \
y > (self.y-self.size-rad) and \
y < (self.y+rad):
return True
else: return False
# ###################### #
# Miscelaneous functions #
# ###################### #
# Draws the scoring board
def drawboard():
pygame.draw.rect(screen,bgcolor,(0,height-49,width,49),0)
printos(screen, "Power = " + str(cannon.power), 2, height-40, white, font)
printos(screen, "Angle = " + str(cannon.alpha), 2, height-20, white, font)
printos(screen, "Score = " + str(cannon.score), width/2-20, height-40, white, font)
printos(screen, "Score = " + str(cannon.score), width/2-20, height-40, white, font)
if cannon.shots == 0:
printos(screen, "Accuracy = 0%", width/2-20, height-20, white, font)
else:
printos(screen, "Accuracy = " + str(int(cannon.score/cannon.shots*100)) + "%", width/2-20, height-20, white, font)
# Reload indicator
if cannon.charge == 1000:
printos(screen, "Cannon: ", 100, height-40, white, font)
printos(screen, "Ready", 160, height-40, green, font)
pygame.draw.rect(screen,white,(100,height-21,100,10),1)
pygame.draw.rect(screen,green,(101,height-20,cannon.charge/10-2,8),0)
else:
printos(screen, "Cannon: ", 100, height-40, white, font)
printos(screen, "Reloading", 160, height-40, red, font)
pygame.draw.rect(screen,white,(100,height-21,100,10),1)
pygame.draw.rect(screen,red,(101,height-20,cannon.charge/10-2,8),0)
# Functions to convert game coordinates to screen coordinates
def scrx(x):
return int(x)
def scry(y):
return int(height-y)
# Generic screen printing function
def printos(surface, text, x, y, color, font):
new = font.render(text, 1, color)
surface.blit(new, (x, y))
# ################## #
# Main program start #
# ################## #
# Initialization
screen = pygame.display.set_mode((width,height))
clock = pygame.time.Clock()
pygame.key.set_repeat(25)
pygame.font.init()
font = pygame.font.Font(None, 20)
screen.fill(bgcolor)
pygame.draw.line(screen, white, (0,height-50),(width,height-50), 1)
t=0
cannon = Cannon(screen)
target = Target(screen)
shell_list = []
drawboard()
running = True
# Main game loop
while running:
# Event handler
for event in pygame.event.get():
if event.type == pygame.QUIT:
print "Thanks for playing!"
running = False
# Keyboard input
elif event.type == pygame.KEYDOWN:
# ESCAPE exits the game
if event.key == pygame.K_ESCAPE:
print "Thanks for playing!"
running = False
# SPACE shoots
elif event.key == pygame.K_SPACE:
if cannon.charge == 1000:
cannon.shoot()
shell_list.append(Shell(screen,cannon.alpha,cannon.power))
# All other key events are handled by the Cannon class
else:
cannon.key_event(event)
# For each shell
if len(shell_list) > 0:
for index,shell in enumerate(shell_list):
shell.move()
if shell.destroyed():
shell_list.pop(index)
if target.hit(shell.pos(),shell.size):
cannon.score += 1
shell.undraw()
shell_list.pop(index)
target.undraw()
target = Target(screen)
# Recharge cannon
if cannon.charge < 1000: cannon.charge+=2
# Draw frame
drawboard()
pygame.display.flip()
# Update time
t += 0.01
clock.tick(speed)