I have designed somewhat of an AI to play 2048. I would like to display the game state after each move the AI makes. To do so, I have created a GUI using Tkinter.
First time using Tkinter, and as the title suggests, it seems like my 'updateDisplay' method blocks the mainloop() from being called. Any help would be appreciated.
The GUI will display if I remove the call to self.after(1000, self.updateDisplay(ai, game)). However, it will then obviously not update
class GameGrid(Frame):
def __init__(self,ai, game):
Frame.__init__(self)
self.game = game
self.ai = ai
self.grid()
self.master.title('2048')
#self.gamelogic = gamelogic
self.grid_cells = []
self.init_grid()
self.update_grid_cells()
self.after(1000, self.updateDisplay(ai, game))
self.mainloop()
def updateDisplay(self, ai, game):
game.move(ai.nextMove(4))
print "hello"
for i in range(GRID_LEN):
for j in range(GRID_LEN):
new_number = int(game.state[i][j])
if new_number == 0:
self.grid_cells[i][j].configure(text="", bg=BACKGROUND_COLOR_CELL_EMPTY)
else:
self.grid_cells[i][j].configure(text=str(new_number), bg=BACKGROUND_COLOR_DICT[new_number], fg=CELL_COLOR_DICT[new_number])
if game.over:
if game.won:
print 'You Won!'
else:
print 'Game Over :( Score:', game.score
return 0
else:
print "test"
self.after(10000, self.updateDisplay(ai, game))
if __name__ == "__main__":
game = Game()
ai = AlphaBetaRecursive(game)
gui = GameGrid(ai, game)
When you do self.after(1000, self.updateDisplay(ai, game)), you're calling self.updateDisplay immediately rather than passing the function as an argument to after. You need to get rid of the inner parentheses! According to the docs, after does take extra *args, but it doesn't actually say what is done with them (maybe they're passed to the callback? I'm not sure). Since ai and game are already attributes of self, you don't actually need to pass them as arguments at all. Just use:
self.after(1000, self.updateDisplay)
And change the definition of updateDisplay to:
def updateDisplay(self):
# use self.ai and self.game rather than ai and game in the implementation of the function
...
Related
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.
Im quite new to python and cant solve a problem after searching a lot. Maybe you guys can help me out. I want to add multiple commands to 1 butten. So if you have 100hp, you will lose every time 10hp and if you are on 0hp you get 50hp back, but dont know how to do that. I have read you need to use 1 function and inside both functions but when im doing that i get an error.
player_1_lose_10_Button = Button(self, text = "10 HP", command=self.myfunction)
player_1_lose_10_Button.place(x=180,y=140)
def myfunction(self):
lose10(self)
check(self)
def check(self):
global player1health
if player1health <= 0:
player1health +=50
player_1_lose_10_Button = Button(self, text = "50 HP", command=self.check)
print('You died, you get 50hp back')
def lose10(self):
global player1health
player1health-=10
print(f'You lost 10 HP, current HP: {player1health}')
You can combine these into one function just fine:
def lose_but_check(self):
self.lose10()
self.check()
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.
I recently created a small game using tkinter (python version 3.6.1) and froze it using cx_Freeze. The game has four buttons: an undo button, a restart button, a "find legal moves" button, and a "find best move button". The "find best move" button uses a shelve database to find the best move for the first three turns and a recursive function that traverses the move tree on the fly for the fourth turn and up. My code disables the buttons when they should not be used.
I made sure to include the necessary DLLs in the setup script and I was able to run the executable without errors. However, three of the buttons are disabled until the fourth turn (when the recursive function begins to be used) and the application is extremely buggy in many other ways. However, it works perfectly when I run the unfrozen version.
I honestly don't know what code snippets I would need to provide to you guys, as this issue has me utterly at a loss. The only clue I have is that the pyc files in the build differ in size from the unfrozen app. I know this is rather vague, but I do not know what specifics would be useful to give. Any help, if possible, would be greatly appreciated.
"Find best move" method:
def _find_best_move(self):
"""Finds best move possible for current game."""
if len(self.game.moves) <= 3:
with shelve.open("paths") as db:
best_paths = db[str(self.game.moves)]
best_path = choice(best_paths)
else:
self.path_finder(self.game)
best_path = self.path_finder.best_path
best_move = best_path[len(self.game.moves)]
best_move = (__class__._add_offset(best_move[0]), best_move[1])
return best_move
Updates Button State:
def update_gui(self):
"""Updates GUI to reflect current game conditions."""
legal_moves = self.game.find_legal_moves()
if self.game.moves:
self.undo_btn["state"] = "!disabled"
self.restart_btn["state"] = "!disabled"
self.best_move_btn["state"] = "!disabled"
else:
self.undo_btn["state"] = "disabled"
self.restart_btn["state"] = "disabled"
if legal_moves:
self.show_moves_btn["state"] = "!disabled"
else:
self.show_moves_btn["state"] = "disabled"
if legal_moves and self.game.moves:
self.best_move_btn["state"] = "!disabled"
else:
self.best_move_btn["state"] = "disabled"
My __init__ file:
initpath = os.path.dirname(__file__)
os.chdir(os.path.join(initpath, "data"))
PathFinder class (traverses move tree on the fly):
class PathFinder:
"""Provides methods to find move paths that meet various criteria.
Designed to be called after the player makes a move.
"""
_game = None
best_path = None
best_score = None
def __call__(self, game):
"""Call self as function."""
if not game:
self._game = DummyGame()
elif not isinstance(game, DummyGame):
self._game = DummyGame(game)
else:
self._game = game
moves = self._game.moves
self.possible_paths = dict.fromkeys(range(1,9))
root = Node(moves[-1])
self._find_paths(root)
self._find_paths.cache_clear()
found_scores = [score for score in self.possible_paths.keys() if
self.possible_paths[score]]
self.best_score = min(found_scores)
self.best_path = self.possible_paths[self.best_score]
#lru_cache(None)
def _find_paths(self, node):
"""Finds possible paths and records them in 'possible_paths'."""
legal_moves = self._game.find_legal_moves()
if not legal_moves:
score = self._game.peg_count
if not self.possible_paths[score]:
self.possible_paths[score] = self._game.moves.copy()
else:
children = []
for peg in legal_moves:
for move in legal_moves[peg]:
children.append(Node((peg, move)))
for child in children:
self._game.move(*child.data)
self._find_paths(child)
try:
self._game.undo()
except IndexError:
pass
Peg class:
class Peg(RawPen):
"""A specialized 'RawPen' that represents a peg."""
def __init__(self, start_point, graphics):
"""Initialize self. See help(type(self)) for accurate signature."""
self.graphics = graphics
self.possible_moves = []
super().__init__(self.graphics.canvas, "circle", _CFG["undobuffersize"],
True)
self.pen(pendown=False, speed=0, outline=2, fillcolor="red",
pencolor="black", stretchfactor=(1.25,1.25))
self.start_point = start_point
self.goto(start_point)
self.ondrag(self._remove)
self.onrelease(self._place)
def _remove(self, x, y):
"""Removes peg from hole if it has moves."""
if self.possible_moves:
self.goto(x,y)
def _place(self, x, y):
"""Places peg in peg hole if legal."""
if self.possible_moves:
target_holes = [tuple(map(add, self.start_point, move)) for move in
self.possible_moves]
distances = [self.distance(hole) for hole in target_holes]
hole_distances = dict(zip(distances, target_holes))
nearest_hole = hole_distances[min(hole_distances)]
if self.distance(nearest_hole) <= 0.45:
self.goto(nearest_hole)
peg = self.graphics._subtract_offset(self.start_point)
move = tuple(map(sub, self.pos(), self.start_point))
move = tuple(map(int, move))
self.graphics.game.move(peg, move)
self.start_point = self.pos()
else:
self.goto(self.start_point)
The frozen application is going to have a different value for __value__ then the unfrozen application. You will have to deal with that accordingly! This is a common issue that bites a lot of people. Anything that assumes that the module is found in the file system is going to stop working properly when frozen. The other gotcha is dynamic importing of modules.
The documentation covers this and other topics that will hopefully help you out!
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