Timed Threaded function as an attribute in a class - python

So I am making a text based adventure game. I am working on the engine right now and I am stuck after long hours searching for a solution for this problem.
I have a class called use_action. One of the arguments for that class is a name of a function. I would like to be able to create this action and have a possible custom function incase the item that calls this use_action does something specific.
The custom function I am working with right now is where the player is hurt and is losing 5 HP every so many seconds.
This should start when he uses a specific item and then stops when he uses the medicine that will link to the stop function. The problem I have is that the function gets called immediately. Even though I am trying to call it at the end of a long if else statement. And then when i get to where i am trying to call it it doesn't call.
I am not posting the whole class as it along with its functions are about 150 lines of code.
class use_action(object):
def __init__(self, function = None):
self.function = function
pizza_act = use_action(function = mechanics.tmr.start())
#This is located at the end of an if else statement after the player types use . . .
if self.function != None:
self.function
else:
pass
From Mechanics:
thread_list = []
class TimerClass(threading.Thread):
def __init__(self, function, time):
threading.Thread.__init__(self)
self.event = threading.Event()
self.function = function
self.time = time
thread_list.append(self)
def run(self):
while not self.event.is_set():
self.event.wait( self.time )
self.function()
def stop(self):
self.event.set()
def blank_current_readline():
# Next line said to be reasonably portable for various Unixes
(rows,cols) = struct.unpack('hh', fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ,'1234'))
text_len = len(readline.get_line_buffer())+2
# ANSI escape sequences (All VT100 except ESC[0G)
sys.stdout.write('\x1b[2K') # Clear current line
sys.stdout.write('\x1b[1A\x1b[2K'*(text_len/cols)) # Move cursor up and clear line
sys.stdout.write('\x1b[0G') # Move to start of line
def pizza_poisoned_action():
# threading.Timer(10, pizza_poisoned_action).start()
blank_current_readline()
print "You lost 5 hp."
initialization.gamer.hp -= 5
sys.stdout.write('> ' + readline.get_line_buffer())
sys.stdout.flush() # Needed or text doesn't show until a key is pressed
tmr = TimerClass(pizza_poisoned_action, 5)
Sorry about the length, I tried to only post the relevant stuff for this. If you think i should post some other piece of code that may be relevant let me know!

If you want to pass a function, don't call it. Or else, you'll be passing the return value.
pizza_act = use_action(function = mechanics.test()) #Wrong!
pizza_act = use_action(function = mechanics.test) #Right

Related

How do I call the function next() without type it again?

The idea is to be able to call the next number every time it is called data, but as you know I cant type next() everytime in the code, is there a way to achieve that? thanks for your help.
class Sample():
def __init__(self, begin, end):
self.begin = begin
self.end = end
#self.counter = 0
def number(self):
for i in range(self.begin, self.end):
#self.counter +=1
yield i
instance = Sample(begin=525, end=535)
data = instance.number()
print(next(data))
print(next(data))
print(next(data))
I cant use loops this time becuse I want to get one number one by one everytime it called data, example call data: 526. calls data 527. calls data 527 like this. not 526,527,528,529...... thanks
You can hide the call to next() in a property getter.
class Sample():
def __init__(self, begin, end):
self.begin = begin
self.end = end
self._sequence = self.number()
def number(self):
for i in range(self.begin, self.end):
yield i
#property
def counter(self):
return next(self._sequence)
instance = Sample(begin=525, end=535)
print(instance.counter) # prints 525
print(instance.counter) # prints 526
However, if you use it this way, you'll need your own handler for the StopIteration exception that's raised when you reach the end of the iterator.

Game with turns, need a way to receive a fuction's return in another turn

I'm making a game where I can gather resources or build when I send Workers, but I can't think of a way to receive those resources or finish building depending on the turn and the time(turns) it takes to finish those actions.
I've already made a Worker class, and it has a method to gather and it gives a random value that I save in a Player class. Also, my Game class keeps track of the turn I and the computers are.
class Game:
def __init__(self, player = None):
self.player = player
self.turn = 1
class Player:
def __init__(self):
self.workers = [Worker(), Worker(), Worker()]
self.resourcers = 0
class Worker:
def __init__(self):
self.hp = 100
def gather(self):
return randint(MIN_CANTIDAD_RECURSO, MAX_CANTIDAD_RECURSO)
player = Player()
game = Game()
game.player = player
for worker in player.workers:
player.resources += worker.gather
game.turn +=1
Gathering should give the result the next turn and build should give it depending on the building.
In a general sense, you store the values you need in the relevant object and pass them as parameters to whatever method requires those values. For example, you would need to store the turn duration of an action in the return value of that action, e.g in class Worker
def gather(self):
# Some code that determines gather_value and duration...
return [gather_value, duration]
and then the resource usage would look something like
def use_gather(gather, turn): # Pass in (return value from gather, game.turn)
# Use parameters...
With such a vague question, it's hard to say anything more.

