I'm trying to create a TKinter based Space Invaders but I ahve trouble with the keyboard inputs.
Indeed, I've 3 "bind" :
If the right arrow is pressed, the "character" moves to the right of 10px
Same with left
Finally, if the space bar is pressed a shoot is created (right now it's only a white rectangle above the "character"
But there comes the problem, if I press the spacebar while holding an arrow the ship stops, I can't make it shoot while moving.
I've found some posts here already (including one saying it may be due to how the system handles keypress, and he gave a solution but I wasn't able to use it) about this problem but I couldn't apply their solutions as my english and Python knowledge are short (first time I use OOP and classes).
So I've tried creating a new type of bind : beginning a non-stop move to the right on press of the arrow and stop of it on release, same for left and a condition on double hold but I wasn't able to code it. Would it even handle the problem or I need to find something else ?
Here's my code (french notes so ask if needed) :
from tkinter import Tk, Canvas, Button, Label
class SpaceInvaders():
def __init__(self):
self.window = Tk()
self.window.geometry("1200x900")
self.label_score = Label(self.window, text="Score : ")
self.label_score.place(x=10, y=10)
self.label_vies = Label(self.window, text="Vies :")
self.label_vies.place(x=1130, y=10)
self.button_new = Button(self.window, text="New Game", width=15)
self.button_new.place(x=400, y=860)
self.button_quit = Button(self.window, text="Quit", width=15, command = lambda:self.window.destroy())
self.button_quit.place(x=680, y=860)
self.canvas = Canvas(self.window, height = "800", width = "1200", bg='black')
self.canvas.pack(expand=True)
#Vaisseau et 1er alien
self.vaisseau = Vaisseau(self.canvas)
self.alien = Alien(self.canvas,self.window)
class Alien():
def __init__(self,canvas,window):
self.canvas = canvas
self.window = window
self.alien_x = 0
self.alien_y = 0
self.direction = 1
self.alien = self.canvas.create_rectangle(self.alien_x,self.alien_y,self.alien_x+100,self.alien_y+20, fill='white')
def run(self):
direction = self.direction
if direction==1:
if self.alien_x<1100:
self.alien_x += 5
else:
self.direction = -1
elif direction == -1:
if self.alien_x>0:
self.alien_x -= 5
else:
self.direction = 1
self.canvas.coords(self.alien,self.alien_x,self.alien_y,self.alien_x+100,self.alien_y+20)
self.window.after(20,self.run) #La méthode after semble retourner une erreur à 13 chiffres
class Vaisseau():
def __init__(self,canvas):
self.canvas = canvas
self.player_x = 0
self.player = self.canvas.create_rectangle(self.player_x,780,self.player_x+60,802,fill='white')
def event_handler(self,event):
if event.keysym == 'Right':
self.move(True)
elif event.keysym == 'Left':
self.move(False)
elif event.keysym == "space":
self.tir()
def move(self,right):
if right and self.player_x<1140:
self.player_x += 10
self.canvas.coords(self.player,self.player_x,780,self.player_x+60,802)
elif not right and self.player_x>0:
self.player_x -= 10
self.canvas.coords(self.player,self.player_x,780,self.player_x+60,802)
self.right_hold = False
def tir(self):
self.canvas.create_rectangle(self.player_x+27,760,self.player_x+33,775,fill="white")
game = SpaceInvaders()
game.alien.run()
game.window.bind("<KeyPress>",game.vaisseau.event_handler)
game.window.mainloop()
If you want actions to be performed simultaneously you need to make them happen in the backround. Your code, as sits right now, is handling every action in serial fashion. For example if you hit the "left" button, your code will be moving your character left before anything else can happen. To deal with this you can use something like the threaded or multiprocess module. Both of them can help you thread your actions so they can be happening in the same time. Their docs are pretty specific on how you can use them.
threaded module : https://pypi.org/project/threaded/
multiprocess module : https://pypi.org/project/multiprocess/
Related
I'm trying to create a class that allows you to have a MenuBar that can be personalized in windows. It's nothing special since I'm new to coding. I'm trying different ways to change the background of the label behind the cascade buttons but I can only change the buttons ones.
from tkinter import *
TESTING = True
root = Tk()
root.attributes('-fullscreen',True)
class menubar():
"""You can use this to create a menu bar
There are some functions:
ButtonCascadeAdd: Lets you create a cascade button to which you will be able to add more buttons with
AddButtons function
AddButtons: just adds buttons to the cascades. you can chose cascades changing the input number from
0 to 9
Soon will be added color switch and others althought you can already do those things by yourself
since every btns[] object is a tkinter.Menubutton()"""
global labelframe
global contx
global btns
contx = 0
labelframe = LabelFrame(root)
btns = [Menubutton(labelframe) for i in range(10)]
def place(self, width = 300,height = 30,rely=0):
labelframe.place(width=width, height=height, rely=rely)
def ButtonCascadeAdd(self,text = "btns[",tearoff = 0):
"""Adds a cascade button.
You can pass in text to change the button title default is the name of the button in the code
so maybe it will prove useful to leave it like that for develop purpose.
You cant add a command to that from here.
If you need a button with a command and not a cascade look aat the FunctionButtonAdd function"""
global contx
if text == "btns[":
text = text+str(contx)+"]"
if contx == 10:
print("Max number of buttons exceeded the program will be stopped")
exit()
b = btns[contx]
b.config(text = text)
b.pack(side=LEFT)
b.menu = Menu(b, tearoff=tearoff)
b["menu"] = b.menu
labelframe.update()
contx += 1
print(contx)
def maincolor(self,bg = "light green",fg = "black",activebg = "green",activefg = "dark blue"):
global contx
for x in range(contx):
btns[x].config(bg=bg, fg=fg, activebackground=activebg, activeforeground=activefg)
btns[x].menu.config(bg = "black",fg = "white")
labelframe.config(bg=bg)
def doNothing():
print("Doing Nothing ^^")
if TESTING == True:
m = menubar()
m.place(width = 1980,height = 20)
m.ButtonCascadeAdd()
m.ButtonCascadeAdd()
btns[0].menu.add_command(label="test")
#print(dir(btns[0].menu))
m.maincolor()
root.mainloop()
There's no help on google, I've asked some people as well but none of them seem to know how to answer my question.
I'm programming a GUI for a project, and it contains an RSS-Feed ticker.
It scrolls through the news and when it updates (every 3 seconds for obvious debug reasons) it speeds up a bit.
This means, if I run the program, after two hours the ticker is scrolling at a non-human readable speed.
The main code wasn't written by me, I modified it and added the update function.
main():
import tkinter as tk
from Press import RSSTicker
def displayRSSticker(win):
# Place RSSTicker portlet
tickerHandle = RSSTicker(win, bg='black', fg='white', highlightthickness=0, font=("avenir", 30))
tickerHandle.pack(side='bottom', fill='x', anchor='se')
def main():
# Set the screen definition, root window initialization
root = tk.Tk()
root.configure(background='black')
width, height = root.winfo_screenwidth(), root.winfo_screenheight()
root.geometry("%dx%d+0+0" % (width, height))
label = tk.Label(root, text="Monitor Dashboard", bg='black', fg='red')
label.pack(side='bottom', fill='x', anchor='se')
# Display portlet
displayRSSticker(root)
# Loop the GUI manager
root.mainloop(0)
###############################
# MAIN SCRIPT BODY PART #
###############################
if __name__ == "__main__":
main()
RSSTicker class:
import feedparser
import tkinter as tk
class RSSTicker(tk.Text):
# Class constructor
def __init__(self, parent, **params):
super().__init__(parent, height=1, wrap="none", state='disabled', **params)
self.newsFeed = feedparser.parse('http://www.repubblica.it/rss/homepage/rss2.0.xml')
self.update()
# Class methods
def update(self):
self.headlineIndex = 0
self.text = ''
self.pos = 0
self.after_idle(self.updateHeadline)
self.after_idle(self.scroll)
self.after(4000, self.update)
def updateHeadline(self):
try:
self.text += ' ' + self.newsFeed['entries'][self.headlineIndex]['title']
except IndexError:
self.headlineIndex = 0
self.text = self.feed['entries'][self.headlineIndex]['title']
self.headlineIndex += 1
self.after(5000, self.updateHeadline)
def scroll(self):
self.config(state='normal')
if self.pos < len(self.text):
self.insert('end', self.text[self.pos])
self.pos += 1
self.see('end')
self.config(state='disabled')
self.after(180, self.scroll)
I thought the problem lied in the self.pos variable, printing it out resulted in it counting up, resetting to 1 and counting up faster.. But it doesn't seem to be problem causing the acceleration of the ticker.
From what I've understood tho, the problem must be in the scroll method.
If someone understand how to keep the original scroll speed when updated, thank you.
I think you can use a couple tracking variables to make sure that update only starts the loop once and then next time update is called it will just run scroll without starting a new loop. At the same time if scroll is not called by update then it will continue looping as needed.
Change your RSSTicker class to the following:
class RSSTicker(tk.Text):
# Class constructor
def __init__(self, parent, **params):
self.scroll_started = False # Tracker for first update.
super().__init__(parent, height=1, wrap="none", state='disabled', **params)
self.newsFeed = feedparser.parse('http://www.repubblica.it/rss/homepage/rss2.0.xml')
self.update()
def update(self):
self.headlineIndex = 0
self.text = ''
self.pos = 0
self.after_idle(self.updateHeadline)
self.after_idle(lambda: self.scroll('update'))
self.after(4000, self.update)
def updateHeadline(self):
try:
self.text += ' ' + self.newsFeed['entries'][self.headlineIndex]['title']
except IndexError:
self.headlineIndex = 0
self.text = self.feed['entries'][self.headlineIndex]['title']
self.headlineIndex += 1
self.after(5000, self.updateHeadline)
def scroll(self, from_after_or_update = 'after'):
self.config(state='normal')
if self.pos < len(self.text):
self.insert('end', self.text[self.pos])
self.pos += 1
self.see('end')
self.config(state='disabled')
# Check if the loop started by after.
if from_after_or_update != 'update':
self.scroll_started = True
self.after(180, self.scroll)
# If not started by after check to see if it is the 1st time loop is started by "update".
elif self.scroll_started is False and from_after_or_update == 'update':
self.scroll_started = True
self.after(180, self.scroll)
# If neither of the above conditions then do not start loop to prevent multiple loops.
else:
print("ran scroll method without adding new after loop!")
I'm trying to make this really simple program, all it does is store the current x/y pos of the mouse on the canvas and then use them to draw a line when you click for the second time. I've already bound it and I'm not getting any errors, it seems like it's not even being activated. Any help is greatly appreciated
from tkinter import *
main = Tk()
c = Canvas(main, width=600, height=600)
c.pack()
#For colored lines
presses = 0
def click(event):
if presses == 0:
initX = int(c.canvasx(event.x))
initY = int(c.canvasy(event.y))
presses == 1
elif presses == 1:
c.create_line(initX, initY,
int(c.canvasx(event.x)),
int(c.canvasy(event.y)))
presses == 0
c.bind("<Button-1>", click)
mainloop()
How does something like this work for you?
from tkinter import *
main = Tk()
c = Canvas(main, width=600, height=600)
c.pack()
line = []
def click(event):
global line
X = int(c.canvasx(event.x))
Y = int(c.canvasy(event.y))
line.append((X,Y))
if len(line) > 1:
startX,startY = line[-2]
c.create_line(startX, startY, X, Y)
c.bind("<Button-1>", click)
mainloop()
I've changed around your code a bit to store a list of the X,Y coordinates that have been clicked on. If more than 1 point on the screen has been clicked, it will draw a line between the current point clicked on and the last point clicked on.
Reason your code wasn't working was that initX and initY are forgotten in between calls on the the click function. Adding them to a list solves this.
I have a problem in Python.
I'm using Tkinter and have four bind events, that listen to key presses on my form.
My problem is, that these don't run asynchronously. So, for example I can press one button, and the events are recognized. But when I press and hold two keys at the same time, just one event gets fired.
Is there an alternative way to do this?
self.f.bind("w", self.player1Up)
self.f.bind("s", self.player1Down)
self.f.bind("o", self.player2Up)
self.f.bind("l", self.player2Down)
Unfortunately, you are somewhat at the mercy of the underlying auto-repeat mechanism of your system. For example, on the mac I'm using at the moment if I press and hold "w" I'll get a stream of press and release events. While pressed, if I press "o" I get a stream of presses and releases for "o" but no more events for "w".
You will need to set up a mini state machine, and bind to both key press and key release events. This will let you track which keys are pressed and which are not. Then, each time you draw a frame you can query the machine to see which keys are pressed and act accordingly.
Here's a quick hack I threw together. I've only tested it on my mac, and only with python 2.5. I've made no real attempt at being "pythonic" or efficient. The code merely serves to illustrate the technique. With this code you can simultaneously press either "w" or "s" and "o" or "l" to move two paddles up and down.
'''Example that demonstrates keeping track of multiple key events'''
from Tkinter import *
class Playfield:
def __init__(self):
# this dict keeps track of keys that have been pressed but not
# released
self.pressed = {}
self._create_ui()
def start(self):
self._animate()
self.root.mainloop()
def _create_ui(self):
self.root = Tk()
self.p1label = Label(text="press w, s to move player 1 up, down",
anchor="w")
self.p2label = Label(text="press o, l to move player 2 up, down",
anchor="w")
self.canvas = Canvas(width=440, height=440)
self.canvas.config(scrollregion=(-20, -20, 420, 420))
self.p1label.pack(side="top", fill="x")
self.p2label.pack(side="top", fill="x")
self.canvas.pack(side="top", fill="both", expand="true")
self.p1 = Paddle(self.canvas, tag="p1", color="red", x=0, y=0)
self.p2 = Paddle(self.canvas, tag="p2", color="blue", x=400, y=0)
self._set_bindings()
def _animate(self):
if self.pressed["w"]: self.p1.move_up()
if self.pressed["s"]: self.p1.move_down()
if self.pressed["o"]: self.p2.move_up()
if self.pressed["l"]: self.p2.move_down()
self.p1.redraw()
self.p2.redraw()
self.root.after(10, self._animate)
def _set_bindings(self):
for char in ["w","s","o", "l"]:
self.root.bind("<KeyPress-%s>" % char, self._pressed)
self.root.bind("<KeyRelease-%s>" % char, self._released)
self.pressed[char] = False
def _pressed(self, event):
self.pressed[event.char] = True
def _released(self, event):
self.pressed[event.char] = False
class Paddle():
def __init__(self, canvas, tag, color="red", x=0, y=0):
self.canvas = canvas
self.tag = tag
self.x = x
self.y = y
self.color = color
self.redraw()
def move_up(self):
self.y = max(self.y -2, 0)
def move_down(self):
self.y = min(self.y + 2, 400)
def redraw(self):
x0 = self.x - 10
x1 = self.x + 10
y0 = self.y - 20
y1 = self.y + 20
self.canvas.delete(self.tag)
self.canvas.create_rectangle(x0,y0,x1,y1,tags=self.tag, fill=self.color)
if __name__ == "__main__":
p = Playfield()
p.start()
You could bind to "<Key>" and then check event.char and perform the action you want based on the value of that? Granted, I have no idea if this works when multiple keys are pressed at the same time, it may still run in to the exact same problem. I haven't used Tk in ages.
"<Key> The user pressed any key. The key is provided in the char member of the event object passed to the callback (this is an empty string for special keys)."
Alrighty I'm trying to implement a GUI for a game of Connect Four by using TKinter. Now I have the grid and everything set up what I'm having trouble with is getting the chip to show up on the board.
Here is my output:
What I'm trying to do is make it so when I click one of the bottom column buttons a chip appears (and since this is connect four it should go from bottom to top)
Here is my code:
from Tkinter import *
from connectfour import *
from minimax import *
from player import *
import tkMessageBox
class ConnectFourGUI:
def DrawGrid(self):
for i in range(0,self.cols+1):
self.c.create_line((i+1)*self.mag,self.mag,\
(i+1)*self.mag,(self.rows+1)*self.mag)
for i in range(0,self.rows+1):
self.c.create_line(self.mag,(i+1)*self.mag,\
self.mag*(1+self.cols),(i+1)*self.mag)
def __init__(self,wdw):
wdw.title("Connect Four")
self.mag = 60
self.rows = 6
self.cols = 7
self.c = Canvas(wdw,\
width=self.mag*self.cols+2*self.mag,\
height = self.mag*self.rows+2*self.mag,\
bg='white')
self.c.grid(row=1,column=1,columnspan=2)
rlabel=Label(root, text="Player1:")
rlabel.grid(row=0,column=0)
self.player1_type=StringVar(root)
options= ["Human", "Random", "Minimax"]
self.player1_type.set(options[2])
self.rowbox=OptionMenu(root, self.player1_type, *options)
self.rowbox.grid(row=0, column=1)
rlabel2=Label(root, text="Player2:")
rlabel2.grid(row=0,column=2)
self.player2_type=StringVar(root)
self.player2_type.set(options[0])
self.rowbox=OptionMenu(root, self.player2_type, *options)
self.rowbox.grid(row=0, column=3)
begin=Button(root, text="Start", command=self.game_start)
begin.grid(row=0, column=4)
self.c.grid(row=1, column=0, columnspan=7)
play_col=[]
for i in range(self.cols):
play_col.append(Button(root, text= "Col %d" %i, command=lambda col= i: self.human_play(col)))
play_col[i].grid(row=10,column="%d"%i)
## self.DrawCircle(1,1,1)
## self.DrawCircle(2,2,1)
## self.DrawCircle(5,3,2)
self.DrawGrid()
self.brd = ConnectFour()
def game_start(self):
self.board=ConnectFour()
print self.player1_type.get()
print self.player2_type.get()
if self.player1_type.get()=="Random":
self.player1 = RandomPlayer(playernum=1)
if self.player2_type.get()== "Random" or self.player2_type.get() == "Minimax":
tkMessageBox.showinfo("Bad Choice", "You Have to choose At least 1 Human Player")
else:
self.player
elif self.player1_type.get()=="Minimax":
self.player1=MinimaxPlayer(playernum=2, ply_depth=4, utility=SimpleUtility(5,1))
if self.player2_type.get()== "Random" or self.player2_type.get() == "Minimax":
tkMessageBox.showinfo("Bad Choice", "You Have to choose At least 1 Human Player")
elif self.player1_type.get()=="Human":
self.player1=Human(playernum=1)
if self.player2_type.get()=="Human":
self.player2=Human(playernum=2)
elif self.player2_type.get()=="Random":
self.player2=RandomPlayer(playernum=2)
elif self.player2_type.get()=="Minimax":
self.player2=MinimaxPlayer(playernum=2, ply_depth=4, utility=SimpleUitlity(5,1))
#self.currentplayer==1
#self.draw()
def human_play(self, col):
if self.player1_type.get()=="Human" and self.player2_type.get() =="Human":
while True:
self.DrawCircle(row,col,1)
if self.brd.is_game_over() is None:
self.DrawCircle(row,col,2)
if self.brd.is_game_over() is None:
pass
else:
print "Player 2 wins!"
break
else:
print "Player 1 wins!"
break
def DrawCircle(self,row,col,player_num):
if player_num == 1:
fill_color = 'red'
elif player_num == 2:
fill_color = 'black'
#(startx, starty, endx, endy)
self.c.create_oval(col*self.mag,row*self.mag,(col+1)*self.mag,(row+1)*self.mag,fill=fill_color)
root=Tk()
ConnectFourGUI(root)
root.mainloop()
I know I'm supposed to call the DrawCircle function in the Human Play function, I'm just unsure as to how I'm supposed to set it all up. any advice as to how I could go about this would be greatly appreciated!
Your code is depending on a few packages I don't have, so I can't be more specific, but the way I'd go about doing something like this is to track the X and Y coords of the chip works out the the row and column that it'd go into in the widget.
You'll need to create a location object, a tuple maybe, and then a way to translate the location object into a drawable location. Then it's just a matter of incrementing either X or Y and detecting if there's a chip below it.