I am creating an online monopoly game using canvas and tkinter and having trouble trying to make the "player1.png" .ie my character to move across the board. Please help!
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("Monopoly Physics Edition")
self.pack(fill=BOTH, expand=1)
canvas = Canvas(self)
global player1
load= Image.open("player1.png")
player1 = ImageTk.PhotoImage(load)
img = Label(image=player1)
img.place(x=834, y=60)
def main():
canvas = Tk()
ex = Example()
canvas.geometry("1150x820+800+700")
if __name__ == '__main__':
main()
Ok so I programmed in the basics here:
import tkinter as tk
from PIL import Image, ImageTk
class Player:
def __init__(self, canvas, sprite, pos):
self.posx, self.posy = pos
self.canvas = canvas
self.sprite = sprite
self.tk_image = None
self.canvas_id = None
def display(self):
# This will display the train card on the board
self.load_sprite()
# If we already displayed the object hide it first
if self.canvas_id is not None:
self.hide_from_canvas()
# We need to create a new canvas object and get its id
# so that later we can move/remove it from the canvas
self.canvas_id = self.canvas.create_image(self.posx, self.posy, image=self.tk_image)
def hide_from_canvas(self):
# This will remove the sprite from the canvas
self.canvas.delete(self.canvas_id)
def load_sprite(self):
# If we already loaded the sprite just don't do anything
if self.tk_image is not None:
return None
# Later you can determine the filename from `self.name`
filename = "img_small.png"
pillow_image = Image.open(filename)
# You must keep a reference to this `tk_image`
# Otherwise the image would show up on the board
self.tk_image = ImageTk.PhotoImage(pillow_image)
def move(self, change_x, change_y):
# Move the object by change_x, change_y
self.canvas.move(self.canvas_id, change_x, change_y)
class Property:
def __init__(self, canvas, name, cost, pos):
self.posx, self.posy = pos
self.canvas = canvas
self.name = name
self.cost = cost
self.tk_image = None
self.canvas_id = None
def display(self):
# This will display the train card on the board
self.load_sprite()
# If we already displayed the object hide it first
if self.canvas_id is not None:
self.hide_from_canvas()
# We need to create a new canvas object and get its id
# so that later we can move/remove it from the canvas
self.canvas_id = self.canvas.create_image(self.posx, self.posy, image=self.tk_image)
def hide_from_canvas(self):
# This will remove the sprite from the canvas
self.canvas.delete(self.canvas_id)
def load_sprite(self):
# If we already loaded the sprite just don't do anything
if self.tk_image is not None:
return None
# Later you can determine the filename from `self.name`
filename = "img_small.png"
pillow_image = Image.open(filename)
# You must keep a reference to this `tk_image`
# Otherwise the image would show up on the board
self.tk_image = ImageTk.PhotoImage(pillow_image)
def move(self, change_x, change_y):
# Move the object by change_x, change_y
self.canvas.move(self.canvas_id, change_x, change_y)
class App(tk.Canvas):
def __init__(self, master, **kwargs):
# Create the canvas
super().__init__(master, **kwargs)
# lets show all of the monopoly properties on the screen
self.init()
# I will also bind to key presses (You might want to use them later)
master.bind("<Key>", self.on_key_press)
# I will also bind to mouse presses
super().bind("<Button-1>", self.on_mouse_press)
def on_key_press(self, event):
print("You pressed this character:", repr(event.char))
print("For non character keys (like Escape):", repr(event.keysym))
# For now I will move the player sprite by 10 pixels to the right
# when "d" or the right key is pressed
if (event.char == "d") or (event.keysym == "Right"):
# Move the first player of list
self.players[0].move(10, 0)
def on_mouse_press(self, event):
print("You clicked with the mouse at this position:", (event.x, event.y))
def init(self):
# A list that will hold all of the properties:
self.properties = []
# A list that will hold all of the players playing
self.players = []
# I will only create 1 property (You can add the rest)
# I will create 1 of the trains where the cost is 200 and
# its position is (50, 50). Note (0, 0) is the top left
# corner of the canvas
train1 = Property(self, "Train", 200, (50, 50))
train1.display()
self.properties.append(train1)
# I will also create a player at position (100, 100)
player1 = Player(self, "player_sprite1", (100, 100))
player1.display()
self.players.append(player1)
# Create the window
root = tk.Tk()
# Make it so that the user can't resize the window
# Otherwise you will have a lot of problems with your images not being in
# the correct places
root.resizable(False, False)
# Create the App, I also passed in `width=200, height=200` those are in pixels
main_app = App(root, width=200, height=200)
# Show the App on the screen:
main_app.pack()
# Enter tkinter's mainloop
root.mainloop()
It allows us to move/hide/display any of the sprites we create. I also added a way for use to detect key/mouse presses (might come in handy later). Using this it shouldn't be hard to create the full program. Note that most of the methods in the Player and Property class are the same so if you know how to use inheritance, you should be able to simplify my code by a lot.
Related
This question already has an answer here:
How do I move multiple objects at once on a Tkinter canvas?
(1 answer)
Closed last year.
Im trying to move some rectangles with text in them around a canvas with mouse dragNdrop. Im using find_overlapping to select rectangles to be moved. This means the text originally created as part of class object Rect is not moved. Is there a way to modify my code to move all objects in a class object or perhaps find the class object ID using find_overlapping?
Text on rectangles can be identical, as shown in example. Tagging all elements in the class object with a random tag to group them together was my first idea, but retrieving such tag info using find_ovelapping has not been succesful.
import tkinter as tk
root=tk.Tk()
PAB=tk.Canvas(width=400, height=400)
#checks if a certain canvas object has a certain tag
def hastag(tag, id):
if any(tag in i for i in PAB.gettags(id)):return True
else:return False
class Rect:
def __init__(self, x1, y1, name):
rec = PAB.create_rectangle(x1,y1,x1+40,y1+40, fill='#c0c0c0', tag=('movable', name))
text = PAB.create_text(x1+20,y1+20, text=name)
#mouse click find object to move
def get_it(event):
delta=5
global cur_rec
for i in PAB.find_overlapping(event.x-delta, event.y-delta, event.x+delta, event.y-delta):
if hastag('movable', i):
cur_rec = i
PAB.bind('<Button-1>', get_it)
#mouse movement moves object
def move_it(event):
xPos, yPos = event.x, event.y
xObject, yObject = PAB.coords(cur_rec)[0],PAB.coords(cur_rec)[1]
PAB.move(cur_rec, xPos-xObject, yPos-yObject)
PAB.bind('<B1-Motion>', move_it)
#test rects
bob = Rect(20,20,'Bob')
rob = Rect(80,80,'Rob')
different_bob = Rect(160,160,'Bob')
PAB.pack()
root.mainloop()
Thanks. If any clarifications are neccesary Id be happy to help.
A better way would be to use the same tag for all the items that you want to move together so in your case both rectangle and text must have the same tag.
import tkinter as tk
root=tk.Tk()
PAB=tk.Canvas(width=400, height=400, bg="gray")
class Rect:
def __init__(self, x1, y1, name):
tag = f"movable{id(self)}"
rec = PAB.create_rectangle(x1,y1,x1+40,y1+40, fill='#c0c0c0', tag=(tag, ))
text = PAB.create_text(x1+20,y1+20, text=name, tag=(tag,))
def in_bbox(event, item): # checks if the mouse click is inside the item
bbox = PAB.bbox(item)
return bbox[0] < event.x < bbox[2] and bbox[1] < event.y < bbox[3]
#mouse click find object to move
def get_it(event):
delta=5
global cur_rec
cur_rec = PAB.find_closest(event.x, event.y) # returns the closest object
if not in_bbox(event, cur_rec): # if its not in bbox then sets current_rec as None
cur_rec = None
#mouse movement moves object
def move_it(event):
if cur_rec:
xPos, yPos = event.x, event.y
xObject, yObject = PAB.coords(cur_rec)[0],PAB.coords(cur_rec)[1]
PAB.move(PAB.gettags(cur_rec)[0], xPos-xObject, yPos-yObject)
PAB.bind('<Button-1>', get_it)
PAB.bind('<B1-Motion>', move_it)
#test rects
bob = Rect(20,20,'Bob')
rob = Rect(80,80,'Rob')
different_bob = Rect(160,160,'Bob')
PAB.pack()
root.mainloop()
This will work but I'm not sure it's the best way to do it.
Basically it uses the fact that the text is added to the canvas after the rectangle so it can be identified using cur_rec+1.
def move_it(event):
xPos, yPos = event.x, event.y
xObject, yObject = PAB.coords(cur_rec)[0],PAB.coords(cur_rec)[1]
# move rectangle
PAB.move(cur_rec, xPos-xObject, yPos-yObject)
# move text associated with rectangle
PAB.move(cur_rec+1, xPos-xObject, yPos-yObject)
Apply the same tag to both the text and the rectangle. Then, use the tag when calling move. Here's one way to do it:
class Rect:
def __init__(self, x1, y1, name):
identifier = f"id:{id(self)}"
rec = PAB.create_rectangle(x1,y1,x1+40,y1+40, fill='#c0c0c0', tags=('movable', name, identifier))
text = PAB.create_text(x1+20,y1+20, text=name, tags=('movable', identifier))
You can then return the identifier rather than then index of the selected item:
def get_it(event):
delta=5
global cur_rec
for i in PAB.find_overlapping(event.x-delta, event.y-delta, event.x+delta, event.y-delta):
if hastag('movable', i):
identifier = [tag for tag in PAB.gettags(i) if tag.startswith("id:")][0]
cur_rec = identifier
I'm making a chess game.
I've created a main file with the Tkinter code in a class "Window". In this class, I created a canvas.
Then I've created a second file with the name "pieces", where I put the behaviour of the different pieces. In this one, I have a superclass "Pieces", and a subclass "Bishop" (because I haven't created the classes for the other pieces yet)
What I tried first to do, is to create a bishop's icon in the constructor of the class "Bishop".
My class "Bishop" has the argument "color", so that, when we create an object "Bishop", we can choose if he's black or white.
So I wrote :
if self.color == "black":
icon_path = 'black_bishop.png'
elif self.color == "white":
icon_path = 'white_bishop.png'
self.icon = PhotoImage(file=icon_path)
main.Window.canvas.create_image(x_position, y_position, self.icon)
The problem is, when I create a new object of the bishop, it makes a loop.
I don't really know how to fix this, a solution would be to put the whole code in the same file, but I don't like it because it's not clean.
If you need the whole code, I can give it to you.
that's the complete error:
Traceback (most recent call last):
File "C:\Users\CSP\PycharmProjects\chess_game\main.py", line 2, in <module>
import pieces
File "C:\Users\CSP\PycharmProjects\chess_game\pieces.py", line 3, in <module>
import main
File "C:\Users\CSP\PycharmProjects\chess_game\main.py", line 28, in <module>
fen = Window()
File "C:\Users\CSP\PycharmProjects\chess_game\main.py", line 20, in __init__
pieces.Bishop("black", 5, 5)
AttributeError: partially initialized module 'pieces' has no attribute 'Bishop' (most likely due to a circular import)
I've written import main in the file pieces, because I want to create the image in the constructor of the class Bishop, I thought it was cleaner than do it in the class Windows, because I would have to do I 32 times (one per piece) and it would be very heavy.
And to create the image in the class bishop, I need to import the module main (because I use canvas.create_image(), and the canvas is in the class Windows)
But if I write import main it makes a loop, so do you have an idea to fix this?
Here's the code, very simplified
main.py
from tkinter import *
import pieces
#GUI class
class Window:
def __init__(self):
self.window = Tk()
self.window.title("My chess game")
self.frame = Frame(self.window, bg='#41B77F')
self.canvas = Canvas(self.frame, width=500, height=500, bg="skyblue", bd=0, highlightthickness=0)
self.canvas.pack()
bishop = Bishop("black",5 , 5)
self.frame.pack(expand=YES)
win = Window()
win.window.mainloop()
pieces.py
from tkinter import PhotoImage
import main
#superclass
class Pieces:
def __init__(self, color, x_position, y_position):
self.color = color
self.x_position = x_position
self.y_position = y_position
#subclass
class Bishop(Pieces):
def __init__(self, color, x_position, y_position):
super().__init__(color, x_position, y_position)
if self.color == "black":
icon_path = 'black_bishop.png'
elif self.color == "white":
icon_path = 'white_bishop.png'
self.icon = PhotoImage(file=icon_path)
main.Window.canvas.create_image(x_position, y_position, image=self.icon)
If you are familiar with Model/View approach of writing code, it will help you find your way around tkinter applications. In such a case you would place all the code relating to views in one class and all the data is managed in the Model class(es).
In your case, you could start with the structure illustrated below and grow from it:
# model.py
from tkinter import *
class GameModel():
# maintains game state/data
# may include positions of pieces on board
# number of moves made by each player
pass
#superclass, inherits from Tk().Canvas
class Pieces(Canvas):
def __init__(self, color, x_position, y_position):
self.color = color
self.x_position = x_position
self.y_position = y_position
#subclass
class Bishop(Pieces):
def __init__(self, color, x_position, y_position):
super().__init__(color, x_position, y_position)
if self.color == "black":
icon_path = 'black_bishop.png'
elif self.color == "white":
icon_path = 'white_bishop.png'
self.icon = PhotoImage(file=icon_path)
# main.Fenetre.canvas.create_image(x_position, y_position, image=self.icon)
self.create_image(x_position, y_position, image=self.icon)
# because bishop inherits from pieces which inherits from Canvas, its possible
# to call .create_image() from within Bishop class using self.create_image()
# gameinterface.py
from tkinter import *
#GUI/View class
class GameInterface():
def __init__(self, window: Tk()):
self.window = window
self.frame = Frame(self.window, bg='#41B77F')
self.canvas = Canvas(self.frame, width=500, height=500, bg="skyblue", bd=0, highlightthickness=0)
self.canvas.pack()
self.frame.pack(expand=YES)
def play(self, game: GameModel):
# the game objects give access to state of game and all data
self.window.mainloop()
# main.py
from tkinter import *
def main() -> None:
"""Entry point to gameplay."""
window = Tk()
# window = Tk()
#set title and position at center of screen
window.title("My chess game")
# game object gives you access to all relevant data
game = GameModel()
# app object gives you access to view
app = GameInterface(window)
# app.play combines the model and the view
app.play(game)
if __name__ == '__main__':
main()
If you need a reference to the window in Bishop, you have to pass it as an argument:
from tkinter import PhotoImage
class Pieces:
def __init__(self, color, x_position, y_position):
self.color = color
self.x_position = x_position
self.y_position = y_position
#subclass
class Bishop(Pieces):
def __init__(self, win, color, x_position, y_position):
super().__init__(color, x_position, y_position)
if self.color == "black":
icon_path = 'black_bishop.png'
elif self.color == "white":
icon_path = 'white_bishop.png'
self.icon = PhotoImage(file=icon_path)
win.canvas.create_image(x_position, y_position, image=self.icon)
I am making a game for the repl.it game jam. I am trying to put an image on a canvas that was created in a different class from the class that the image is being created on. The canvas has successfully displayed text and a button with the image that I need, but create_image is not working.
This might be an issue with resolution, but I cannot check that right now, the image is 1920 x 1080 and so is the game.
I've already tried create_image.
Full program (including images)
class game(Application):
"""Where game elements are to be held"""
def __init__(self, players, location, sWidth, sHeight, canvas):
"""Initiate variables & start game"""
#Initiate variables
self.players = players
self.location = location
self.screenWidth = sWidth
self.screenHeight = sHeight
self.Canvas1 = canvas
self.LOCATIONS = Application.LOCATIONS
self.font = Application.font
#Gathering Images
self.map1BG = PhotoImage(file = "polasib.gif")
#Debugging
print("Loading Map", self.location\
, "\nPlayers:", self.players)
self.createLevel(self.location)
def createUI(self, players):
"""Creates the UI that all levels will have"""
self.Canvas1.create_text(self.screenWidth/2, self.screenHeight/16, fill = "white", \
font = (self.font, self.screenWidth//34), text = self.LOCATIONS[self.location - 1])
def createLevel(self, location):
"""Creates the elements of the level"""
if self.location == 1:
#Polasi b
print("Creating Polasi b Level")
self.createUI(self.players)
self.Canvas1.create_image(self.screenWidth/2, self.screenHeight/2, image = self.map1BG, \
anchor = NW)
Expectation: I expect the image to load (and that it will require some realignment)
Result: No image appears but everything else added (as a test) works.
Since you did not save the reference to the instance of game, it will be destroyed after exiting Application.gameScreen(). Therefore the reference of the image of create_image will be lost and no image will be shown. Try assigning the instance of game to an instance variable of Application, like below:
self.running_game = game(self.players, self.mapChosen, self.screenWidth, self.screenHeight, self.Canvas1)
I am using tkinter in python3. I created a die class as a subclass of the canvas widget. Then, I created a subclass of the original die class that allows me to freeze the die and prevent it from being rolled. Finally, I created a custom widget as a subclass of frame, which contains both the freezable die and a toggle freeze button. However, even though I grid the die canvas before I grid the button, the button still appears above the canvas. (It did not work to try to control the rows manually) Here is my code:
from tkinter import *
import random
class GUIDie(Canvas):
'''6-sided Die class for GUI'''
def __init__(self,master,valueList=[1,2,3,4,5,6],colorList=['black']*6):
'''GUIDie(master,[valueList,colorList]) -> GUIDie
creates a GUI 6-sided die
valueList is the list of values (1,2,3,4,5,6 by default)
colorList is the list of colors (all black by default)'''
# create a 60x60 white canvas with a 5-pixel grooved border
Canvas.__init__(self,master,width=60,height=60,bg='white',\
bd=5,relief=GROOVE)
# store the valuelist and colorlist
self.valueList = valueList
self.colorList = colorList
# initialize the top value
self.top = 1
def get_top(self):
'''GUIDie.get_top() -> int
returns the value on the die'''
return self.valueList[self.top-1]
def roll(self):
'''GUIDie.roll()
rolls the die'''
self.top = random.randrange(1,7)
self.draw()
def draw(self):
'''GUIDie.draw()
draws the pips on the die'''
# clear old pips first
self.erase()
# location of which pips should be drawn
pipList = [[(1,1)],
[(0,0),(2,2)],
[(0,0),(1,1),(2,2)],
[(0,0),(0,2),(2,0),(2,2)],
[(0,0),(0,2),(1,1),(2,0),(2,2)],
[(0,0),(0,2),(1,0),(1,2),(2,0),(2,2)]]
for location in pipList[self.top-1]:
self.draw_pip(location,self.colorList[self.top-1])
def draw_pip(self,location,color):
'''GUIDie.draw_pip(location,color)
draws a pip at (row,col) given by location, with given color'''
(centerx,centery) = (17+20*location[1],17+20*location[0]) # center
self.create_oval(centerx-5,centery-5,centerx+5,centery+5,fill=color)
def erase(self):
'''GUIDie.erase()
erases all the pips'''
pipList = self.find_all()
for pip in pipList:
self.delete(pip)
class GUIFreezeableDie(GUIDie):
'''a GUIDie that can be "frozen" so that it can't be rolled'''
def __init__(self,master,valueList=[1,2,3,4,5,6],colorList=['black']*6):
'''GUIFreezeableDie(master,[valueList,colorList]) -> GUIFreezeableDie
creates a GUI 6-sided freeze-able die
valueList is the list of values (1,2,3,4,5,6 by default)
colorList is the list of colors (all black by default)'''
# you add code here
GUIDie.__init__(self, master, valueList, colorList)
self.isFrozen = False
def is_frozen(self):
'''GUIFreezeableDie.is_frozen() -> bool
returns True if the die is frozen, False otherwise'''
return self.isFrozen
def toggle_freeze(self):
'''GUIFreezeableDie.toggle_freeze()
toggles the frozen status'''
if self.is_frozen():
self.isFrozen = False
self.configure(background = "white")
else:
self.isFrozen = True
self.configure(background = "grey")
def roll(self):
'''GuiFreezeableDie.roll()
overloads GUIDie.roll() to not allow a roll if frozen'''
if not self.is_frozen():
GUIDie.roll(self)
class GUIDieSet(Frame):
def __init__(self,master,valueList=[1,2,3,4,5,6],colorList=['black']*6):
'''GUIFreezeableDie(master,[valueList,colorList]) -> GUIFreezeableDie
creates a GUI 6-sided freeze-able die
valueList is the list of values (1,2,3,4,5,6 by default)
colorList is the list of colors (all black by default)'''
Frame.__init__(self,master)
self.grid()
self.die = GUIFreezeableDie(master, valueList, colorList)
self.die.grid()
self.toggleFreeze = Button(self,text='Freeze',command=self.die.toggle_freeze)
self.toggleFreeze.grid(row=1)
def roll(self):
self.die.roll()
class FreezeTest(Frame):
'''a small application to test the freezeable die'''
def __init__(self,master):
Frame.__init__(self,master)
self.grid()
self.die = GUIDieSet(self)
self.die.grid()
# test application
root = Tk()
test = TestFrame(root)
root.mainloop()
In the class GUIDieSet(), remove the line self.grid() in __init()__ and it seems to work.
I am trying to put an image on a TKinter canvas with other buttons under the image. For some reason I can not get this picture to appear. I have not yet implemented the buttons. Here is my code thus far.
class GUI_Control:
def __init__(self, player):
self.player = player
self.map = Tk()
self.MAP_WIDTH = 600
self.MAP_HEIGHT = 375
#define map gui here
self.canvas = Canvas(self.map, width=self.MAP_WIDTH, height=self.MAP_HEIGHT)
self.map_picture = PhotoImage(file=r"images/archipelago.gif")
self.canvas.create_image(0, 0, image=self.map_picture)
#define level gui's here
def open(self):
self.map.mainloop()
def hide_map(self):
self.map.destroy()
#debugging
if __name__ == "__main__":
gui = GUI_Control(Player.Player())
gui.open()
You'll need to use one of Tk’s geometry-management mechanisms to tell it where to render the canvas within it's container.
the simplest way would be to add self.canvas.pack() like so:
#define map gui here
self.canvas = Canvas(self.map, width=self.MAP_WIDTH, height=self.MAP_HEIGHT)
self.canvas.pack()
self.map_picture = PhotoImage(file=r"images/archipelago.gif")
self.canvas.create_image(0, 0, image=self.map_picture)
#define level gui's here
You need to call the pack() (or grid()) method of widgets for them to be displayed:
class GUI_Control:
def __init__(self, player):
self.player = player
self.map = Tk()
self.MAP_WIDTH = 600
self.MAP_HEIGHT = 375
#define map gui here
self.canvas = Canvas(self.map, width=self.MAP_WIDTH, height=self.MAP_HEIGHT)
self.canvas.pack(expand=YES, fill=BOTH) # ADDED
self.map_picture = PhotoImage(file="images/archipelago.gif")
self.canvas.create_image(0, 0, image=self.map_picture, anchor='nw')
#define level gui's here
def open(self):
self.map.mainloop()
def hide_map(self):
self.map.destroy()
#debugging
if __name__ == "__main__":
gui = GUI_Control(Player.Player())
gui.open()