Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
PyProcGameHD-SkeletonGame/procgame/game/basicgame.py
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
243 lines (202 sloc)
8.6 KB
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
from . import GameController | |
from ..dmd import DisplayController, font_named | |
from ..modes import ScoreDisplay | |
from .. import config | |
from .. import auxport | |
from .. import alphanumeric | |
import pinproc | |
import time | |
import datetime | |
import traceback | |
class BasicGame(GameController): | |
""":class:`BasicGame` is a subclass of :class:`~procgame.game.GameController` | |
that includes and configures various useful helper classes to provide: | |
* A :class:`~procgame.modes.ScoreDisplay` mode/layer at priority 1, available | |
at ``self.score_display``. | |
* A :class:`~procgame.dmd.DisplayController` mode to manage the DMD layers, | |
at ``self.dmd``. | |
* A :class:`~procgame.desktop.Desktop` helper at ``self.desktop`` configured | |
to display the most recent DMD frames on the desktop, as well as interpret | |
keyboard input as switch events. | |
It is a recommended base class to build your game upon, or use as a template | |
if your game has special requirements. | |
""" | |
dmd = None | |
alpha_display = None | |
score_display = None | |
aux_port = None | |
desktop = None | |
def __init__(self, machine_type): | |
# loading the desktop first allows the pygame code to use convert, because | |
# pygame will be loaded. | |
use_desktop = config.value_for_key_path(keypath='use_desktop', default=True) | |
if use_desktop: | |
import procgame.desktop | |
from ..desktop import Desktop | |
self.desktop = Desktop() | |
super(BasicGame, self).__init__(machine_type) | |
self.aux_port = auxport.AuxPort(self) | |
if self.machine_type == pinproc.MachineTypeWPCAlphanumeric: | |
self.alpha_display = alphanumeric.AlphanumericDisplay(self.aux_port) | |
dots_w = config.value_for_key_path(keypath='dmd_dots_w', default=128) | |
dots_h = config.value_for_key_path(keypath='dmd_dots_h', default=32) | |
self.dmd = DisplayController(self, width=dots_w, height=dots_h) #, message_font=hdfont_named('Font07x5.dmd')) | |
# self.score_display = ScoreDisplay(self, 0) | |
if self.dmd: self.dmd.frame_handlers.append(self.set_last_frame) | |
def load_config(self, path): | |
super(BasicGame,self).load_config(path) | |
# Setup the key mappings from the config.yaml. | |
# We used to do this in __init__, but at that time the | |
# configuration isn't loaded so we can't peek into self.switches. | |
key_map_config = config.value_for_key_path(keypath='keyboard_switch_map', default={}) | |
if self.desktop: | |
for k, v in key_map_config.items(): | |
switch_name = str(v) | |
if self.switches.has_key(switch_name): | |
switch_number = self.switches[switch_name].number | |
else: | |
switch_number = pinproc.decode(self.machine_type, switch_name) | |
if(type(k)!=int): # letter keys are added as letters (obv) | |
self.desktop.add_key_map(ord(str(k)), switch_number) | |
elif(k<10): # 0-9 as keys | |
self.desktop.add_key_map(ord(str(k)), switch_number) | |
else: # numbers used as bindings for specials -- examples below | |
self.desktop.add_key_map(k, switch_number) | |
# K_LSHIFT: 304 | |
# K_RSHIFT: 303 | |
# K_F1: 282 | |
# K_F12: 293 | |
def reset(self): | |
"""Calls super's reset and adds the :class:`ScoreDisplay` mode to the mode queue.""" | |
super(BasicGame, self).reset() | |
#self.modes.add(self.score_display) | |
def dmd_event(self): | |
"""Updates the DMD via :class:`DisplayController`.""" | |
if self.dmd: self.dmd.update() | |
def get_events(self): | |
"""Overriding GameController's implementation in order to append keyboard events.""" | |
events = super(BasicGame, self).get_events() | |
if self.desktop: events.extend(self.desktop.get_keyboard_events()) | |
return events | |
def tick(self): | |
"""Called once per run loop. | |
Displays the last-received DMD frame on the desktop.""" | |
super(BasicGame, self).tick() | |
self.show_last_frame() | |
def score(self, points): | |
"""Convenience method to add *points* to the current player.""" | |
p = self.current_player() | |
p.score += points | |
# | |
# Support for showing the last DMD frame on the desktop. | |
# | |
# Because showing each frame on the desktop can be pretty time-consuming, | |
# we show it only once per run loop cycle (via tick()), and only when there | |
# is a new frame (via last_frame). By showing it this way (and not directly | |
# from DisplayController's frame_handlers), we allow the run loop to progress | |
# quickly without getting bogged down drawing the DMD on the desktop if a | |
# large number of DMD events arrive 'at once'. | |
# | |
last_frame = None | |
def set_last_frame(self, frame): | |
self.last_frame = frame | |
def show_last_frame(self): | |
if self.desktop and self.last_frame: | |
self.desktop.draw(self.last_frame) | |
self.last_frame = None | |
class BasicRecordableGame(BasicGame): | |
"""RecordableGameController provides the ability to record all switch events to a | |
simulation file. The simulation file can then be played back using fakePinPROC in | |
order to reproduce events or develop code further. | |
""" | |
_switch_record_file = None | |
_start_time = 0 | |
_is_currently_recording = False | |
def __init__(self, machine_type): | |
super(BasicRecordableGame, self).__init__(machine_type) | |
# Mark down our start time so we get relative simulator timestamps when recording events | |
self._start_time = (time.time() * 1000) | |
def start_recording(self): | |
""" Grabs the current switch matrix state snapshot and begins recording | |
switch events starting at simulator time zero (0) | |
""" | |
# Grab the current timestamp and key the switch record file off of that timestamp | |
current_time = datetime.datetime.now() | |
timestamp = current_time.strftime("%Y-%m-%d-%H%M") | |
# Open the switch record file for writing. | |
# Note, we don't close it until after the game loop exits | |
self._switch_record_file = open("switch-record-"+timestamp+".txt", 'w') | |
# Grab switch matrix snapshot and write it to the file | |
self.take_switch_snapshot() | |
self._is_currently_recording = True | |
self.logger.info("Recording Started") | |
def stop_recording(self): | |
""" Stops a currently recording switch file and closes it """ | |
self._is_currently_recording = False | |
self._switch_record_file.close() | |
self.logger.info("Recording Stopped") | |
def is_recording(self): | |
return self._is_currently_recording; | |
def take_switch_snapshot(self): | |
""" Iterates through the entire switch matrix and writes the states of all switches | |
into the switch record file. | |
""" | |
states = self.proc.switch_get_states() | |
for sw in self.switches: | |
self._switch_record_file.write(str(sw.number) + "|" + str(states[sw.number]) + "\n") | |
def process_event(self, event): | |
""" Called each time an event happens on the machine. This is where | |
we intercept switch state changes and write them to a file before | |
passing them down the mode queue | |
""" | |
event_type = event['type'] | |
event_value = event['value'] | |
if event_type == 99: # CTRL-C to quit | |
print "CTRL-C detected, quiting..." | |
self.end_run_loop() | |
elif event_type == pinproc.EventTypeDMDFrameDisplayed: # DMD events | |
# print "% 10.3f Frame event. Value=%x" % (time.time()-self.t0, event_value) | |
self.dmd_event() | |
else: | |
try: | |
sw = self.switches[event_value] | |
if 'time' in event: | |
sw.hw_timestamp = event['time'] | |
except KeyError: | |
self.logger.warning("Received switch event but couldn't find switch %s." % event_value) | |
return | |
if sw.debounce: | |
recvd_state = event_type == pinproc.EventTypeSwitchClosedDebounced | |
else: | |
recvd_state = event_type == pinproc.EventTypeSwitchClosedNondebounced | |
# If we're recording, write the switch event to our file | |
if self.is_recording(): | |
self.write_event_to_file(event,sw.name) | |
if sw.state != recvd_state: | |
sw.set_state(recvd_state) | |
self.logger.info("%s:\t%s\t(%s)", sw.name, sw.state_str(),event_type) | |
self.modes.handle_event(event) | |
sw.reset_timer() | |
def write_event_to_file(self, event, friendly_switch_name = ""): | |
""" Writes the specified event array to a switch record file """ | |
currentTime = (time.time() * 1000) - self._start_time | |
eventStr = str(currentTime) + "|" + str(event['type']) + "|" + str(event['value']) + "|" + friendly_switch_name; | |
if 'time' in event: | |
eventStr = eventStr + "|" + str(event['time']) | |
self._switch_record_file.write(eventStr+"\n") | |
self.logger.info("%s:\tswitch recorded-\t%s",str(currentTime),friendly_switch_name) | |
print event | |
def run_loop(self, min_seconds_per_cycle=None): | |
""" We override the original run loop to encapsulate it inside of a | |
try catch block. That way we don't have to constantly open/close switch report | |
files at each event because that can be excessive. If we catch an exception, gracefully | |
close the file. | |
""" | |
try: | |
super(BasicRecordableGame,self).run_loop(min_seconds_per_cycle) | |
except Exception as e: | |
print e | |
print traceback.format_exc() | |
""" Close the switch record file """ | |
if self.is_recording(): | |
self._switch_record_file.close() | |