Imagine I have two buttons, if I press button1 I earn a point and if I press button2 I lose a point. When I reach 10 points, the game stops. The way I have been doing it right now calls the GameApp().run() every time I click a button to "re-launch" the interface, however if I do it too many times I get a RecursionError. I tried doing it with a while loop but the interface never launches.
Note: this is a simplified version of my program, it has to be done this way (essentially the number of button changes everytime a button is pressed, so the layout updates accordingly, that's why I call GameApp().run() over and over).
n = 0
class Game(GridLayout):
def __init__(self):
super(InterfacesGame, self).__init__()
global n
self.rows = 2
if n < 10:
button1 = Button(text="1")
button2 = Button(text="2")
button1.bind(on_press = self.add)
button2.bind(on_press = self.substract)
self.add_widget(button1)
self.add_widget(button2)
def add(self):
global n
n += 1
GameApp().run()
def substract(self):
global n
n -= 1
GameApp().run()
class GameApp(App):
def build(self):
return Game()
if __name__ == '__main__':
GameApp().run()
So eventually if I do too many clicks without reaching n = 10, I naturally get a
RecursionError: maximum recursion depth exceeded calling class app
error.
I tried doing this:
n = 0
class Game(GridLayout):
def __init__(self):
super(InterfacesGame, self).__init__()
global n
self.rows = 2
while n < 10:
button1 = Button(text="1")
button2 = Button(text="2")
button1.bind(on_press = self.add)
button2.bind(on_press = self.substract)
self.add_widget(button1)
self.add_widget(button2)
def add(self):
global n
n += 1
def substract(self):
global n
n -= 1
class GameApp(App):
def build(self):
return Game()
if __name__ == '__main__':
GameApp().run()
But the GUI doesn't run (it keeps loading and loading).
Based on your question and your following comment :
When 10 points are reached, the layout is remplaced by a single button with the text "You won!", and when clicking on it, the game starts all over again (with n = 0).
I implemented a complete example using Screens and ScreenManager.
from kivy.app import App
from kivy.lang import Builder
kv = ("""
ScreenManager:
id: sm
Screen:
name: "First Screen"
BoxLayout:
orientation: "vertical"
Button:
text: "1"
on_release: app.add_point()
Button:
text: "2"
on_release: app.subtract_point()
Screen:
name: "Second Screen"
Label:
text: "You Won"
font_size: "40sp"
Button:
text: "Play again"
size_hint: 0.75, None
height: "50dp"
pos_hint: {"center_x" : 0.5, "center_y" : 0.25}
on_release: sm.current = "First Screen"
""")
class GameApp(App):
count = 0
def build(self):
self.root = Builder.load_string(kv)
return self.root
def add_point(self):
if self.count < 10:
self.count += 1
else:
self.root.current = "Second Screen"
self.count = 0 # Reset counter.
def subtract_point(self):
if 0 < self.count: # Assuming only positive values.
self.count -= 1
GameApp().run()
Related
My app currently has 10 "players" with a name, offensive rating, and defensive rating. I want to give the user the opportunity to change the names of the players before running my game simulation code, but it appears I can only temporarily change the name inside of the current screen. Here's my current python code for when the user inputs the name from the gui:
class SecondWindow(Screen):
def pg1_name_val(self, widget):
pg1.name = widget.text
If I code in a print(pg1.name) right below that, it will print the correct name, but when the user hits "play" to shift to a results screen, the names go back to the default names.
How do I make pg1.name from my SecondWindow(Screen) become the global pg1.name? Is there a simple line of code I can use to return pg1.name as the global variable?
EDIT ----
So I've came a long way with my app this week, but now that I want to have a separate "results" screen, I've realized that I lost all of my data from the previous screen. It looks like all of the changes I make to my "players" are going back to defaults once the screen changes. I'll paste some code below for reference. Basically, my question now is this: Is there a way to keep the objects from one screen and return them back as the global instance of the object. Not just the local instance of the object.
.py -
class Player():
def __init__(self,name,off,deff):
self.name = name
self.off = off
self.deff = deff
self.stats = 0
class Team():
def init(self,pg, sg, sf, pf, c):
self.pg = pg
self.sg = sg
self.sf = sf
self.pf = pf
self.c = c
self.score = 0
self.results = None
self.to = 0
On the screen where I'm creating my team, I have a button to run the simulation. This button runs the sim correctly, records all of the game results in a string, prints all of the results to the console, attributes, and score correctly. The button also changes the screen to where I want to display a scroll view of the results string, but the results are empty.
I'd really appreciate any help on this, as I'm at a roadblock. Thanks!
It would kinda look the same. Depending on how exactly you wanna keep the 10 different players.
class Player():
def __init__(self, name, off, deff):
self.name = name
self.off = off
self.deff = deff
self.stats = 0
player = [Player('', 0, 0)] * 10
class SecondWindow(Screen):
def __init__(self, **kwargs):
super(SecondWindow, self).__init__(**kwargs)
self.player = player
def name_change(self):
self.player[0].name = self.ids.name.text
player[0].name = self.player[0].name
def next_screen(self):
self.manager.current = 'result'
class ResultWindow(Screen):
def __init__(self, **kwargs):
super(ResultWindow, self).__init__(**kwargs)
def on_enter(self, *args):
print(player[0].name)
class Manager(ScreenManager):
pass
class Player(App):
def build(self):
sm = ScreenManager()
sm.add_widget(SecondWindow(name='second'))
sm.add_widget(ResultWindow(name='result'))
return sm
if __name__ == '__main__':
Player().run()
You would have to work with OOP, here I created a list of you player object for all ten players it is an easy way to keep the code short.
You might like to keep them as 10 different objects the good part of a list it is easy to iterate through in a for loop and then get all the data for a stats board on a screen and also easy to expand later if you need 20 players or more.
I didn't change anything in the .kv file.
.kv
<SecondWindow>:
TextInput:
id: name
hint_text: 'Player name'
size: 260, 32
pos: (root.width / 2) - (self.width / 2), 248
multiline: False
on_text_validate: root.name_change()
Button:
id: create_btn
text: 'Next Screen'
size: 260, 32
pos: (root.width / 2) - (self.width / 2), 206
color: utils.get_color_from_hex('#ffffff')
background_color: utils.get_color_from_hex('#016bf8')
on_press: root.next_screen()
<ResultWindow>:
Label:
id: name
The on_text_validate only works with multiline false and when enter is pressed if you don't want to press enter user on_text instead.
--Edit--
I did some research this morning and thought maybe to update this a bit with my findings.
You could instead of doing it the import to all screens do it in the screenmanager and just call it from the screen like this:
pyhon.py
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
class Player:
def __init__(self, name, off, deff):
self.name = name
self.off = off
self.deff = deff
self.stats = 0
class SecondWindow(Screen):
def __init__(self, **kwargs):
super(SecondWindow, self).__init__(**kwargs)
def name_change(self):
self.manager.player.name = self.ids.name.text
def next_screen(self):
self.manager.current = 'result'
class ResultWindow(Screen):
def __init__(self, **kwargs):
super(ResultWindow, self).__init__(**kwargs)
def on_enter(self):
self.ids.name.text = self.manager.player.name
class Manager(ScreenManager):
player = Player('', 0, 0)
def __init__(self, **kwargs):
super(Manager, self).__init__(**kwargs)
class PlayerApp(App):
def build(self):
return Builder.load_file('test.kv')
if __name__ == '__main__':
PlayerApp().run()
the kv file would look something like this:
.kv
#:import utils kivy.utils
Manager:
SecondWindow:
name: 'second'
ResultWindow:
name: 'result'
<SecondWindow>:
TextInput:
id: name
hint_text: 'Player name'
size: 260, 32
size_hint: None, None
pos: (root.width / 2) - (self.width / 2), 248
multiline: False
on_text_validate: root.name_change()
Button:
id: create_btn
text: 'Next Screen'
size_hint: None, None
size: 260, 32
pos: 0, 0
color: utils.get_color_from_hex('#ffffff')
background_color: utils.get_color_from_hex('#016bf8')
on_press: root.next_screen()
<ResultWindow>:
Label:
id: name
hope it helps
I am trying to create a simple countdown timer, i want to be able to restart it on the Button click, right now it won't start other, i just can not get it right. What am i missing here?
the KV
<RootWidget>:
#:import randint random.randint
orientation: "vertical"
CountDownLbl:
id: anim_label
text: "{0:.3f}".format(float(self.startCount - self.angle / 360))
font_size: 30
canvas:
Color:
rgb: 0,1,0
Line:
circle:self.center_x, self.center_y, 90, 0, self.angle % 360
width: 30
Button:
size_hint_y: 0.1
text: "Start"
on_press: anim_label.start()
and the code
COUNT=1
class RootWidget(FloatLayout):
pass
class CountDownLbl(Label):
startCount = COUNT
angle = NumericProperty(0)
def __init__(self, **kwargs):
super(CountDownLbl, self).__init__(**kwargs)
def start(self):
self.startCount = COUNT
self.anim = Animation(angle=360 * self.startCount, duration=self.startCount)
self.in_progress = True
self.anim.start(self)
class TestApp(App):
def build(self):
return RootWidget()
if __name__ == '__main__':
TestApp().run()
The problem is that the first animation animates the angle property to 360, and further animations try to animate the angle property to 360 again, which results in no actual animation. The fix is to increment your COUNT after each animation, like this:
def start(self):
global COUNT
self.startCount = COUNT
self.anim = Animation(angle=360 * self.startCount, duration=self.startCount)
self.in_progress = True
self.anim.start(self)
COUNT += 1
Answering my own quesstion, Kudos to John.
Thank's John Anderson. This is the final version. Most important, i am updating text in a function instead of doing it in the KV, and second the increment has to be by TIMER value and not by 1. Latest example can be found here https://github.com/vmindru/amb_pit_timer/blob/main/simple_timer.py
code
TIMER_DURATION = 5
class CountDownLbl(Label):
angle = NumericProperty(0)
timer_duration = NumericProperty(0)
def __init__(self, **kwargs):
super(CountDownLbl, self).__init__(**kwargs)
self.anim_duration = self.timer_duration
self.in_progress = False
def start(self):
if not self.in_progress:
self.anim = Animation(angle=360 * self.anim_duration, duration=self.timer_duration)
self.in_progress = True
self.anim.bind(on_complete=self.finish, on_progress=self.update_timer)
self.anim.start(self)
self.anim_duration += TIMER_DURATION
def finish(self, animation, widget):
widget.text = "FINISHED"
self.in_progress = False
def update_timer(self, animation, widget, progression):
text = ((self.timer_duration * 60000)*(1-progression))/60000
widget.text = "{0:.3f}".format(float(text))
kv
CountDownLbl:
id: anim_label
timer_duration: 5
font_size: 30
text: "{}.000".format(self.timer_duration)
canvas:
Color:
rgb: 0,1,0
Line:
circle:self.center_x, self.center_y, 90, 0, self.angle % 360
width: 30
I'm trying to build a Kivy application that has 2 screens which are re-used over and over again with different text.
So I go from a FirstScreen with a Label that says "First1" to a SecondScreen with a Label that says "Second1", and then back to the FirstScreen but this time with the Label "First2", then SecondScreen and "Second2", and so on and so forth.
The code for this is pretty straightforward, but there seems to be a problem in updating the Label text without a designated update button. For some reason, my Python code manages to update the text, but it isn't updated in my .kv file. So for instance, my print statements will tell me that the Label text is "First2", but Kivy displays "First1" for me. I've illustrated this in the Screenshot below:
By adding a Button that updates the text on press, everything is updated, synced up and works, but I'd really like it to work without the extra user input. Does anybody know how I can go about this? I've scoured the docs and stackoverflow questions left and right but can't seem to find the answer to my seemingly simple problem.
Here's the code:
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import StringProperty, ObjectProperty
from kivy.lang import Builder
S_ID = 1 # global screen ID. I'm using this to detect what text to use.
class FirstScreen(Screen):
text = StringProperty("")
lbl = ObjectProperty(None)
def __init__(self, **kwargs):
super(FirstScreen, self).__init__(**kwargs)
global S_ID
print("\nS_ID is ", S_ID)
self.update()
def update(self):
print("FIRST - UPDATE")
if S_ID == 1:
print("FIRST1")
self.text = "FIRST1"
elif S_ID == 2:
print("FIRST2")
self.text = "FIRST2"
print("self.lbl.text", self.lbl.text)
else:
print("FIRST ELSE")
self.text = "FIRST ELSE"
def pressed(self):
sm.current = "second"
class SecondScreen(Screen):
text = StringProperty("")
def __init__(self, **kwargs):
super(SecondScreen, self).__init__(**kwargs)
self.update()
def update(self):
print("SECOND - UPDATE")
if S_ID == 1:
print("SECOND1")
self.text = "SECOND1"
elif S_ID == 2:
print("SECOND2")
self.text = "SECOND2"
else:
print("SECOND ELSE")
self.text = "SECOND ELSE"
def pressed(self):
global S_ID
S_ID += 1
FirstScreen.update(FirstScreen())
sm.current = "first"
sm = ScreenManager()
kv = Builder.load_file("test.kv")
sm.add_widget(FirstScreen(name='first'))
sm.add_widget(SecondScreen(name='second'))
sm.current = "first"
class MyApp(App):
def build(self):
return sm
if __name__ == '__main__':
MyApp().run()
and here's the .kv file:
<FirstScreen>:
name: "first"
lbl: lbl:
GridLayout:
cols:2
Label:
id: lbl
text: root.text
Button:
text: "next"
on_press: root.pressed()
<SecondScreen>:
name: "second"
GridLayout:
cols:2
Label:
text: root.text
Button:
text: "next"
on_press:
root.pressed()
The problem is your statement:
FirstScreen.update(FirstScreen())
This statement is creating a new instance of FirstScreen and updating that instance. Unfortunately, that instance is not the one shown in your GUI. You can correct that by replacing the above statement with:
first_screen = self.manager.get_screen('first')
first_screen.update()
This code gets the instance of FirstScreen from the ScreenManager and calls update() on that instance.
How do you update a progress bar that is displayed in Kivy. In the following example I get AttributeError: 'super' object has no attribute '__getattr__'. The issue is in the following line
self.ids.progress.value = value
I can see why since the progressBar widget is in <LoadingPopup> and not <MainScreen> but after trying several different things I can't reference the progressBar widget in <LoadingPopup> from the do_update method.
Thanks in advance
import threading
from functools import partial
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
from kivy.properties import StringProperty
from kivy.uix.spinner import Spinner
from kivy.app import App
from kivy.clock import Clock
from kivy.uix.popup import Popup
Builder.load_string('''
<LoadingPopup>:
title: "Popup"
size_hint: None, None
size: 400, 400
auto_dismiss: False
BoxLayout:
orientation: "vertical"
ProgressBar:
id: progress
size_hint: (1.0, 0.06)
<MainScreen>:
BoxLayout:
orientation: 'vertical'
Spinner:
id: first
text: ' First Number'
values: ['1','2','3','4','5','6','7','8','9']
Spinner:
id: second
text: ' Second Number'
values: ['1','2','3','4','5','6','7','8','9']
Label:
id: result
text: ' Result'
color: 0,0,0,1
Button:
id: res
on_press: root.doit_in_thread(first.text,second.text)
text: 'Multiply'
''')
class LoadingPopup(Popup):
def __init__(self, obj, **kwargs):
super(LoadingPopup, self).__init__(**kwargs)
class MainScreen(FloatLayout):
changet = StringProperty()
def __init__(self, **kwargs):
super(MainScreen, self).__init__(**kwargs)
def doit_in_thread(self, fir, sec):
popup = LoadingPopup(self)
popup.open()
threading.Thread(target=partial(self.onMul, fir, sec, popup)).start()
def do_update(self, value, text, *args):
self.ids.progress.value = value
self.ids.result.text = text
def onMul(self,fir,sec, popup):
a = (int(fir)*int(sec))
print(a)
b = 0
old_value = 0
endRange = 1000000
for i in range(endRange):
progress = int(((i+1)*100)/endRange)
if progress != old_value and progress % 5 == 0:
text = str(b*(int(fir)*int(sec)))
Clock.schedule_once(partial(self.do_update, progress, text))
old_value = progress
b+=1
popup.dismiss()
class TestApp(App):
def build(self):
return MainScreen()
if __name__ == "__main__":
TestApp().run()
One way to do it, is to keep a reference of the popup so you can address it later:
def __init__(self, **kwargs):
super(MainScreen, self).__init__(**kwargs)
self.popup = LoadingPopup(self)
def doit_in_thread(self, fir, sec):
self.popup.open()
threading.Thread(target=partial(self.onMul, fir, sec, self.popup)).start()
def do_update(self, value, text, *args):
self.popup.ids.progress.value = value
self.ids.result.text = text
Python Code
Replace all references of popup with self.popup so that you can access it in the methods inside class MainScreen.
Replace self.ids.progress.value with self.popup.ids.progress.value
Since a = (int(fir)*int(sec), replace str(b*(int(fir)*int(sec))) with str(b * a)
Don't need to pass popup to onMul method, access it using self.popup
Snippet
class MainScreen(FloatLayout):
changet = StringProperty()
def doit_in_thread(self, fir, sec):
self.popup = LoadingPopup(self)
self.popup.open()
threading.Thread(target=partial(self.onMul, fir, sec)).start()
def do_update(self, value, text, *args):
self.popup.ids.progress.value = value
self.ids.result.text = text
def onMul(self, fir, sec):
a = (int(fir)*int(sec))
print(a)
b = 0
old_value = 0
endRange = 1000000
for i in range(endRange):
progress = int(((i+1)*100)/endRange)
if progress != old_value and progress % 5 == 0:
text = str(b*(int(fir)*int(sec)))
# text = str(b * a)
print("\ttext=", text)
Clock.schedule_once(partial(self.do_update, progress, text))
old_value = progress
b+=1
self.popup.dismiss()
kv file
The result is not visible because the default background colour of Label widget is black, and the text colour for result is also set to black, color: 0,0,0,1. Therefore, remove color: 0,0,0,1.
Snippet
Label:
id: result
text: ' Result'
Output
When I click on the buttons in the left part, the label "Top 10 Plays of 2015" should be changed with the text of the button that I've clicked. I can see the variable changing text but the label is not changing text.
this is my work: when I click the buttons on left side the title in the middle should change:
python
class MainApp(Screen, EventDispatcher):
vid = StringProperty("Videos/Top 10 Plays of 2015.mp4")
title = StringProperty("Top 10 Plays of 2015")
def __init__(self,*args,**kwargs):
super(MainApp,self).__init__(*args, **kwargs)
pass
class OtherVideos(BoxLayout, EventDispatcher):
root = MainApp
def __init__(self, *args, **kwargs):
super(OtherVideos,self).__init__(*args, **kwargs)
self.loadVideos()
def loadVideos(self):
con = MongoClient()
db = con.nba
vids = db.videos.find()
vidnum = 1
for filename in vids:
myid = "vid" + str(vidnum)
getfilename = filename['filename']
button = Button(id=myid,
text=getfilename,
color=[0,0.7,1],
bold=1)
button.bind(on_release=lambda x:(self.change_Title(getfilename), self.change_Vid(getfilename)))
self.add_widget(button)
vidnum += 1
def change_Title(self, title):
self.root.title = title
def change_Vid(self, myfilename):
con = MongoClient()
db = con.nba
vids = db.videos.find()
for filename in vids:
file = os.path.basename(filename['video_path'])
if myfilename == filename['filename']:
self.root.vid = filename['video_path']
break
pass
kivy
BoxLayout:
orientation: 'vertical'
Label:
id: lblTitle
text: root.title
size_hint_y: None
height: 40
font_size: 25
bold: 1
canvas.before:
Color:
rgba: 1, 0, 0, 0.7
Rectangle:
pos: self.pos
size: self.size
VideoPlayer:
id: activeVid
source: root.vid
state: 'play'
when I'm printing root.title it's changing its text but the label is not changing which it should because it's getting its text from that variable.
But when I'm putting the change_title() method in __init__ of OtherVideos like this, the label is changing:
class OtherVideos(BoxLayout, EventDispatcher):
root = MainApp
def __init__(self, *args, **kwargs):
super(OtherVideos,self).__init__(*args, **kwargs)
self.change_Title("my title")
This is already taking me days of work, anyone can help?
There are several problems with your code:
Every widget inherits from EventDispatcher, so inheriting from it the second time is pointless.
This root = MainApp is not a reference to the MainApp (object), but to its class.
You don't need to specify a StringProperty to achieve your goal.
The easiest way to do this is to add a reference of the label you want to change in the function change_Title:
def change_Title(self, title):
# self.root.title = title
self.ids.my_label_to_change.text = title