How to recursively call different methods for levels of a video game?

I attempting to design a simple choice based video game. Essentially what I want is a recursive loop that will continue to call the new levels based off the results of the previous. For example, in level 1 based off choice made it will either trigger level 2 or 3. This is the code I have so far:
class Levels:
def __init__(self, Next = 1):
self.Next = Next
def Call(self):
VarLevel = "Level" + "{}".format(self.Next)
return ("{}".format(VarLevel))
This is the super class, it returns the VarLevel which equals Level1 to trigger the subclass Level1. This is the code I have for the levels, I've excluded the context of the game because I don't think it is necessary.
class Level1(Levels):
def __init__(self,):
# this just includes information about the level to show the user
# (objective etc.)
def Action(self):
# this will include the content of the level. based off the choices
# made I want it to return to the super with VarLevel as Level2 or
# Level3 and then to trigger the next level running and repeat
# throughout the program to run the game. For the purpose of testing
# the program the only content of Level1 is setting the return to 2 so
# that Level2 is called. I'm having trouble actually getting it to
# recognize my return and to trigger the next level. This is the
# actual method I want to do the calling with
class LevelCall():
def __init__(self, Levels = Levels):
self.Levels = Levels
def Calling(self):
result = (Levels, "{}".format(Levels()))()
it gives me the error TypeError: 'tuple' object is not callable. I have been doing a lot of different attempts to get it to work so I'm not certain that this is even the real problem with the code. Also of note I am decent in Java and am now transitioning to Python (this is my first attempt in Python other then basic tests to read/write etc.) Any help is greatly appreciated to help figure out how to format the game and I apologize in advance because I know this is a long question, I've never posted here before so if you need more info or clarification please feel free to ask.
Edit:
This is the full error message
Traceback (most recent call last):
line 54, in <module>
Tester.Calling()
line 50, in Calling
result = (Levels, "{}".format(Levels()))()
TypeError: 'tuple' object is not callable
Another Edit:
I think I am getting closer. I made the following changes
class LevelCall():
def __init__(self, Levels = Levels):
self.Levels = Levels
def Calling(self):
Hold = Levels()
result = (getattr(Levels, "{}".format(Hold.Call()))())
It now gives the following error message.
Traceback (most recent call last):
line 55, in <module>
Tester.Calling()
line 51, in Calling
result = (getattr(Levels, "{}".format(Hold.Call()))())
AttributeError: type object 'Levels' has no attribute 'Level1'
If I understand correctly it is now attempting to do what I want but isn't finding the class "Level1". Again all help is much appreciated.
Edit______________________
I would like to thank all who replied and attempted to help, I am truly grateful for the support. With the clarification you were able to help me with as well as a fresh start today and mapping it out in java first to make the transition easier I was able to solve my problem. Once again thank you all very much I will add the solution I found beneath this edit.
global Stop
class Level1 :
def __init__(self):
self
def Action(self):
print ("1")
global Stop
Stop = input("Would you like to advance to the next level?")
if (Stop == "yes"):
# Lev = Level2()
# return Lev.Action()
return Level2
if (Stop == "no"):
return "stop"
class Level2:
def __init__(self):
self
def Action(self):
print("2")
global Stop
Stop = input("Would you like to advance to the next level?")
if (Stop == "yes"):
# Lev = Level3()
# return Lev.Action()
return Level3
if (Stop == "no"):
return "stop"
class Level3 :
def __init__(self):
self
def Action(self):
print ("3")
global Stop
Stop = input ("Next level??")
if (Stop == "yes"):
# Lev = Level4()
# return Lev.Action()
return Level4
if (Stop == "no"):
return "stop"
class Level4:
def __init__(self):
self
def Action(self):
print ("Complete")
return "Done"
def Runner (Level):
if (Level == "Done"):
print ("Bye")
else :
if (Level != "stop"):
Lev = Level()
Next = Lev.Action()
Runner(Next)
if (Level == "stop"):
print ("you chose to stop")
Runner(Level1)
(a,b,c) is tuple syntax. (a,b,c)() is a tuple being called like a function. That is what the error is referring to.
If we break the offending code down you can tell. What does it look like when you replace the call to format with an arg placeholder:
(Levels, "{}".format(Levels()))() becomes...
(Levels, arg)() # this is now clearly a tuple and you're treating it like a function.
Not really sure how fixing that will help you with your levels problem tho.
If you want to call a function, do so like: func(args).
If you want to define a tuple, do so like: (a, b, ..., z).
But don't call a tuple like a function.

How can I use an 'update' function to 'open' and update if the object is not yet open?

