Translations of this page:

Schiff

Schiff is the German word for ship. It needs more work, but is in a playable state.

screenshot

screenshot of the game Schiff


how to play

steer your ship(s)1) with the keyboard to rotate or move forward / backward. Try to avoid enemy bullets and the floating yellow rectangles. Become last ship floating to win the game.

features

no external files necessary apart python and pygame

players

up to 4 players can play sharing one keyboard.

You need Python (2.5.2 or better) and Pygame (1.8.1 or better) to run this game.

Source Code

schiff.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
 
#         - - - - - -
# example with vector class for my pygamebook
# LICENSE: Gnu General Public License (GPL)
# includes vec2d class from www.pygame.org
# AUTHOR: (from the game, not from the vec2dclass): Horst JENS
# email: horst.jens@gmail.com
# web: http://www.spielend-programmieren.at
#        - - - - - - - 
 
#       
#       This program is free software; you can redistribute it and/or modify
#       it under the terms of the GNU General Public License as published by
#       the Free Software Foundation; either version 2 of the License, or
#       (at your option) any later version.
#       
#       This program is distributed in the hope that it will be useful,
#       but WITHOUT ANY WARRANTY; without even the implied warranty of
#       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#       GNU General Public License for more details.
#       
#       You should have received a copy of the GNU General Public License
#       along with this program; if not, write to the Free Software
#       Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#       MA 02110-1301, USA.
 
 
 
# idee Theo:
# schlachtschiff
# ölflecken, minen
# single mit mehr tasten
# peace-modus
# 4 Tasten für Schiffsteuerung, 2 Tasten für Geschützsturm-Steuerung
# Items
#  bonus wenn mehrere Items der selben ARt (Laser, Gescütz etc) aufgesammelt werden -> Upgrades
# Item in Fässern, Fässer muss man zerschiessen ( viele Hitpoints)
 
 
#FIXME: seit pygame.clock-reparatur alles zu schnell ?
#FIXME: tank.reload ist derzeit sinnlos
#FIXME: bar vom AI Schiff hinkt nach
#FIXME: rotation ordentlich machen, ohne -90° und *-1
#FIXME: self.rect.center - oldcenter in Ordnung bringen
#FIXME: Modus sinnvoll machen (derzeit nur space sinnvoll)
#FIXME: spacemove und newmoving vereinheitlichen
#FIXME: division by Zero kapieren
#FIXME: myfont eine globale Variable oder Konstante machen (im Sprite Text)
 
 
#Fixme: öltropfen spawnen zu sehr/dauernd nach Explosion... nicht-spawn-zeit einbauen ?
 
#TODO: AI-Schiff fährt immer in die Mitte der 4 Spieler...4 Viereck hat nicht unbedingt einen exakten Mittelpunkt
#TODO: AI-Schiff Masse (soll nicht so leicht aus der Bahn schiessbar sein)
#TODO: AI-Schiff drehen
#TODO: braunes AI Schiff ruckelt
#TODO: recalc raushaun
#TODO: tracer code entfernen oder Bugwellensystem / Blasen
#TODO: braunes Schiff: schönere Form, drehung, Antrieb, schüsse
 
#TODO: 4 Player / Bots / AI !
#TODO: verstehen: wird ein pos-Vektor als positionsargument übergeben wandert das ding mit (siehe victory)
#TODO: GUI oder config
 
#TODO: Reibung, 
#TODO: optional sich in die Kurve legen (x-schrumpfen) bei rotation-speed<>0
#TODO: Wind
#TODO: Feuer, Riffe oder gefährlicher Rand
#TODO: Segelschiffe, schiessen seitlich, eventuell Buggeschütz, schwaches Heckgeschütz(e)
#TODO: Rammen (Rammwinkel)
#TODO: mehr Segelschiff-sim
#TODO: Vektoren schöner machen, mit Pfeilspitzen oder Dreiecke
 
#TODO: wenn ölfleck getroffen, schneller / grösser / driften
 
#done: AI-Schiff hat 3 Kreise (Geschütztürme)
#done: schussweite (flytime) verringert.
#done: patrol oil rausgehaut. ölflecken nur noch dann wenn ein schiff abgeschossen wird (inkl. grosses AI schiff)
#done: patrol oil spawnt nur im eck und schrumpft nicht
#done: schwarzes AI Schiff verschwindet manchmal
#done: stossen 2 Ölflecken längere Zeit zusammen dann "kalben" Sie einen baby-Ölfleck der ins Spielfeld schwimmt und bei Kollision mit Player explodiert.
#done: AI-Schiffe kommen auch vom Zentrum. Sind selbst Feuerempfindlich-
#done: gelbe Vektoren für Feuer, schwarze Vektoren für AI Schiff
#done: schwarze statt braun, violett statt schwarz.
#done: Ölflecken patrollieren am Rand, spawnen von rand-mitte
#done: GhostTank dreht sich nicht ordentlich mit boss mit.
#done: regeneration (self.regen) von Tanks
#done: Schüsse werden kleiner, mehr damage wenn Schüsse gross sind, Tank hat flytime (für Schüsse)
#done: cooler Bug am Anfang, Schiffe schiessen am Start
#done: Spiel erkennt nicht Game-Over Situation
#done: hitratio - division by zero bug gelöst
#done: pygame <1.8 versionsabfrage, sollte jetzt auch mit pygame 1.7 laufen
#done: Tastaturkürzel sinnvoller (links/rechts), auf mac/netbook testen. Vorschlag: player1: wasd, player2: ijkl oder cursor
#done: 4 Player !
#done: feuervektorliste, gegnervektorliste
#done: Mausvecktor und Positionsvektor mit variabler Länge
#done: Vektorenkreise schön an screenrectseiten ausrichten (Blocksatz)
#done: Vektoren wachsen aus kreis raus (bei ship-coll)
#done: seltsamer zoom-fehler debugt (textsize erhöht anstatt rotozoom bei text verwendet)
#done: Text-classe die schön zoomt und davon abgeleitete hitpointtext mit hitpoint-update
#done: hitpointtext machen
#done: gelber Damagetext bei Ölflecken (danger)
#done: Vectorsprite holt sich koordinaten von bosssprite (Tank)
#done: Victory und Looser Text, vorbereitet für mehr als 2 Spieler
#done: Ölflecken "fressen" Bälle
#done: 4 Ölflecken, am Rand
#done: player2 startet rechts unten
#done: finale Explosion
#done: 2. Vektorspirte mehr nach rechts
#done: unglieche tick-zeit..kugeln fliegen unterschiedlich schnell (?)
#done: schiffe stoppen total bei zusammenstoss
#done: schüsse bremsen (gegenvektor), nicht nur verschieben der Position
#done: schwimmendes Feuer (sehr primitv)
#done:  hitpoints-balken, trefferanzeige
#done: Kugeln haben Farbe von Bosssprite
#done: keine Schmutzflecken mehr. Vorher: entstehen bei speed und schiessen.
#done: ball und schuss
#done: screen und screenrect
#done: move-vektor
#done: Forcevektor
#done: Mausvektor
#done: brems-steuerungslichter
#done: speedlimit
#done: modus umschalten mit m
#done: time-based movement
#done: 2 Tracer Linien von den Eckpunkten, wie Kondensstreifen
 
from __future__ import division
import operator # für vec2d
import math     # für vec2d
# für mich
import pygame   
import random
random.seed() # init random generator with time or other random value
#import vec2d
 
 
 
 
 
