# Gulp Catch, part 3: through the mouth
#
# Flip walks along the top and drops a pancake from his pan every so often.
# The Left and Right arrows drive Gulp. A pancake that touches his mouth is caught
# and disappears; a missed one lands on the counter.
# Nobody keeps score yet: that comes in part 4.
#
# If the arrows do nothing, click the game window first.
# To run: open this file in IDLE and press F5.
# Closing the window stops the loop with a red message.
# Part 4 shows how a game closes its window properly.
#
# Everything after a # is a note for people. Python skips it.

from tkinter import Tk, Canvas
import time

width = 480
height = 640
counter_top = 600         # pancakes land here
fastest_fall = 12         # top falling speed. Much faster, and a pancake can jump past the mouth
toss_every = 90           # steps between two pancakes: one is down before the next


def touching(a, b):
    # a and b are boxes: [left, top, right, bottom]. They touch when they overlap across and down.
    return a[0] < b[2] and b[0] < a[2] and a[1] < b[3] and b[1] < a[3]


class Flip:
    def __init__(self, canvas):
        self.canvas = canvas
        self.x = 120
        self.speed = 2
        x = self.x
        self.parts = [
            canvas.create_line(x, 58, x, 40, width=3, fill="gray30"),
            canvas.create_oval(x - 6, 32, x + 6, 44, fill="orange red", outline=""),
            canvas.create_rectangle(x - 22, 58, x + 22, 90, fill="light sea green",
                                    outline="dark slate gray", width=2),
            canvas.create_oval(x - 13, 68, x - 5, 76, fill="black"),
            canvas.create_oval(x + 5, 68, x + 13, 76, fill="black"),
            canvas.create_rectangle(x - 28, 92, x + 28, 140, fill="light sea green",
                                    outline="dark slate gray", width=2),
            canvas.create_line(x + 28, 108, x + 50, 108, width=4, fill="gray30"),
            canvas.create_oval(x + 46, 100, x + 86, 114, fill="gray20", outline=""),
        ]

    def step(self):
        # Flip walks along the top and turns around at the walls.
        self.x = self.x + self.speed
        for part in self.parts:
            self.canvas.move(part, self.speed, 0)
        if self.x <= 40 or self.x >= width - 90:
            self.speed = -self.speed


class Pancake:
    def __init__(self, canvas, x, y):
        self.canvas = canvas
        self.x = x
        self.y = y
        self.speed_y = 7
        self.landed = False
        self.shape = canvas.create_oval(x - 20, y - 7, x + 20, y + 7,
                                        fill="goldenrod", outline="dark goldenrod", width=2)

    def step(self):
        # Never fall further in one step than the mouth is tall (40),
        # or a pancake can jump right past it.
        if self.speed_y > fastest_fall:
            self.speed_y = fastest_fall
        # Land on the counter.
        if self.y + self.speed_y > counter_top - 7:
            self.speed_y = counter_top - 7 - self.y
            self.landed = True
        self.y = self.y + self.speed_y
        self.canvas.move(self.shape, 0, self.speed_y)

    def box(self):
        # The pancake's box: [left, top, right, bottom]
        return [self.x - 20, self.y - 7, self.x + 20, self.y + 7]

    def hide(self):
        # A caught pancake is not deleted: it is hidden.
        self.canvas.itemconfig(self.shape, state="hidden")

gulp_speed = 7            # how far Gulp moves in one step


class Gulp:
    def __init__(self, canvas):
        self.canvas = canvas
        self.x = width / 2
        self.speed = 0
        x = self.x
        c = canvas
        self.parts = [
            c.create_oval(x - 100, 470, x + 100, 730, fill="plum4", outline=""),
            c.create_oval(x - 88, 548, x - 70, 566, fill="thistle", outline=""),
            c.create_oval(x + 66, 534, x + 84, 552, fill="thistle", outline=""),
            c.create_oval(x - 62, 584, x - 46, 600, fill="thistle", outline=""),
            c.create_oval(x + 44, 582, x + 60, 598, fill="thistle", outline=""),
            c.create_oval(x - 28, 490, x - 14, 504, fill="white", outline=""),
            c.create_oval(x - 24, 494, x - 18, 500, fill="black"),
            c.create_oval(x + 14, 490, x + 28, 504, fill="white", outline=""),
            c.create_oval(x + 18, 494, x + 24, 500, fill="black"),
            c.create_oval(x - 60, 520, x + 60, 580, fill="gray10", outline=""),
        ]

    # A key only sets the speed. The loop does the moving, a little every pass.
    # The speed lives in the object: a plain name set inside a function would stay inside it.
    def go_left(self, event):
        self.speed = -gulp_speed

    def go_right(self, event):
        self.speed = gulp_speed

    def stop_left(self, event):
        if self.speed < 0:
            self.speed = 0

    def stop_right(self, event):
        if self.speed > 0:
            self.speed = 0

    def mouth_box(self):
        return [self.x - 60, 520, self.x + 60, 560]

    def step(self):
        # Move only if the new place is inside: Gulp is 200 wide, so his middle stays 100 from each wall.
        new_x = self.x + self.speed
        if new_x > 100 and new_x < width - 100:
            self.x = new_x
            for part in self.parts:
                self.canvas.move(part, self.speed, 0)


class Game:
    def __init__(self):
        self.window = Tk()
        self.window.title("Gulp Catch")
        self.canvas = Canvas(self.window, width=width, height=height, bg="powder blue")
        self.canvas.pack()
        self.flip = Flip(self.canvas)
        self.gulp = Gulp(self.canvas)
        self.pancake = None
        self.toss_timer = 30
        self.canvas.create_rectangle(0, 600, width, height, fill="slate gray", outline="")
        self.canvas.bind_all("<KeyPress-Left>", self.gulp.go_left)
        self.canvas.bind_all("<KeyPress-Right>", self.gulp.go_right)
        self.canvas.bind_all("<KeyRelease-Left>", self.gulp.stop_left)
        self.canvas.bind_all("<KeyRelease-Right>", self.gulp.stop_right)

    def step(self):
        self.flip.step()
        self.gulp.step()
        self.toss_timer = self.toss_timer - 1
        if self.toss_timer == 0:
            # Flip drops a new pancake from his pan; the last one is left where it is
            self.pancake = Pancake(self.canvas, self.flip.x + 66, 107)
            self.toss_timer = toss_every
        if self.pancake != None and not self.pancake.landed:
            self.pancake.step()
            if touching(self.pancake.box(), self.gulp.mouth_box()):
                self.pancake.hide()
                self.pancake = None

    def run(self):
        # The game is this loop: move everything a little, show it, wait, again.
        while True:
            self.step()
            self.window.update()
            time.sleep(0.02)


game = Game()
game.run()
