Home > Enterprise >  tkinter breakout game beginner problems
tkinter breakout game beginner problems

Time:06-15

I am trying to build a Breakout game for python tkinter with different levels. I don't quite understand the self and __init__ functions in this code.

Is there a way to create the game without those functions or replacing them with simpler functions if it's possible? Also, I don't quite understand the self.radius parts in the code as well.

import tkinter as tk


def new_window():
    root1 = tk.Tk()
    root1.title('Jeu')
    game = Game(root1)

    class GameObject(object):
        def __init__(self, canvas, item):
            self.canvas = canvas
            self.item = item

        def get_position(self):
            return self.canvas.coords(self.item)

        def move(self, x, y):
            self.canvas.move(self.item, x, y)

        def delete(self):
            self.canvas.delete(self.item)

    class Ball(GameObject):
        def __init__(self, canvas, x, y):
            self.radius = 10
            self.direction = [1, -1]
            # increase the below value to increase the speed of ball
            self.speed = 5
            item = canvas.create_oval(x - self.radius, y - self.radius,
                                      x   self.radius, y   self.radius,
                                      fill='white')
            super(Ball, self).__init__(canvas, item)

        def update(self):
            coords = self.get_position()
            width = self.canvas.winfo_width()
            if coords[0] <= 0 or coords[2] >= width:
                self.direction[0] *= -1
            if coords[1] <= 0:
                self.direction[1] *= -1
            x = self.direction[0] * self.speed
            y = self.direction[1] * self.speed
            self.move(x, y)

        def collide(self, game_objects):
            coords = self.get_position()
            x = (coords[0]   coords[2]) * 0.5
            if len(game_objects) > 1:
                self.direction[1] *= -1
            elif len(game_objects) == 1:
                game_object = game_objects[0]
                coords = game_object.get_position()
                if x > coords[2]:
                    self.direction[0] = 1
                elif x < coords[0]:
                    self.direction[0] = -1
                else:
                    self.direction[1] *= -1

            for game_object in game_objects:
                if isinstance(game_object, Brick):
                    game_object.hit()

    class Paddle(GameObject):
        def __init__(self, canvas, x, y):
            self.width = 80
            self.height = 10
            self.ball = None
            item = canvas.create_rectangle(x - self.width / 2,
                                           y - self.height / 2,
                                           x   self.width / 2,
                                           y   self.height / 2,
                                           fill='#FFB643')
            super(Paddle, self).__init__(canvas, item)

        def set_ball(self, ball):
            self.ball = ball

        def move(self, offset):
            coords = self.get_position()
            width = self.canvas.winfo_width()
            if coords[0]   offset >= 0 and coords[2]   offset <= width:
                super(Paddle, self).move(offset, 0)
                if self.ball is not None:
                    self.ball.move(offset, 0)

    class Brick(GameObject):
        COLORS = {1: '#4535AA', 2: '#ED639E', 3: '#8FE1A2'}

        def __init__(self, canvas, x, y, hits):
            self.width = 75
            self.height = 20
            self.hits = hits
            color = Brick.COLORS[hits]
            item = canvas.create_rectangle(x - self.width / 2,
                                           y - self.height / 2,
                                           x   self.width / 2,
                                           y   self.height / 2,
                                           fill=color, tags='brick')
            super(Brick, self).__init__(canvas, item)

        def hit(self):
            self.hits -= 1
            if self.hits == 0:
                self.delete()
            else:
                self.canvas.itemconfig(self.item,
                                       fill=Brick.COLORS[self.hits])

    class Game(tk.Frame):
        def __init__(self, master):
            super(Game, self).__init__(master)
            self.lives = 3
            self.width = 610
            self.height = 400
            self.canvas = tk.Canvas(self, bg='#D6D1F5',
                                    width=self.width,
                                    height=self.height, )
            self.canvas.pack()
            self.pack()

            self.items = {}
            self.ball = None
            self.paddle = Paddle(self.canvas, self.width / 2, 326)
            self.items[self.paddle.item] = self.paddle
            # adding brick with different hit capacities - 3,2 and 1
            for x in range(5, self.width - 5, 75):
                self.add_brick(x   37.5, 50, 3)
                self.add_brick(x   37.5, 70, 2)
                self.add_brick(x   37.5, 90, 1)

            self.hud = None
            self.setup_game()
            self.canvas.focus_set()
            self.canvas.bind('<Left>',
                             lambda _: self.paddle.move(-10))
            self.canvas.bind('<Right>',
                             lambda _: self.paddle.move(10))

        def setup_game(self):
            self.add_ball()
            self.update_lives_text()
            self.text = self.draw_text(300, 200,
                                       'Press Space to start')
            self.canvas.bind('<space>', lambda _: self.start_game())

        def add_ball(self):
            if self.ball is not None:
                self.ball.delete()
            paddle_coords = self.paddle.get_position()
            x = (paddle_coords[0]   paddle_coords[2]) * 0.5
            self.ball = Ball(self.canvas, x, 310)
            self.paddle.set_ball(self.ball)

        def add_brick(self, x, y, hits):
            brick = Brick(self.canvas, x, y, hits)
            self.items[brick.item] = brick

        def draw_text(self, x, y, text, size='40'):
            font = ('Forte', size)
            return self.canvas.create_text(x, y, text=text,
                                           font=font)

        def update_lives_text(self):
            text = 'Lives: %s' % self.lives
            if self.hud is None:
                self.hud = self.draw_text(50, 20, text, 15)
            else:
                self.canvas.itemconfig(self.hud, text=text)

        def start_game(self):
            self.canvas.unbind('<space>')
            self.canvas.delete(self.text)
            self.paddle.ball = None
            self.game_loop()

        def game_loop(self):
            self.check_collisions()
            num_bricks = len(self.canvas.find_withtag('brick'))
            if num_bricks == 0:
                self.ball.speed = None
                self.draw_text(300, 200, 'You win! You the Breaker of Bricks.')
            elif self.ball.get_position()[3] >= self.height:
                self.ball.speed = None
                self.lives -= 1
                if self.lives < 0:
                    self.draw_text(300, 200, 'You Lose! Game Over!')
                else:
                    self.after(1000, self.setup_game)
            else:
                self.ball.update()
                self.after(50, self.game_loop)

        def check_collisions(self):
            ball_coords = self.ball.get_position()
            items = self.canvas.find_overlapping(*ball_coords)
            objects = [self.items[x] for x in items if x in self.items]
            self.ball.collide(objects)
            game.mainloop()