class vec2d(object):
    """2d vector class, supports vector and scalar operators,
       and also provides a bunch of high level functions
       """
    __slots__ = ['x', 'y']
 
    def __init__(self, x_or_pair, y = None):
        if y == None:
            self.x = x_or_pair[0]
            self.y = x_or_pair[1]
        else:
            self.x = x_or_pair
            self.y = y
 
    def __len__(self):
        return 2
 
    def __getitem__(self, key):
        if key == 0:
            return self.x
        elif key == 1:
            return self.y
        else:
            raise IndexError("Invalid subscript "+str(key)+" to vec2d")
 
    def __setitem__(self, key, value):
        if key == 0:
            self.x = value
        elif key == 1:
            self.y = value
        else:
            raise IndexError("Invalid subscript "+str(key)+" to vec2d")
 
    # String representaion (for debugging)
    def __repr__(self):
        return 'vec2d(%s, %s)' % (self.x, self.y)
 
    # Comparison
    def __eq__(self, other):
        if hasattr(other, "__getitem__") and len(other) == 2:
            return self.x == other[0] and self.y == other[1]
        else:
            return False
 
    def __ne__(self, other):
        if hasattr(other, "__getitem__") and len(other) == 2:
            return self.x != other[0] or self.y != other[1]
        else:
            return True
 
    def __nonzero__(self):
        return self.x or self.y
 
    # Generic operator handlers
    def _o2(self, other, f):
        "Any two-operator operation where the left operand is a vec2d"
        if isinstance(other, vec2d):
            return vec2d(f(self.x, other.x),
                         f(self.y, other.y))
        elif (hasattr(other, "__getitem__")):
            return vec2d(f(self.x, other[0]),
                         f(self.y, other[1]))
        else:
            return vec2d(f(self.x, other),
                         f(self.y, other))
 
    def _r_o2(self, other, f):
        "Any two-operator operation where the right operand is a vec2d"
        if (hasattr(other, "__getitem__")):
            return vec2d(f(other[0], self.x),
                         f(other[1], self.y))
        else:
            return vec2d(f(other, self.x),
                         f(other, self.y))
 
    def _io(self, other, f):
        "inplace operator"
        if (hasattr(other, "__getitem__")):
            self.x = f(self.x, other[0])
            self.y = f(self.y, other[1])
        else:
            self.x = f(self.x, other)
            self.y = f(self.y, other)
        return self
 
    # Addition
    def __add__(self, other):
        if isinstance(other, vec2d):
            return vec2d(self.x + other.x, self.y + other.y)
        elif hasattr(other, "__getitem__"):
            return vec2d(self.x + other[0], self.y + other[1])
        else:
            return vec2d(self.x + other, self.y + other)
    __radd__ = __add__
 
    def __iadd__(self, other):
        if isinstance(other, vec2d):
            self.x += other.x
            self.y += other.y
        elif hasattr(other, "__getitem__"):
            self.x += other[0]
            self.y += other[1]
        else:
            self.x += other
            self.y += other
        return self
 
    # Subtraction
    def __sub__(self, other):
        if isinstance(other, vec2d):
            return vec2d(self.x - other.x, self.y - other.y)
        elif (hasattr(other, "__getitem__")):
            return vec2d(self.x - other[0], self.y - other[1])
        else:
            return vec2d(self.x - other, self.y - other)
    def __rsub__(self, other):
        if isinstance(other, vec2d):
            return vec2d(other.x - self.x, other.y - self.y)
        if (hasattr(other, "__getitem__")):
            return vec2d(other[0] - self.x, other[1] - self.y)
        else:
            return vec2d(other - self.x, other - self.y)
    def __isub__(self, other):
        if isinstance(other, vec2d):
            self.x -= other.x
            self.y -= other.y
        elif (hasattr(other, "__getitem__")):
            self.x -= other[0]
            self.y -= other[1]
        else:
            self.x -= other
            self.y -= other
        return self
 
    # Multiplication
    def __mul__(self, other):
        if isinstance(other, vec2d):
            return vec2d(self.x*other.x, self.y*other.y)
        if (hasattr(other, "__getitem__")):
            return vec2d(self.x*other[0], self.y*other[1])
        else:
            return vec2d(self.x*other, self.y*other)
    __rmul__ = __mul__
 
    def __imul__(self, other):
        if isinstance(other, vec2d):
            self.x *= other.x
            self.y *= other.y
        elif (hasattr(other, "__getitem__")):
            self.x *= other[0]
            self.y *= other[1]
        else:
            self.x *= other
            self.y *= other
        return self
 
    # Division
    def __div__(self, other):
        return self._o2(other, operator.div)
    def __rdiv__(self, other):
        return self._r_o2(other, operator.div)
    def __idiv__(self, other):
        return self._io(other, operator.div)
 
    def __floordiv__(self, other):
        return self._o2(other, operator.floordiv)
    def __rfloordiv__(self, other):
        return self._r_o2(other, operator.floordiv)
    def __ifloordiv__(self, other):
        return self._io(other, operator.floordiv)
 
    def __truediv__(self, other):
        return self._o2(other, operator.truediv)
    def __rtruediv__(self, other):
        return self._r_o2(other, operator.truediv)
    def __itruediv__(self, other):
        return self._io(other, operator.floordiv)
 
    # Modulo
    def __mod__(self, other):
        return self._o2(other, operator.mod)
    def __rmod__(self, other):
        return self._r_o2(other, operator.mod)
 
    def __divmod__(self, other):
        return self._o2(other, operator.divmod)
    def __rdivmod__(self, other):
        return self._r_o2(other, operator.divmod)
 
    # Exponentation
    def __pow__(self, other):
        return self._o2(other, operator.pow)
    def __rpow__(self, other):
        return self._r_o2(other, operator.pow)
 
    # Bitwise operators
    def __lshift__(self, other):
        return self._o2(other, operator.lshift)
    def __rlshift__(self, other):
        return self._r_o2(other, operator.lshift)
 
    def __rshift__(self, other):
        return self._o2(other, operator.rshift)
    def __rrshift__(self, other):
        return self._r_o2(other, operator.rshift)
 
    def __and__(self, other):
        return self._o2(other, operator.and_)
    __rand__ = __and__
 
    def __or__(self, other):
        return self._o2(other, operator.or_)
    __ror__ = __or__
 
    def __xor__(self, other):
        return self._o2(other, operator.xor)
    __rxor__ = __xor__
 
    # Unary operations
    def __neg__(self):
        return vec2d(operator.neg(self.x), operator.neg(self.y))
 
    def __pos__(self):
        return vec2d(operator.pos(self.x), operator.pos(self.y))
 
    def __abs__(self):
        return vec2d(abs(self.x), abs(self.y))
 
    def __invert__(self):
        return vec2d(-self.x, -self.y)
 
    # vectory functions
    def get_length_sqrd(self): 
        return self.x**2 + self.y**2
 
    def get_length(self):
        return math.sqrt(self.x**2 + self.y**2)    
    def __setlength(self, value):
        length = self.get_length()
        if length == 0:
            pass # do nothing
        else:
            self.x *= value/length
            self.y *= value/length
 
    length = property(get_length, __setlength, None, "gets or sets the magnitude of the vector")
 
    def rotate(self, angle_degrees):
        radians = math.radians(angle_degrees)
        cos = math.cos(radians)
        sin = math.sin(radians)
        x = self.x*cos - self.y*sin
        y = self.x*sin + self.y*cos
        self.x = x
        self.y = y
 
    def rotated(self, angle_degrees):
        radians = math.radians(angle_degrees)
        cos = math.cos(radians)
        sin = math.sin(radians)
        x = self.x*cos - self.y*sin
        y = self.x*sin + self.y*cos
        return vec2d(x, y)
 
    def get_angle(self):
        if (self.get_length_sqrd() == 0):
            return 0
        return math.degrees(math.atan2(self.y, self.x))
    def __setangle(self, angle_degrees):
        self.x = self.length
        self.y = 0
        self.rotate(angle_degrees)
    angle = property(get_angle, __setangle, None, "gets or sets the angle of a vector")
 
    def get_angle_between(self, other):
        cross = self.x*other[1] - self.y*other[0]
        dot = self.x*other[0] + self.y*other[1]
        return math.degrees(math.atan2(cross, dot))
 
    def normalized(self):
        length = self.length
        if length != 0:
            return self/length
        return vec2d(self)
 
    def normalize_return_length(self):
        length = self.length
        if length != 0:
            self.x /= length
            self.y /= length
        return length
 
    def perpendicular(self):
        return vec2d(-self.y, self.x)
 
    def perpendicular_normal(self):
        length = self.length
        if length != 0:
            return vec2d(-self.y/length, self.x/length)
        return vec2d(self)
 
    def dot(self, other):
        return float(self.x*other[0] + self.y*other[1])
 
    def get_distance(self, other):
        return math.sqrt((self.x - other[0])**2 + (self.y - other[1])**2)
 
    def get_dist_sqrd(self, other):
        return (self.x - other[0])**2 + (self.y - other[1])**2
 
    def projection(self, other):
        other_length_sqrd = other[0]*other[0] + other[1]*other[1]
        projected_length_times_other_length = self.dot(other)
        return other*(projected_length_times_other_length/other_length_sqrd)
 
    def cross(self, other):
        return self.x*other[1] - self.y*other[0]
 
    def interpolate_to(self, other, range):
        return vec2d(self.x + (other[0] - self.x)*range, self.y + (other[1] - self.y)*range)
 
    def convert_to_basis(self, x_vector, y_vector):
        return vec2d(self.dot(x_vector)/x_vector.get_length_sqrd(), self.dot(y_vector)/y_vector.get_length_sqrd())
 
    def __getstate__(self):
        return [self.x, self.y]
 
    def __setstate__(self, dict):
        self.x, self.y = dict
 
