I'm trying to make a text adventure with tkinter and I'm slowly getting something together. I'm trying to display commands as they come from room to room but even though the buttons appear, nothing happens when I press them.
game.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
import world
from player import Player
from ui import *
def main():
gui = Window(root())
while True:
gui.mainloop()
else:
pass
if __name__ == '__main__':
main()
ui.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
import tkinter as tk
from tkinter import ttk
import world, tiles, action
from player import Player
class Window(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master=master)
self.master = master
self.player = Player()
self.init_ui()
def init_ui(self):
self.master.title("****")
self.tabs = Tabs(self.master)
world.load_tiles()
self.world = world.tile_exists(self.player.location_x, self.player.location_y)
self.update_main()
def update_main(self):
self.world.scene_init()
self.world.modify_player(self.player)
self.tabs.update_tab(self.world, self.player)
def _label(self, master, text, side=None, anchor=None):
new_label = tk.Label(master, text=text)
new_label.pack(side=side, anchor=anchor)
def _button(self, master, text, command, side=None, anchor=None):
new_button = tk.Button(master, text=text, command=command)
new_button.pack(side=side, anchor=anchor)
class Tabs(Window):
def __init__(self, master):
self.master = master
self.nb = ttk.Notebook(self.master)
nb_1 = ttk.Frame(self.nb)
self.frame_1 = tk.Frame(nb_1, bg='red', bd=2, relief=tk.SUNKEN, padx=5, pady=5)
self.frame_1.pack(expand=1, fill='both', side=tk.LEFT)
self.nb.add(nb_1, text='Game')
self.nb.pack(expand=1, fill='both', side=tk.LEFT)
def update_tab(self, world, player):
avaliable_actions = world.avaliable_actions()
self._label(self.frame_1, world.display_text(), side=tk.LEFT, anchor=tk.N)
for action in avaliable_actions:
self._button(self.frame_1, text=action, command=player.do_action(action, **action.kwargs), side=tk.BOTTOM, anchor=tk.E)
def root():
root = tk.Tk()
root.geometry("600x350+200+200")
return root
world.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
_world = {}
def tile_exists(x, y):
"""Returns the tile at the given coordinates or None if there is no tile.
:param x: the x-coordinate in the worldspace
:param y: the y-coordinate in the worldspace
:return: the tile at the given coordinates or None if there is no tile
"""
return _world.get((x, y))
def load_tiles():
with open('scenes.txt', 'r') as f:
rows = f.readlines()
x_max = len(rows[0].split('\t'))
for y in range(len(rows)):
cols = rows[y].split('\t')
for x in range(x_max):
tile_name = cols[x].replace('\n', '')
_world[(x, y)] = None if tile_name == '' else getattr(__import__('tiles'),
tile_name)(x, y)
return _world
tiles.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
import world, action
from player import Player
class MapTile():
def __init__(self, x, y):
self.x = x
self.y = y
def display_text(self):
pass
# raise NotImplementedError()
def modify_player(self, the_player):
raise NotImplementedError()
def adjacent_moves(self):
moves = []
if world.tile_exists(self.x + 1, self.y):
moves.append(action.MoveEast())
if world.tile_exists(self.x - 1, self.y):
moves.append(action.MoveWest())
if world.tile_exists(self.x, self.y - 1):
moves.append(action.MoveNorth())
if world.tile_exists(self.x, self.y + 1):
moves.append(action.MoveSouth())
return moves
def avaliable_actions(self):
'''Returns all of the default avaliable_actions in a room'''
moves = self.adjacent_moves()
# moves.append(action.ViewInventory())
return moves
class Scene_1(MapTile):
def scene_init(self):
self.location = 'Scene_1'
self.long_desc = 'Welcome to {}, the shittiest place on earth.'.format(self.location)
self.short_desc = 'Eh, I don\'t care.'
def display_text(self):
return self.long_desc
def modify_player(self, the_player):
self.first = True
return self.display_text()
class Scene_2(MapTile):
def scene_init(self):
self.location = 'Scene_2'
self.long_desc = 'This is {}, but noone gives a damn.'.format(self.location)
self.short_desc = 'Eh, I don\'t care, really.'
def display_text(self):
return self.long_desc
def modify_player(self, the_player):
self.first = True
return self.display_text()
player.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
class Player():
'''Base for player'''
def __init__(self):
self.inventory = []
self.hp = 100
self.location_x, self.location_y = 1, 1
self.victory = False
def is_alive(self):
return self.hp >= 0
def do_action(self, action, **kwargs):
action_method = getattr(self, action.method.__name__)
if action_method:
action_method(**kwargs)
def print_inventory(self):
for item in self.inventory:
print(item, 'n')
def move(self, dx, dy):
self.location_x += dx
self.location_y += dy
def move_north(self):
self.move(dx=0, dy=-1)
def move_south(self):
self.move(dx=0, dy=1)
def move_east(self):
self.move(dx=1, dy=0)
def move_west(self):
self.move(dx=-1, dy=0)
action.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
from player import Player
class Action():
def __init__(self, method, name, **kwargs):
"""Creates a new action
:param method: the function object to execute
:param name: the name of the action
:param ends_turn: True if the player is expected to move after this action else False
:param hotkey: The keyboard key the player should use to initiate this action
"""
self.method = method
self.name = name
self.kwargs = kwargs
def __str__(self):
return "{}".format(self.name)
class MoveNorth(Action):
def __init__(self):
super().__init__(method=Player.move_north, name='north')
class MoveSouth(Action):
def __init__(self):
super().__init__(method=Player.move_south, name='south')
class MoveEast(Action):
def __init__(self):
super().__init__(method=Player.move_east, name='east')
class MoveWest(Action):
def __init__(self):
super().__init__(method=Player.move_west, name='west')
class ViewInventory(Action):
"""Prints the player's inventory"""
def __init__(self):
super().__init__(method=Player.print_inventory, name='View inventory', hotkey='i')
class Attack(Action):
def __init__(self, enemy):
super().__init__(method=Player.attack, name="Attack", hotkey='a', enemy=enemy)
class Flee(Action):
def __init__(self, tile):
super().__init__(method=Player.flee, name="Flee", hotkey='f', tile=tile)
command expect function name without () and arguments.
Mistake:
command=player.do_action(action, **action.kwargs)
This way you assign to command value returned by player.do_action() but this functions returns None
You have to use lambda function
command=lambda:player.do_action(action, **action.kwargs)
but maybe you will need also arguments in lambda because you create this in for loop.
command=lambda act=action, kws=action.kwargs : player.do_action(act, **kws)
Related
I'm making a ide for brainf*ck in python using tkinter and I'm adding a recent projects section but when I'm placing the buttons they do not appear on the screen.
Here is the code for the Scene:
from tkinter import *
from tkinter import filedialog as File
import tkinter as tk
class HoverButton(tk.Button):
def __init__(self, master, **kw):
tk.Button.__init__(self, master=master, **kw)
self.defaultBackground = "#5d5d5d"
self['background'] = self.defaultBackground
self['activebackground'] = "#6d6d6d"
self.bind("<Enter>", self.on_enter)
self.bind("<Leave>", self.on_leave)
def on_enter(self, e):
self['background'] = "#6d6d6d"
def on_leave(self, e):
self['background'] = self.defaultBackground
class ProjectPage(Frame):
def __init__(self, master, projects=[]):
super().__init__(master)
self.projects = projects
self.buttons = []
self.mstr = self.master.master
self.title = "PesolIde: Projets"
self.width = 800
self.height = 500
self.color = "#4d4d4d"
self.projectFrame = tk.Frame(self.mstr,width=800,height=50,bg="#5d5d5d")
self.newProject = HoverButton(self.mstr,text="New Project", height=1, bg="#6d6d6d")
self.openProject = HoverButton(self.mstr,text="Open Project", height=1,bg="#6d6d6d", command=OpenAsk)
self.projectdisplay = tk.Frame(self.mstr, width=700, height=300, bg="#5d5d5d", highlightbackground="black", highlightthickness=1)
for i in range(len(self.projects)):
self.buttons.append(HoverButton(master, text=self.projects[i].split(':')[0], width=50, height=1))
if len(self.buttons)>=40:
break
self.loaded = False
def show(self):
self.projectFrame.place(x=0, y=0)
self.newProject.place(x=20, y=10)
self.openProject.place(x=120, y=10)
self.projectdisplay.place(x=50,y=100)
self.y = 100
print(len(self.buttons))
for i in range(len(self.buttons)):
print("placing " + str(self.buttons[i]))
self.buttons[i].place(x=50,y=100+(20*i))
self.master.set(title=self.title,width=self.width,height=self.height)
self.master.master['bg'] = self.color
def hide(self):
self.newProject.place_forget()
self.openProject.place_forget()
def load(self):
if not self.loaded:
self.newProject.place_forget()
self.openProject.place_forget()
self.loaded = True
def unload(self):
self.newProject.destroy()
self.openProject.destroy()
def OpenAsk():
name = File.askopenfilename()
and here is the code for main.py:
from tkinter import *
import src.framework.modules.Window.Window as windows
import src.framework.src.Scenes.all as Scenes
import tkinter as tk
root = tk.Tk()
window = windows.window(root, "", 800, 500)
window.place()
projects = open("projects.txt",'r').read().split("\n")
start = Scenes.ProjectPage.ProjectPage(window,projects)
start.show()
window.mainloop()
When I make a HoverButton outside the ProjectPage class in the ProjectPage file, it appears as expected but not when initialised from within the class of directly from the main file.
Here are some screenshots.
The output from running main.py:
The output from running from outside the ProjectPage class with the code on the left:
Try to insert values for relx, rely, relwidth, relheight as attributes in "place" or you can as well insert height, width as attributes for place.
Check out the documentation: https://www.tutorialspoint.com/python/tk_place.htm
I'm trying to build a Python application that has a Tkinter GUI.
I got stuck there that I don’t understand how to literate through the read yaml string by keys. I want the values of field_names to be the names of the Entries in the code and the values of labels are the properties of labels text.
cfg.yml looks like this:
- field_name: neptun_code
label: Neptun code
- field_name: result
label: Result [%]
- field_name: mark
label: Mark [1-5]
config.py looks like this:
from pathlib import Path
class Config(object):
def __init__(self):
self.document_yaml = None
def function(self):
self.from_yaml()
def from_yaml(self):
self.document_yaml = Path("./cfg.yaml").read_text()
return self.document_yaml
Main.py The problem here is that I don’t know how to use the yaml list and how to iterate through it in def labels(self) and def entries(self)
import tkinter as tk
from tkinter import messagebox
class Window(object):
def __init__(self):
self.name = None
self.window = None
self.lbl = None
self.txt = None
def show(self):
self.create_window()
self.labels()
self.entries()
self.window.mainloop()
def create_window(self):
self.window = tk.Tk()
self.window.title("MainWindow")
self.window.geometry('350x200')
def labels(self):
# label
row = 0
for key, value in yaml_str: #?????
self.lbl = tk.Label(self.window, text=value)
self.lbl.grid(column=0, row=row)
row += 1
def entries(self):
# entry
row = 0
for key, value in yaml_str: # ??????
self.txt = tk.Entry(self.window, textvariable=self.name, width=20)
self.txt.grid(column=1, row=row)
row += 1
my_window = Window()
my_window.show()
Install the pyyaml library:
pip install pyyaml
Now convert your yaml to a python list and process it:
import yaml
from pathlib import Path
document_yaml = Path("./cfg.yaml").read_text()
yaml_list = yaml.load(document_yaml)
print(yaml_list)
# [{'field_name': 'neptun_code', 'label': 'Neptun code'}, {'field_name': 'result', #'label': 'Result [%]'}, {'field_name': 'mark', 'label': 'Mark [1-5]'}]
# So labels, for instance, will be:
def labels(self):
# label
row = 0
for item in yaml_list:
self.lbl = tk.Label(self.window, text=item["label"])
self.lbl.grid(column=0, row=row)
row += 1
Full Example:
import yaml
from pathlib import Path
class Config(object):
def __init__(self):
self.document_yaml = Path("./cfg.yaml").read_text()
def load_yaml(self) -> list:
return yaml.load(self.document_yaml)
class Window(object):
def __init__(self):
self.name = None
self.window = None
self.lbl = None
self.txt = None
self.config = Config().load_yaml()
def show(self):
self.create_window()
self.labels()
self.window.mainloop()
def create_window(self):
self.window = tk.Tk()
self.window.title("MainWindow")
self.window.geometry('350x200')
def labels(self):
# label
row = 0
for item in self.config:
self.lbl = tk.Label(self.window, text=item["label"])
self.lbl.grid(column=0, row=row)
row += 1
So I have a simple program where when you click a button from a grid it will be filled a colour. I wish to be able to drag across over the buttons and they get filled, unlike at the moment where you have to click every single button. Can this be done?
Here's my code that probably isn't the best:
from tkinter import *
root=Tk()
grid= Frame(root)
grid.pack()
img0=PhotoImage(file="0.png")
img1=PhotoImage(file="1.png")
img2=PhotoImage(file="2.png")
fill = 1
class button:
def __init__(self, x, y):
self.type=0
self.but=Button(grid,command=self.change, image=img0, borderwidth=0)
self.but.grid(row=y, column=x)
def change(self):
if self.type==fill:
self.but.config(image=img0)
self.type=0
else:
self.but.config(image=eval("img"+str(fill)))
self.type=fill
def create(x,y):
grid_buttons = []
for Y in range(y):
grid_buttons.append([])
for X in range(x):
grid_buttons[Y].append(button(X, Y))
create(15,15)
root.mainloop()
Here's one way:
from tkinter import *
root=Tk()
grid= Frame(root)
grid.pack()
img0=PhotoImage(file="0.png")
img1=PhotoImage(file="1.png")
img2=PhotoImage(file="2.png")
fill = 1
class button:
def __init__(self, x, y):
self.type=0
self.but=Button(grid,command=self.change, image=img0, borderwidth=0)
self.but.grid(row=y, column=x)
#Changed
self.already_changed = False
def change(self):
if self.type==fill:
self.but.config(image=img0)
self.type=0
else:
self.but.config(image=eval("img"+str(fill))) #I left this in here, but you should NEVER use eval(). It's unsafe.
self.type=fill
#Changed
def mouse_entered(self):
if not self.already_changed:
self.change()
self.already_changed = True
def mouse_up(self):
self.already_changed = False
#Changed
class Container:
def __init__(self, x, y):
grid_buttons = []
for Y in range(y):
grid_buttons.append([])
for X in range(x):
grid_buttons[Y].append(button(X, Y))
self.buttons = grid_buttons
grid.bind_all("<Button-1>", self.mouse_down)
grid.bind_all("<ButtonRelease-1>", self.mouse_up)
grid.bind_all("<B1-Motion>", self.mouse_motion)
self.mouse_pressed = False
def mouse_down(self, e):
self.mouse_pressed = True
def mouse_up(self, e):
self.mouse_pressed = False
for row in self.buttons:
for but in row:
but.mouse_up()
def mouse_motion(self, e):
for row in self.buttons:
for but in row:
if grid.winfo_containing(e.x_root, e.y_root) is but.but:
but.mouse_entered()
container = Container(15,15)
root.mainloop()
Now, I noticed that some of the things you did aren't quite Python style. So here's a version that more closely follows Python convention. Be warned that it's quite different.
from tkinter import *
root = Tk()
images = {0: PhotoImage(file="0.png"),
1: PhotoImage(file="1.png"),
2: PhotoImage(file="2.png")}
fill = 1
class MyButton(Button): #Convention is for class names to start with uppercase letters
def __init__(self, master):
super(MyButton, self).__init__(master, image = images[0], borderwidth = 0)
self.type = 0
self.already_changed = False
def change(self):
if self.type == fill:
self.type = 0
else:
self.type = fill
self.config(image=images[self.type])
def mouse_entered(self):
if not self.already_changed:
self.change()
self.already_changed = True
def mouse_up(self):
self.already_changed = False
class Container(Frame):
def __init__(self, master, width, height):
super(Container, self).__init__(master)
buttons = []
for y in range(height):
buttons.append([])
for x in range(width):
button = MyButton(self)
button.grid(row = x, column = y)
buttons[y].append(button)
self.buttons = buttons
self.bind_all("<Button-1>", self.mouse_down)
self.bind_all("<ButtonRelease-1>", self.mouse_up)
self.bind_all("<B1-Motion>", self.mouse_motion)
self.mouse_pressed = False
def mouse_down(self, e):
self.update_containing_button(e)
self.mouse_pressed = True
def mouse_up(self, e):
self.mouse_pressed = False
for row in self.buttons:
for button in row:
button.mouse_up()
def mouse_motion(self, e):
self.update_containing_button(e)
def update_containing_button(self, e):
for row in self.buttons:
for button in row:
if self.winfo_containing(e.x_root, e.y_root) is button:
button.mouse_entered()
grid = Container(root, 15, 15)
grid.pack()
root.mainloop()
Why post both? Because it looks like you have more code in the actual application (that's good, it's a minimal example). I didn't want to force you to rewrite my code to make it work with the rest of your code, or vice versa.
Functionality differences between the two versions:
The second version has been modified so it uses object-oriented features instead of global variables, making it more flexible and easier to change.
The second version removes the binding on the buttons themselves, instead having the container handle everything.
I am setting up animations to call another function on_complete and then repeat itself with redefined variables. Except that my variable doesn't change so it applies the repeated animations on the 1st widget spawned. Here is an example:
from kivy.config import Config
Config.set('input', 'mouse', 'mouse,multitouch_on_demand')
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.uix.image import Image
from kivy.animation import Animation
from kivy.graphics import Color, Rectangle
from kivy.uix.button import Button
global Draw_Card
Draw_Card=0
global SpadeCount
SpadeCount=0
##Window.fullscreen = True
##Window.size = (1920, 1080)
Player1Deck=['Spade', 'Spade', 'Spade']
class Spade1(Widget):
def __init__(self, **kwargs):
super(Spade1, self).__init__(**kwargs)
global CardSetup
CardSetup=True
self.b=Button(text='Hello World!')
self.add_widget(self.b)
self.bind(pos=self.redraw, size=self.redraw)
self.size=(186.5 / 2, 259.25 / 2)
self.pos=(200, 50)
def Set_Originals(self, x):
self.Original_pos=x.pos
self.Original_size=x.size
def redraw(self, *args):
self.b.pos=self.pos
self.b.size=self.size
class Spade2(Widget):
def __init__(self, **kwargs):
super(Spade2, self).__init__(**kwargs)
global CardSetup
CardSetup=True
self.b=Button(text='Hello World!')
self.add_widget(self.b)
self.bind(pos=self.redraw, size=self.redraw)
self.size=(186.5 / 2, 259.25 / 2)
self.pos=(200, 50)
def Set_Originals(self, x):
self.Original_pos=x.pos
self.Original_size=x.size
def redraw(self, *args):
self.b.pos=self.pos
self.b.size=self.size
class Spade3(Widget):
def __init__(self, **kwargs):
super(Spade3, self).__init__(**kwargs)
global CardSetup
CardSetup=True
self.b=Button(text='Hello World!')
self.add_widget(self.b)
self.bind(pos=self.redraw, size=self.redraw)
self.size=(186.5 / 2, 259.25 / 2)
self.pos=(200, 50)
def Set_Originals(self, x):
self.Original_pos=x.pos
self.Original_size=x.size
def redraw(self, *args):
self.b.pos=self.pos
self.b.size=self.size
class DrawCard(Widget):
def __init__(self, **kwargs):
super(DrawCard, self).__init__(**kwargs)
self.anim_queue = []
self.Draw_Card()
def Draw_Card(self):
global Draw_Card
global CardSetup
global SpadeCount
if Draw_Card > 0:
NextCard=Player1Deck.pop(0)
if NextCard == 'Spade':
if SpadeCount==0:
self.add_widget(Spade1())
elif SpadeCount==1:
self.add_widget(Spade2())
elif SpadeCount==2:
self.add_widget(Spade3())
SpadeCount += 1
PlayerHandCardsList=self.children[:]
print(PlayerHandCardsList)
x=len(PlayerHandCardsList)-1
print(x)
print(PlayerHandCardsList[x])
anim = Animation(pos=((493.75) + ((x * 25)), 0),
t='in_out_quad', duration=1)
anim &= Animation(size=(186.5, 259.25),
t='in_out_quad', duration=1)
self.anim_queue.append((PlayerHandCardsList[x], anim))
w, a = self.anim_queue.pop(0)
a.bind(on_complete=lambda pos, size: self.WrapUpAnimation(PlayerHandCardsList, x))
a.start(w)
else:
CardSetup=False
def WrapUpAnimation(self, instance, x):
global Draw_Card
instance[x].Set_Originals(instance[x])
Draw_Card += -1
self.Draw_Card()
class Game(Widget):
def __init__(self):
super(Game, self).__init__()
global Draw_Card
Draw_Card += 3
self.add_widget(DrawCard())
class GameApp(App):
def build(self):
top = Widget()
top.add_widget(Game())
return top
if __name__ == '__main__':
GameApp().run()
When the console prints PlayerHandCardsList it correctly shows all widgets added so far. When it prints x, this also is correct. However when PlayerHandCardsList[x] prints, it displays the very first value it was assigned and does not update when the Draw_Card function is repeated. It does however correctly redefine the position x * 25. Why is it doing this!? Please help
Ok, so I figured it out! Because I was redefining the self.children list every time instead of .appending, the newest item was always at index 0! So for this, all you have to do is reference index 0 of the list for each run through!
[edit]
It seems I solved the problem... In fact, I now do that:
class Gameboard(QGraphicsScene):
def deletePawn(self, num):
pawnToDelete = self.pawns.pop(num)
pawnToDelete.delete()
class Pawn(QGraphicsItem):
def delete(self):
child.prepareGemotryChange()
child.setParent(None)
#idem for each child
self.gameboard.removeItem(self)
self.gameboard = None
[/edit]
What is the good way to implement references in a pyqt QGraphicsScene?
I made a class Gameboard which inherits QGraphicsScene, and this gameboard contains a variable number of pawns (wich inherit QGraphicsPolygonItem)
Pawns can be created or deleted dynamically, creation is ok but deletion sometimes crash...
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4 import QtOpenGL
class Gameboard(QGraphicsScene):
def __init__(self):
super(Gameboard, self).__init__()
self.pawns = {}
self.currentPawn = None
def createSquares(self):
#create the polygons of the squares and affect their coordinates x,y
def createPawn(self):
pawn = Pawn(self)
num = len(self.pawns)
self.pawns[num] = pawn
self.addItem(self.pawns[num])
def deletePawn(self, num):
self.currentPawn = None
self.pawns[num].beforeDelete()
self.pawns[num].prepareGeometryChange()
self.removeItem(self.pawns[num])
del self.pawns[num]
def selectPawn(self, pawn):
#a pawn is selected, by click for example
self.currentPawn = pawn
def keyPressEvent(self, event):
ekey = event.key()
if ekey == Qt.Key_Delete and self.currentPawn != None:
num = self.currentPawn.number
self.deletePawn(num)
class Pawn(QGraphicsItem):
def __init__(self, gameboard):
super(Pawn, self).__init__()
self.gameboard = gameboard
self.number = 0
self.pos = (-1,-1)
self.name = ""
def create(self, x, y, num):
#create a QGraphicsPolygonItem, a QGraphixPixmapItem and a QGraphicsTextItem which are child of the QGraphicsItem Pawn, and set position
self.number = num
self.pos = (x,y)
self.gameboard.squares[(x,y)].occupiedBy = self
def move(self, newPos):
self.gameboard.squares[self.pos].occupiedBy = None
self.pos = newPos
self.gameboard.squares[self.pos].occupiedBy = None
def beforeDelete(self):
#a function I add trying to get rid of the crash
self.gameboard = None
self.graphicsPolygon.setParent = None
self.graphicsPix.setParent = None
self.text.setParent = None
def mousePressEvent(self, event):
super(Pawn, self).mousePressEvent(event)
if event.button() == 1:
self.gameboard.currentPawn = self
event.accept()
class Square(QGraphicsPolygonItem):
def __init__(self, gameboard):
self.coordinates = (x,y)
self.occupiedBy = None
What is the proper way to proceed, should I use deleteLater?
Or maybe something with the weakref lib?
Is it because of the variable gameboard in Pawn?