root = tk.Tk()
root.title('Jeu')
game = Game(root)
btn1= tk.Button(root, text='Click me', bd= '5',command=new_window)

CodePudding user response:

Is there a way to create the game without those functions or replacing them with simpler functions if it's possible?

As a proof of concept below a much simpler code of the game for beginners rewritten to get rid of classes as an example that it is possible to write the game without them. You can compare the rewritten code with the original code (link is given in a comment at the beginning of code) to see how it can be done and then rewrite yourself a more complex code:

# https://codingshiksha.com/python/python-3-tkinter-2d-brick-breaker-breakout-game-in-gui-desktop-app-full-project-for-beginners/

from tkinter import Tk, Canvas
import random
import time
tk = Tk()
tk.title("Game")
tk.resizable(0, 0)
tk.wm_attributes("-topmost", 1)

canvas_height = 400
canvas_width  = 500
canvas = Canvas(tk, width=canvas_width, height=canvas_height, bd=0, highlightthickness=0)
canvas.pack()
tk.update()

ball_id = canvas.create_oval(10, 10, 25, 25, fill='red')
canvas.move(ball_id, 245, 100)
starts = [-3, -2, -1, 1, 2, 3]
random.shuffle(starts)
ball_x = starts[0]
ball_y = -3

paddle_id = canvas.create_rectangle(0, 0, 100, 10, fill='blue')
canvas.move(paddle_id, 200, 300)
paddle_x = 0

def draw_ball():
    global ball_x, ball_y
    canvas.move(ball_id, ball_x, ball_y)
    pos = canvas.coords(ball_id)
    if pos[1] <= 0:
        ball_y = 3
    if pos[3] >= canvas_height:
        ball_y = -3
    if hit_paddle(pos) == True:
        ball_y = -3
    if pos[0] <= 0:
        ball_x = 3
    if pos[2] >= canvas_width:
        ball_x = -3
            
def hit_paddle(pos):
    paddle_pos = canvas.coords(paddle_id)
    if pos[2] >= paddle_pos[0] and pos[0] <= paddle_pos[2]:
        if pos[3] >= paddle_pos[1] and pos[3] <= paddle_pos[3]:
            return True
    return False

def turn_left(evt):
    global paddle_x
    paddle_x = -2
        
def turn_right(evt):
    global paddle_x
    paddle_x = 2
        
def draw_paddle():
    global paddle_x
    canvas.move(paddle_id, paddle_x, 0)
    pos = canvas.coords(paddle_id)
    if pos[0] <= 0:
        paddle_x = 0
    elif pos[2] >= canvas_width:
        paddle_x = 0

canvas.bind_all('<KeyPress-Left>' , turn_left)
canvas.bind_all('<KeyPress-Right>', turn_right)
      
while True:
    draw_ball()
    draw_paddle()
    tk.update_idletasks()
    tk.update()
    time.sleep(0.01)

By the way: not always usage of classes makes sense if there is a simpler and sometimes even more intuitive way of achieving the same result.

CodePudding user response:

The init function is a part of a class that is run every time you create an instance of that class. Self makes the variable an attribute to the class, or that it can be accessed in other parts of your code by, for example ball = Ball() print(ball.radius). There is more information here at the documentation: https://docs.python.org/3/tutorial/classes.html#python-scopes-and-namespaces . If you are using object-oriented programming, there aren't any alternatives to self and init. The only way not to use them would be to not use classes in your code.

The self.radius part of the code is creating a variable representing the size of the ball. This link describes how the different points you set creates the shape of the oval: https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/create_oval.html. The short answer is that self.radius creates the size by spacing out the two points of the oval.

  • Related