# ---------------------------
# start
 
 
class Tank(pygame.sprite.Sprite):
    def __init__(self, screenrect, id="player1", mass=100.0):
        pygame.sprite.Sprite.__init__(self)
        #, color=(0,255,0) , pos=(100.0,100.0)
        self.screenrect = screenrect
        #self.mass = mass # how heavy the ship is
        self.mass = 100
        self.geist = False # i am not a ghost
        self.friction = 0.9 # 0.9 reduce speed for 10% each second
        if id == "player1":
            self.vc = (160,200)
            self.pos = vec2d(160,200)
            self.rotating=-30.0 # heading west
            self.color = (255,64,64)
            self.color2 = (255,0,128)
            self.color3 = (255,128,0)
            self.l = pygame.K_a
            self.r = pygame.K_d
            self.u = pygame.K_w
            self.d = pygame.K_s
 
        elif id == "player2":
            #self.rotating = 0.0 
            self.vc = (screenrect.width - 160, 200)
            self.pos = vec2d(screenrect.width - 160, 200)
            self.rotating=-150.0 # heading west
            self.color = (64,64,255)
            self.color2 = (0,128,255)
            self.color3 = (128,0,255)
            self.l = pygame.K_LEFT
            self.r = pygame.K_RIGHT
            self.u = pygame.K_UP
            self.d = pygame.K_DOWN
        elif id=="player3":
            self.vc = (160, screenrect.height - 200)
            self.pos = vec2d(160, screenrect.height - 200)
            self.rotating= 30.0 # heading west
            self.color = (64,255,64)
            self.color2 = (0,255,128)
            self.color3 = (128,255,0)
            self.l = pygame.K_j
            self.r = pygame.K_l
            self.u = pygame.K_i
            self.d = pygame.K_k
        elif id=="player4":
            self.vc = (screenrect.width - 160, screenrect.height - 200)
            self.pos = vec2d(screenrect.width - 160, screenrect.height - 200)
            self.rotating= 150.0 # heading west
            self.color = (255,64,255) #64x3
            self.color2 = (32,64,0)
            self.color3 = (10,64,32)
            self.l = pygame.K_KP4 
            self.r = pygame.K_KP6 
            self.u = pygame.K_KP8  # or KP8
            self.d = pygame.K_KP5
 
        self.shotby = {}
        self.shotby["player1"] = 0
        self.shotby["player2"] = 0
        self.shotby["player3"] = 0
        self.shotby["player4"] = 0
        self.shotby["danger"] = 0
        self.shotbysomebody = 0
        self.wrap = True # wrap-around world
        self.force = 0
        self.regen = 1 # regenarte hitpoints per full second
        #--- reloading and ammo
        self.flytime =  2.5 #1.0 # 5.0 seconds ... how long a shot fly
        self.reloading = 0.1 # tank can not fire while relaoding -------- BUG !!!! -----
        self.reloadingtime = 0.3 # seconds needed to reload, 0.0 means automatic fire
        self.ammowrap = 1 # how many time the ammo will wrap around world edge
        self.moving = vec2d(0.0,0.0)
        self.image1 = pygame.Surface((50,50))
        self.image1.fill((255,255,255))         # fill with white
        self.image1.set_colorkey((255,255,255)) # make white transparent
        pygame.draw.rect(self.image1 , (10,10,10), (0,0,50,8))
        pygame.draw.rect(self.image1, (10,10,10), (0,42,50,50))
        self.p1 = (-25.0,-25.0) # middle is 25,25
        self.p2 = (-25.0, 25.0) # middle is 25,25
        pygame.draw.polygon(self.image1, (64,64,64), [(5,10),(5,40),(50,25)],0)
        pygame.draw.circle(self.image1, self.color,(25,25),16,0) # 0 füllt den Kreis
        self.image1.convert_alpha()
        self.image = self.image1.copy() # copy to not destroy image1
        self.rect = self.image.get_rect()
        self.rect.center = (round(self.pos.x,0),round(self.pos.y,0)) 
        self.limit = 100 # the speed limit # --------------------- ausprobieren
        self.rotspeed = 0.0
        self.maxrotspeed =300 # maximum turnspeed
        self.rotdelta = 30   # um wieviel gedreht wird
        self.forcedelta = 5  # 
        self.oldcenter = (0,0)
        self.oldcenter2 = (0,0)
        self.oldrot = 0
        self.spacemove= vec2d(0,0)
        self.newmoving = vec2d(0,0)
        self.cm = 0.0 # angle to mouse
        self.cmd = 0.0 # distance to mouse
        self.showvector = True
        self.modus = "space"
        self.speedflag = False
        self.showtext = False
        self.drawTracer = False
        self.id= id
        self.hitpoints = 500.0  # float division ! # 500.0
        self.hitpointsfull = 500.0       # 500.0
        self.peaceful = False
        self.hitradius = 20.0
        #self.damagemax = 1 # how much damage a bullet in the center does (20)
        # damagemax can be higher by 5 because it is difficult to
        # teleport a bullet into the center
        #self.damagemin = 1  # how much damage a bullet on the edge does
        self.offset = 15 # if set to 0 tank can complete disappear behind
                        # screen edge bevor wrap-around on other site
                        # a offset > 0 spoil that, so that a bit of the tank
                        # should be always visible
        #self.shotby = None
        self.shots = 0 # how many balls this tank shoots in his lifetime
        self.hits = 0 # how many times this thank hits an enemy in his lifetime
        self.age = 0.0
        self.ageseconds = 0
        self.alive = True
 
 
    def fire(self):
        """check if firing is allowed, return True if yes and set the reloadingtime"""
        if self.peaceful:
            return False
        elif self.reloading > 0:
            return False # Tank is still reloading
        elif not self.screenrect.collidepoint(self.rect.center):
            # firing not allowed if Tank center out of screenrect
            return False
        else:
            self.reloading = self.reloadingtime # now the weapon must be reloaded for some time
            self.shots += 1
            return True
 
    def update(self, tick_seconds):
        """tick is the time passed since last frame in seconds"""
        #save old position
        self.age += tick_seconds # Tank get older
        if self.age - self.ageseconds > 1: 
            # a full second survived, time for regeneration
            self.ageseconds = int(self.age)
            self.hitpoints += self.regen # heals each full second
        self.rect = self.image.get_rect()
        self.oldcenter = self.rect.center # from the image, only for rotating
 
        # check reloading time,check if ready to fire again
        if self.reloading: # <>0..True, 0..False
            self.reloading -= tick_seconds
        if self.reloading < 0:
            self.reloading = 0
 
        # image selector / rotation / speed lights painting
        self.image = self.image1.copy()
        if self.rotspeed < 0:
            #self.image = self.image2
            pygame.draw.rect(self.image,  (255,0,0), (0,0,8,8))
            pygame.draw.rect(self.image,  (255,0,0), (42,42,50,50))
        elif self.rotspeed > 0:
            pygame.draw.rect(self.image, (255,0,0), (0,42,8,50))
            pygame.draw.rect(self.image, (255,0,0), (42,0,50,8))           
        if self.force > 0:
            pygame.draw.rect(self.image, (255,0,0), (0,10,5,30))
        elif self.force < 0:
            pygame.draw.rect(self.image, (255,0,0), (45,10,50,30))
 
 
        #--- rotate the sprite
        self.rot(self.image)
 
        #--- calculate new position
        self.rect.center = (round(self.pos.x,0), round(self.pos.y,0))
        #self.oldcenter2 = self.rect.center # save for drawing
 
        #--- render mousevector  # FIXME...ugly !
        c = vec2d(self.rect.center) # vector to center on screen
        m = vec2d(pygame.mouse.get_pos()) #vector to mousepos on screen
        cm =  m - c # vector from sprite-center to mousepos
        self.cm = cm.angle
        self.cmd = cm.length 
        cm.length = 16 # shrink length to radius of sprite circle
        #    calulate the new middle of the rotated sprite
        nm = vec2d(self.image.get_rect().width/2,self.image.get_rect().height/2)
        cm += nm  # correction because sprite is rotated, middle point moves
        pygame.draw.line(self.image, (0,0,0), (nm.x,nm.y), (int(cm.x), int(cm.y)),1)
 
        # more precise keyboard event handler
        pressed_keys = pygame.key.get_pressed()
        if pressed_keys[self.l]:
            self.rotspeed += self.rotdelta 
        if pressed_keys[self.r]:
            self.rotspeed -= self.rotdelta
        if pressed_keys[self.u]:
            self.force += self.forcedelta
        if pressed_keys[self.d]:
            self.force -= self.forcedelta
 
        # halt rotation when not-auto
        if self.modus != "auto":
            if (not pressed_keys[self.l] and 
                not pressed_keys[self.r]):
                    self.rotspeed = 0 # stop rotation
            if (not pressed_keys[self.u] and
                not pressed_keys[self.d]):
                    self.force = 0 # stop moving
 
        #limit to limit, maxrotspeed
        if self.rotspeed > self.maxrotspeed:
            self.rotspeed = self.maxrotspeed
        if self.rotspeed < -self.maxrotspeed:
            self.rotspeed = -self.maxrotspeed
 
        if self.force >= self.limit:
            self.force = self.limit
            self.speedflag = True
        elif self.force <= -self.limit:
            self.force = -self.limit
            self.speedflag = True
        else:
            self.speedflag = False
 
        #reset rotation on 360 Degree
        if self.rotating < -360:
            self.rotating += 360
        if self.rotating > 360:
            self.rotating -= 360
 
        # time-based rotation        
        self.rotating += self.rotspeed * tick_seconds 
 
        #FIXME: das geht sicher einfacher auch
        # simple forward movement
        if self.modus=="space":
            if self.force!=0:
                if self.rotating != 0:
                    dazu = vec2d(self.force, 0)
                    dazu.rotate(-self.rotating)
                    self.spacemove += dazu
                else:
                    self.spacemove += vec2d(self.force,0)
            # check speed limit
            if self.spacemove.length >= self.limit:
                self.spacemove.length = self.limit
                self.speedflag = True
            else:
                self.speedflag = False
            #self.pos += self.spacemove * tick_seconds
            self.newmoving = self.spacemove # komaptibel machen ?
 
        else:
            if self.force!= 0:
                self.newmoving = vec2d(self.force,0)
                if self.rotating != 0:
                    self.newmoving.rotate(-self.rotating)
                #self.pos += self.newmoving * tick_seconds
        self.newmoving *= self.friction ** tick_seconds # Reibung Leo
        self.pos += self.newmoving * tick_seconds 
        if self.wrap: 
            # wrap-around world
            #if self.pos.x + self.rect.width/2 < 0:
            #    #self.pos.x = self.screenrect.get_width()  + self.rect.width/2
            #    self.pos.x = self.screenrect.width  + self.rect.width/2 - self.offset
            #if self.pos.x - self.rect.width/2 > self.screenrect.width:
            #    self.pos.x = 0 - self.rect.width/2 + self.offset
            #if self.pos.y + self.rect.height/2 < 0:
            #    self.pos.y = self.screenrect.height + self.rect.height/2 - self.offset
            #if self.pos.y - self.rect.height/2 > self.screenrect.height:
            #    self.pos.y = 0 - self.rect.height/2 + self.offset
            #--------- wrap at self.rect.center, TankGhost will care about the Rest
            if self.pos.x < 0:
                self.pos.x = self.screenrect.width
            if self.pos.x > self.screenrect.width:
                self.pos.x = 0
            if self.pos.y < 0:
                self.pos.y = self.screenrect.height
            if self.pos.y > self.screenrect.height:
                self.pos.y = 0
 
 
        else:
             #no wrap-around  , stop on end of screen
            if self.pos.x < 0:
                self.pos.x = 0 
            if self.pos.x > self.screenrect.width:
                self.pos.x = self.screenrect.width
            if self.pos.y < 0:
                self.pos.y = 0
            if self.pos.y > self.screenrect.height:
                self.pos.y = self.screenrect.height
 
        #draw tracer ?
        if self.drawTracer:
            self.tracer()
        #else:
        #    self.dirtyrect=(0,0,0,0)
 
    def tracer(self):  
        #test if tracer is not too long (because world-wrap)
        nc = vec2d(self.rect.center)
        oc = vec2d(self.oldcenter2)
        if (nc - oc).length < self.rect.width:
            pygame.draw.line(background, self.color,self.oldcenter2, self.rect.center,2)
            nc1 = vec2d(self.p1)
            nc2 = vec2d(self.p2)
            oc1 = vec2d(self.p1)
            oc2 = vec2d(self.p2)
            # rotate the tracer-vectors
            vec2d.rotate(nc1,-self.rotating)
            vec2d.rotate(nc2,-self.rotating)
            vec2d.rotate(oc1,-self.rotating)
            vec2d.rotate(oc2,-self.rotating)
            #"----adding rect center----"
            nc1 += nc 
            nc2 += nc
            #"------ adding oldcenter ------"
            oc1 += oc
            oc2 += oc
            # draw the lines from old to new
            pygame.draw.line(background, self.color2, (oc1.x, oc1.y),(nc1.x,nc1.y),1)
            pygame.draw.line(background, self.color3, (oc2.x, oc2.y),(nc2.x,nc2.y),1)
            #self.dirtyrect =(int(min(nc1.x, nc2.x, oc1.x, oc2.x, oc.x, nc.x))-1,
            #                 int(min(nc1.y, nc2.y, oc1.y, oc2.y, oc.y, nc.y))-1,
            #                 int(max(nc1.x, nc2.x, oc1.x, oc2.x, oc.x, nc.x))+1,
            #                 int(max(nc1.y, nc2.y, oc1.y, oc2.y, oc.y, nc.y))+1)
        self.oldcenter2 = self.rect.center # save old tracerposition        
 
    def rot(self, image):
        self.image = pygame.transform.rotate(image, self.rotating)
        self.rect.center = self.oldcenter 
    def kill(self):
        self.alive = False
        pygame.sprite.Sprite.kill(self)
 
