Countdown with Python and Gtk, problems with timeout_add - python

I'm trying to make a workout app, so I have to count each push-up or display a countdown for side plank. To do that I tried to use GObject.timeout_add but it appears it doesn't work like I thought it would.
In the current situation, all exercises of a session are run simultaneously, instead of one at a time, in the proper order.
I am certainly missing something, and through my web searching, I still haven't found it.
Here is my code :
#!/usr/bin/python
"""
Work out app to keep track of your progression through the session
"""
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GObject
from Programms import *
from time import sleep
def time_hours(t):
return t // 3600
def time_minutes(t):
return (t % 3600) // 60
def time_seconds(t):
return int((t % 3600) % 60)
def time_format(t):
hours = double_digit(time_hours(t))
minutes = double_digit(time_minutes(t))
seconds = double_digit(time_seconds(t))
return "{}:{}:{}".format(hours, minutes, seconds)
def double_digit(t):
if t == 0:
return "00"
elif t < 10:
return "0{}".format(t)
return t
class StartingLine:
"""
Gtk
"""
def __init__(self):
# main window
self.window = Gtk.Window()
self.window.set_title("Work out !")
self.window.set_size_request(200, 200)
self.window.connect('destroy', lambda x: Gtk.main_quit())
# start button
self.button = Gtk.Button("Start")
self.button.connect('clicked', self.start_work_out)
self.window.add(self.button)
self.window.show_all()
def start_work_out(self, widget):
self.window.hide()
work = Two
duration = work.duration
for exo in work.exercises:
Instructor(exo, duration)
class Instructor:
"""
Gtk
"""
def __init__(self, exo, duration):
# main window
self.window = Gtk.Window()
self.window.set_title("Work out !")
self.window.set_size_request(200, 200)
self.window.connect("destroy", lambda x: Gtk.main_quit())
# timer
self.current_time = 0
self.counter = 0
self.timer_label = Gtk.Label(time_format(self.counter))
# exercise
self.current_exercise = Gtk.Label(exo.name)
# overall progression
self.bar = Gtk.ProgressBar.new()
# hierarchy
grid = Gtk.Grid()
grid.attach(self.timer_label, 1, 0, 1, 1)
grid.attach(self.current_exercise, 1, 1, 1, 1)
grid.attach(self.bar, 1, 2, 1, 1)
self.window.add(grid)
# display
self.window.show_all()
if exo.type == ExoType.Reps:
print('exercise : ', exo.name)
self.counter = 0
self.timer_label.set_label(str(self.counter))
rep_id = GObject.timeout_add(1000*exo.in_between, self.display_rep, exo, duration)
print("rep ID : ", rep_id)
elif exo.type == ExoType.Timer:
self.counter = exo.number
self.timer_label.set_label(time_format(self.counter))
time_id = GObject.timeout_add(1000*exo.in_between, self.display_timer, exo, duration)
print("time ID : ", time_id)
def display_rep(self, exo, duration):
print("current time : ", self.current_time)
print("current exercise : ", exo.name)
print("rep : ", self.counter)
self.counter += 1
self.current_time += exo.in_between
self.timer_label.set_label(str(self.counter)) # update counter
self.current_exercise.set_label(exo.name) # update exercise name
self.bar.set_fraction(self.current_time/duration) # update progression bar
return self.counter < exo.number
def display_timer(self, exo, duration):
print("current time : ", self.current_time)
print("current exercise : ", exo.name)
print("timer : ", self.counter)
self.counter -= 1
self.current_time += exo.in_between
self.timer_label.set_label(time_format(self.counter)) # update counter
self.current_exercise.set_label(exo.name) # update name
self.bar.set_fraction(self.current_time/duration) # update progression bar
return self.counter > 0
if __name__ == "__main__":
StartingLine()
Gtk.main()
In this code I have used two types, which are :
#!/usr/bin/python
"""
define class exercise and class session
"""
class Exercise:
def __init__(self, name, typo, unilateral, number, preparation=0, in_between=1):
"""
:param name: name of the exercise
:param typo: either timer or reps
:param number: either a time in seconds or a a number of reps
:param preparation: time allocated to prepare the exercise, to put yourself in position
:param in_between: time between reps. 1s by default.
"""
self.name = name
self.type = typo
self.unilateral = unilateral
self.number = number
self.prep = preparation
self.in_between = in_between
class ExoType(enumerate):
Reps = 0
Timer = 1
class Session:
def __init__(self, name, exercises):
self.name = name
self.exercises = exercises
self.duration = time_length(exercises)
def time_length(exercises):
t = 0
for exo in exercises:
if exo.type == ExoType.Timer:
t += exo.number
elif exo.type == ExoType.Reps:
t += exo.number*exo.in_between
else:
print("Error : Session duration")
return t
First time I ask a question here, please tell me if I'm doing wrong.

