-
Notifications
You must be signed in to change notification settings - Fork 0
/
gameplay.py
243 lines (209 loc) · 10.2 KB
/
gameplay.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
#!/usr/bin/env python
import time
import numpy
import math
from parameters import SharedParameters, EEGInfo
from effects.base import EffectLayer, HeadsetResponsiveEffectLayer
from renderer import Renderer
MAX_DELTA = 1.0
MIN_DELTA = 0.01
MAX_GAME_LENGTH = 60 * 4
CHANGE_IN_DELTA_PER_SECOND = (MAX_DELTA - MIN_DELTA) / MAX_GAME_LENGTH
ELAPSED_STARTUP_TIME = 2.0
TIME_AT_MAX = 1.0
STATE_WAITING = 0
STATE_COUNTDOWN = 1
STATE_PLAYING = 2
STATE_WINNER = 3
DEFAULT_ATTRIBUTE = 'meditation'
class GameObject(object):
"""
Class to calculate the game between two headset players
"""
def __init__(self, params, renderer_low, renderer_high):
self.params = params
self.renderer_low = renderer_low
self.renderer_high = renderer_high
self.layer1 = None
self.layer2 = None
self.last_eeg1 = None
self.last_eeg2 = None
self.start_time = None
self.win_time = None
self.potential_winner = None
self.winner = None
self.game_state = STATE_WAITING
def percentage_from_values(self, value1, value2):
def delta_needed_to_win():
#delta needed to win starts at a max value and slowly goes down over time
elapsed_time = self.params.time - (self.start_time + ELAPSED_STARTUP_TIME)
elapsed_time = max(elapsed_time, 0)
return max(MAX_DELTA - (CHANGE_IN_DELTA_PER_SECOND * elapsed_time), MIN_DELTA)
percentage = 0.5
#don't do anything unless we have both values
if value1 > 0.001 and value2 > 0.001 and self.start_time != None:
delta = value1 - value2
#take the difference between the values
#(delta / delta_needed_to_win) will be centered around zero
percentage = delta / delta_needed_to_win()
#make sure we don't go out of bounds [-1, 1]
percentage = numpy.clip(percentage, -1.0, 1.0)
#transform that into [0, 1]
percentage = (1 + percentage) * 0.5
return percentage
def start(self, player_one_attr=DEFAULT_ATTRIBUTE, player_two_attr=DEFAULT_ATTRIBUTE):
self.layer1 = HeadsetResponsiveEffectLayer(respond_to=player_one_attr,
smooth_response_over_n_secs=self.params.frames_to_average)
self.layer2 = HeadsetResponsiveEffectLayer(respond_to=player_two_attr,
smooth_response_over_n_secs=self.params.frames_to_average)
self.last_eeg1 = None
self.last_eeg2 = None
self.change_playlist(self.params.NO_HEADSET_STATE)
self.params.percentage = 0.5
self.start_time = time.time()
self.win_time = None
self.potential_winner = None
self.winner = None
self.game_state = STATE_WAITING
print 'waiting'
def begin_countdown(self):
self.game_state = STATE_COUNTDOWN
self.change_playlist(self.params.STARTUP_STATE, 0.5)
self.start_time = time.time()
print 'counting down'
def begin_gameplay(self):
self.params.percentage = 0.5
self.start_time = self.params.time
self.game_state = STATE_PLAYING
print 'playing'
def change_effects(self, renderer, eeg, last_eeg):
if eeg and eeg != last_eeg:
if not eeg or not eeg.on:
renderer.swapPlaylists(self.params.NO_HEADSET_STATE, fadeTime=0.1)
else:
renderer.swapPlaylists(self.params.PLAY_STATE, fadeTime=0.1)
last_eeg = eeg
return last_eeg
def change_playlist(self, playlist, fadeTime=0.1):
self.renderer_low.swapPlaylists(playlist, fadeTime=fadeTime)
self.renderer_high.swapPlaylists(playlist, fadeTime=fadeTime)
def update_waiting(self, renderer_waiting, renderer_disconnected):
renderer_waiting.swapPlaylists(self.params.WAITING_FOR_OTHER_PLAYER_STATE, fadeTime=0.1)
renderer_disconnected.swapPlaylists(self.params.NO_HEADSET_STATE, fadeTime=0.1)
def loop(self):
# don't start the countdown until we know that both headsets are connected and giving good data
if self.game_state == STATE_WAITING:
def valid_eeg(eeg):
return eeg and eeg.on
if valid_eeg(self.params.eeg1) and valid_eeg(self.params.eeg2):
self.begin_countdown()
elif valid_eeg(self.params.eeg1):
self.update_waiting(self.renderer_low, self.renderer_high)
elif valid_eeg(self.params.eeg2):
self.update_waiting(self.renderer_high, self.renderer_low)
else:
self.change_playlist(self.params.NO_HEADSET_STATE)
return
elif self.game_state == STATE_COUNTDOWN:
if self.params.time - self.start_time < self.params.COUNTDOWN_TIME:
return
else:
self.begin_gameplay()
elif self.game_state == STATE_WINNER:
return
self.last_eeg1 = self.change_effects(self.renderer_low, self.params.eeg1, self.last_eeg1)
self.last_eeg2 = self.change_effects(self.renderer_high, self.params.eeg2, self.last_eeg2)
value1 = self.layer1.calculate_response_level(params=self.params)
value2 = self.layer2.calculate_response_level(params=self.params, use_eeg2=True)
if value1 != None and value2 != None:
percentage = self.percentage_from_values(value1, value2)
self.params.percentage = percentage
# if self.params.debug == True:
# NUM_PIXELS = 100
# p1_chars = int(round(percentage * NUM_PIXELS)) * '*'
# p2_chars = (NUM_PIXELS - len(p1_chars)) * '^'
# bar = p1_chars + p2_chars
# print 'value1: %f, value2: %f, percentage %f' % (value1, value2, percentage)
# print '|'+bar+'|'
#winner must 'win' for a certain amount of time to be legit
if percentage == 0.0 or percentage == 1.0:
self.potential_winner = percentage
if self.win_time == None:
self.win_time = self.params.time
else:
self.potential_winner = None
self.win_time = None
if self.potential_winner != None and (self.params.time - self.win_time) > TIME_AT_MAX:
self.winner = 'one' if self.potential_winner == 1.0 else 'two'
print 'winner is player %s' % self.winner
self.change_playlist(self.params.WIN_STATE)
self.game_state = STATE_WINNER
# sys.exit(0)
class PercentageLayerMixer(EffectLayer):
"""An effect layer that computes a composite of two frames based on the current percentage
in the shared parameters."""
def render(self, params, low_frame, high_frame, out_frame, mix_radius=3):
length = len(out_frame)
max_index = length - 1
dividing_float = params.percentage * length
dividing_index = int(round(dividing_float))
mix_radius = max(mix_radius, 0)
if params.percentage == 0.0:
out_frame[:] = high_frame[:]
elif dividing_index > max_index:
out_frame[:] = low_frame[:]
elif mix_radius == 0:
# simple mixing over one pixel - smoother transitions between percentage levels
idx = int(math.floor(dividing_float))
#everything below the index is low_frame
out_frame[:idx] = low_frame[:idx]
# mix the pixel at the index based on the fractional value
alpha = dividing_float - idx
out_frame[idx] = (1 - alpha) * high_frame[idx] + alpha * low_frame[idx]
idx += 1
#everything above the index is high_frame
out_frame[idx:] = high_frame[idx:]
else:
window_radius = mix_radius
#shorten the window_radius if we're close to either end of the frame
window_radius = min(window_radius, dividing_index)
window_radius = min(window_radius, max_index - dividing_index)
#calculate start and end indices, making sure they don't go out of bounds
window_start = max(dividing_index - window_radius, 0)
#include an extra 1 in window_end to include the dividing index itself
window_end = min(dividing_index + window_radius, max_index) + 1
window_length = 2 * window_radius + 1
window_step = min(1.0 / window_length, 0.5)
alpha = window_step
#before window_start it is only values in the low_frame
if window_start > 0:
out_frame[:window_start] = low_frame[:window_start]
for idx in range (window_start, window_end):
out_frame[idx] += (1 - alpha) * low_frame[idx] + alpha * high_frame[idx]
# print alpha
alpha += window_step
#after window_end it is only values in the high_frame
if window_end < max_index:
out_frame[window_end:] = high_frame[window_end:]
# print ' %d +- %d = %d => (%d - %d)' % (dividing_index, window_radius, window_length, window_start, window_end)
class PercentageResponsiveEffectLayer(EffectLayer):
"""A layer effect that responds to the percentage of two MindWave headsets in some way.
Two major differences from EffectLayer:
1) Constructor expects one parameter:
-- inverse: If this is true, the layer will respond to (1-response_level)
instead of response_level
2) Subclasses now only implement the render_responsive() function, which
is the same as EffectLayer's render() function but has one extra
parameter, response_level, which is the current EEG value of the indicated
field (assumed to be on a 0-1 scale, or None if no value has been read yet).
"""
def __init__(self, inverse=False):
self.inverse = inverse;
def render(self, params, frame):
response_level = params.percentage
if self.inverse:
response_level = 1.0 - response_level
self.render_responsive(params, frame, response_level)
def render_responsive(self, params, frame, response_level):
raise NotImplementedError(
"Implement render_responsive() in your PercentageResponsiveEffectLayer subclass")