class TankGhost(Tank):
    def __init__(self, boss, id):
        """ id = N, S , W, E"""
        Tank.__init__(self, boss.screenrect, boss.id, mass=100.0)
        self.boss = boss
        self.id = id
        self.color = boss.color
        self.peaceful = True # Ghost does not fire
        self.geist = True # i am a ghost
 
    def update(self, tick_seconds):
        # is boss dead ?
        if self.boss.alive == False:
            pygame.sprite.Sprite.kill(self)
            #TankGhost.kill(self)
 
        self.image = self.boss.image
        self.rect = self.image.get_rect()
        if self.id == "N":
            self.rect.center = (self.boss.rect.centerx, self.boss.rect.centery - self.boss.screenrect.height)
        elif self.id == "S":
            self.rect.center = (self.boss.rect.centerx, self.boss.rect.centery + self.boss.screenrect.height)
        elif self.id == "W":
            self.rect.center = (self.boss.rect.centerx - self.boss.screenrect.width , self.boss.rect.centery)
        elif self.id =="E":
            self.rect.center = (self.boss.rect.centerx + self.boss.screenrect.width , self.boss.rect.centery)
 
 
 
 
 
 
 
class Bar(pygame.sprite.Sprite):
        """ a health-bar floating above each player"""
 
        def __init__(self, boss, vectorbar=False, color=(0,255,0)):
            pygame.sprite.Sprite.__init__(self)
            self.vectorbar = vectorbar
            self.boss = boss
            if self.vectorbar:
                self.long = 300 # vectorsprite circle radius * 2
            else:
                self.long = boss.rect.width
            self.longold = self.long
            self.percent = 1.0
            self.percentold = 0.0
            self.color = color
            self.image = pygame.Surface((self.long,5))
 
        def update(self, tick):
            """tick is the time passed since last frame in seconds"""
            if self.boss.hitpoints <= 0:
                self.kill()
            self.percent = (self.boss.hitpoints /
                            self.boss.hitpointsfull)
            if not self.vectorbar:
                self.long = self.boss.rect.width
            if self.percent != self.percentold or self.longold != self.long:
                self.image = pygame.Surface((self.long,5))
                self.rect = self.image.get_rect()
                self.image.fill((255,255,255))        # white
                pygame.draw.rect(self.image,(0,255,0),(0,0,
                                 int(self.long*self.percent), 5))
                #black rectangle
                pygame.draw.rect(self.image,(0,0,0),(0,0,self.long,5), 1) 
                self.image.set_colorkey((255,255,255))
                self.image.convert_alpha()
            if self.vectorbar:
                self.rect.centerx = self.boss.vc[0]
                self.rect.centery = self.boss.vc[1] - 160 # vectorbar above vectorcircle
            else:
                self.rect.centerx = self.boss.rect.centerx
                self.rect.centery = self.boss.rect.y - 10
            self.percentold = self.percent
            self.longold = self.long
 
class Text(pygame.sprite.Sprite):
        """a changable text"""
        def __init__(self, pos, msg="Hello World",color=(5,5,5), maxlifetime=-1.0, textsize=25, vec=vec2d(0,0),msgchange=False, zoom=False):
            """ negative maxlifetime means stay until Game Over
                msgchange means the message may change over time
                pos in format(x,y), will be changed into vec2d in Text__init__"""
            pygame.sprite.Sprite.__init__(self)
            #self.static = static #static means text does not disappear
            self.zoom = zoom
            self.zoomfactor = 4.0
            self.textsize = int(textsize)
            self.textsize0 = self.textsize
            self.msgchange = msgchange
            self.vec = vec
            self.pos = vec2d(pos)
            self.msg = msg
            self.color = color
            self.recalcimage()
            self.rect.center = pos
            self.lifetime = 0
            self.maxlifetime = maxlifetime #seconds
            self.tick = 0.0 # seconds since last frame
            #self.recalc(self.msg)
 
 
        def update(self, tick):
            """tick is passed seconds"""
            self.tick = tick
            self.lifetime += tick
            if self.zoom:
                self.textsize = int(self.textsize0 + self.lifetime * self.zoomfactor) # int instead of round becaouse round makes float and float upset pygame.font
                self.msgchange = True
            if self.msgchange:
                self.recalcimage()
            if self.vec != vec2d(0,0):
                self.pos += self.vec * self.tick # self.pos is a vec2d object
                self.rect.center  = (round(self.pos.x,0), round(self.pos.y,0))
            if self.maxlifetime > 0 and self.lifetime > self.maxlifetime:
                self.kill()
 
        #def kill(self):
        #    pygame.sprite.Sprite.kill(self)
 
        def recalcimage(self):
            """renders the text to self.image"""
            self.font = pygame.font.SysFont("None",self.textsize)
            self.textsurface1 = self.font.render(self.msg, True, self.color) #antialias = False
            self.image = pygame.Surface((self.textsurface1.get_size()))
            self.image.fill((255,255,255))         # fill with white
            self.image.set_colorkey((255,255,255)) # make white transparent
            #self.image = self.image1.copy() # to save image1
            self.image.blit(self.textsurface1,(0,0))
            self.rect = self.image.get_rect()
            self.rect.center  = (round(self.pos.x,0), round(self.pos.y,0))
 
 
 
class HitpointText(Text):
    """ this class inherit from Text class and updated the hitpoints of a given player"""
    def __init__(self, boss):
        Text.__init__(self, boss.vc , "dummytext", (0,0,0),-1,20, vec2d(0,0), True, False)
        self.boss = boss
        self.pos += vec2d(0,-170) # self.pos is a vec2d object from Text ?
    def update(self, tick):
        self.msg = "Hitpoints:"+str(self.boss.hitpoints)
        Text.update(self, tick)
 
 
 
 
 
 