rep_id = GObject.timeout_add(1000*exo.in_between, self.display_rep, exo, duration)
When you timeout_add you tell Mainloop to call function every n seconds.
for exo in work.exercises:
Instructor(exo, duration)
Here you add every exercise to mainloop, which leads to simultaneous run, because you create mainloop once.
There are several solutions and in my opinion none of them includes main_iteration_do.
Reorganise things so that 1 exercise runs it's own mainloop.
Emit a signal when one exercise is finished and in it's handler start another exercise.

Related

pyqt5 update() function not working for QGroupBox

Ok so I think the best way for me to show what I'm trying to accomplish is visually, so I created a reproducible example of what I'm trying to do and strangely enough... it worked perfectly fine. Here it is
import sys
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
big_ar = ["1","2","3"]
class MyMainWindow(QWidget):
def __init__(self):
super(MyMainWindow,self).__init__()
self.setGeometry(300,300,300,300)
self.initUI()
def initUI(self):
self.lay = QVBoxLayout()
self.window_opener = WindowOpenerButton("open window",self)
self.group_box = UpdatingGroupBox(self)
self.lay.addWidget(self.window_opener)
self.lay.addWidget(self.group_box)
self.setLayout(self.lay)
def cust_update(self):
self.group_box.addButton()
self.group_box.update()
class WindowOpenerButton(QPushButton):
def __init__(self,txt,par):
super(WindowOpenerButton,self).__init__(txt,par)
self.clicked.connect(self.openWin)
def openWin(self):
self.smallWindow = MySmallWindow(psp=self.parentWidget())
self.smallWindow.show()
class MySmallWindow(QWidget):
def __init__(self,psp=None,parent=None):
super(MySmallWindow,self).__init__(parent)
self.setGeometry(100,100,100,100)
self.pseudo_parent = psp
self.populate()
def populate(self):
self.layout = QVBoxLayout()
self.line_edit = QLineEdit(self)
self.done = DoneButton("done",self,self.line_edit)
self.layout.addWidget(self.line_edit)
self.layout.addWidget(self.done)
self.setLayout(self.layout)
def closeEvent(self,event):
if self.pseudo_parent != None:
self.pseudo_parent.cust_update()
class UpdatingGroupBox(QGroupBox):
def __init__(self,par):
super(UpdatingGroupBox,self).__init__(par)
self.layout = QVBoxLayout()
self.addPreexisting()
self.setLayout(self.layout)
def addPreexisting(self):
global big_ar
for i in range(len(big_ar)):
self.layout.addWidget(QPushButton(big_ar[i],self))
def addButton(self):
global big_ar
self.layout.addWidget(QPushButton(big_ar[ len(big_ar) - 1],self))
class DoneButton(QPushButton):
def __init__(self,txt,par,src):
super(DoneButton,self).__init__(txt,par)
self.txt_source = src
self.clicked.connect(self.addToArr)
def addToArr(self):
global big_ar
big_ar.append(self.txt_source.text())
self.parentWidget().close()
print(big_ar)
def main():
app = QApplication(sys.argv)
app.setStyle("Fusion")
x = MyMainWindow()
x.show()
app.exec()
if __name__ == "__main__":
main()
Now this example outlines what I'm trying to do quite accurately and simply where it gets the string from the line edit on the smaller window and makes it into a pushbutton in the larger window's qgroupbox which is then updated immediately afterwards. The only difference between this example and my code is that instead of using a global array to add onto the qgroupbox, I use an instance variable, take a look.
def cust_update(self):
mem_f = open(self.file,"r")
raw_file_ml = mem_f.read().split("{")[1]
file_ml = raw_file_ml.split(";")
self.list.append(Member(file_ml[len(file_ml) - 2]))
mem_f.close()
self.mb_gb.addButton()
self.mb_gb.update()
This is the cust_update method for my actual program (you can disregard the first couple lines) and mb_gb is a MemberList which is this:
class MemberList(comps.MyButtonList): #list of memButtons derived from members.txt
def __init__(self,ttl,labl_ls,par,**kwargs):
super(MemberList,self).__init__(ttl,labl_ls,par,**kwargs)
self.layout = QVBoxLayout()
self.addPrexisting()
self.setLayout(self.layout)
def addPrexisting(self):
for i in range(len(self.list)):
self.layout.addWidget(QPushButton(self.list[i].fullName()))
def addButton(self):
nb = QPushButton(self.list[len(self.list) - 1])
self.layout.addWidget(nb)
the self.list represents a list of members which, as you can see in the cust_update method, is updated. The MemberList then takes the last element of the list and makes it into a button which is then added to its layout.
It's very similar to the first example's ugb but for some reason when the cust_event is called, even though it adds the button to the layout, it isn't being drawn (discovered through print debugging) and I have already tried using repaint()
EDIT:
I now see the confusion with what a Member and MyButtonList is, the reason I didn't explain them in the first place is because I didn't find it necessary as those parts of the code are working (my bad!). Here's the member class :
class Member:
def __init__(self,info_str):
info_ar = info_str.split(",")
self.first_name = info_ar[0]
self.last_name = info_ar[1]
self.idnum = info_ar[2]
self.grade_when_joined = info_ar[3]
self.gender = info_ar[4]
self.position = info_ar[5]
self.date_joined = info_ar[6][0:len(info_ar[6])]
def fullName(self):
return self.first_name.capitalize() + " " + self.last_name.capitalize()
def __str__(self):
return (self.fullName() + ": " + self.id() + " " + self.gender + " " + self.getPosition() + " " + str(self.date_joined))
def id(self):
lln = self.last_name.lower()
return lln + str(self.idnum)
def dateJoined(self):
y = int(self.date_joined[0:4])
m = int(self.date_joined[5:7])
d = int(self.date_joined[8:10])
return dt.date(y,m,d)
def sex(self):
s = "Other"
if self.gender == 'm':
s = "Male"
elif self.gender == 'f':
s = "Female"
return s
def getPosition(self):
pos = "Member"
if self.position == "o":
pos = "Officer"
elif self.position == "v":
pos = "Vice President"
elif self.position == "p":
pos = "President"
return pos
def idInt(self):
return int(self.idnum)
Prior to the closeEvent, a line is added to a txt file based on user input which follows the format
First_Name,Last_Name,ID#,Grade,Gender,Position,Date_When_Joined
when the close event happens, the cust_update method reads the file, splits it into individual members by ";" and takes the last one (the member that was just added) and makes it into a qpushbutton with its first name.
The MyButtonList class is nearly identical to the MemberList class, the only difference is in what goes on the buttons so it looks like this:
class MyButtonList(MyGroupBox):
def __init__(self,ttl,lab_ls,par,**kwargs):
super(MyButtonList,self).__init__(title=ttl,pare=par,**kwargs)
self.setUpdatesEnabled(True)
self.label_ls = lab_ls
self.list = self.parentWidget().list
self.layout = QVBoxLayout()
self.addPrexisting()
self.setLayout(self.layout)
def addPrexisting(self):
for i in range(len(self.list)):
self.layout.addWidget(QPushButton(str(i),self))
def addButton(self):
self.layout.addWidget(QPushButton("added",self))
Please let me know if there's anything I missed or I didn't communicate properly, sorry I didn't put everything here in the first place!

