#! /usr/bin/env python
# -*- coding: Utf-8 -*-

from Tkinter import *
from math import sin, cos, pi
from random import randrange
from threading import Thread, Lock
import time

class VezerloPult(Frame):
    """Egy ágyúhoz asszociált vezérlõ pult""" 
    def __init__(self, boss, canon):
        Frame.__init__(self, bd =3, relief =GROOVE)
        self.score =0
        s =Scale(self, from_ =88, to =55,
                 troughcolor ='dark grey',
                 command =canon.iranyzas)
        s.set(45)                       # loves kezdoszoge
        s.pack(side =LEFT)
        Label(self, text ='Dõlésszög').pack(side =TOP, anchor =W, pady =5)        
        Button(self, text ='Tűz !', command =canon.tuz).\
                                    pack(side =BOTTOM, padx =5, pady =5)
        Label(self, text ="pont").pack()
        self.pontok =Label(self, text=' 0 ', bg ='white')
        self.pontok.pack()
        # az agyu iranya szerint balra vagy jobbra pozicionalunk :
        gd =(LEFT, RIGHT)[canon.irany == -1]
        self.pack(padx =3, pady =5, side =gd)

    def pontHozzaadasa(self, p):
        "a pontszám növelése vagy csökkentése"
        self.score += p
        self.pontok.config(text = ' %s ' % self.score)


class Canon:
    """Ágyúrajz"""
    def __init__(self, boss, num, x, y, irany):
        self.boss = boss            # a vászon hivatkozása
        self.num = num              # az ágyú száma a listában
        self.x1, self.y1 = x, y     # ágyú forgástengelye
        self.irany = irany          # lövés iránya (-1:balra, +1:jobbra)
        self.lagyucso = 30               # ágyúcsõ hossza
        # ágyúcsõ rajzolása (vízszintes) :
        self.x2, self.y2 = x + self.lagyucso * irany, y
        self.cso = boss.create_line(self.x1, self.y1,
                                     self.x2, self.y2, width =10)
        # ágyútest rajzolása (színes kör) :
        self.rc = 15                # a kör sugara
        self.test = boss.create_oval(x -self.rc, y -self.rc, x +self.rc,
                                      y +self.rc, fill ='black')
        # a vászon szélességének és magasságának meghatározása :
        self.xMax = int(boss.cget('width'))
        self.yMax = int(boss.cget('height'))
        self.tuzeles_folyamatban = 0

    def iranyzas(self, angle):
        "ágyú dõlésszögének beállítása"
        # rem : az <angle> (szög) paramétert stringként kapja.
        # át kell alakítani valós számmá, majd radiánná :
        self.angle = float(angle)*2*pi/360      
        self.x2 = self.x1 + self.lagyucso * cos(self.angle) * self.irany
        self.y2 = self.y1 - self.lagyucso * sin(self.angle)
        self.boss.coords(self.cso, self.x1, self.y1, self.x2, self.y2)
        
    def tuz(self):
        "golyó kilövésének indítása"
        if self.tuzeles_folyamatban > 5: return         # max. 3 lövedék 
        self.tuzeles_folyamatban += 1 
        # lövedék kezdõ pozíciója (ez az ágyú torka) :
        xo, yo = self.x2, self.y2
        v = 11              # kezdõ sebesség
        # ennek a sebességnek a függõleges és vízszintes komponensei :
        vy = -v *sin(self.angle)
        vx = v *cos(self.angle) *self.irany
        tob = Thread_lovedek(self.boss, xo, yo, vx, vy)
        tob.start()
        
    def ujratolt(self):
        "agyu toltese"
        self.tuzeles_folyamatban -= 1


class Thread_lovedek(Thread):
    def __init__(self, can, xo, yo, vx, vy):
        Thread.__init__(self)
        self.can = can                  # vászon hivatkozása
        self.celtargyak = can.master.celtargyak # cél-threadek listája
        self.xo, self.yo = xo, yo       # kezdõ pozíció
        self.vx, self.vy = vx, vy       # kezdõsebesség
        # a vászon szélességének és magasságának meghatározása :
        self.xMax = int(can.cget('width'))
        self.yMax = int(can.cget('height'))
        # lovedek rajzolasa (indulaskor egy pont) :
        self.lovedek = can.create_oval(xo, yo, xo, yo, fill='red')
        
    def run(self):
        "lövedék animációja (ballisztikus pálya)"
        self.anim =1
        # lövedék pozícionálása koordinátái újradefiniálásával :
        while self.anim:
            xa, xb = int(self.xo -3), int(self.xo +3)
            ya, yb = int(self.yo -3), int(self.yo +3)
            try:
                self.can.coords(self.lovedek, xa, ya, xb, yb)
            except:
                self.anim =0            #print "obus égaré"
            # a következõ pozíció számolása :
            self.xo += self.vx
            self.yo += self.vy
            self.vy += .12              # action de la pesanteur
            self.test_akadaly()        # akadályba ütközött ?
            time.sleep(.008)
        
        # animáció vége: töröljük a lövedéket és zárjuk a thread-et :
        self.can.delete(self.lovedek) 
        self.can.master.gun.ujratolt()
        
    def test_akadaly(self):
        "Eldöntjük: a lövedék célba talált-e vagy elérte-e a játéktér határát"
        x, y = self.xo, self.yo
        if y >self.yMax or x <0 or x >self.xMax:
            self.anim =0
            return
        self.can.master.locking.acquire()
        for n in self.celtargyak:
            c = self.celtargyak[n]
            if y > c.y -3 and y < c.y +18 and x > c.x -3 and x < c.x +43:
                self.anim =0
                self.can.master.explosion(x, y)
                self.can.master.eltavolit_celtargy(c.num)
                break
        self.can.master.locking.release()
    