class VectorSprite(pygame.sprite.Sprite):
    """draws a drawing of the tank in a fixed position
       including vectors for relative speed, enemy etc."""
    def __init__(self, boss):
        pygame.sprite.Sprite.__init__(self)
        self.boss = boss
        #self.startx = startx
        self.image0 = pygame.Surface((300,300)) # 200,200
        self.image0.fill((255,255,255))         # fill with white
        self.image0.set_colorkey((255,255,255)) # make white transparent
        self.image0.convert_alpha()
        self.vc = (int(self.boss.vc[0]),int(self.boss.vc[1])) # Vectorcenter from boss_sprite
        self.color = self.boss.color # get color from boss
        # draw polygon, same as Tank, but without colors
        self.image1 = pygame.Surface((50,50))
        self.image1.fill((255,255,255))         # fill with white
        self.image1.set_colorkey((255,255,255)) # make white transparent
        pygame.draw.rect(self.image1 , self.color, (0,0,50,8),1)
        pygame.draw.rect(self.image1 , self.color, (0,42,50,8),1) # small bug
        pygame.draw.polygon(self.image1, self.color, [(0,10),(0,40),(50,25)],1)
        pygame.draw.circle(self.image1, self.color,(25,25),16,1) # 0 füllt den Kreis
        pygame.draw.line(self.image1, self.color,(25,25),(50,25),1) # strich schaut nach rechts
        self.image1.convert_alpha()
        self.image = self.image0.copy() # to save image1
        self.rect = self.image.get_rect()
        self.size = 5
        self.dangerlist = []
        self.enemylist = []
 
    def learn(self, enemy, danger=True):
        """ learn about one enemy at a time, but not about self"""
        if danger:
            self.dangerlist.append(enemy)
        else:
            if enemy.color != self.boss.color:
                self.enemylist.append(enemy)
 
 
    def update(self, tick_seconds):
        """ update the vectors and rotation of the tank.
            tick_seconds is only here to get accepted, but i do nothing with it"""
        #self.dangerlist = []
        #self.enemylist = []
        if self.boss.showvector:
            #--- draw vectorsprite with correct rotation
            self.image = self.image0.copy()
            self.image2 = self.image1.copy()
            self.image2 = pygame.transform.rotate(self.image2 , self.boss.rotating)
            rect = self.image2.get_rect()
            center2 = rect.center
            #self.image.blit(self.image2, (100-rect.width/2, 100-rect.height/2))
            self.image.blit(self.image2, (150-rect.width/2, 150-rect.height/2))
            self.rect.center = self.vc
 
            #--- draw big mousevector
            #cm = vec2d(0,min(150, self.boss.cmd)) # cmd is sprite-mousedistance
            #cm.rotate(self.boss.cm-90) # self.boss.cm contains the angle from tank to mouse
            #cm += vec2d(self.vc)
            #pygame.draw.line(screen, (0,255,255),(self.vc), (cm.x, cm.y),1) # cyan
 
            #--- draw force vector
            f = 1.0 * self.boss.force / self.boss.limit # 1.5 is around the vectorcircle 
            cm = vec2d(0, f*150.0)
            cm.rotate(-self.boss.rotating-90)
            cm += vec2d(self.vc)
            pygame.draw.line(screen, self.boss.color, (self.vc), (round(cm.x,0), round(cm.y,0)),20)
 
 
            #--- draw space vector
            if self.boss.modus =="space":
                speed = 1.0 * self.boss.spacemove.length / self.boss.limit
                s = min(1, speed)
                if speed > 1:
                    self.size +=1
                else:
                    self.size = 5
                cm = vec2d(0, s*150.0)
                cm.rotate(self.boss.spacemove.angle-90)
                cm += vec2d(self.vc)
                pygame.draw.line(screen, (255,0,255), (self.vc), (int(cm.x), int(cm.y)),self.size) # pink
 
            #--- draw sprite vector
            cm =  vec2d(self.boss.rect.center) - vec2d(self.vc)  
            #diagonal = vec2d(self.vc) - vec2d(self.boss.screenrect.width, self.boss.screenrect.height)
            #dia = diagonal.length * 1.0 # to get float
            #cm.length = (cm.length / dia)*100
            cm.length = min(cm.length, 150.0)  #100
            cm+=vec2d(self.vc)
            pygame.draw.line(screen, (0,0,0), (self.vc), (int(cm.x), int(cm.y)),1)
 
            #--- draw dangervector
            for dang in self.dangerlist:
                size = 1
                cd = dang.pos - self.boss.pos
                cd.length = min(cd.length, 150.0)
                if cd.length < 150:
                    size = int(150 - cd.length)
                cd+=vec2d(self.vc)
                if dang.type == "oil":
                    col = (255,255,0)
                else:
                    col = (0,0,0)
                pygame.draw.line(screen, col, (self.vc), (int(cd.x), int(cd.y)),size)
            self.dangerlist = []
 
            #--- draw enemyvector
            for enemy in self.enemylist:
                size = 1
                cd = enemy.pos - self.boss.pos
                cd.length = min(cd.length, 150.0)
                if cd.length < 150:
                    size = int(150 - cd.length)
                cd+=vec2d(self.vc)
                pygame.draw.line(screen, enemy.color, (self.vc), (int(cd.x), int(cd.y)),size)
            self.enemylist = []
 
        else:
            self.rect.center = (-400,-400) # hide vectorsprite out of screenrect
 
 
 
class Ball(pygame.sprite.Sprite):
    def __init__(self, bossSprite, smoke=False, mass=1):
        pygame.sprite.Sprite.__init__(self)
        self.boss = bossSprite
        self.radius = 5  #5
        Ball.paintme(self, self.radius)
        self.pos = vec2d(self.boss.rect.center)
        self.rect.center = (round(self.pos.x,0), round(self.pos.y,0))
        self.moving=vec2d(self.boss.limit * 1.1,0) # speeed of bullet = 110% of max speed of player 
        #self.moving.rotate(self.boss.cm) # shoot to mousepointer
        self.moving.rotate(-self.boss.rotating) # shoot to direction of Tank/Ship
        self.moving += self.boss.spacemove # add tank velocity to ball velocity
        self.massfactor = 0.2 # reduces the impact in relation to a players mass. #.91 a bissl gross
        self.ammowrap = self.boss.ammowrap # how many times bullet can wrap around screen edge
        self.age = 0.0 # lifetime of ball since start in seconds
        self.maxage = self.boss.flytime # ball is killed if he gets older than this time (in seconds)
 
 
    def update(self, tick_seconds):
        '''tick is time passed in seconds'''
        self.age += tick_seconds # ball get older
        if self.age > self.maxage:
            self.kill()
        else:
            factor = self.age / self.maxage # % of oldness of Ball
            Ball.paintme(self, 1 + self.radius - int(self.radius * factor))
        self.pos += self.moving * tick_seconds
        self.rect.center = (round(self.pos.x,0), round(self.pos.y,0))
        if not self.boss.screenrect.collidepoint(self.rect.center):
            if self.ammowrap > 0:
                self.ammowrap -=1 # one less time allowed to wrap world
                if self.pos.x > self.boss.screenrect.width:
                    self.pos.x = 0
                if self.pos.x < 0:
                    self.pos.x = self.boss.screenrect.width
                if self.pos.y > self.boss.screenrect.height:
                    self.pos.y = 0
                if self.pos.y < 0:
                    self.pos.y= self.boss.screenrect.height
            else:
                self.kill()
 
 
 
    def paintme(self, radius):
        self.tempradius = radius
        self.image = pygame.Surface((radius*2,radius*2))
        self.image.fill((255,255,255))         # fill with white
        self.image.set_colorkey((255,255,255)) # make white transparent
        pygame.draw.circle( self.image,self.boss.color, (radius,radius), radius , 0)
        self.image.convert_alpha()
        self.rect = self.image.get_rect()   
 
class Danger(pygame.sprite.Sprite):
    """ an enemy to all players. can be a floating piece of burning oil that damage players
    but ignore shooting.
    or can be an enemy ship."""
    oilborder = [0,0,0,0] # n,w,s,o # this is a class variable
    def __init__(self, screenrect, type="oil", size=40, pos=(0,0)):
        pygame.sprite.Sprite.__init__(self)
        self.size = size
        self.lifetime = 0.0
        self.oldsize = size
        self.screenrect = screenrect
        self.limit = 500 # absolute speed limit
        self.type = type # what kind of Danger, an oil or an boat ?
        self.bar = False # check if i have an hitpoint-bar
        self.wo = -1 # indicator of border of oil, -1 means free floating
        #self.pregnant = 0 # spawn a baby danger ?
        #self.birth = 100 # if self.pregnant reach this value, it will spawn a new baby-danger
 
        distance = 30 # distance from screenrect border to oil.rect.center
 
        if self.type == "oil":
            self.vulnerable = False # can  be shot down            
            self.damagePlayer = True # can damage player that bumb into it
            self.color = (255,random.randint(128,255),0) # between yellow and red ?
 
            if self.size==40:
                pass
                ## big oil, patrol border
                #self.hitpoints = 100
                #self.hitpointsfull = 100
                #self.size = 40
                #self.min = 50
                #self.max = 100 # oil float very slow
 
                ## where to spawn oil ?
                #for side in Danger.oilborder:
                    #if side == min(Danger.oilborder):
                        #wo = Danger.oilborder.index(min(Danger.oilborder))
                        #self.wo = wo
                        #Danger.oilborder[wo] += 1
                        #break
                #if wo == 0:
                    #self.pos = vec2d(self.screenrect.centerx, distance) 
                    #self.vec = vec2d(random.randrange(self.min,self.max) * random.choice([-1,1]), 0)
                #elif wo == 1:
                    #self.pos = vec2d(distance, self.screenrect.centery)
                    #self.vec = vec2d(0, random.randrange(self.min,self.max) * random.choice([-1,1]))
                #elif wo == 2:
                    #self.pos = vec2d(self.screenrect.centerx, self.screenrect.height - distance)
                    #self.vec = vec2d(random.randrange(self.min,self.max) * random.choice([-1,1]), 0)
                #elif wo == 3:
                    #self.pos = vec2d(self.screenrect.width - distance, self.screenrect.centery)
                    #self.vec = vec2d(0, random.randrange(self.min,self.max) * random.choice([-1,1]))
 
            else:
                # small oil. self.wo = -1
                self.hitpoints = 10
                self.hitpointsfull = 10
                self.min = 100
                self.max = 200
                self.pos = vec2d(pos)
                self.vec = vec2d(random.randrange(self.min, self.max) * random.choice([-1,1]),
                                 random.randrange(self.min, self.max) * random.choice([-1,1]))
 
            #for all oil:
            self.image = pygame.Surface((self.size,self.size))
            self.damage = 1 # hitpoint loss for player
            pygame.draw.rect( self.image,self.color, (0,0,self.size,self.size), 0)    
 
        elif self.type == "boat":
            self.vulnerable = True
            self.hitpoints = 100
            self.hitpointsfull = 100
            self.size = 50
            self.damagePlayer = True
            self.min = 5 # minimal speed
            self.max = 100 # maximal speed
            self.pos = vec2d(self.screenrect.centerx, self.screenrect.centery) 
            self.vec = vec2d(random.randrange(self.min,self.max) * random.choice([-1,1]), random.randrange(self.min,self.max) * random.choice([-1,1]))
            #self.damageBall = True
            self.heading = self.vec.angle
            self.vulnerable = True
            self.color = (100,100,100) # grey
            #-------
            self.damage = 10 # damage to player ?
            self.image = pygame.Surface((200,200))
            self.image.fill((255,255,255)) # fill white
            self.image.set_colorkey((255,255,255)) # make white transparent
            pygame.draw.polygon(self.image, self.color, [(0,100), (20,70), (190,70), (200,100), (190,130), (20,130) ] , 0)
            pygame.draw.circle(self.image, (40,100,40), (50,100), 20, 0)
            pygame.draw.circle(self.image, (40,100,40), (100,100), 20, 0)
            pygame.draw.rect(self.image, (40,40,100), [120,72,30,58])
            pygame.draw.circle(self.image, (40,100,40), (170,100), 20, 0)
            self.image.convert_alpha()
            #pygame.transform.flip(self.image, True, False)
            self.image0 = self.image.copy()
 
        # start in screenrect
 
        #complete random direction
 
        #self.image.convert_alpha()
        self.rect = self.image.get_rect()
        self.rect.center=  (round(self.pos.x,0), round(self.pos.y,0))
 
 
 
 
    def update(self, tick_seconds):
        '''tick is time passed in seconds'''
        self.lifetime+= tick_seconds
        if self.hitpoints < 0:
            self.kill()
        if self.type == "oil":
            if self.size != self.oldsize:
                 self.image = pygame.Surface((self.size,self.size))
                 self.rect = self.image.get_rect()
            self.color = (255,random.randint(128,255),0) # between yellow and red ?
            pygame.draw.rect( self.image,self.color, (0,0,self.size,self.size), 0)
        elif self.type == "boat":
            #heading is actual mouse position
            #self.vec = vec2d(pygame.mouse.get_pos) - vec2d(self.rect.center)
            c = vec2d(self.rect.center) # vector to center on screen
            m = vec2d(pygame.mouse.get_pos()) #vector to mousepos on screen
            cm =  m - c # vector from sprite-center to mousepos
            #self.cm = cm.angle
            #self.cmd = cm.length 
            cm.length = min(self.max, cm.length) # not faster than self.max
            self.vec = cm
            #rotate boat toward heading
            #self.oldpos = self.rect.center
            self.image = pygame.transform.rotate( self.image0, self.vec.angle)
            #self.rect.center = self.oldpos
        if self.vec.length > self.limit:
            self.vec.length = self.limit
        self.pos += self.vec * tick_seconds
 
 
 
        grow = False
        #no wrap-around  , stop on end of screen
        if self.pos.x < 0:
            #grow = True
            self.pos.x = 0  # stuck on the left side
            #if self.wo == 0 or self.wo == 2:
                #self.vec = vec2d(random.randrange(self.min,self.max), 0)
            #else:
            self.vec = vec2d(random.randrange(self.min,self.max), random.randrange(self.min,self.max) * random.choice([-1,1]))
        if self.pos.x > self.screenrect.width:
            #grow = True
            self.pos.x = self.screenrect.width # stuck on the right side
            #if self.wo == 0 or self.wo == 2:
                #self.vec = vec2d(random.randrange(self.min,self.max)*-1, 0)
            #else:
            self.vec = vec2d(random.randrange(self.min,self.max)*-1, random.randrange(self.min,self.max) * random.choice([-1,1]))
        if self.pos.y < 0:
            #grow = True
            self.pos.y = 0 # stuck on top border
            #if self.wo == 1 or self.wo == 3:
                #self.vec = vec2d(0,random.randrange(self.min, self.max))
            #else:
            self.vec = vec2d(random.randrange(self.min,self.max) * random.choice([-1,1]), random.randrange(self.min,self.max) )
        if self.pos.y > self.screenrect.height:
            #grow = True
            self.pos.y = self.screenrect.height #stuck on bottom
            #if self.wo == 1 or self.wo == 3:
                #self.vec = vec2d(0,random.randrange(self.min, self.max)*-1)
            #else:
            self.vec = vec2d(random.randrange(self.min,self.max) * random.choice([-1,1]), random.randrange(self.min,self.max) * -1)
        self.rect.center = (round(self.pos.x,0), round(self.pos.y,0))
        #self.oldsize = self.size       
        #if grow and self.wo > -1:
            #self.size +=1
 
class Wound(pygame.sprite.Sprite):
        """ a little explosion marking the 'wound' of a hit"""
        def __init__(self, pos, size, maxlifetime=0.5, boss_sprite=None):
            pygame.sprite.Sprite.__init__(self)
            self.size = int(size) # sometimes, wound comes as float (from tux?)
            #if self.size < medium:
            #    self.size *=2
 
            self.pos = pos
            self.maxlifetime = maxlifetime # in seconds
            self.boss_sprite = boss_sprite
            self.age = 0 # age in decimal seconds
            self.frames = 0 #frames in integer
            self.image = pygame.Surface((size*2, size*2))
            self.surface_center = (self.size, self.size) 
            self.rect = self.image.get_rect()
            self.rect.center = (round(self.pos.x,0),round(self.pos.y,0))
            #if self.boss_sprite:
            #    self.dx = self.pos[0] - self.boss_sprite.rect.centerx 
            #    self.dy = self.pos[1] - self.boss_sprite.rect.centery
            self.update(0) # update need the argument tick
 
        def update(self, tick_seconds):
            """ tick_seconds is decimalseconds passed since last frame"""
            self.age += tick_seconds
            self.frames +=1
            if self.age > self.maxlifetime: #one second at 30 fps
                self.kill()
            else:
                self.rect.center = (round(self.pos.x, 0), round(self.pos.y,0))
                pygame.draw.circle(self.image, (255,random.randint(0,255),0),
                     self.surface_center,min(self.size,self.frames), 0)
                self.image.set_colorkey((0,0,0))
                self.image.convert_alpha()
                #if self.boss_sprite:
                #    self.rect.centerx = self.boss_sprite.rect.centerx + self.dx
                #    self.rect.centery = self.boss_sprite.rect.centery + self.dy
                #else:
                #    self.rect.center = self.pos
 
 
 
 
def paint(x, y,  color):
    pygame.draw.circle(background, color, (x,y), 150, 1)
    pygame.draw.line(background, color, (x-160, y) , (x+160,y), 1)
    pygame.draw.line(background, color, (x,y-160), (x,y+160),1)
    textsurface = myFont.render(u"0° = 360°", True, color)
    background.blit(textsurface, (x+50,y-20))
    textsurface = myFont.render(u"90°", True, color)
    background.blit(textsurface, (x-10,y-150))
    textsurface = myFont.render(u"180°", True, color)
    background.blit(textsurface, (x-140,y-20))
    textsurface = myFont.render(u"270°", True, color)
    background.blit(textsurface, (x-10,y+125))
 
#--- main start  --
pygame.init()
try:
    screen=pygame.display.set_mode((0,0)) # (0,0) uses the full screensize
except:
    x = raw_input("enter x resolution of screen:", 800)
    y = raw_input("enter y resolution of screen:", 600)
    if int(x) <= 0 or int(y) <= 0:
        print "bad integer value. defaulting to 800x600"
        x = 800
        y = 600
    screen=pygame.display.set_mode((x,y))
screenrect = screen.get_rect()
pygame.display.set_caption("press Esc to exit")
#--- background
background = pygame.Surface(screen.get_size())
#background = background.convert()
background.fill((255,255,255))     #fill the background white
#a bit of text
myFont = pygame.font.SysFont("None",25)
#textsurface = myFont.render("Player1: F1=modus, w,as,d,cursor=move, space=shoot", True, (0,0,255))
textsurface0 = myFont.render("pres ESC to Quit", True, (0,0,0))
textsurface1 = myFont.render("Player1: w,a,s,d", True, (0,0,0))
#textsurface2 = myFont.render("Player2: F12=modus, j,i,l,k, Numpad=move, RCTRl,NUM_Enter=shoot", True, (0,0,255))
textsurface2 = myFont.render("Player2: j,i,l,k", True, (0,0,0))
textsurface3 = myFont.render("Player3: cursor-keys", True, (0,0,0))
textsurface4 = myFont.render("Player4: Numpad 4,8,5,6", True, (0,0,0))
#background.blit(textsurface, (20,410)) # blit the textsurface on the backgroundsurface
#background.blit(textsurface2, (20, 430))
#background.blit(textsurface3, (20, 450))
# red circle with crosshair
 