Implement datetime module in Python OOP

I am trying to implement datetime module to my Clock OOP, but the data is not being updated with the current hour, minute and second. Please have a look at the code. I can't see what I am doing wrong.
import datetime
import time
# Python class to emulate CLock
class ClockNow2:
def __init__(self):
self.theHour = 0
self.theMinute = 0
self.theSecond = 0
self.theLocale ="Unknow"
self.AM = True
#setters and getters (transformers and accessors)
#one setter and getter for each attribute
def setHour(self):
self.theHour =datetime.timezone
def getHour(self):
return self.theHour
def setMinute(self):
self.theMinute = now.minute
def getMinute(self):
return self.theMinute
def setSecond(self):
self.theSecond = now.second
def getSecond(self):
return self.theSecond
def setLocale(self,newval):
self.theLocale = newval
def getLocale(self):
return self.theLocale
def toString(self):
clockstring = "The time in " +self.theLocale + " is " +str(self.theHour) \
+ ":" +str(self.theMinute) + ":" + str(self.theSecond)
return clockstring

Initializing Class instance within a class

the entire counter list of methods in side counter class do not work. I want setcap to set of cap, and check cap to see if each counter have reached their limit as hr min sec are what a clock should know i would like to initialize them inside the clock.
import time
class counter():
count = 0
cap = 0
def _init_(self):pass
def reset(self):
self.count = 0
def increment(self):
self.count += 1
def setcap(self,x):
print x
self.cap = x
def checkcap(self):
if self.cap > self.count:
return False
else:
return True
class clock():
_hr = counter()
_min = counter()
_sec = counter()
def _init_(self):
self._hr.setcap(23)
self._min.setcap(59)
self._sec.setcap(59)
def manualreset(self):
self._hr.reset()
self._min.reset()
self_sec.reset()
def tick(self):
if self._sec.checkcap():
self._sec.reset()
self._min.increment()
if self._min.checkcap():
self._min.reset()
self._hr.increment()
if self._hr.checkcap():
self._hr.reset()
else:
self._sec.increment()
newClock = clock()
raw_input("Press enter to start clock")
while newClock._hr != 24:
newClock.tick()
print str(newClock._hr.count).zfill(2) + str(newClock._min.count).zfill(2) + str(newClock._sec.count).zfill(2)
One of the problems in your code is that your init functions are init.
Try using
def __init__(self):
pass
This should solve one of your problems

