Skip to content

Instantly share code, notes, and snippets.

@8Observer8
Created February 10, 2023 11:40
Show Gist options
  • Save 8Observer8/026781606c0ad525659b626d11601802 to your computer and use it in GitHub Desktop.
Save 8Observer8/026781606c0ad525659b626d11601802 to your computer and use it in GitHub Desktop.
#!/usr/bin/python
# coding=utf-8
import os, pygame, time, random, uuid, sys
class myRect(pygame.Rect):
""" Add type property """
def __init__(self, left, top, width, height, type):
pygame.Rect.__init__(self, left, top, width, height)
self.type = type
class Timer(object):
def __init__(self):
self.timers = []
def add(self, interval, f, repeat = -1):
options = {
"interval" : interval,
"callback" : f,
"repeat" : repeat,
"times" : 0,
"time" : 0,
"uuid" : uuid.uuid4()
}
self.timers.append(options)
return options["uuid"]
def destroy(self, uuid_nr):
for timer in self.timers:
if timer["uuid"] == uuid_nr:
self.timers.remove(timer)
return
def update(self, time_passed):
for timer in self.timers:
timer["time"] += time_passed
if timer["time"] > timer["interval"]:
timer["time"] -= timer["interval"]
timer["times"] += 1
if timer["repeat"] > -1 and timer["times"] == timer["repeat"]:
self.timers.remove(timer)
try:
timer["callback"]()
except:
try:
self.timers.remove(timer)
except:
pass
class Castle():
""" Player's castle/fortress """
(STATE_STANDING, STATE_DESTROYED, STATE_EXPLODING) = range(3)
def __init__(self):
global sprites
# images
self.img_undamaged = sprites.subsurface(0, 15*2, 16*2, 16*2)
self.img_destroyed = sprites.subsurface(16*2, 15*2, 16*2, 16*2)
# init position
self.rect = pygame.Rect(12*16, 24*16, 32, 32)
# start w/ undamaged and shiny castle
self.rebuild()
def draw(self):
""" Draw castle """
global screen
screen.blit(self.image, self.rect.topleft)
if self.state == self.STATE_EXPLODING:
if not self.explosion.active:
self.state = self.STATE_DESTROYED
del self.explosion
else:
self.explosion.draw()
def rebuild(self):
""" Reset castle """
self.state = self.STATE_STANDING
self.image = self.img_undamaged
self.active = True
def destroy(self):
""" Destroy castle """
self.state = self.STATE_EXPLODING
self.explosion = Explosion(self.rect.topleft)
self.image = self.img_destroyed
self.active = False
class Bonus():
""" Various power-ups
When bonus is spawned, it begins flashing and after some time dissapears
Available bonusses:
grenade : Picking up the grenade power up instantly wipes out ever enemy presently on the screen, including Armor Tanks regardless of how many times you've hit them. You do not, however, get credit for destroying them during the end-stage bonus points.
helmet : The helmet power up grants you a temporary force field that makes you invulnerable to enemy shots, just like the one you begin every stage with.
shovel : The shovel power up turns the walls around your fortress from brick to stone. This makes it impossible for the enemy to penetrate the wall and destroy your fortress, ending the game prematurely. The effect, however, is only temporary, and will wear off eventually.
star : The star power up grants your tank with new offensive power each time you pick one up, up to three times. The first star allows you to fire your bullets as fast as the power tanks can. The second star allows you to fire up to two bullets on the screen at one time. And the third star allows your bullets to destroy the otherwise unbreakable steel walls. You carry this power with you to each new stage until you lose a life.
tank : The tank power up grants you one extra life. The only other way to get an extra life is to score 20000 points.
timer : The timer power up temporarily freezes time, allowing you to harmlessly approach every tank and destroy them until the time freeze wears off.
"""
# bonus types
(BONUS_GRENADE, BONUS_HELMET, BONUS_SHOVEL, BONUS_STAR, BONUS_TANK, BONUS_TIMER) = range(6)
def __init__(self, level):
global sprites
# to know where to place
self.level = level
# bonus lives only for a limited period of time
self.active = True
# blinking state
self.visible = True
self.rect = pygame.Rect(random.randint(0, 416-32), random.randint(0, 416-32), 32, 32)
self.bonus = random.choice([
self.BONUS_GRENADE,
self.BONUS_HELMET,
self.BONUS_SHOVEL,
self.BONUS_STAR,
self.BONUS_TANK,
self.BONUS_TIMER
])
self.image = sprites.subsurface(16*2*self.bonus, 32*2, 16*2, 15*2)
def draw(self):
""" draw bonus """
global screen
if self.visible:
screen.blit(self.image, self.rect.topleft)
def toggleVisibility(self):
""" Toggle bonus visibility """
self.visible = not self.visible
class Bullet():
# direction constants
(DIR_UP, DIR_RIGHT, DIR_DOWN, DIR_LEFT) = range(4)
# bullet's stated
(STATE_REMOVED, STATE_ACTIVE, STATE_EXPLODING) = range(3)
(OWNER_PLAYER, OWNER_ENEMY) = range(2)
def __init__(self, level, position, direction, damage = 100, speed = 5):
global sprites
self.level = level
self.direction = direction
self.damage = damage
self.owner = None
self.owner_class = None
# 1-regular everyday normal bullet
# 2-can destroy steel
self.power = 1
self.image = sprites.subsurface(75*2, 74*2, 3*2, 4*2)
# position is player's top left corner, so we'll need to
# recalculate a bit. also rotate image itself.
if direction == self.DIR_UP:
self.rect = pygame.Rect(position[0] + 11, position[1] - 8, 6, 8)
elif direction == self.DIR_RIGHT:
self.image = pygame.transform.rotate(self.image, 270)
self.rect = pygame.Rect(position[0] + 26, position[1] + 11, 8, 6)
elif direction == self.DIR_DOWN:
self.image = pygame.transform.rotate(self.image, 180)
self.rect = pygame.Rect(position[0] + 11, position[1] + 26, 6, 8)
elif direction == self.DIR_LEFT:
self.image = pygame.transform.rotate(self.image, 90)
self.rect = pygame.Rect(position[0] - 8 , position[1] + 11, 8, 6)
self.explosion_images = [
sprites.subsurface(0, 80*2, 32*2, 32*2),
sprites.subsurface(32*2, 80*2, 32*2, 32*2),
]
self.speed = speed
self.state = self.STATE_ACTIVE
def draw(self):
""" draw bullet """
global screen
if self.state == self.STATE_ACTIVE:
screen.blit(self.image, self.rect.topleft)
elif self.state == self.STATE_EXPLODING:
self.explosion.draw()
def update(self):
global castle, players, enemies, bullets
if self.state == self.STATE_EXPLODING:
if not self.explosion.active:
self.destroy()
del self.explosion
if self.state != self.STATE_ACTIVE:
return
""" move bullet """
if self.direction == self.DIR_UP:
self.rect.topleft = [self.rect.left, self.rect.top - self.speed]
if self.rect.top < 0:
if play_sounds and self.owner == self.OWNER_PLAYER:
sounds["steel"].play()
self.explode()
return
elif self.direction == self.DIR_RIGHT:
self.rect.topleft = [self.rect.left + self.speed, self.rect.top]
if self.rect.left > (416 - self.rect.width):
if play_sounds and self.owner == self.OWNER_PLAYER:
sounds["steel"].play()
self.explode()
return
elif self.direction == self.DIR_DOWN:
self.rect.topleft = [self.rect.left, self.rect.top + self.speed]
if self.rect.top > (416 - self.rect.height):
if play_sounds and self.owner == self.OWNER_PLAYER:
sounds["steel"].play()
self.explode()
return
elif self.direction == self.DIR_LEFT:
self.rect.topleft = [self.rect.left - self.speed, self.rect.top]
if self.rect.left < 0:
if play_sounds and self.owner == self.OWNER_PLAYER:
sounds["steel"].play()
self.explode()
return
has_collided = False
# check for collisions with walls. one bullet can destroy several (1 or 2)
# tiles but explosion remains 1
rects = self.level.obstacle_rects
collisions = self.rect.collidelistall(rects)
if collisions != []:
for i in collisions:
if self.level.hitTile(rects[i].topleft, self.power, self.owner == self.OWNER_PLAYER):
has_collided = True
if has_collided:
self.explode()
return
# check for collisions with other bullets
for bullet in bullets:
if self.state == self.STATE_ACTIVE and bullet.owner != self.owner and bullet != self and self.rect.colliderect(bullet.rect):
self.destroy()
self.explode()
return
# check for collisions with players
for player in players:
if player.state == player.STATE_ALIVE and self.rect.colliderect(player.rect):
if player.bulletImpact(self.owner == self.OWNER_PLAYER, self.damage, self.owner_class):
self.destroy()
return
# check for collisions with enemies
for enemy in enemies:
if enemy.state == enemy.STATE_ALIVE and self.rect.colliderect(enemy.rect):
if enemy.bulletImpact(self.owner == self.OWNER_ENEMY, self.damage, self.owner_class):
self.destroy()
return
# check for collision with castle
if castle.active and self.rect.colliderect(castle.rect):
castle.destroy()
self.destroy()
return
def explode(self):
""" start bullets's explosion """
global screen
if self.state != self.STATE_REMOVED:
self.state = self.STATE_EXPLODING
self.explosion = Explosion([self.rect.left-13, self.rect.top-13], None, self.explosion_images)
def destroy(self):
self.state = self.STATE_REMOVED
class Label():
def __init__(self, position, text = "", duration = None):
self.position = position
self.active = True
self.text = text
self.font = pygame.font.SysFont("Arial", 13)
if duration != None:
gtimer.add(duration, lambda :self.destroy(), 1)
def draw(self):
""" draw label """
global screen
screen.blit(self.font.render(self.text, False, (200,200,200)), [self.position[0]+4, self.position[1]+8])
def destroy(self):
self.active = False
class Explosion():
def __init__(self, position, interval = None, images = None):
global sprites
self.position = [position[0]-16, position[1]-16]
self.active = True
if interval == None:
interval = 100
if images == None:
images = [
sprites.subsurface(0, 80*2, 32*2, 32*2),
sprites.subsurface(32*2, 80*2, 32*2, 32*2),
sprites.subsurface(64*2, 80*2, 32*2, 32*2)
]
images.reverse()
self.images = [] + images
self.image = self.images.pop()
gtimer.add(interval, lambda :self.update(), len(self.images) + 1)
def draw(self):
global screen
""" draw current explosion frame """
screen.blit(self.image, self.position)
def update(self):
""" Advace to the next image """
if len(self.images) > 0:
self.image = self.images.pop()
else:
self.active = False
class Level():
# tile constants
(TILE_EMPTY, TILE_BRICK, TILE_STEEL, TILE_WATER, TILE_GRASS, TILE_FROZE) = range(6)
# tile width/height in px
TILE_SIZE = 16
def __init__(self, level_nr = None):
""" There are total 35 different levels. If level_nr is larger than 35, loop over
to next according level so, for example, if level_nr ir 37, then load level 2 """
global sprites
# max number of enemies simultaneously being on map
self.max_active_enemies = 4
tile_images = [
pygame.Surface((8*2, 8*2)),
sprites.subsurface(48*2, 64*2, 8*2, 8*2),
sprites.subsurface(48*2, 72*2, 8*2, 8*2),
sprites.subsurface(56*2, 72*2, 8*2, 8*2),
sprites.subsurface(64*2, 64*2, 8*2, 8*2),
sprites.subsurface(64*2, 64*2, 8*2, 8*2),
sprites.subsurface(72*2, 64*2, 8*2, 8*2),
sprites.subsurface(64*2, 72*2, 8*2, 8*2)
]
self.tile_empty = tile_images[0]
self.tile_brick = tile_images[1]
self.tile_steel = tile_images[2]
self.tile_grass = tile_images[3]
self.tile_water = tile_images[4]
self.tile_water1= tile_images[4]
self.tile_water2= tile_images[5]
self.tile_froze = tile_images[6]
self.obstacle_rects = []
level_nr = 1 if level_nr == None else level_nr%35
if level_nr == 0:
level_nr = 35
self.loadLevel(level_nr)
# tiles' rects on map, tanks cannot move over
self.obstacle_rects = []
# update these tiles
self.updateObstacleRects()
gtimer.add(400, lambda :self.toggleWaves())
def hitTile(self, pos, power = 1, sound = False):
"""
Hit the tile
@param pos Tile's x, y in px
@return True if bullet was stopped, False otherwise
"""
global play_sounds, sounds
for tile in self.mapr:
if tile.topleft == pos:
if tile.type == self.TILE_BRICK:
if play_sounds and sound:
sounds["brick"].play()
self.mapr.remove(tile)
self.updateObstacleRects()
return True
elif tile.type == self.TILE_STEEL:
if play_sounds and sound:
sounds["steel"].play()
if power == 2:
self.mapr.remove(tile)
self.updateObstacleRects()
return True
else:
return False
def toggleWaves(self):
""" Toggle water image """
if self.tile_water == self.tile_water1:
self.tile_water = self.tile_water2
else:
self.tile_water = self.tile_water1
def loadLevel(self, level_nr = 1):
""" Load specified level
@return boolean Whether level was loaded
"""
filename = "levels/"+str(level_nr)
if (not os.path.isfile(filename)):
return False
level = []
f = open(filename, "r")
data = f.read().split("\n")
self.mapr = []
x, y = 0, 0
for row in data:
for ch in row:
if ch == "#":
self.mapr.append(myRect(x, y, self.TILE_SIZE, self.TILE_SIZE, self.TILE_BRICK))
elif ch == "@":
self.mapr.append(myRect(x, y, self.TILE_SIZE, self.TILE_SIZE, self.TILE_STEEL))
elif ch == "~":
self.mapr.append(myRect(x, y, self.TILE_SIZE, self.TILE_SIZE, self.TILE_WATER))
elif ch == "%":
self.mapr.append(myRect(x, y, self.TILE_SIZE, self.TILE_SIZE, self.TILE_GRASS))
elif ch == "-":
self.mapr.append(myRect(x, y, self.TILE_SIZE, self.TILE_SIZE, self.TILE_FROZE))
x += self.TILE_SIZE
x = 0
y += self.TILE_SIZE
return True
def draw(self, tiles = None):
""" Draw specified map on top of existing surface """
global screen
if tiles == None:
tiles = [TILE_BRICK, TILE_STEEL, TILE_WATER, TILE_GRASS, TILE_FROZE]
for tile in self.mapr:
if tile.type in tiles:
if tile.type == self.TILE_BRICK:
screen.blit(self.tile_brick, tile.topleft)
elif tile.type == self.TILE_STEEL:
screen.blit(self.tile_steel, tile.topleft)
elif tile.type == self.TILE_WATER:
screen.blit(self.tile_water, tile.topleft)
elif tile.type == self.TILE_FROZE:
screen.blit(self.tile_froze, tile.topleft)
elif tile.type == self.TILE_GRASS:
screen.blit(self.tile_grass, tile.topleft)
def updateObstacleRects(self):
""" Set self.obstacle_rects to all tiles' rects that players can destroy
with bullets """
global castle
self.obstacle_rects = [castle.rect]
for tile in self.mapr:
if tile.type in (self.TILE_BRICK, self.TILE_STEEL, self.TILE_WATER):
self.obstacle_rects.append(tile)
def buildFortress(self, tile):
""" Build walls around castle made from tile """
positions = [
(11*self.TILE_SIZE, 23*self.TILE_SIZE),
(11*self.TILE_SIZE, 24*self.TILE_SIZE),
(11*self.TILE_SIZE, 25*self.TILE_SIZE),
(14*self.TILE_SIZE, 23*self.TILE_SIZE),
(14*self.TILE_SIZE, 24*self.TILE_SIZE),
(14*self.TILE_SIZE, 25*self.TILE_SIZE),
(12*self.TILE_SIZE, 23*self.TILE_SIZE),
(13*self.TILE_SIZE, 23*self.TILE_SIZE)
]
obsolete = []
for i, rect in enumerate(self.mapr):
if rect.topleft in positions:
obsolete.append(rect)
for rect in obsolete:
self.mapr.remove(rect)
for pos in positions:
self.mapr.append(myRect(pos[0], pos[1], self.TILE_SIZE, self.TILE_SIZE, tile))
self.updateObstacleRects()
class Tank():
# possible directions
(DIR_UP, DIR_RIGHT, DIR_DOWN, DIR_LEFT) = range(4)
# states
(STATE_SPAWNING, STATE_DEAD, STATE_ALIVE, STATE_EXPLODING) = range(4)
# sides
(SIDE_PLAYER, SIDE_ENEMY) = range(2)
def __init__(self, level, side, position = None, direction = None, filename = None):
global sprites
# health. 0 health means dead
self.health = 100
# tank can't move but can rotate and shoot
self.paralised = False
# tank can't do anything
self.paused = False
# tank is protected from bullets
self.shielded = False
# px per move
self.speed = 2
# how many bullets can tank fire simultaneously
self.max_active_bullets = 1
# friend or foe
self.side = side
# flashing state. 0-off, 1-on
self.flash = 0
# 0 - no superpowers
# 1 - faster bullets
# 2 - can fire 2 bullets
# 3 - can destroy steel
self.superpowers = 0
# each tank can pick up 1 bonus
self.bonus = None
# navigation keys: fire, up, right, down, left
self.controls = [pygame.K_SPACE, pygame.K_UP, pygame.K_RIGHT, pygame.K_DOWN, pygame.K_LEFT]
# currently pressed buttons (navigation only)
self.pressed = [False] * 4
self.shield_images = [
sprites.subsurface(0, 48*2, 16*2, 16*2),
sprites.subsurface(16*2, 48*2, 16*2, 16*2)
]
self.shield_image = self.shield_images[0]
self.shield_index = 0
self.spawn_images = [
sprites.subsurface(32*2, 48*2, 16*2, 16*2),
sprites.subsurface(48*2, 48*2, 16*2, 16*2)
]
self.spawn_image = self.spawn_images[0]
self.spawn_index = 0
self.level = level
if position != None:
self.rect = pygame.Rect(position, (26, 26))
else:
self.rect = pygame.Rect(0, 0, 26, 26)
if direction == None:
self.direction = random.choice([self.DIR_RIGHT, self.DIR_DOWN, self.DIR_LEFT])
else:
self.direction = direction
self.state = self.STATE_SPAWNING
# spawning animation
self.timer_uuid_spawn = gtimer.add(100, lambda :self.toggleSpawnImage())
# duration of spawning
self.timer_uuid_spawn_end = gtimer.add(1000, lambda :self.endSpawning())
def endSpawning(self):
""" End spawning
Player becomes operational
"""
self.state = self.STATE_ALIVE
gtimer.destroy(self.timer_uuid_spawn_end)
def toggleSpawnImage(self):
""" advance to the next spawn image """
if self.state != self.STATE_SPAWNING:
gtimer.destroy(self.timer_uuid_spawn)
return
self.spawn_index += 1
if self.spawn_index >= len(self.spawn_images):
self.spawn_index = 0
self.spawn_image = self.spawn_images[self.spawn_index]
def toggleShieldImage(self):
""" advance to the next shield image """
if self.state != self.STATE_ALIVE:
gtimer.destroy(self.timer_uuid_shield)
return
if self.shielded:
self.shield_index += 1
if self.shield_index >= len(self.shield_images):
self.shield_index = 0
self.shield_image = self.shield_images[self.shield_index]
def draw(self):
""" draw tank """
global screen
if self.state == self.STATE_ALIVE:
screen.blit(self.image, self.rect.topleft)
if self.shielded:
screen.blit(self.shield_image, [self.rect.left-3, self.rect.top-3])
elif self.state == self.STATE_EXPLODING:
self.explosion.draw()
elif self.state == self.STATE_SPAWNING:
screen.blit(self.spawn_image, self.rect.topleft)
def explode(self):
""" start tanks's explosion """
if self.state != self.STATE_DEAD:
self.state = self.STATE_EXPLODING
self.explosion = Explosion(self.rect.topleft)
if self.bonus:
self.spawnBonus()
def fire(self, forced = False):
""" Shoot a bullet
@param boolean forced. If false, check whether tank has exceeded his bullet quota. Default: True
@return boolean True if bullet was fired, false otherwise
"""
global bullets, labels
if self.state != self.STATE_ALIVE:
gtimer.destroy(self.timer_uuid_fire)
return False
if self.paused:
return False
if not forced:
active_bullets = 0
for bullet in bullets:
if bullet.owner_class == self and bullet.state == bullet.STATE_ACTIVE:
active_bullets += 1
if active_bullets >= self.max_active_bullets:
return False
bullet = Bullet(self.level, self.rect.topleft, self.direction)
# if superpower level is at least 1
if self.superpowers > 0:
bullet.speed = 8
# if superpower level is at least 3
if self.superpowers > 2:
bullet.power = 2
if self.side == self.SIDE_PLAYER:
bullet.owner = self.SIDE_PLAYER
else:
bullet.owner = self.SIDE_ENEMY
self.bullet_queued = False
bullet.owner_class = self
bullets.append(bullet)
return True
def rotate(self, direction, fix_position = True):
""" Rotate tank
rotate, update image and correct position
"""
self.direction = direction
if direction == self.DIR_UP:
self.image = self.image_up
elif direction == self.DIR_RIGHT:
self.image = self.image_right
elif direction == self.DIR_DOWN:
self.image = self.image_down
elif direction == self.DIR_LEFT:
self.image = self.image_left
if fix_position:
new_x = self.nearest(self.rect.left, 8) + 3
new_y = self.nearest(self.rect.top, 8) + 3
if (abs(self.rect.left - new_x) < 5):
self.rect.left = new_x
if (abs(self.rect.top - new_y) < 5):
self.rect.top = new_y
def turnAround(self):
""" Turn tank into opposite direction """
if self.direction in (self.DIR_UP, self.DIR_RIGHT):
self.rotate(self.direction + 2, False)
else:
self.rotate(self.direction - 2, False)
def update(self, time_passed):
""" Update timer and explosion (if any) """
if self.state == self.STATE_EXPLODING:
if not self.explosion.active:
self.state = self.STATE_DEAD
del self.explosion
def nearest(self, num, base):
""" Round number to nearest divisible """
return int(round(num / (base * 1.0)) * base)
def bulletImpact(self, friendly_fire = False, damage = 100, tank = None):
""" Bullet impact
Return True if bullet should be destroyed on impact. Only enemy friendly-fire
doesn't trigger bullet explosion
"""
global play_sounds, sounds
if self.shielded:
return True
if not friendly_fire:
self.health -= damage
if self.health < 1:
if self.side == self.SIDE_ENEMY:
tank.trophies["enemy"+str(self.type)] += 1
points = (self.type+1) * 100
tank.score += points
if play_sounds:
sounds["explosion"].play()
labels.append(Label(self.rect.topleft, str(points), 500))
self.explode()
return True
if self.side == self.SIDE_ENEMY:
return False
elif self.side == self.SIDE_PLAYER:
if not self.paralised:
self.setParalised(True)
self.timer_uuid_paralise = gtimer.add(10000, lambda :self.setParalised(False), 1)
return True
def setParalised(self, paralised = True):
""" set tank paralise state
@param boolean paralised
@return None
"""
if self.state != self.STATE_ALIVE:
gtimer.destroy(self.timer_uuid_paralise)
return
self.paralised = paralised
class Enemy(Tank):
(TYPE_BASIC, TYPE_FAST, TYPE_POWER, TYPE_ARMOR) = range(4)
def __init__(self, level, type, position = None, direction = None, filename = None):
Tank.__init__(self, level, type, position = None, direction = None, filename = None)
global enemies, sprites
# if true, do not fire
self.bullet_queued = False
# chose type on random
if len(level.enemies_left) > 0:
self.type = level.enemies_left.pop()
else:
self.state = self.STATE_DEAD
return
if self.type == self.TYPE_BASIC:
self.speed = 1
elif self.type == self.TYPE_FAST:
self.speed = 3
elif self.type == self.TYPE_POWER:
self.superpowers = 1
elif self.type == self.TYPE_ARMOR:
self.health = 400
# 1 in 5 chance this will be bonus carrier, but only if no other tank is
if random.randint(1, 5) == 1:
self.bonus = True
for enemy in enemies:
if enemy.bonus:
self.bonus = False
break
images = [
sprites.subsurface(32*2, 0, 13*2, 15*2),
sprites.subsurface(48*2, 0, 13*2, 15*2),
sprites.subsurface(64*2, 0, 13*2, 15*2),
sprites.subsurface(80*2, 0, 13*2, 15*2),
sprites.subsurface(32*2, 16*2, 13*2, 15*2),
sprites.subsurface(48*2, 16*2, 13*2, 15*2),
sprites.subsurface(64*2, 16*2, 13*2, 15*2),
sprites.subsurface(80*2, 16*2, 13*2, 15*2)
]
self.image = images[self.type+0]
self.image_up = self.image;
self.image_left = pygame.transform.rotate(self.image, 90)
self.image_down = pygame.transform.rotate(self.image, 180)
self.image_right = pygame.transform.rotate(self.image, 270)
if self.bonus:
self.image1_up = self.image_up;
self.image1_left = self.image_left
self.image1_down = self.image_down
self.image1_right = self.image_right
self.image2 = images[self.type+4]
self.image2_up = self.image2;
self.image2_left = pygame.transform.rotate(self.image2, 90)
self.image2_down = pygame.transform.rotate(self.image2, 180)
self.image2_right = pygame.transform.rotate(self.image2, 270)
self.rotate(self.direction, False)
if position == None:
self.rect.topleft = self.getFreeSpawningPosition()
if not self.rect.topleft:
self.state = self.STATE_DEAD
return
# list of map coords where tank should go next
self.path = self.generatePath(self.direction)
# 1000 is duration between shots
self.timer_uuid_fire = gtimer.add(1000, lambda :self.fire())
# turn on flashing
if self.bonus:
self.timer_uuid_flash = gtimer.add(200, lambda :self.toggleFlash())
def toggleFlash(self):
""" Toggle flash state """
if self.state not in (self.STATE_ALIVE, self.STATE_SPAWNING):
gtimer.destroy(self.timer_uuid_flash)
return
self.flash = not self.flash
if self.flash:
self.image_up = self.image2_up
self.image_right = self.image2_right
self.image_down = self.image2_down
self.image_left = self.image2_left
else:
self.image_up = self.image1_up
self.image_right = self.image1_right
self.image_down = self.image1_down
self.image_left = self.image1_left
self.rotate(self.direction, False)
def spawnBonus(self):
""" Create new bonus if needed """
global bonuses
if len(bonuses) > 0:
return
bonus = Bonus(self.level)
bonuses.append(bonus)
gtimer.add(500, lambda :bonus.toggleVisibility())
gtimer.add(10000, lambda :bonuses.remove(bonus), 1)
def getFreeSpawningPosition(self):
global players, enemies
available_positions = [
[(self.level.TILE_SIZE * 2 - self.rect.width) / 2, (self.level.TILE_SIZE * 2 - self.rect.height) / 2],
[12 * self.level.TILE_SIZE + (self.level.TILE_SIZE * 2 - self.rect.width) / 2, (self.level.TILE_SIZE * 2 - self.rect.height) / 2],
[24 * self.level.TILE_SIZE + (self.level.TILE_SIZE * 2 - self.rect.width) / 2, (self.level.TILE_SIZE * 2 - self.rect.height) / 2]
]
random.shuffle(available_positions)
for pos in available_positions:
enemy_rect = pygame.Rect(pos, [26, 26])
# collisions with other enemies
collision = False
for enemy in enemies:
if enemy_rect.colliderect(enemy.rect):
collision = True
continue
if collision:
continue
# collisions with players
collision = False
for player in players:
if enemy_rect.colliderect(player.rect):
collision = True
continue
if collision:
continue
return pos
return False
def move(self):
""" move enemy if possible """
global players, enemies, bonuses
if self.state != self.STATE_ALIVE or self.paused or self.paralised:
return
if self.path == []:
self.path = self.generatePath(None, True)
new_position = self.path.pop(0)
# move enemy
if self.direction == self.DIR_UP:
if new_position[1] < 0:
self.path = self.generatePath(self.direction, True)
return
elif self.direction == self.DIR_RIGHT:
if new_position[0] > (416 - 26):
self.path = self.generatePath(self.direction, True)
return
elif self.direction == self.DIR_DOWN:
if new_position[1] > (416 - 26):
self.path = self.generatePath(self.direction, True)
return
elif self.direction == self.DIR_LEFT:
if new_position[0] < 0:
self.path = self.generatePath(self.direction, True)
return
new_rect = pygame.Rect(new_position, [26, 26])
# collisions with tiles
if new_rect.collidelist(self.level.obstacle_rects) != -1:
self.path = self.generatePath(self.direction, True)
return
# collisions with other enemies
for enemy in enemies:
if enemy != self and new_rect.colliderect(enemy.rect):
self.turnAround()
self.path = self.generatePath(self.direction)
return
# collisions with players
for player in players:
if new_rect.colliderect(player.rect):
self.turnAround()
self.path = self.generatePath(self.direction)
return
# collisions with bonuses
for bonus in bonuses:
if new_rect.colliderect(bonus.rect):
bonuses.remove(bonus)
# if no collision, move enemy
self.rect.topleft = new_rect.topleft
def update(self, time_passed):
Tank.update(self, time_passed)
if self.state == self.STATE_ALIVE and not self.paused:
self.move()
def generatePath(self, direction = None, fix_direction = False):
""" If direction is specified, try continue that way, otherwise choose at random
"""
all_directions = [self.DIR_UP, self.DIR_RIGHT, self.DIR_DOWN, self.DIR_LEFT]
if direction == None:
if self.direction in [self.DIR_UP, self.DIR_RIGHT]:
opposite_direction = self.direction + 2
else:
opposite_direction = self.direction - 2
directions = all_directions
random.shuffle(directions)
directions.remove(opposite_direction)
directions.append(opposite_direction)
else:
if direction in [self.DIR_UP, self.DIR_RIGHT]:
opposite_direction = direction + 2
else:
opposite_direction = direction - 2
if direction in [self.DIR_UP, self.DIR_RIGHT]:
opposite_direction = direction + 2
else:
opposite_direction = direction - 2
directions = all_directions
random.shuffle(directions)
directions.remove(opposite_direction)
directions.remove(direction)
directions.insert(0, direction)
directions.append(opposite_direction)
# at first, work with general units (steps) not px
x = int(round(self.rect.left / 16))
y = int(round(self.rect.top / 16))
new_direction = None
for direction in directions:
if direction == self.DIR_UP and y > 1:
new_pos_rect = self.rect.move(0, -8)
if new_pos_rect.collidelist(self.level.obstacle_rects) == -1:
new_direction = direction
break
elif direction == self.DIR_RIGHT and x < 24:
new_pos_rect = self.rect.move(8, 0)
if new_pos_rect.collidelist(self.level.obstacle_rects) == -1:
new_direction = direction
break
elif direction == self.DIR_DOWN and y < 24:
new_pos_rect = self.rect.move(0, 8)
if new_pos_rect.collidelist(self.level.obstacle_rects) == -1:
new_direction = direction
break
elif direction == self.DIR_LEFT and x > 1:
new_pos_rect = self.rect.move(-8, 0)
if new_pos_rect.collidelist(self.level.obstacle_rects) == -1:
new_direction = direction
break
# if we can go anywhere else, turn around
if new_direction == None:
new_direction = opposite_direction
print("nav izejas. griezhamies")
# fix tanks position
if fix_direction and new_direction == self.direction:
fix_direction = False
self.rotate(new_direction, fix_direction)
positions = []
x = self.rect.left
y = self.rect.top
if new_direction in (self.DIR_RIGHT, self.DIR_LEFT):
axis_fix = self.nearest(y, 16) - y
else:
axis_fix = self.nearest(x, 16) - x
axis_fix = 0
pixels = self.nearest(random.randint(1, 12) * 32, 32) + axis_fix + 3
if new_direction == self.DIR_UP:
for px in range(0, pixels, self.speed):
positions.append([x, y-px])
elif new_direction == self.DIR_RIGHT:
for px in range(0, pixels, self.speed):
positions.append([x+px, y])
elif new_direction == self.DIR_DOWN:
for px in range(0, pixels, self.speed):
positions.append([x, y+px])
elif new_direction == self.DIR_LEFT:
for px in range(0, pixels, self.speed):
positions.append([x-px, y])
return positions
class Player(Tank):
def __init__(self, level, type, position = None, direction = None, filename = None):
Tank.__init__(self, level, type, position = None, direction = None, filename = None)
global sprites
if filename == None:
filename = (0, 0, 16*2, 16*2)
self.start_position = position
self.start_direction = direction
self.lives = 3
# total score
self.score = 0
# store how many bonuses in this stage this player has collected
self.trophies = {
"bonus" : 0,
"enemy0" : 0,
"enemy1" : 0,
"enemy2" : 0,
"enemy3" : 0
}
self.image = sprites.subsurface(filename)
self.image_up = self.image;
self.image_left = pygame.transform.rotate(self.image, 90)
self.image_down = pygame.transform.rotate(self.image, 180)
self.image_right = pygame.transform.rotate(self.image, 270)
if direction == None:
self.rotate(self.DIR_UP, False)
else:
self.rotate(direction, False)
def move(self, direction):
""" move player if possible """
global players, enemies, bonuses
if self.state == self.STATE_EXPLODING:
if not self.explosion.active:
self.state = self.STATE_DEAD
del self.explosion
if self.state != self.STATE_ALIVE:
return
# rotate player
if self.direction != direction:
self.rotate(direction)
if self.paralised:
return
# move player
if direction == self.DIR_UP:
new_position = [self.rect.left, self.rect.top - self.speed]
if new_position[1] < 0:
return
elif direction == self.DIR_RIGHT:
new_position = [self.rect.left + self.speed, self.rect.top]
if new_position[0] > (416 - 26):
return
elif direction == self.DIR_DOWN:
new_position = [self.rect.left, self.rect.top + self.speed]
if new_position[1] > (416 - 26):
return
elif direction == self.DIR_LEFT:
new_position = [self.rect.left - self.speed, self.rect.top]
if new_position[0] < 0:
return
player_rect = pygame.Rect(new_position, [26, 26])
# collisions with tiles
if player_rect.collidelist(self.level.obstacle_rects) != -1:
return
# collisions with other players
for player in players:
if player != self and player.state == player.STATE_ALIVE and player_rect.colliderect(player.rect) == True:
return
# collisions with enemies
for enemy in enemies:
if player_rect.colliderect(enemy.rect) == True:
return
# collisions with bonuses
for bonus in bonuses:
if player_rect.colliderect(bonus.rect) == True:
self.bonus = bonus
#if no collision, move player
self.rect.topleft = (new_position[0], new_position[1])
def reset(self):
""" reset player """
self.rotate(self.start_direction, False)
self.rect.topleft = self.start_position
self.superpowers = 0
self.max_active_bullets = 1
self.health = 100
self.paralised = False
self.paused = False
self.pressed = [False] * 4
self.state = self.STATE_ALIVE
class Game():
# direction constants
(DIR_UP, DIR_RIGHT, DIR_DOWN, DIR_LEFT) = range(4)
TILE_SIZE = 16
def __init__(self):
global screen, sprites, play_sounds, sounds
# center window
os.environ['SDL_VIDEO_WINDOW_POS'] = 'center'
if play_sounds:
pygame.mixer.pre_init(44100, -16, 1, 512)
pygame.init()
pygame.display.set_caption("Battle City")
size = width, height = 480, 416
if "-f" in sys.argv[1:]:
screen = pygame.display.set_mode(size, pygame.FULLSCREEN)
else:
screen = pygame.display.set_mode(size)
self.clock = pygame.time.Clock()
# load sprites (funky version)
#sprites = pygame.transform.scale2x(pygame.image.load("images/sprites.gif"))
# load sprites (pixely version)
sprites = pygame.transform.scale(pygame.image.load("images/sprites.gif"), [192, 224])
#screen.set_colorkey((0,138,104))
pygame.display.set_icon(sprites.subsurface(0, 0, 13*2, 13*2))
# load sounds
if play_sounds:
pygame.mixer.init(44100, -16, 1, 512)
sounds["start"] = pygame.mixer.Sound("sounds/gamestart.ogg")
sounds["end"] = pygame.mixer.Sound("sounds/gameover.ogg")
sounds["score"] = pygame.mixer.Sound("sounds/score.ogg")
sounds["bg"] = pygame.mixer.Sound("sounds/background.ogg")
sounds["fire"] = pygame.mixer.Sound("sounds/fire.ogg")
sounds["bonus"] = pygame.mixer.Sound("sounds/bonus.ogg")
sounds["explosion"] = pygame.mixer.Sound("sounds/explosion.ogg")
sounds["brick"] = pygame.mixer.Sound("sounds/brick.ogg")
sounds["steel"] = pygame.mixer.Sound("sounds/steel.ogg")
self.enemy_life_image = sprites.subsurface(81*2, 57*2, 7*2, 7*2)
self.player_life_image = sprites.subsurface(89*2, 56*2, 7*2, 8*2)
self.flag_image = sprites.subsurface(64*2, 49*2, 16*2, 15*2)
# this is used in intro screen
self.player_image = pygame.transform.rotate(sprites.subsurface(0, 0, 13*2, 13*2), 270)
# if true, no new enemies will be spawn during this time
self.timefreeze = False
# load custom font
self.font = pygame.font.Font("fonts/prstart.ttf", 16)
# pre-render game over text
self.im_game_over = pygame.Surface((64, 40))
self.im_game_over.set_colorkey((0,0,0))
self.im_game_over.blit(self.font.render("GAME", False, (127, 64, 64)), [0, 0])
self.im_game_over.blit(self.font.render("OVER", False, (127, 64, 64)), [0, 20])
self.game_over_y = 416+40
# number of players. here is defined preselected menu value
self.nr_of_players = 1
del players[:]
del bullets[:]
del enemies[:]
del bonuses[:]
def triggerBonus(self, bonus, player):
""" Execute bonus powers """
global enemies, labels, play_sounds, sounds
if play_sounds:
sounds["bonus"].play()
player.trophies["bonus"] += 1
player.score += 500
if bonus.bonus == bonus.BONUS_GRENADE:
for enemy in enemies:
enemy.explode()
elif bonus.bonus == bonus.BONUS_HELMET:
self.shieldPlayer(player, True, 10000)
elif bonus.bonus == bonus.BONUS_SHOVEL:
self.level.buildFortress(self.level.TILE_STEEL)
gtimer.add(10000, lambda :self.level.buildFortress(self.level.TILE_BRICK), 1)
elif bonus.bonus == bonus.BONUS_STAR:
player.superpowers += 1
if player.superpowers == 2:
player.max_active_bullets = 2
elif bonus.bonus == bonus.BONUS_TANK:
player.lives += 1
elif bonus.bonus == bonus.BONUS_TIMER:
self.toggleEnemyFreeze(True)
gtimer.add(10000, lambda :self.toggleEnemyFreeze(False), 1)
bonuses.remove(bonus)
labels.append(Label(bonus.rect.topleft, "500", 500))
def shieldPlayer(self, player, shield = True, duration = None):
""" Add/remove shield
player: player (not enemy)
shield: true/false
duration: in ms. if none, do not remove shield automatically
"""
player.shielded = shield
if shield:
player.timer_uuid_shield = gtimer.add(100, lambda :player.toggleShieldImage())
else:
gtimer.destroy(player.timer_uuid_shield)
if shield and duration != None:
gtimer.add(duration, lambda :self.shieldPlayer(player, False), 1)
def spawnEnemy(self):
""" Spawn new enemy if needed
Only add enemy if:
- there are at least one in queue
- map capacity hasn't exceeded its quota
- now isn't timefreeze
"""
global enemies
if len(enemies) >= self.level.max_active_enemies:
return
if len(self.level.enemies_left) < 1 or self.timefreeze:
return
enemy = Enemy(self.level, 1)
enemies.append(enemy)
def respawnPlayer(self, player, clear_scores = False):
""" Respawn player """
player.reset()
if clear_scores:
player.trophies = {
"bonus" : 0, "enemy0" : 0, "enemy1" : 0, "enemy2" : 0, "enemy3" : 0
}
self.shieldPlayer(player, True, 4000)
def gameOver(self):
""" End game and return to menu """
global play_sounds, sounds
print("Game Over")
if play_sounds:
for sound in sounds:
sounds[sound].stop()
sounds["end"].play()
self.game_over_y = 416+40
self.game_over = True
gtimer.add(3000, lambda :self.showScores(), 1)
def gameOverScreen(self):
""" Show game over screen """
global screen
# stop game main loop (if any)
self.running = False
screen.fill([0, 0, 0])
self.writeInBricks("game", [125, 140])
self.writeInBricks("over", [125, 220])
pygame.display.flip()
while 1:
time_passed = self.clock.tick(50)
for event in pygame.event.get():
if event.type == pygame.QUIT:
quit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_RETURN:
self.showMenu()
return
def showMenu(self):
""" Show game menu
Redraw screen only when up or down key is pressed. When enter is pressed,
exit from this screen and start the game with selected number of players
"""
global players, screen
# stop game main loop (if any)
self.running = False
# clear all timers
del gtimer.timers[:]
# set current stage to 0
self.stage = 1
self.animateIntroScreen()
main_loop = True
while main_loop:
time_passed = self.clock.tick(50)
for event in pygame.event.get():
if event.type == pygame.QUIT:
quit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_q:
quit()
elif event.key == pygame.K_UP:
if self.nr_of_players == 2:
self.nr_of_players = 1
self.drawIntroScreen()
elif event.key == pygame.K_DOWN:
if self.nr_of_players == 1:
self.nr_of_players = 2
self.drawIntroScreen()
elif event.key == pygame.K_RETURN:
main_loop = False
del players[:]
self.nextLevel()
def reloadPlayers(self):
""" Init players
If players already exist, just reset them
"""
global players
if len(players) == 0:
# first player
x = 8 * self.TILE_SIZE + (self.TILE_SIZE * 2 - 26) / 2
y = 24 * self.TILE_SIZE + (self.TILE_SIZE * 2 - 26) / 2
player = Player(
self.level, 0, [x, y], self.DIR_UP, (0, 0, 13*2, 13*2)
)
players.append(player)
# second player
if self.nr_of_players == 2:
x = 16 * self.TILE_SIZE + (self.TILE_SIZE * 2 - 26) / 2
y = 24 * self.TILE_SIZE + (self.TILE_SIZE * 2 - 26) / 2
player = Player(
self.level, 0, [x, y], self.DIR_UP, (16*2, 0, 13*2, 13*2)
)
player.controls = [102, 119, 100, 115, 97]
players.append(player)
for player in players:
player.level = self.level
self.respawnPlayer(player, True)
def showScores(self):
""" Show level scores """
global screen, sprites, players, play_sounds, sounds
# stop game main loop (if any)
self.running = False
# clear all timers
del gtimer.timers[:]
if play_sounds:
for sound in sounds:
sounds[sound].stop()
hiscore = self.loadHiscore()
# update hiscore if needed
if players[0].score > hiscore:
hiscore = players[0].score
self.saveHiscore(hiscore)
if self.nr_of_players == 2 and players[1].score > hiscore:
hiscore = players[1].score
self.saveHiscore(hiscore)
img_tanks = [
sprites.subsurface(32*2, 0, 13*2, 15*2),
sprites.subsurface(48*2, 0, 13*2, 15*2),
sprites.subsurface(64*2, 0, 13*2, 15*2),
sprites.subsurface(80*2, 0, 13*2, 15*2)
]
img_arrows = [
sprites.subsurface(81*2, 48*2, 7*2, 7*2),
sprites.subsurface(88*2, 48*2, 7*2, 7*2)
]
screen.fill([0, 0, 0])
# colors
black = pygame.Color("black")
white = pygame.Color("white")
purple = pygame.Color(127, 64, 64)
pink = pygame.Color(191, 160, 128)
screen.blit(self.font.render("HI-SCORE", False, purple), [105, 35])
screen.blit(self.font.render(str(hiscore), False, pink), [295, 35])
screen.blit(self.font.render("STAGE"+str(self.stage).rjust(3), False, white), [170, 65])
screen.blit(self.font.render("I-PLAYER", False, purple), [25, 95])
#player 1 global score
screen.blit(self.font.render(str(players[0].score).rjust(8), False, pink), [25, 125])
if self.nr_of_players == 2:
screen.blit(self.font.render("II-PLAYER", False, purple), [310, 95])
#player 2 global score
screen.blit(self.font.render(str(players[1].score).rjust(8), False, pink), [325, 125])
# tanks and arrows
for i in range(4):
screen.blit(img_tanks[i], [226, 160+(i*45)])
screen.blit(img_arrows[0], [206, 168+(i*45)])
if self.nr_of_players == 2:
screen.blit(img_arrows[1], [258, 168+(i*45)])
screen.blit(self.font.render("TOTAL", False, white), [70, 335])
# total underline
pygame.draw.line(screen, white, [170, 330], [307, 330], 4)
pygame.display.flip()
self.clock.tick(2)
interval = 5
# points and kills
for i in range(4):
# total specific tanks
tanks = players[0].trophies["enemy"+str(i)]
for n in range(tanks+1):
if n > 0 and play_sounds:
sounds["score"].play()
# erase previous text
screen.blit(self.font.render(str(n-1).rjust(2), False, black), [170, 168+(i*45)])
# print new number of enemies
screen.blit(self.font.render(str(n).rjust(2), False, white), [170, 168+(i*45)])
# erase previous text
screen.blit(self.font.render(str((n-1) * (i+1) * 100).rjust(4)+" PTS", False, black), [25, 168+(i*45)])
# print new total points per enemy
screen.blit(self.font.render(str(n * (i+1) * 100).rjust(4)+" PTS", False, white), [25, 168+(i*45)])
pygame.display.flip()
self.clock.tick(interval)
if self.nr_of_players == 2:
tanks = players[1].trophies["enemy"+str(i)]
for n in range(tanks+1):
if n > 0 and play_sounds:
sounds["score"].play()
screen.blit(self.font.render(str(n-1).rjust(2), False, black), [277, 168+(i*45)])
screen.blit(self.font.render(str(n).rjust(2), False, white), [277, 168+(i*45)])
screen.blit(self.font.render(str((n-1) * (i+1) * 100).rjust(4)+" PTS", False, black), [325, 168+(i*45)])
screen.blit(self.font.render(str(n * (i+1) * 100).rjust(4)+" PTS", False, white), [325, 168+(i*45)])
pygame.display.flip()
self.clock.tick(interval)
self.clock.tick(interval)
# total tanks
tanks = sum([i for i in players[0].trophies.values()]) - players[0].trophies["bonus"]
screen.blit(self.font.render(str(tanks).rjust(2), False, white), [170, 335])
if self.nr_of_players == 2:
tanks = sum([i for i in players[1].trophies.values()]) - players[1].trophies["bonus"]
screen.blit(self.font.render(str(tanks).rjust(2), False, white), [277, 335])
pygame.display.flip()
# do nothing for 2 seconds
self.clock.tick(1)
self.clock.tick(1)
if self.game_over:
self.gameOverScreen()
else:
self.nextLevel()
def draw(self):
global screen, castle, players, enemies, bullets, bonuses
screen.fill([0, 0, 0])
self.level.draw([self.level.TILE_EMPTY, self.level.TILE_BRICK, self.level.TILE_STEEL, self.level.TILE_FROZE, self.level.TILE_WATER])
castle.draw()
for enemy in enemies:
enemy.draw()
for label in labels:
label.draw()
for player in players:
player.draw()
for bullet in bullets:
bullet.draw()
for bonus in bonuses:
bonus.draw()
self.level.draw([self.level.TILE_GRASS])
if self.game_over:
if self.game_over_y > 188:
self.game_over_y -= 4
screen.blit(self.im_game_over, [176, self.game_over_y]) # 176=(416-64)/2
self.drawSidebar()
pygame.display.flip()
def drawSidebar(self):
global screen, players, enemies
x = 416
y = 0
screen.fill([100, 100, 100], pygame.Rect([416, 0], [64, 416]))
xpos = x + 16
ypos = y + 16
# draw enemy lives
for n in range(len(self.level.enemies_left) + len(enemies)):
screen.blit(self.enemy_life_image, [xpos, ypos])
if n % 2 == 1:
xpos = x + 16
ypos+= 17
else:
xpos += 17
# players' lives
if pygame.font.get_init():
text_color = pygame.Color('black')
for n in range(len(players)):
if n == 0:
screen.blit(self.font.render(str(n+1)+"P", False, text_color), [x+16, y+200])
screen.blit(self.font.render(str(players[n].lives), False, text_color), [x+31, y+215])
screen.blit(self.player_life_image, [x+17, y+215])
else:
screen.blit(self.font.render(str(n+1)+"P", False, text_color), [x+16, y+240])
screen.blit(self.font.render(str(players[n].lives), False, text_color), [x+31, y+255])
screen.blit(self.player_life_image, [x+17, y+255])
screen.blit(self.flag_image, [x+17, y+280])
screen.blit(self.font.render(str(self.stage), False, text_color), [x+17, y+312])
def drawIntroScreen(self, put_on_surface = True):
""" Draw intro (menu) screen
@param boolean put_on_surface If True, flip display after drawing
@return None
"""
global screen
screen.fill([0, 0, 0])
if pygame.font.get_init():
hiscore = self.loadHiscore()
screen.blit(self.font.render("HI- "+str(hiscore), True, pygame.Color('white')), [170, 35])
screen.blit(self.font.render("1 PLAYER", True, pygame.Color('white')), [165, 250])
screen.blit(self.font.render("2 PLAYERS", True, pygame.Color('white')), [165, 275])
screen.blit(self.font.render("(c) 1980 1985 NAMCO LTD.", True, pygame.Color('white')), [50, 350])
screen.blit(self.font.render("ALL RIGHTS RESERVED", True, pygame.Color('white')), [85, 380])
if self.nr_of_players == 1:
screen.blit(self.player_image, [125, 245])
elif self.nr_of_players == 2:
screen.blit(self.player_image, [125, 270])
self.writeInBricks("battle", [65, 80])
self.writeInBricks("city", [129, 160])
if put_on_surface:
pygame.display.flip()
def animateIntroScreen(self):
""" Slide intro (menu) screen from bottom to top
If Enter key is pressed, finish animation immediately
@return None
"""
global screen
self.drawIntroScreen(False)
screen_cp = screen.copy()
screen.fill([0, 0, 0])
y = 416
while (y > 0):
time_passed = self.clock.tick(50)
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RETURN:
y = 0
break
screen.blit(screen_cp, [0, y])
pygame.display.flip()
y -= 5
screen.blit(screen_cp, [0, 0])
pygame.display.flip()
def chunks(self, l, n):
""" Split text string in chunks of specified size
@param string l Input string
@param int n Size (number of characters) of each chunk
@return list
"""
return [l[i:i+n] for i in range(0, len(l), n)]
def writeInBricks(self, text, pos):
""" Write specified text in "brick font"
Only those letters are available that form words "Battle City" and "Game Over"
Both lowercase and uppercase are valid input, but output is always uppercase
Each letter consists of 7x7 bricks which is converted into 49 character long string
of 1's and 0's which in turn is then converted into hex to save some bytes
@return None
"""
global screen, sprites
bricks = sprites.subsurface(56*2, 64*2, 8*2, 8*2)
brick1 = bricks.subsurface((0, 0, 8, 8))
brick2 = bricks.subsurface((8, 0, 8, 8))
brick3 = bricks.subsurface((8, 8, 8, 8))
brick4 = bricks.subsurface((0, 8, 8, 8))
alphabet = {
"a" : "0071b63c7ff1e3",
"b" : "01fb1e3fd8f1fe",
"c" : "00799e0c18199e",
"e" : "01fb060f98307e",
"g" : "007d860cf8d99f",
"i" : "01f8c183060c7e",
"l" : "0183060c18307e",
"m" : "018fbffffaf1e3",
"o" : "00fb1e3c78f1be",
"r" : "01fb1e3cff3767",
"t" : "01f8c183060c18",
"v" : "018f1e3eef8e08",
"y" : "019b3667860c18"
}
abs_x, abs_y = pos
for letter in text.lower():
binstr = ""
for h in self.chunks(alphabet[letter], 2):
binstr += str(bin(int(h, 16)))[2:].rjust(8, "0")
binstr = binstr[7:]
x, y = 0, 0
letter_w = 0
surf_letter = pygame.Surface((56, 56))
for j, row in enumerate(self.chunks(binstr, 7)):
for i, bit in enumerate(row):
if bit == "1":
if i%2 == 0 and j%2 == 0:
surf_letter.blit(brick1, [x, y])
elif i%2 == 1 and j%2 == 0:
surf_letter.blit(brick2, [x, y])
elif i%2 == 1 and j%2 == 1:
surf_letter.blit(brick3, [x, y])
elif i%2 == 0 and j%2 == 1:
surf_letter.blit(brick4, [x, y])
if x > letter_w:
letter_w = x
x += 8
x = 0
y += 8
screen.blit(surf_letter, [abs_x, abs_y])
abs_x += letter_w + 16
def toggleEnemyFreeze(self, freeze = True):
""" Freeze/defreeze all enemies """
global enemies
for enemy in enemies:
enemy.paused = freeze
self.timefreeze = freeze
def loadHiscore(self):
""" Load hiscore
Really primitive version =] If for some reason hiscore cannot be loaded, return 20000
@return int
"""
filename = ".hiscore"
if (not os.path.isfile(filename)):
return 20000
f = open(filename, "r")
hiscore = int(f.read())
if hiscore > 19999 and hiscore < 1000000:
return hiscore
else:
print("cheater =[")
return 20000
def saveHiscore(self, hiscore):
""" Save hiscore
@return boolean
"""
try:
f = open(".hiscore", "w")
except:
print("Can't save hi-score")
return False
f.write(str(hiscore))
f.close()
return True
def finishLevel(self):
""" Finish current level
Show earned scores and advance to the next stage
"""
global play_sounds, sounds
if play_sounds:
sounds["bg"].stop()
self.active = False
gtimer.add(3000, lambda :self.showScores(), 1)
print("Stage "+str(self.stage)+" completed")
def nextLevel(self):
""" Start next level """
global castle, players, bullets, bonuses, play_sounds, sounds
del bullets[:]
del enemies[:]
del bonuses[:]
castle.rebuild()
del gtimer.timers[:]
# load level
self.stage += 1
self.level = Level(self.stage)
self.timefreeze = False
# set number of enemies by types (basic, fast, power, armor) according to level
levels_enemies = (
(18,2,0,0), (14,4,0,2), (14,4,0,2), (2,5,10,3), (8,5,5,2),
(9,2,7,2), (7,4,6,3), (7,4,7,2), (6,4,7,3), (12,2,4,2),
(5,5,4,6), (0,6,8,6), (0,8,8,4), (0,4,10,6), (0,2,10,8),
(16,2,0,2), (8,2,8,2), (2,8,6,4), (4,4,4,8), (2,8,2,8),
(6,2,8,4), (6,8,2,4), (0,10,4,6), (10,4,4,2), (0,8,2,10),
(4,6,4,6), (2,8,2,8), (15,2,2,1), (0,4,10,6), (4,8,4,4),
(3,8,3,6), (6,4,2,8), (4,4,4,8), (0,10,4,6), (0,6,4,10)
)
if self.stage <= 35:
enemies_l = levels_enemies[self.stage - 1]
else:
enemies_l = levels_enemies[34]
self.level.enemies_left = [0]*enemies_l[0] + [1]*enemies_l[1] + [2]*enemies_l[2] + [3]*enemies_l[3]
random.shuffle(self.level.enemies_left)
if play_sounds:
sounds["start"].play()
gtimer.add(4330, lambda :sounds["bg"].play(-1), 1)
self.reloadPlayers()
gtimer.add(3000, lambda :self.spawnEnemy())
# if True, start "game over" animation
self.game_over = False
# if False, game will end w/o "game over" bussiness
self.running = True
# if False, players won't be able to do anything
self.active = True
self.draw()
while self.running:
time_passed = self.clock.tick(50)
for event in pygame.event.get():
if event.type == pygame.MOUSEBUTTONDOWN:
pass
elif event.type == pygame.QUIT:
quit()
elif event.type == pygame.KEYDOWN and not self.game_over and self.active:
if event.key == pygame.K_q:
quit()
# toggle sounds
elif event.key == pygame.K_m:
play_sounds = not play_sounds
if not play_sounds:
pygame.mixer.stop()
else:
sounds["bg"].play(-1)
for player in players:
if player.state == player.STATE_ALIVE:
try:
index = player.controls.index(event.key)
except:
pass
else:
if index == 0:
if player.fire() and play_sounds:
sounds["fire"].play()
elif index == 1:
player.pressed[0] = True
elif index == 2:
player.pressed[1] = True
elif index == 3:
player.pressed[2] = True
elif index == 4:
player.pressed[3] = True
elif event.type == pygame.KEYUP and not self.game_over and self.active:
for player in players:
if player.state == player.STATE_ALIVE:
try:
index = player.controls.index(event.key)
except:
pass
else:
if index == 1:
player.pressed[0] = False
elif index == 2:
player.pressed[1] = False
elif index == 3:
player.pressed[2] = False
elif index == 4:
player.pressed[3] = False
for player in players:
if player.state == player.STATE_ALIVE and not self.game_over and self.active:
if player.pressed[0] == True:
player.move(self.DIR_UP);
elif player.pressed[1] == True:
player.move(self.DIR_RIGHT);
elif player.pressed[2] == True:
player.move(self.DIR_DOWN);
elif player.pressed[3] == True:
player.move(self.DIR_LEFT);
player.update(time_passed)
for enemy in enemies:
if enemy.state == enemy.STATE_DEAD and not self.game_over and self.active:
enemies.remove(enemy)
if len(self.level.enemies_left) == 0 and len(enemies) == 0:
self.finishLevel()
else:
enemy.update(time_passed)
if not self.game_over and self.active:
for player in players:
if player.state == player.STATE_ALIVE:
if player.bonus != None and player.side == player.SIDE_PLAYER:
self.triggerBonus(bonus, player)
player.bonus = None
elif player.state == player.STATE_DEAD:
self.superpowers = 0
player.lives -= 1
if player.lives > 0:
self.respawnPlayer(player)
else:
self.gameOver()
for bullet in bullets:
if bullet.state == bullet.STATE_REMOVED:
bullets.remove(bullet)
else:
bullet.update()
for bonus in bonuses:
if bonus.active == False:
bonuses.remove(bonus)
for label in labels:
if not label.active:
labels.remove(label)
if not self.game_over:
if not castle.active:
self.gameOver()
gtimer.update(time_passed)
self.draw()
if __name__ == "__main__":
gtimer = Timer()
sprites = None
screen = None
players = []
enemies = []
bullets = []
bonuses = []
labels = []
play_sounds = True
sounds = {}
game = Game()
castle = Castle()
game.showMenu()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment