-
-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds support for changing paces to experiental FFA implementation.
* We add a 'PaceManager' that keeps track of tempos, and switches between them at configured intervals. * Instead of pushing controller state to the game loop, the Player object computes deltas, and sends 'events' like BUTTON_DOWN. Based on 'top', the new game mode consumes about 1/4 as much CPU as the old FFA.
- Loading branch information
1 parent
1188b0d
commit a1f41c6
Showing
7 changed files
with
354 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,30 @@ | |||
import asyncio | |||
import sys | |||
|
|||
sys.path.append('/home/pi/psmoveapi/build') | |||
|
|||
import piaudio | |||
|
|||
|
|||
def Main(): | |||
music = piaudio.Music('audio/Joust/music/classical.wav') | |||
music.start_audio_loop() | |||
|
|||
loop = asyncio.get_event_loop() | |||
print("Enter a FP number:") | |||
async def ProcessInput(): | |||
while True: | |||
line = await loop.run_in_executor(None, sys.stdin.readline) | |||
try: | |||
ratio = float(line) | |||
except ValueError: | |||
print("invalid value: %s" % line) | |||
await music.transition_ratio(ratio) | |||
print("OK.") | |||
|
|||
loop.run_until_complete(ProcessInput()) | |||
music.stop_audio() | |||
|
|||
|
|||
if __name__ == '__main__': | |||
Main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,72 @@ | |||
import asyncio | |||
import collections | |||
import random | |||
import typing | |||
|
|||
import common | |||
|
|||
PaceSettings_ = collections.namedtuple('PaceSettings_', ['weight', 'min_duration', 'max_duration']) | |||
|
|||
|
|||
class PaceManager: | |||
"""Manages transitions between game paces, and notifies users of changes via a callback. | |||
The game starts out in the initial pace, then switches pace according to parameters | |||
passed in. The actual pace is treated as an opaque object -- this class does not care | |||
what the pace represents, it is just in charge of timing transitions. | |||
Sample usage: | |||
pm = PaceManager(cb, pace1, 10) | |||
pm.add_or_update_pace(pace2, 1.0, 10, 20) | |||
pm.add_or_update_pace(pace3, 2.0, 5, 10) | |||
pm.start() | |||
.... | |||
pm.stop() | |||
Here, we start off with pace1 for 10 seconds. After that, we will switch to either pace2, or pace3, | |||
with pace3 being twice as likely. If pace2 is chosen, it will be kept for 10-20 seconds. pace3 will | |||
be kept for 5-10 seconds. | |||
""" | |||
|
|||
def __init__(self, callback, initial_pace, initial_pace_time: float, rng=random.uniform): | |||
self.initial_pace_ = initial_pace | |||
self.initial_pace_time_ = initial_pace_time | |||
self.available_paces_ = {} | |||
self.task_ = None | |||
self.rng_ = rng | |||
self.callback_ = callback | |||
|
|||
def add_or_update_pace(self, pace, weight: float, min_duration: float, max_duration: float): | |||
self.available_paces_[pace] = PaceSettings_(weight, min_duration, max_duration) | |||
|
|||
def start(self): | |||
self.task_ = asyncio.ensure_future(self.run_()) | |||
return self.task_ | |||
|
|||
def stop(self): | |||
self.task_.cancel() | |||
|
|||
def set_pace_(self, pace): | |||
self.callback_(pace) | |||
|
|||
def choose_new_pace_(self, old_pace) -> typing.Tuple[object, float]: | |||
if len(self.available_paces_) == 0: | |||
raise RuntimeError("No paces registered.") | |||
candidates = self.available_paces_ | |||
total_weight = sum([ params.weight for params in candidates.values() ]) | |||
index = self.rng_(0, total_weight) | |||
cumulative_weight = 0 | |||
for pace, params in candidates.items(): | |||
cumulative_weight += params.weight | |||
if cumulative_weight >= index: | |||
return pace, self.rng_(params.min_duration, params.max_duration) | |||
raise ValueError("Couldn't find pace with index %s/%s!?" % (index, total_weight)) | |||
|
|||
@common.async_print_exceptions | |||
async def run_(self): | |||
await asyncio.sleep(self.initial_pace_time_) | |||
|
|||
pace = self.initial_pace_ | |||
while True: | |||
pace, duration_secs = self.choose_new_pace_(pace) | |||
self.set_pace_(pace) | |||
await asyncio.sleep(duration_secs) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,65 @@ | |||
import asyncio | |||
import collections | |||
import time | |||
import unittest | |||
|
|||
import pacemanager | |||
|
|||
PACE1 = object() | |||
PACE2 = object() | |||
PACE3 = object() | |||
|
|||
class PaceManagerTest(unittest.TestCase): | |||
def assertPrettyClose(self, a, b, error=0.1): | |||
self.assertGreater(error, abs(a - b)) | |||
|
|||
def test_distribution(self): | |||
pm = pacemanager.PaceManager(lambda x: True, PACE1, 5) | |||
pm.add_or_update_pace(PACE1, 1.0, 1, 2) | |||
|
|||
# Test to make sure we don't double up on PACE2 | |||
pm.add_or_update_pace(PACE2, 1.0, 1, 2) | |||
pm.add_or_update_pace(PACE2, 1.0, 1, 2) | |||
pm.add_or_update_pace(PACE3, 2.0, 1, 2) | |||
|
|||
results = collections.defaultdict(int) | |||
num_trials = 1000 | |||
for i in range(num_trials): | |||
pace, duration = pm.choose_new_pace_(PACE1) | |||
results[pace] += 1 | |||
self.assertLessEqual(1, duration) | |||
self.assertGreater(2, duration) | |||
|
|||
self.assertEqual(3, len(results)) | |||
self.assertPrettyClose(1/4, results[PACE1]/num_trials) | |||
self.assertPrettyClose(1/4, results[PACE2]/num_trials) | |||
self.assertPrettyClose(1/2, results[PACE3]/num_trials) | |||
|
|||
def test_async(self): | |||
Entry = collections.namedtuple('Entry', ['pace', 'time']) | |||
results = [] | |||
begin = time.time() | |||
def UpdatePace(pace): | |||
results.append(Entry(pace, time.time() - begin)) | |||
uniform = lambda a, b: a | |||
|
|||
DELTA = 0.1 | |||
pm = pacemanager.PaceManager(UpdatePace, PACE1, DELTA, rng=uniform) | |||
pm.add_or_update_pace(PACE1, 1.0, DELTA, 1 + DELTA) | |||
pm.add_or_update_pace(PACE2, 1.0, DELTA, 1 + DELTA) | |||
loop = asyncio.get_event_loop() | |||
try: | |||
# This should get us 4 events. | |||
timeout = DELTA * 4.1 | |||
loop.run_until_complete(asyncio.wait_for(pm.start(), timeout=timeout)) | |||
except asyncio.TimeoutError: | |||
pass | |||
|
|||
# We should have registered a new pace 4 times, about DELTA seconds apart. | |||
self.assertEqual(4, len(results)) | |||
for i in range(4): | |||
self.assertPrettyClose(results[i].time, DELTA * (i+1)) | |||
|
|||
|
|||
if __name__ == '__main__': | |||
unittest.main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.