Python command line print on the same lines

There are many questions relating to printing on the same line but there aren't any for printing multiple lines on the same line within the terminal.
For example:
ogeno#OH-ogeno-MBP:~|⇒ python my_script.py
Process 1: 5%
Process 2: 14%
Process 3: 55%
I want the progress of these processes to update on the same line rather than printing over and over again. I have looked at other questions that say to use the return carriage character \r and sys.stdout.flush() but it doesn't seem to change the caret to go up a line, just to the end of the current line.
EDIT: My question is different because it's to do with printing MULTIPLE lines on the same lines in the terminal. It's easy if it's just one line.
This can easily be done by using backspace. Following is the sample code that will print the percentage on the same line.
import time
print "Work in progress(0%%)", # Python 2 print without newline
for work_done in range(10):
print "\b\b\b\b\b%2d%%)" % work_done, # Backspace then overwrite
time.sleep(1)
One approach is to use the ANSI escape-code "\033[F" for going to the beginning of the previous line. The following worked well in all my terminals, just writing to the next two lines from the current terminal position:
import time
import sys
progress_1 = 'Process 1: {}%'
progress_2 = 'Process 2: {}%'
print
print
for i in range(100):
sys.stdout.write('\033[F')
sys.stdout.write('\033[F')
print(progress_1.format(i))
print(progress_2.format(i))
time.sleep(0.02)
for python 2.7 you can use,
print 2%, 3% # Using comma will print it in same line
for python 3.x
print('2%', end=" ")
Or you can use sys.stdout.write for doing it with sys.stdout.flush()
Please check my below code, I have created a demo progress bar.
"""ProgressBar Module."""
import sys
import time
class ProgressBar(object):
"""Main class for the ProgressBa."""
DEFAULT_BAR_LENGTH = float(30)
def __init__(self, start=0, step=1):
"""Init for the class."""
self.end = ProgressBar.DEFAULT_BAR_LENGTH
self.start = start
self.step = step
self.total = self.end - self.start
self.counts = self.total / self.step
self._barLength = ProgressBar.DEFAULT_BAR_LENGTH
self.set_level(self.start)
self._plotted = False
def set_level_old(self, level, initial=False):
"""Setting Level."""
self._level = level
if level < self.start:
self._level = self.start
if level > self.end:
self._level = self.end
self._ratio = float(
self._level - self.start) / float(self.end - self.start)
self._levelChars = int(self._ratio * self._barLength)
def set_level(self, level, initial=False):
"""Setting Level."""
self._level = level
if level < self.start:
self._level = self.start
if level > self.end:
self._level = self.end
self._ratio = float(self._level) / float(self._barLength)
self._levelChars = int(self._ratio * self._barLength) * self.step
def plot_progress(self):
"""Plotting the bar."""
sys.stdout.write("\r %3i%% |%s%s|" % (
int(self._ratio * self.step * 100.0),
u'\u2588' * int(self._levelChars),
' ' * int(self._barLength - self._levelChars),
))
sys.stdout.flush()
self._plotted = True
def set_and_plot(self, level):
"""Call the plot."""
old_chars = self._levelChars
self.set_level(level)
if (not self._plotted) or (old_chars != self._levelChars):
self.plot_progress()
def __del__(self):
"""Del for the class."""
sys.stdout.write("\n")
if __name__ == "__main__":
pb = ProgressBar(0, 1)
curProgress = 0
pb.plot_progress()
while curProgress <= pb.counts:
pb.set_and_plot(curProgress)
curProgress += 1
time.sleep(0.1)
del pb

