# Framework for a simple Frogger game, built with Tkinter.
# Animates cars and logs, and moves frog, but does not (yet) handle
# interactions.
# Written by Michael Scott, April 2010, using a GUI skinned off
# pymike.pynguins.com.

from Tkinter import *
from random import randrange
from threading import Thread
from time import sleep

# The game board is an H*W grid of squares, G pixels on a side
# The top left square is (0, 0); the bottom right square is (W-1, H-1).

G = 24  # width of a grid square
W = 9   # squares
H = 10  # squares

# Tk frame in which to display the game:
root = Tk()
canvas = Canvas(root, width=W*G, height=H*G)

# Cars and logs are represented by Python threads, which run
# concurrently.  A thread is created in the usual way:
#   myCar = Car()
# This calls the __init__ constructor, but does not start the thread
# running.  That is accomplished by
#   myCar.start()
# which tells Python to start a thread in the car's run() method.

carRpic = PhotoImage(file="car_R.gif")
carLpic = PhotoImage(file="car_L.gif")
class Car (Thread):
    def __init__(self, lane):   # constructor
        Thread.__init__(self)
        self.Y = lane
        direc = randrange(2)
        self.nap = (randrange(5)+1) * 0.3   # tenths of a sec to sleep
        self.X = randrange(W)
        if direc > 0:
            self.pic = carRpic
            self.delta = 1
        else:
            self.pic = carLpic
            self.delta = -1
        self.icon = canvas.create_image(self.X*G, self.Y*G,
                                        anchor=NW, image=self.pic)
        canvas.update()
    def run(self):              # code to be run by thread
        while True:
            sleep(self.nap)
            self.X += self.delta
            if self.X < 0:
                self.X += W
            if self.X >= W:
                self.X -= W
            canvas.coords(self.icon, self.X*G, self.Y*G)
            canvas.update()

logPic = PhotoImage(file="log.gif")
class Log (Thread):
    def __init__(self, lane):   # constructor
        Thread.__init__(self)
        self.Y = lane
        direc = randrange(2)
        self.nap = (randrange(5)+1) * 0.3   # tenths of a sec to sleep
        self.X = randrange(W-1)
        if direc > 0:
            self.delta = 1
        else:
            self.delta = -1
        self.icon = canvas.create_image(self.X*G, self.Y*G,
                                        anchor=NW, image=logPic)
        canvas.update()
    def run(self):              # code to be run by thread
        while True:
            sleep(self.nap)
            self.X += self.delta
            if self.X < -1:
                self.X += W+1
            if self.X >= W:
                self.X -= W+1
            canvas.coords(self.icon, self.X*G, self.Y*G)
            canvas.update()

# The frog (or more accurately series of frogs) is NOT a thread.
# It's a collection of event handlers, which run (in the Tkinter thread)
# in response to key presses.

frogUpic = PhotoImage(file="frog_U.gif")
frogDpic = PhotoImage(file="frog_D.gif")
frogLpic = PhotoImage(file="frog_L.gif")
frogRpic = PhotoImage(file="frog_R.gif")
class Frog:
    def __init__(self):
        self.won = False
        self.lost = False
        self.X = 4
        self.Y = 9
        self.lives = 5      # five frogs to try
        self.fullPads = [0, 2, 4, 6, 8]
        self.frogsHome = 0
        self.icon = canvas.create_image(self.X*G, self.Y*G,
                                        anchor=NW, image=frogUpic)
        # set up handlers:
        root.bind_all('<Key-Up>', self.up)
        root.bind_all('<Key-Down>', self.down)
        root.bind_all('<Key-Left>', self.left)
        root.bind_all('<Key-Right>', self.right)

    def home(self):     # have I reached a pad in safety?
        return self.Y == 0 and self.X not in self.fullPads
    def dead(self):     # have I moved off the board?
        return self.X < 0 or self.X >= W \
            or self.Y < 0 or self.Y >= H \
            or self.Y == 0 and self.Y in self.fullPads
    def done(self):     # game over
        print self.frogsHome,
        if self.frogsHome != 1:
            print "frogs home; better luck next time!"
        else:
            print "frog home; better luck next time!"
        sleep(3)
        quit()
    def move(self):         # assess my situation
        if self.home():
            # leave a frog on the pad
            self.lives -= 1
            newFrog = canvas.create_image(self.X*G, self.Y*G,
                anchor=NW, image=frogUpic)
            self.fullPads.append(self.X)
            self.frogsHome += 1
            if self.frogsHome == 4:
                print "You win!"
                sleep(3)
                quit()
            elif self.lives <= 0:
                self.done()
            else:
                print "starting frog", 6-self.lives
                self.X = 4
                self.Y = 9
                canvas.itemconfigure(self.icon, image=frogUpic)
                    # aim frog up
        elif self.dead():
            self.lives -= 1
            if self.lives <= 0:
                self.done()
            else:
                print "oops!", self.lives,
                if self.lives != 1:
                    print "frogs left"
                else:
                    print "frog left"
                self.X = 4
                self.Y = 9
                canvas.itemconfigure(self.icon, image=frogUpic)
                    # aim frog up
        canvas.coords(self.icon, self.X*G, self.Y*G)
            # change my location
        canvas.update()     # tell GUI to refresh

    # handlers:
    def up(self, e):
        self.Y -= 1
        canvas.itemconfigure(self.icon, image=frogUpic)
            # aim frog up
        self.move()
    def down(self, e):
        self.Y += 1
        canvas.itemconfigure(self.icon, image=frogDpic)
            # aim frog down
        self.move()
    def left(self, e):
        self.X -= 1
        canvas.itemconfigure(self.icon, image=frogLpic)
            # aim frog left
        self.move()
    def right(self, e):
        self.X += 1
        canvas.itemconfigure(self.icon, image=frogRpic)
            # aim frog right
        self.move()

# create the GUI:

def makeRectangle(x0, y0, x1, y1, hue):
    return canvas.create_rectangle(x0*G, y0*G, x1*G, y1*G,
                                    fill=hue, outline='')

grass = makeRectangle(0, H-0, W, H-1, 'ForestGreen')
road  = makeRectangle(0, H-1, W, H-5, 'Black')
bank  = makeRectangle(0, H-5, W, H-6, 'sienna4')
river = makeRectangle(0, H-6, W, 0,   'Blue')
pads = [None]*4
for p in range(4):
    pads[p] = makeRectangle(2*p+1, 0, 2*p+2, 1, 'ForestGreen')
canvas.pack()       # display created components

# create the obstacles:
cars = [None]*4
for c in range(4):
    cars[c] = Car(c+5)
    cars[c].start()
logs = [None]*3
for l in range(3):
    logs[l] = Log(l+1)
    logs[l].start()

# establish event handlers to run the frog:
frog = Frog()

# end cleanly if window is closed
def cleanup():
    quit()
root.protocol("WM_DELETE_WINDOW", cleanup)
    # what to do if the user closes the graphics window

# start the graphics system
try:
    root.mainloop()     # give Tkinter the main thread to use
except KeyboardInterrupt:
    cleanup()