paint(160, 200, (255,32,32)) # player1, red, left upper corner
paint(screenrect.width - 160, 200, (32,32,255)) # player2, blue right upper corner
paint(160, screenrect.height - 200 , (32,255,32)) # player3, green, lower left corner
paint(screenrect.width - 160, screenrect.height - 200 , (32,32,32)) # player4, black, lower right corner
 
background.blit(textsurface1, (screenrect.width/2-40,50))
background.blit(textsurface2, (screenrect.width/2-40,70))
background.blit(textsurface3, (screenrect.width/2-40,90))
background.blit(textsurface4, (screenrect.width/2-40,110))
background.blit(textsurface0, (screenrect.width/2-40,130))
 
backgroundnew = background.copy()
screen.blit(background, (0,0)) # blit the  backgroundsurface on the screen
 
#--- sprite groups
vectorgroup = pygame.sprite.Group()
ballgroup = pygame.sprite.Group()
tankgroup = pygame.sprite.Group()
bargroup = pygame.sprite.Group()
dangergroup = pygame.sprite.Group()
woundgroup = pygame.sprite.Group()
textgroup = pygame.sprite.Group()
newgroup = pygame.sprite.Group()
# create some sprites
#--- player1 
player1 = Tank(screenrect,"player1") #red
player1vector = VectorSprite(player1)
textgroup.add( HitpointText(player1)  )
#--- player2
player2 = Tank(screenrect,"player2") # blue
player2vector = VectorSprite(player2)
textgroup.add( HitpointText(player2)  )
#-- player3
player3 = Tank(screenrect, "player3") # green
player3vector = VectorSprite(player3)
textgroup.add( HitpointText(player3))
#-- player4
player4 = Tank(screenrect, "player4") # black
player4vector = VectorSprite(player4)
textgroup.add( HitpointText(player4))
 
 
 
# put the sprites in the correct group
bargroup.add(Bar(player1), Bar(player1, True),
             Bar(player2), Bar(player2, True),
             Bar(player3), Bar(player3, True),
             Bar(player4), Bar(player4, True) )
 
tankgroup.add(player1, TankGhost(player1, "N"), TankGhost(player1, "S"), TankGhost(player1, "W"), TankGhost(player1, "E"),  
              player2, TankGhost(player2, "N"), TankGhost(player2, "S"), TankGhost(player2, "W"), TankGhost(player2, "E"),  
              player3, TankGhost(player3, "N"), TankGhost(player3, "S"), TankGhost(player3, "W"), TankGhost(player3, "E"),  
              player4, TankGhost(player4, "N"), TankGhost(player4, "S"), TankGhost(player4, "W"), TankGhost(player4, "E"))
#stuffgroup.add(player1text, player1speedtext, player1vector,  player2text, player2speedtext, player2vector)
vectorgroup.add(player1vector, player2vector, player3vector, player4vector)
#dangergroup.add( Danger(screenrect, "oil"),
#                 Danger(screenrect, "oil"),
#                 Danger(screenrect, "oil"),
#                 Danger(screenrect, "oil"),
#                 Danger(screenrect, "boat"))
dangergroup.add( Danger(screenrect, "boat") )
# hitpoints for boat
for dang in dangergroup:
    #if dang.vulnerable and not dang.bar:
    if (dang.bar == False)  and (dang.type != "oil"):
        bargroup.add(Bar(dang))
        dang.bar = True
 
#allgroup = pygame.sprite.LayeredUpdates(playervector, playertext, speedtext, player, ballgroup)
if pygame.ver < "1.8.1":
    allgroup = pygame.sprite.LayeredUpdates(vectorgroup,  dangergroup, woundgroup, textgroup, bargroup, ballgroup, tankgroup)
else:
    allgroup = pygame.sprite.Group(vectorgroup,  dangergroup, woundgroup, textgroup, bargroup, ballgroup, tankgroup)
 
 
 
 