Simpy: don't understand why my simulation never ends

I'm trying to simulate a game of Counter Strike. Basically I have two teams with different players (all the players are identical for now) and I want them to "fight" and when all the players on one team are dead, the simulation should end.
I'm trying to understand why the simulation I'm running never ends. I feel like I'm misunderstanding some core element of simpy but I don't really know what.
All of the process and simpy related code are in main.py and player.py.
I'm trying to get my simulation to end once every player has "died".
Basically I want every player to be a process that constantly checks their surrounding area (the node they are in which is represented by the Hotspot class) to see if there are any enemies. If there are any enemies they will choose one at random and "attack" them. Once all of the players from any team have health below 0 the simulation should end and the team that won should increment their win count by 1.
EDIT: Also of note, when I ran it through pdb it seemed like none of the player's health were decreasing and that the play method wasn't being run.
EDIT 2: I don't think all of the code needs to be read to find the problem, I think it's mostly in the main and player files but I'm not 100% sure because the code loops infinitely without error
Here is my code
main.py
from player import Player
from game_map import Game_Map
from team import Team
from sides import Sides
import simpy
import pdb
def main():
team_a = Team("Team_A", Sides.CT)
team_b = Team("Team_B", Sides.T)
gmap = Game_Map()
gmap.spawn_team(team_a)
gmap.spawn_team(team_b)
env = simpy.Environment()
for team in (team_a, team_b):
for player in team.players:
env.process(player.play(env))
env.run(until=round(team_a, team_b, env))
def round(team_a, team_b, env):
while True:
if team_a.all_dead():
team_b.round_wins += 1
print team_b
env.exit()
if team_b.all_dead():
team_a.round_wins += 1
print team_a
env.exit()
if __name__ == "__main__":
main()
player.py
import simpy
from sides import Sides
import numpy as np
import pdb
class Player(object):
""" Class that represents a CSGO player"""
def __init__(self, steam_id, team, acc, hs_percentage):
# the player's id
self.steam_id = steam_id
# percentage of shots that hit, accuracy
self.acc = acc
# percentage of hits that hit the head
self.hs_percentage = hs_percentage
# the team
self.team = team
# the player's health, this changes when the teams "fight"
self.health = 100
# the current hotspot that the player is in
self.current_location = 0
# if the player is alive or dead
self.is_alive = True
def play(self, env):
"""Process that simulates the player's actions. This is run once every round until
the round is over"""
while(self.is_alive):
target = self.choose_target()
if target == -1:
continue
yield env.timeout(5)
else:
target.inflict_self(self.determine_damage())
yield env.timeout(5)
def determine_damage(self):
"""The amount of damage the player will inflict on the enemy"""
return 27
def choose_target(self):
"""Choose a target to attack from the enemies in the hotspot"""
# 1 - side converts 0 to 1 and 1 to 0
enemy_list = self.current_location.players[1 - self.team.side]
num_enemies = len(enemy_list)
# if there are no enemies currently in the same location of the player
# simply return 0
if num_enemies == 0:
return -1
# pick an enemy randomly from the list of enemies and return their object
return enemy_list[np.random.random_integers(0, num_enemies - 1)]
def get_side(self):
return self.team.side
def inflict_self(self, damage):
"""Inflict damage onto own class. If damage moves health below 0, mark the
player as "Dead" and remove them from the map"""
self.health = self.health - damage
if self.health <= 0:
self.current_location.players[self.team.side].remove(self)
self.is_alive = False
def __str__(self):
return "Steam id: {0}\tIs Alive: {1}\tCurrent Location: {2}".format(self.steam_id, self.is_alive, self.current_location)
def tests():
return
if __name__ == "__main__":
tests()
game_map.py
import networkx as nx
from hotspot import Hotspot
import matplotlib.pyplot as plt
class Game_Map(object):
""" Generic map that represents general outline of all counter strike maps"""
def __init__(self):
self.graph = nx.Graph()
self.spawns = [Hotspot()]
self.graph.add_node(self.spawns[0])
def add_team(team):
#side = team.side
# side is 0 because for testing the simulation
# we are only using one node
side = 0
for player in team.players:
self.spawns[side].move_into(player)
def spawn_team(self, team):
for player in team.players:
self.spawns[0].move_into(player)
def draw(self):
nx.draw(self.graph)
plt.show()
def tests():
"""Tests to see that Game_Map class works properly"""
# initialize the map
gmap = Game_Map()
gmap.draw()
# if this module is being run explicitly from the command line
# run tests to assure that this module is working properly
if __name__ == "__main__":
tests()
hotspot.py
import simpy
from player import Player
from team import Team
from sides import Sides
class Hotspot(object):
"""Hotspots are the representation for different areas of the map. This is where players 'fight'."""
def __init__(self):
self.players = [[], []]
def move_into(self, player):
side = player.get_side()
self.players[side].append(player)
player.current_location = self
return 1
def __eq__(self, other):
return id(self) == id(other)
def __hash__(self):
return id(self)
def tests():
"""Tests to see that hotspot works properly"""
hotspot_list = []
for i in range(5):
hotspot_list.append(Hotspot())
for spot in hotspot_list:
team_a = Team("team_a", Sides.CT)
team_b = Team("team_b", Sides.T)
spot.move_into(Player(1, team_a, .5, .5))
spot.move_into(Player(1, team_b, .5, .5))
print "Hotspot id = {0}".format(id(spot))
for team in spot.players:
for player in team:
print "player = {0} in team {1}".format(player, player.team)
if __name__ == "__main__":
tests()
sides.py
class Sides(object):
"""Enum object, simply represents CT (Counter Terrorists) as 0 and
T (Terrorists) as 1"""
CT, T = range(2)
team.py
from player import Player
class Team(object):
"""Class that holds critical team information"""
def __init__(self, name, side):
self.round_wins = 0
self.players = []
self.name = name
self.side = side
self.generate_team()
def all_dead(self):
count = 0
for player in self.players:
if player.is_alive == False:
count += 1
if count == 5:
return True
else:
return False
def __str__(self):
rep = "Team: {0}, Round Wins: {1}\n".format(self.name, self.round_wins)
for player in self.players:
rep += player.__str__() + '\n'
return rep
def generate_team(self):
for i in range(5):
self.players.append(Player(1, self, .5, .2))
__rep__ = __str__
requirements.txt
decorator==3.4.2
matplotlib==1.4.3
mock==1.0.1
networkx==1.9.1
nose==1.3.6
numpy==1.9.2
pyparsing==2.0.3
python-dateutil==2.4.2
pytz==2015.2
scipy==0.15.1
simpy==3.0.7
six==1.9.0
Your round() function is the culprit:
env.run(until=round(team_a, team_b, env))
def round(team_a, team_b, env):
while True:
if team_a.all_dead():
team_b.round_wins += 1
print team_b
env.exit()
if team_b.all_dead():
team_a.round_wins += 1
print team_a
env.exit()
The function contains an infinite loop without any yields. This means it never returns and env.run() isn’t even executed.
I have a feeling this might be an infinite loop:
while(self.is_alive):
target = self.choose_target()
if target == -1:
continue
yield env.timeout(5)
You probably want to yield before the continue (which is unneeded anyway).

Categories

Resources