1 | import json
|
---|
2 | from copy import deepcopy
|
---|
3 | from flowtimer.Phase import Phase
|
---|
4 |
|
---|
5 |
|
---|
6 | class RecurringPhaseSequence:
|
---|
7 |
|
---|
8 | @classmethod
|
---|
9 | def from_json(cls, a_json_string):
|
---|
10 | def custom_object_hook(d):
|
---|
11 | if 'title' in d and 'initial_ticks' in d:
|
---|
12 | return Phase(d['title'], d['initial_ticks'])
|
---|
13 | if 'phases' in d and 'initial_repetitions' in d:
|
---|
14 | return RecurringPhaseSequence(d["title"], d['phases'], d['initial_repetitions'])
|
---|
15 | print("Wrong format")
|
---|
16 | return d
|
---|
17 | return json.loads(a_json_string, object_hook=custom_object_hook)
|
---|
18 |
|
---|
19 | @classmethod
|
---|
20 | def default_json_string(cls):
|
---|
21 | return json.dumps({"title": "default",
|
---|
22 | "phases": [{"title": "Tasking", "initial_ticks": 5},
|
---|
23 | {"title": "Work", "initial_ticks": 45},
|
---|
24 | {"title": "Break", "initial_ticks": 15}],
|
---|
25 | "initial_repetitions": 3})
|
---|
26 |
|
---|
27 | @classmethod
|
---|
28 | def default(cls):
|
---|
29 | return cls.from_json(cls.default_json_string())
|
---|
30 |
|
---|
31 | def __init__(self, title, phases, repetitions):
|
---|
32 | assert repetitions > 0
|
---|
33 | assert phases is not []
|
---|
34 | self._title = title
|
---|
35 | self._state = "initial"
|
---|
36 | self.phases = phases
|
---|
37 | self.current_phase = phases[0]
|
---|
38 | self.initial_repetitions = repetitions
|
---|
39 | self.passes_left = repetitions - 1
|
---|
40 |
|
---|
41 | def to_json(self):
|
---|
42 | return json.dumps(self.__dict__, default=lambda each: each.to_json())
|
---|
43 |
|
---|
44 | @property
|
---|
45 | def title(self):
|
---|
46 | return self._title
|
---|
47 |
|
---|
48 | def __str__(self):
|
---|
49 | return ("Sequence title:" + self.title + "\n" + str(self.current_phase))
|
---|
50 |
|
---|
51 | def is_sequence(self):
|
---|
52 | return True
|
---|
53 |
|
---|
54 | def current_phase_number(self):
|
---|
55 | return self.phases.index(self.current_phase)
|
---|
56 |
|
---|
57 | def phases_left_in_pass(self):
|
---|
58 | return len(self.upcoming_phases_in_pass())
|
---|
59 |
|
---|
60 | def upcoming_phases_in_pass(self):
|
---|
61 | if self.current_phase_number() < len(self.phases) - 1:
|
---|
62 | return self.phases[self.current_phase_number()+1:]
|
---|
63 | return []
|
---|
64 |
|
---|
65 | @property
|
---|
66 | def initial_ticks(self):
|
---|
67 | return sum([each.initial_ticks for each in self.phases])
|
---|
68 |
|
---|
69 | @property
|
---|
70 | def ticks_left(self):
|
---|
71 | return (
|
---|
72 | (self.passes_left-1) * sum([each.initial_ticks for each in self.phases]) +
|
---|
73 | self.current_phase.ticks_left +
|
---|
74 | sum([each.ticks_left for each in self.upcoming_phases_in_pass()]))
|
---|
75 |
|
---|
76 | def state(self):
|
---|
77 | if self.is_completed():
|
---|
78 | return "completed"
|
---|
79 | return self._state
|
---|
80 |
|
---|
81 | def is_final_round(self):
|
---|
82 | print("Final round", self.passes_left)
|
---|
83 | return self.passes_left == 0
|
---|
84 |
|
---|
85 | def is_completed(self):
|
---|
86 | return ((self.passes_left < 1) and
|
---|
87 | (not self.upcoming_phases_in_pass() and
|
---|
88 | self.current_phase.is_completed()))
|
---|
89 |
|
---|
90 | def is_terminated(self):
|
---|
91 | return (self.is_aborted() or self.is_completed())
|
---|
92 |
|
---|
93 | def is_aborted(self):
|
---|
94 | return self._state == 'aborted'
|
---|
95 |
|
---|
96 | def abort(self):
|
---|
97 | self.current_phase.abort()
|
---|
98 | self._state = "aborted"
|
---|
99 |
|
---|
100 | def start(self):
|
---|
101 | self._state = "running"
|
---|
102 |
|
---|
103 | def skip(self):
|
---|
104 | if self.upcoming_phases_in_pass():
|
---|
105 | self.current_phase.reset()
|
---|
106 | self.current_phase = self.upcoming_phases_in_pass()[0]
|
---|
107 | return
|
---|
108 | else:
|
---|
109 | if self.is_final_round():
|
---|
110 | print("Abort on final round")
|
---|
111 | self.abort()
|
---|
112 | return
|
---|
113 | else:
|
---|
114 | self.passes_left -= 1
|
---|
115 | print("Skip", self.passes_left)
|
---|
116 | self.current_phase.reset()
|
---|
117 | self.current_phase = self.phases[0]
|
---|
118 |
|
---|
119 | def advance_to_next_phase(self):
|
---|
120 | current_index = self.current_phase_number()
|
---|
121 | if current_index < len(self.phases) - 1:
|
---|
122 | # Move to the next phase in the sequence
|
---|
123 | self.current_phase.reset()
|
---|
124 | self.current_phase = self.phases[current_index + 1]
|
---|
125 | else:
|
---|
126 | # Completed a full sequence; check if more repetitions are needed
|
---|
127 | self.passes_left -= 1
|
---|
128 | if self.passes_left < 1:
|
---|
129 | self._state = "completed"
|
---|
130 | print("Passes left", self.passes_left)
|
---|
131 | else:
|
---|
132 | self.current_phase.reset()
|
---|
133 | self.current_phase = self.phases[0] # Reset to the first phase
|
---|
134 |
|
---|
135 | def tick(self, ticks):
|
---|
136 | if not self.is_completed():
|
---|
137 | result = self.current_phase.tick(ticks)
|
---|
138 | if self.current_phase.is_completed():
|
---|
139 | self.advance_to_next_phase()
|
---|
140 | result = self.tick(abs(result))
|
---|
141 | return
|
---|
142 |
|
---|
143 | def unrolled(self):
|
---|
144 | return [deepcopy(seq) for seq in [each for each in self.initial_repetitions * self.phases]]
|
---|