#--- loop prepare ---
mainloop = True
#clock = pygame.time.Clock()
fps = 30 #frames per second
seconds_played = 0.0
recalc = False # recalculate the sprite groups reset to False
finale = False # draw the game-over scene (final explosion etc.)
startGameOverMsg = False
rank = 4 # decrease at each playerkill. 4 because 4 players at maximum
clock = pygame.time.Clock() # create Clock object
#--- mainloop ------
while mainloop:
    tick_time = clock.tick(fps) # milliseconds since last frame
    tick_seconds = tick_time / 1000.0 # decimal-seconds since last frame
    seconds_played += tick_seconds # counter, will not be resetted    
    #clock.tick(30)
    #event handler
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            mainloop = False
        elif event.type == pygame.KEYDOWN:
            # Toggle keys better with keydown than with pressed_keys
            #--- Esc quit -------------
            if event.key == pygame.K_ESCAPE:
                mainloop = False
 
 
            #--- clean F5 -------------
            if event.key == pygame.K_F5:    
                background = backgroundnew.copy()
                screen.blit(background, (0,0)) # redraw the background
            #---tracer F6 -----------------    
            if event.key == pygame.K_F6:
                for tank in tankgroup:
                    tank.drawTracer = not tank.drawTracer          
 
    #--- fire 
    for tank in tankgroup:
        if tank.fire():
            ballgroup.add(Ball(tank))
            recalc = True        
 
    #--- mainloop core -----------                  
    #--- clear the sprites
    allgroup.clear(screen, background)
 
    if recalc:
        # it is very important that this lines comes AFTER allgroup.clear!
        # else there will be ugly uncleaned sprites all around
        # allgroup = pygame.sprite.LayeredUpdates(stuffgroup,  woundgroup, textgroup, dangergroup, bargroup, tankgroup, ballgroup)
        #allgroup = pygame.sprite.LayeredUpdates(vectorgroup,  dangergroup, woundgroup, textgroup, bargroup, ballgroup, tankgroup)
        if newgroup:
            # any new dangersprites in this group ?
            for dang in newgroup:
                dangergroup.add(dang)
            #newgroup = [] # will be empty at beginning of danger-danger-collision-detection
            print len(dangergroup)
            for dang in dangergroup:
                # add hitbar if missing
                if (dang.bar == False)  and (dang.type != "oil"):
                    bargroup.add(Bar(dang))
                    dang.bar = True
            newgroup.empty() # remove all sprites from this group
 
 
        if pygame.ver < "1.8.1":
            allgroup = pygame.sprite.LayeredUpdates(vectorgroup,  dangergroup, woundgroup, textgroup, bargroup, ballgroup, tankgroup)
        else:
            allgroup = pygame.sprite.Group(vectorgroup,  dangergroup, woundgroup, textgroup, bargroup, ballgroup, tankgroup)
 
 
 
    recalc = False # reset recalc
    #--- update all sprites
    allgroup.update(tick_seconds)
    #--- feed sprite-position to vectorgroup
    for vsprite in vectorgroup:
        for dang in dangergroup:
            vsprite.learn(dang, True)
        for tank in tankgroup:
            vsprite.learn(tank, False)
 
    #--- collision detection
 
 
    # ball kills ball:--- removed, because too slow
    #for ball in ballgroup:
    #    ballgroup2 = ballgroup.copy()
    #    crashgroup = pygame.sprite.spritecollide(ball, ballgroup2, False)
    #    killme = False
    #    for ball2 in crashgroup:
    #        if ball2.boss == ball.boss:
    #            #friendly fire
    #            pass # survive
    #        else:
    #            ball2.kill()
    #            killme = True
    #    if killme:
    #        ball.kill()
 
    # --- collision with tank and ball ?
    for tank in tankgroup:
        # ball is always inside screenrect.
        crashgroup = pygame.sprite.spritecollide(tank, ballgroup, False)
        for ball in crashgroup:
            if ball.boss != tank: # no friendly fire
               distance1 = vec2d(tank.pos) #calculate distance to tank center
               distance2 = vec2d(ball.pos)
               distance = distance1 - distance2
               if distance.length > tank.hitradius:
                    pass # nothing happened, bullet missed tank
               else:
                   tank.shotby[ball.boss.id] += 1 
                   tank.shotbysomebody += 1
                   ball.boss.hits += 1 # one point for the shooter                   
                   # ----- more damage depending on  impact speed 
                   impact = tank.spacemove - ball.moving
                   damage = int(impact.length/100)
                   # ----- additional damage if radius of Ball > 1 (point blank shot)
                   damage += ball.tempradius - 1 # tempradius is minimum 1
                   #print "imact: %i + ballsize: %i = damage: %i " % (int(impact.length/100), ball.tempradius - 1, damage)
                   textgroup.add(Text(vec2d(tank.pos), str(damage), ball.boss.color, 1.5, 25, 
                                      vec2d(random.randint(-90,90),random.randint(-250,-120)),
                                      False, True )) # vc = vectorcenter
                   if tank.id[0] == "p":
                       # is it the Tank or a TankGhost ? Tank name begins with player
                       tank.hitpoints -= damage 
                       tank.spacemove += ball.moving * ball.massfactor
                   else:
                       tank.boss.hitpoints -= damage
                       tank.boss.spacemove += ball.moving * ball.massfactor
                   woundgroup.add(Wound(ball.pos, 5))
                   recalc = True
                   ball.kill()
 
    # -- collision tank  with danger ?
    for tank in tankgroup:
        crashgroup = pygame.sprite.spritecollide(tank, dangergroup, False)
        for dang in crashgroup:
            if dang.damagePlayer:
                damage = dang.damage
                textgroup.add(Text(vec2d(tank.pos), str(damage), dang.color, 1.5, 25, 
                              vec2d(random.randint(-90,90),random.randint(-250,-120)),
                              False, True )) # vc = vectorcenter
                if tank.id[0] == "p":
                    # is it the Tank or a TankGhost ?                  
                    tank.hitpoints -= damage
                    tank.shotby["danger"] += 1
                else:
                    tank.boss.hitpoints -= damage
                    tank.boss.shotby["danger"] +=1
                tank.shotbysomebody += 1
                if dang.type == "oil":
                    dang.size -=1
                    #dang.size +=1
                    pass
                else:
                    dang.hitpoints -= 1
 
    #-- collision between ball and danger, explosion, :
    for dang in dangergroup:
        if dang.vulnerable:
            # ---------dang is an AI boat
            crashgroup = pygame.sprite.spritecollide(dang, ballgroup, True) #kill shot
            for ball in crashgroup:
                dang.vec += ball.moving * ball.massfactor
                woundgroup.add(Wound(ball.pos, 5))
                dang.hitpoints -= 1
 
                # dang dead ? explosion, drop goodie, respawn at center 
 
    # -- collision danger with danger ? 
    for dang in dangergroup:
         dangergroup2 = dangergroup.copy()
         dangergroup2.remove(dang) # remove myself from the clone group
         crashgroup = pygame.sprite.spritecollide(dang, dangergroup2, False) # dang survives
         for dang2 in crashgroup:
             if dang.type == "oil" and dang2.type == "oil" and dang.wo == -1 and dang2.wo == -1:
                 #  border patrols spawn only at corner
                 #the bigger oil eats the smaller oil
                 if dang.size > dang2.size and dang2.lifetime > 2.0:
                     dang.size += 1
                     dang2.size = max(5, dang2.size-1)
             if dang.type == "boat" and dang2.type == "oil":
                 dang.hitpoints -= 1
                 dang2.size -=1
                 textgroup.add(Text(vec2d(dang.pos), str(1), (255,255,0), 1.5, 25, 
                    vec2d(random.randint(-90,90),random.randint(120,240)),
                    False, True ))
 
    # -- collision tank with other player ?
    for tank in tankgroup:
        if tank.id[0] == "p":
            # is it the Tank or a TankGhost ?
            tankgroup2 = tankgroup.copy() # copy of tankgroup, include self
            tankgroup2.remove(tank) # remove self of group
            crashgroup = pygame.sprite.spritecollide(tank, tankgroup2, False)
            for tank2 in crashgroup:
                #FIXME --- bis mir was bessers einfällt
                tank.hitpoints -= 1
                textgroup.add(Text(vec2d(tank.pos), str(1), tank2.color, 1.5, 25, 
                  vec2d(random.randint(-90,90),random.randint(120,240)),
                  False, True )) # tank-collision: damage text floats DOWN instead up
        else:
            pass # no collision damage for TankGhost (yet)
 
    #-- danger hitpoint check and oilspawn check
    for dang in dangergroup:
        if dang.type == "oil":
            if dang.wo != -1:
                # immortal oil,  patrol the border
                dang.size = max(40, dang.size) # size can not be less than 10
            else:
                if dang.size < 1:
                    woundgroup.add(Wound(dang.pos, 50, .5)) # small explosion
                    dang.kill()
            if dang.size > 40:
                     dang.size = 40 #  reduce size and spawn child
                     newgroup.add(Danger(screenrect, "oil", 10, dang.pos))
        else:
            #dang is a boat
            if dang.hitpoints < 1:
                woundgroup.add(Wound(dang.pos, 75, .5)) # final explosion
                #rebirth of ship in the middel of the screen
                #spawn 5 mini-oils
                newgroup.add(Danger(screenrect, "oil", 10, dang.pos),
                         Danger(screenrect, "oil", 10, dang.pos),
                         Danger(screenrect, "oil", 10, dang.pos),
                         Danger(screenrect, "oil", 10, dang.pos),
                         Danger(screenrect, "oil", 10, dang.pos))
                dang.pos = vec2d(screenrect.centerx, screenrect.centery)
                dang.hitpoints = dang.hitpointsfull                
 
 
    #--- calculate middle position of polygon made by surviving players
    calclist = []
    for tank in tankgroup:
        if not tank.geist:
            calclist.append(tank.rect.center)
 
    #print calclist
 
    #--- tank hitpoint check, game over ?  
    for tank in tankgroup:
        if tank.hitpoints < 0:
            woundgroup.add(Wound(tank.pos, 100, .75)) # final explosion
            tank.peaceful = True # dead Tank cannot shoot
            #spawn oil
            newgroup.add(Danger(screenrect, "oil", 10, tank.pos),
                         Danger(screenrect, "oil", 10, tank.pos),
                         Danger(screenrect, "oil", 10, tank.pos),
                         Danger(screenrect, "oil", 10, tank.pos),
                         Danger(screenrect, "oil", 10, tank.pos))
            if len(tankgroup) > 5:
                textgroup.add(Text(tank.pos, "Loser", tank.color, 5.0, 72, vec2d(0,0), False, True))
                textgroup.add(Text(tank.vc+ vec2d(0,-50), "Loser", (0,0,0), -1, 48, vec2d(0,0), False, False))
                textgroup.add(Text(tank.vc+ vec2d(0,-25), "Rank: %i " % rank, (0,0,0), -1, 48, vec2d(0,0), False, False))
                rank -= 1
                textgroup.add(Text(tank.vc+ vec2d(0,35), "shot by:", (0,0,0), -1, 24, vec2d(0,0), False, False))
                if tank.shotbysomebody == 0:
                    tank.shotbysomebody = 1 # to avoid division by zero
                dy = 75
                if tank.id != "player1" and tank.shotby["player1"] > 0:
                    textgroup.add(Text(tank.vc+ vec2d(0,dy), "player1: %.2f%% " % (100*tank.shotby["player1"] / tank.shotbysomebody), (255,0,0), -1, 24, vec2d(0,0), False, False))
                    dy += 20
                if tank.id != "player2" and tank.shotby["player2"] > 0:
                    textgroup.add(Text(tank.vc+ vec2d(0,dy), "player2: %.2f%% " % (100*tank.shotby["player2"] / tank.shotbysomebody), (0,0,255), -1, 24, vec2d(0,0), False, False))
                    dy += 20
                if tank.id != "player3" and tank.shotby["player3"] > 0:
                    textgroup.add(Text(tank.vc+ vec2d(0,dy), "player3: %.2f%% " % (100*tank.shotby["player3"] / tank.shotbysomebody), (0,255,0), -1, 24, vec2d(0,0), False, False))
                    dy += 20
                if tank.id != "player4" and tank.shotby["player4"] > 0:
                    textgroup.add(Text(tank.vc+ vec2d(0,dy), "player4: %.2f%% " % (100*tank.shotby["player4"] / tank.shotbysomebody), (255,0,255), -1, 24, vec2d(0,0), False, False))
                if tank.hits == 0:
                    quota = 0.0
                else:
                    quota = tank.shots / tank.hits
                textgroup.add(Text(tank.vc+ vec2d(0,5), "hit ratio: %f" % (quota))) # future division, result in float
                tank.showvector = False
            # kill TankGhost's
            tank.kill()
    #if len(tankgroup) == 1 and not startGameOverMsg:
    if len(tankgroup) == 5 and not startGameOverMsg:
        # 1 Tank and 4 TankGhost
        startGameOverMsg = True
        for tank in tankgroup:
            textgroup.add(Text(tank.pos, "Victory", tank.color, 5.0, 72, vec2d(0,-50), False, True))
            textgroup.add(Text(tank.vc + vec2d(0,-50), "Winner", (0,0,0), -1, 48, vec2d(0,0), False, False))
            textgroup.add(Text(tank.vc + vec2d(0,-25), "Rank: %i " % rank, (0,0,0), -1, 48, vec2d(0,0), False, False))
            if tank.hits == 0:
                quota = 0.0
            else:
                quota = tank.shots / tank.hits
            textgroup.add(Text(tank.vc+ vec2d(0,5), "hit ratio: %.2f" % (quota))) # future division, result in float
        textgroup.add(Text((screenrect.width/2,screenrect.height/2), "Game Over", (1,1,1), 5.0,100, vec2d(0,-20), False, True))
        finale = True
 
 
    if finale:
        if len(woundgroup) == 0:
            mainloop = False # leave game
 
    #--- draw the sprites
    allgroup.draw(screen)
    #--- decorate screen
    #pygame.display.set_caption("player1 [F1]: %s player2 [F12]: %s mouse: %s balls: %i "
    #% (player.modus, p2.modus , pygame.mouse.get_pos(),len(ballgroup)))
    pygame.display.set_caption( "# text: %i, # balls: %i fps: %.2f" % (len(textgroup), len(ballgroup), 1000.0/tick_time))
    pygame.display.flip()          # flip the screen 30 times a second
#--- end of loop

comment this page

1) you can steer 2 ships if you use both hands

en/resources/games/schiff.txt · Last modified: 2014/01/09 11:07 (external edit)