class Thread_celtargy(Thread):
    """a céltárgy animációját irányító thread-objektum"""
    def __init__(self, can, x, y, num, coul, dt):
        Thread.__init__(self)
        self.mobile = can.create_oval(x, y, x+40, y+15, fill =coul)
        self.can = can              # vászon hivatkozása
        self.x, self.y = x, y       # céltárgy pozíciója
        self.num = num              # azonosító
        # a vászon szélességének és magasságának meghatározása :
        self.xMax = int(can.cget('width'))
        self.yMax = int(can.cget('height'))
        self.sx, self.sy = -6, 3     # távolság- és idõnövekmények
        self.dt = dt                 # az animációhoz
   
    def run(self):
        "a céltárgy animálása"
        self.anim = 1 
        while self.anim:
            self.x += self.sx
            self.y += self.sy
            try:
                self.can.coords(self.mobile, self.x, self.y, self.x+40, self.y+15)
            except:
                self.anim =0        # objet "égaré" > on ferme !
            if self.x < -20:
                # a céltárgy megérkezett a balszélre :
                self.can.master.locking.acquire()
                self.can.master.eltavolit_celtargy(self.num)
                self.can.master.locking.release()
                self.can.master.pult.pontHozzaadasa(-1)
            if self.y < self.yMax /3 or self.y > self.yMax -20:
                self.sy = -self.sy
            time.sleep(self.dt)
        
        # animáció vége: töröljük a céltárgyat és lezárjuk a thread-et :
        self.can.delete(self.mobile)
        
    def stop(self):
        "kényszerítjük az animáció végétés a thread lezárását"
        self.anim =0
        
#    def gyorsitas(self):
#        "a mozgás gyorsítása"
#        self.x = self.xMax
#        self.dt /= 2


class Application(Frame):
    def __init__(self):
        Frame.__init__(self)
        self.master.title('<<< Lövés mozgó céltárgyra >>>')
        self.pack()
        self.xm, self.ym = 800, 500
        self.jatek = Canvas(self, width =self.xm, height =self.ym,
                          bg ='ivory', bd =3, relief =SUNKEN)
        self.jatek.pack(padx =4, pady =4, side =TOP)
        self.img = PhotoImage(file ='gust.gif')
        self.jatek.create_image(400, 250, image =self.img)
        # Egy ágyú és egy vezérlõ pult objektum létrehozása :
        x, y = 30, self.ym -20
        self.gun =Canon(self.jatek, 1, x, y, 1)
        self.pult =VezerloPult(self, self.gun)
        # Gondoskodunk egy reteszrõl a céltárgy thread-ek szinkronizációjához :
        self.locking = Lock()
        # Mindegyik céltárgy számára létre fogunk hozni egy thread-objektumot
        # egy szótárban :
        self.celtargyak ={}
        self.num_c =0       # a céltárgyak azonosító száma
        self.dt = .2        # dT a céltárgyak animálásához használjuk (s)
        self.ti = 3500      # dT a különbözõ céltárgyak indulását választja el (ms)
        self.hozzaad_celtargy()
        # az ablak bezárása a thread-ek leállását fogja elõidézni :
        self.master.protocol('WM_DELETE_WINDOW', self.threadek_zarasa)
        #self.bind('<Destroy>',self.threadek_zarasa)

    def explosion(self, x, y):
        "a lövedék robbanását rajzolja ki (sárga kör)"
        self.jatek.create_oval(x-15, y-15, x+15, y+15, fill ='yellow',
                             outline ='orange', width =2, tags ='bang')
        self.jatek.after(100, self.fin_explosion)
    
    def fin_explosion(self):
        "a robbanási kör törlése"
        self.jatek.delete('bang')
        self.pult.pontHozzaadasa(1)

    def hozzaad_celtargy(self):
        col =['maroon', 'purple', 'green', 'blue', 'navy', 'dark cyan']
        # a céltárgy színének véletlen sorsolása :
        coul = col[randrange(len(col))]
        # kezdõ Y koordinátájának véletlen sorsolása :
        yo = randrange(int(self.ym/3), self.ym -20)
        self.num_c += 1         # azonosító szám 
        if self.ti > 200 :
            self.ti /= 1.05     # egyre több céltárgy indítása 
        if self.dt >.005:
            self.dt /= 1.05     # a céltárgyak egyre gyorsabban mennek
        #self.dt =.2 / (1 + randrange(20))    # véletlenszerû sebesség
        tc = Thread_celtargy(self.jatek, self.xm, yo, self.num_c, coul, self.dt)
        self.locking.acquire()
        self.celtargyak[self.num_c] = tc
        self.locking.release()
        self.celtargyak[self.num_c].start()
        # folyamatosan indít új céltárgyakat :
        self.after(int(self.ti), self.hozzaad_celtargy)
        
    def eltavolit_celtargy(self, num):
        self.celtargyak[num].stop()         # a thread leállítása
        del self.celtargyak[num]            # a thread-objektum törlése
        
    def threadek_zarasa(self):
        "az összes még aktív céltárgy-thread leállítása, majd kilépés"
        for c in self.celtargyak:
            self.celtargyak[c].stop()
        time.sleep(1)                   # hagyjuk ...
        self.master.destroy()
        
if __name__ =='__main__':
    Application().mainloop()