My example is a progress bar
In its simplest form a progress bar is
bar = ProgressBar.Open()
for item in list:
bar.Update(count, len(list))
I would instead like my calling code to be
for item in list:
bar.Update(count, len(list))
I want my Update() function to Open() a bar for the caller if one is not open. The caller doesn't need any other access to the bar than to update it so there's no value in having the meter` handle.
How can I retain state to tell if the Update had been previously called?
I could create a global variable and keep track that way, but I have a gut sense there's a Pythonista way of doing it.
Trying again, but in a way that has no application to stumble on.
The base question is:
I have a function that will be called multiple times.
I want to do something different the first time it is called.
How can a function in Python do that?
In C, that of course would be a...
static variable
I'm just now kinda figuring it out as I type, sorry.
========================
I'm sure all these edits are not how stackoverflow is supposed to work. I'm sorry for not getting it right yet, but am very appreciative of the replies.
Despite it sounding like I'm breaking all the rules of good practices, it's when looked at from the CALLER'S point of view that I had hoped to make an impact.
What if the only thing you needed to do to add a progress meter, even for debugging, to your program was make a call to a progress meter update in the location you want to show progress?
That's the underlying motivation. Slide in 1-line, get something cool for the trouble.
This progress meter was added to my otherwise boring file de-duplicator by adding just the single call:
msg = f'Deduplicating {idx} of {total_files} files\n' f'{dup_count} Dupes found\n' f'{small_count} Too small'
not_cancelled = sGUI.ProgressBar('De-dupe', msg, idx, total_files)
To avoid using global variables, you can use decorator. Here's a simple example:
def open():
print 'open'
def update():
print 'update'
def call_once(func1, *args1, **kwargs1):
def decorator(func2):
called = [False]
def wrapper(*args2 ,**kwargs2):
if not called[0]:
func1(*args1, **kwargs1)
called[0] = True
return func2(*args2, **kwargs2)
return wrapper
return decorator
#call_once(open)
def my_update():
update()
for i in xrange(5):
my_update()
which give the result:
open
update
update
update
update
update
For more information about decorator, please visit: https://wiki.python.org/moin/PythonDecorators
For what you want, you can use a class:
class ProgressBar:
def __init__(self):
self._opened = False
def Open(self):
print("Open")
def Update(self):
if self._opened:
print("Update!")
else:
self.Open()
print("set flag")
self._opened = True
print("Update")
In action:
In [32]: bar = ProgressBar()
In [33]: bar.Update()
Open
set flag
Update
In [34]: bar.Update()
Update!
Note: I copied your casing so as to make it more clear to you, however, the official Python style would be like this:
class ProgressBar:
def __init__(self):
self._opened = False
def open(self):
pass # open stuff
def update(self):
if self._opened:
pass # update stuff
else:
self.open()
self._opened = True
Using snake_case for everything except the ClassName.
OK, I found a solution using 'globals'. I thought that a nested function was the way to do it... then I mixed the two.
By 'globals' I meant variables declared outside the scope of a function. I want to be able to import my module without the import creating anything.
Here's the code that shows how to do this with globals
def my_update(amount):
global flag
if 'flag' in globals():
print('I have been here')
else:
print('I have not been here')
flag = True
return
for i in range(10):
print(f'Calling number {i}')
result = my_update(1)
It does the job for the goals I had set out, but I'm SURE there are better, safer ways that are more elegant as well.
I posted this question on a Python forum and got back the best answer so far using a function attribute. It's brilliant and it works.
Here is code that demonstrates this construct... it should go in everyone's 'Favorite Python Constructs' notebook in my opinion.
def func():
if not hasattr(func, 'static_variable'):
func.static_variable = 0
func.static_variable += 1
return func.static_variable
def main():
for i in range(10):
print('func = {}'.format(func()))
if __name__ == '__main__':
main()
The output is
func = 1
func = 2
func = 3
func = 4
func = 5
func = 6
func = 7
func = 8
func = 9
func = 10

Pyglet, exit after all sounds played

AVbin is installed. Both .wav and .mp3 files work.
import pyglet
music = pyglet.media.load('A.mp3')
music.play()
player = pyglet.media.Player()
player.queue( pyglet.media.load('B.mp3'))
player.queue( pyglet.media.load('C.wav'))
player.play()
pyglet.app.run()
pyglet.app.exit()
I want to create a program that plays A, then plays the queue with B and then C, and finally quits after all three sounds play.
I tried the code above but according to this post, "this is [solely] because app.run() is a never-ending loop."
How can I modify my code minimally so that the program quits after the three sounds are played?
Bonus, but how can I modify my code minimally so that the program can play two (or more) sound files, E.mp3 and F.mp3, at once?
Thanks!
Because what you're asking is not as simple as you'd might think it is.
I've put together a code example with as much comments as I possibly could fit in without making the example to hard to read.
Below the code, I'll try to explain a few key functions as detailed as possible.
import pyglet
from pyglet.gl import *
from collections import OrderedDict
key = pyglet.window.key
class main(pyglet.window.Window):
def __init__ (self, width=800, height=600, fps=False, *args, **kwargs):
super(main, self).__init__(width, height, *args, **kwargs)
self.keys = OrderedDict() # This just keeps track of which keys we're holding down. In case we want to do repeated input.
self.alive = 1 # And as long as this is True, we'll keep on rendering.
## Add more songs to the list, either here, via input() from the console or on_key_ress() function below.
self.songs = ['A.wav', 'B.wav', 'C.wav']
self.song_pool = None
self.player = pyglet.media.Player()
for song in self.songs:
media = pyglet.media.load(song)
if self.song_pool is None:
## == if the Song Pool hasn't been setup,
## we'll set one up. Because we need to know the audio_format()
## we can't really set it up in advance (consists more information than just 'mp3' or 'wav')
self.song_pool = pyglet.media.SourceGroup(media.audio_format, None)
## == Queue the media into the song pool.
self.song_pool.queue(pyglet.media.load(song))
## == And then, queue the song_pool into the player.
## We do this because SourceGroup (song_pool) as a function called
## .has_next() which we'll require later on.
self.player.queue(self.song_pool)
## == Normally, you would do self.player.eos_action = self.function()
## But for whatever windows reasons, this doesn't work for me in testing.
## So below is a manual workaround that works about as good.
self.current_track = pyglet.text.Label('', x=width/2, y=height/2+50, anchor_x='center', anchor_y='center')
self.current_time = pyglet.text.Label('', x=width/2, y=height/2-50, anchor_x='center', anchor_y='center')
def on_draw(self):
self.render()
def on_close(self):
self.alive = 0
def on_key_release(self, symbol, modifiers):
try:
del self.keys[symbol]
except:
pass
def on_key_press(self, symbol, modifiers):
if symbol == key.ESCAPE: # [ESC]
self.alive = 0
elif symbol == key.SPACE:
if self.player.playing:
self.player.pause()
else:
self.player.play()
elif symbol == key.RIGHT:
self.player.seek(self.player.time + 15)
## == You could check the user input here,
## and add more songs via the keyboard here.
## For as long as self.song_pool has tracks,
## this player will continue to play.
self.keys[symbol] = True
def end_of_tracks(self, *args, **kwargs):
self.alive=0
def render(self):
## Clear the screen
self.clear()
## == You could show some video, image or text here while the music plays.
## I'll drop in a example where the current Track Name and time are playing.
## == Grab the media_info (if any, otherwise this returns None)
media_info = self.player.source.info
if not media_info:
## == if there were no meta-data, we'll show the file-name instead:
media_info = self.player.source._file.name
else:
## == But if we got meta data, we'll show "Artist - Track Title"
media_info = media_info.author + ' - ' + media_info.title
self.current_track.text = media_info
self.current_track.draw()
## == This part exists of two things,
## 1. Grab the Current Time Stamp and the Song Duration.
## Check if the song_pool() is at it's end, and if the track Cur>=Max -> We'll quit.
## * (This is the manual workaround)
cur_t, end_t = int(self.player.time), int(self.player.source._get_duration())
if self.song_pool.has_next() is False and cur_t >= end_t:
self.alive=False
## 2. Show the current time and maximum time in seconds to the user.
self.current_time.text = str(cur_t)+'/'+str(end_t) + 'seconds'
self.current_time.draw()
## This "renders" the graphics:
self.flip()
def run(self):
while self.alive == 1:
self.render()
# -----------> This is key <----------
# This is what replaces pyglet.app.run()
# but is required for the GUI to not freeze
#
event = self.dispatch_events()
x = main()
x.run()
Now, normally you'd decorate your way trough this with a bunch of functions.
But I like to subclass and OOP my way through any graphical libraries, because it gets messy quite fast otherwise.
So instead of pyglet.app.run(), I've got a custom made run() function.
All this does is mimic the pyglet.app.run(), for the most part. Enough to get going at least.
Because player.eos_* events appears to be broken.
I've added a manual example of how you could check if the songs are done playing or not.
This is a combination of self.song_pool pyglet.media.SourceGroup, self.player.time pyglet.media.player.time and self.player.source._get_duration() which returns the track duration.
The SourceGroup gives us a has_next() function which tells us if we're at the end of the queued songs. The other two variables tells us if we've reached the end of the current track. This is all we need to determinate if we want to exit or not.
Now, I haven't technically added a way to add more songs. Because again, that would also be harder than you think. Unless you opt in for if symbol == key.LCTRL: self.song_pool.queue(pyglet.media.load(input('Song: '))) for instance. But again, all you would need to do, is add more songs to the self.song_pool queue, and there you go.
I hope this answers your question. Even the bonus one.

Categories