diff --git a/bambam.py b/bambam.py index 8f5a577..ee3c38a 100755 --- a/bambam.py +++ b/bambam.py @@ -253,8 +253,8 @@ def glob_dir(self, path, suffixes): def glob_data(self, suffixes): """ - Search for files ending with any of the provided suffixes. Eg: - suffixes = ['.abc'] will be similar to `ls *.abc` in the configured + Search for files ending with any of the provided suffixes in data directories. + Eg: suffixes = ['.abc'] will be similar to `ls *.abc` in the configured data dirs. Matching will be case-insensitive. """ suffixes = [x.lower() for x in suffixes] @@ -263,6 +263,19 @@ def glob_data(self, suffixes): file_list.extend(self.glob_dir(data_dir, suffixes)) return file_list + def glob_extension(self, suffixes, extension_name): + """ + Search for files ending with any of the provided suffixes in extension directories. + Eg: suffixes = ['.abc'] will be similar to `ls *.abc` in the configured + extension directories. Matching will be case-insensitive. + """ + suffixes = [s.lower() for s in suffixes] + file_list = [] + for extension_dir in self.extensions_dirs: + extension_subdir = os.path.join(extension_dir, extension_name) + file_list.extend(self.glob_dir(extension_subdir, suffixes)) + return file_list + def _prepare_background(self): if self._sound_enabled: # TRANSLATORS: the inserted string is space-separated list of supported command strings (more than one). @@ -408,6 +421,26 @@ def _add_base_dir(self, base_dir): print(_('Using extension directory %s') % extensions_subdir) self.extensions_dirs.append(extensions_subdir) + def _get_extension_mappers(self, extension_name: str): + for extension_dir in self.extensions_dirs: + extension_subdir = os.path.join(extension_dir, extension_name) + event_map_file_name = os.path.join(extension_subdir, 'event_map.yaml') + if not os.path.exists(event_map_file_name): + continue + import yaml + with open(event_map_file_name) as event_map_file: + event_map = yaml.safe_load(event_map_file) + apiVersion = event_map.get('apiVersion', 'undefined') + if apiVersion not in ['0', 0]: + raise ResourceLoadException(event_map_file_name, 'Unrecognized API version %s' % apiVersion) + image_map = event_map.get('image', {}) + sound_map = event_map.get('sound', {}) + for k in event_map: + if k not in ['apiVersion', 'image', 'sound']: + raise ResourceLoadException(event_map_file_name, 'unrecognized key %s' % k) + return DeclarativeMapper(sound_map), DeclarativeMapper(image_map) + raise ResourceLoadException(os.path.join(extension_name, 'event_map.yaml'), 'File not found.') + def run(self): """ Main application entry point. @@ -419,6 +452,8 @@ def run(self): parser = argparse.ArgumentParser( description=_('Keyboard mashing and doodling game for babies and toddlers.')) + parser.add_argument('-e', '--extension', + help=_('Use the specified extension.')) parser.add_argument('-u', '--uppercase', action='store_true', help=_('Show UPPER-CASE letters.')) parser.add_argument('--sound_blacklist', action='append', default=[], @@ -479,19 +514,31 @@ def run(self): pygame.display.flip() self.sound_muted = args.mute + self._image_mapper = LegacyImageMapper() - self._sound_mapper = LegacySoundMapper(args.deterministic_sounds) if self._sound_enabled: - sounds = self.load_items( + if args.extension: + extension_sounds = self.load_items( + self.glob_extension(['.wav', '.ogg'], args.extension), + [], + self.load_sound, + _("All extension sounds failed to load.")) + self._sound_mapper, self._image_mapper = self._get_extension_mappers(args.extension) + print(_('Using extension "%s".') % args.extension) + else: + self._sound_mapper = LegacySoundMapper(args.deterministic_sounds) + extension_sounds = [] + generic_sounds = self.load_items( self.glob_data(['.wav', '.ogg']), args.sound_blacklist, self.load_sound, _("All sounds failed to load.")) self._sound_policies = dict( - deterministic=DeterministicPolicy(sounds), - random=RandomPolicy(sounds), + deterministic=DeterministicPolicy(generic_sounds), + random=RandomPolicy(generic_sounds), + single=SinglePolicy(extension_sounds) ) images = self.load_items( @@ -537,7 +584,9 @@ def run(self): def _map_and_select(event, mapper, policies): policy_name, policy_args = mapper.map(event) policy = policies[policy_name] - return policy.select(event, policy_args) + if not policy_args: + policy_args = [] + return policy.select(event, *policy_args) class CollectionPolicyBase: @@ -548,16 +597,21 @@ def __init__(self, named_things): self._things.append(thing) self._things_by_file_name[name] = thing - def select(self, event, arg): + def select(self, *_): raise NotImplementedError() class DeterministicPolicy(CollectionPolicyBase): - def select(self, event, _): + def select(self, event): thing_idx = event.key % len(self._things) return self._things[thing_idx] +class SinglePolicy(CollectionPolicyBase): + def select(self, _, file_name): + return self._things_by_file_name[file_name] + + class RandomPolicy(CollectionPolicyBase): def select(self, *_): return random.choice(self._things) @@ -573,7 +627,7 @@ class FontImagePolicy: def __init__(self, upper_case: bool) -> None: self._upper_case = upper_case - def select(self, event, _): + def select(self, event, *_): font = pygame.font.Font(None, 256) char = event.unicode if self._upper_case: @@ -595,6 +649,50 @@ def map(self, event): return "random", None +class DeclarativeMapper: + + def __init__(self, spec): + self._spec = spec + + def map(self, event): + for step in self._spec: + if 'check' in step: + check_list = step['check'] + if not self._match_list(event, check_list): + continue + return step['policy'], step.get('args', None) + raise Exception('ran out of steps in spec %s' % self._spec) + + @classmethod + def _match_list(cls, event, check_list): + return all(cls._match_check(event, check) for check in check_list) + + @classmethod + def _match_check(cls, event, check): + if len(check) != 1: + raise ValueError('only one key permitted in checks, found %s' % check.keys()) + if 'type' in check: + t = check['type'] + if t == 'KEYDOWN': + return event.type == KEYDOWN + else: + raise ValueError('only supported check type is currently KEYDOWN') + elif 'unicode' in check: + u = check['unicode'] + if len(u) != 1: + raise ValueError('only one key is permitted in unicode check, found %s' % u.keys()) + if 'value' in u: + return event.unicode == u['value'] + elif 'isalpha' in u: + return event.unicode.isalpha() + elif 'isdigit' in u: + return event.unicode.isdigit() + else: + raise ValueError('unsupported key in unicode check: %s' % u.keys()) + else: + raise ValueError('only checks for type and unicode are curerntly supported, found %s' % check.keys()) + + class LegacyImageMapper: def map(self, event): diff --git a/extensions/alphanumeric/event_map.yaml b/extensions/alphanumeric/event_map.yaml new file mode 100644 index 0000000..83f96fe --- /dev/null +++ b/extensions/alphanumeric/event_map.yaml @@ -0,0 +1,234 @@ +apiVersion: 0 +image: +- check: + - type: KEYDOWN + - unicode: + isalpha: True + policy: font + +- check: + - type: KEYDOWN + - unicode: + isdigit: True + policy: font + +- policy: random + +sound: +- check: + - unicode: + value: "0" + policy: single + args: ["0.ogg"] + +- check: + - unicode: + value: "1" + policy: single + args: ["1.ogg"] + +- check: + - unicode: + value: "2" + policy: single + args: ["2.ogg"] + +- check: + - unicode: + value: "3" + policy: single + args: ["3.ogg"] + +- check: + - unicode: + value: "4" + policy: single + args: ["4.ogg"] + +- check: + - unicode: + value: "5" + policy: single + args: ["5.ogg"] + +- check: + - unicode: + value: "6" + policy: single + args: ["6.ogg"] + +- check: + - unicode: + value: "7" + policy: single + args: ["7.ogg"] + +- check: + - unicode: + value: "8" + policy: single + args: ["8.ogg"] + +- check: + - unicode: + value: "9" + policy: single + args: ["9.ogg"] + +- check: + - unicode: + value: "a" + policy: single + args: ["a.ogg"] + +- check: + - unicode: + value: "b" + policy: single + args: ["b.ogg"] + +- check: + - unicode: + value: "c" + policy: single + args: ["c.ogg"] + +- check: + - unicode: + value: "d" + policy: single + args: ["d.ogg"] + +- check: + - unicode: + value: "e" + policy: single + args: ["e.ogg"] + +- check: + - unicode: + value: "f" + policy: single + args: ["f.ogg"] + +- check: + - unicode: + value: "g" + policy: single + args: ["g.ogg"] + +- check: + - unicode: + value: "h" + policy: single + args: ["h.ogg"] + +- check: + - unicode: + value: "i" + policy: single + args: ["i.ogg"] + +- check: + - unicode: + value: "j" + policy: single + args: ["j.ogg"] + +- check: + - unicode: + value: "k" + policy: single + args: ["k.ogg"] + +- check: + - unicode: + value: "l" + policy: single + args: ["l.ogg"] + +- check: + - unicode: + value: "m" + policy: single + args: ["m.ogg"] + +- check: + - unicode: + value: "n" + policy: single + args: ["n.ogg"] + +- check: + - unicode: + value: "o" + policy: single + args: ["o.ogg"] + +- check: + - unicode: + value: "p" + policy: single + args: ["p.ogg"] + +- check: + - unicode: + value: "q" + policy: single + args: ["q.ogg"] + +- check: + - unicode: + value: "r" + policy: single + args: ["r.ogg"] + +- check: + - unicode: + value: "s" + policy: single + args: ["s.ogg"] + +- check: + - unicode: + value: "t" + policy: single + args: ["t.ogg"] + +- check: + - unicode: + value: "u" + policy: single + args: ["u.ogg"] + +- check: + - unicode: + value: "v" + policy: single + args: ["v.ogg"] + +- check: + - unicode: + value: "w" + policy: single + args: ["w.ogg"] + +- check: + - unicode: + value: "x" + policy: single + args: ["x.ogg"] + +- check: + - unicode: + value: "y" + policy: single + args: ["y.ogg"] + +- check: + - unicode: + value: "z" + policy: single + args: ["z.ogg"] + +- policy: random diff --git a/extensions/alphanumeric/sounds/0.ogg b/extensions/alphanumeric/sounds/0.ogg new file mode 100644 index 0000000..c6f152c Binary files /dev/null and b/extensions/alphanumeric/sounds/0.ogg differ diff --git a/extensions/alphanumeric/sounds/1.ogg b/extensions/alphanumeric/sounds/1.ogg new file mode 100644 index 0000000..2e7724d Binary files /dev/null and b/extensions/alphanumeric/sounds/1.ogg differ diff --git a/extensions/alphanumeric/sounds/2.ogg b/extensions/alphanumeric/sounds/2.ogg new file mode 100644 index 0000000..5baa903 Binary files /dev/null and b/extensions/alphanumeric/sounds/2.ogg differ diff --git a/extensions/alphanumeric/sounds/3.ogg b/extensions/alphanumeric/sounds/3.ogg new file mode 100644 index 0000000..dc8c2d0 Binary files /dev/null and b/extensions/alphanumeric/sounds/3.ogg differ diff --git a/extensions/alphanumeric/sounds/4.ogg b/extensions/alphanumeric/sounds/4.ogg new file mode 100644 index 0000000..ca41312 Binary files /dev/null and b/extensions/alphanumeric/sounds/4.ogg differ diff --git a/extensions/alphanumeric/sounds/5.ogg b/extensions/alphanumeric/sounds/5.ogg new file mode 100644 index 0000000..1bd61e2 Binary files /dev/null and b/extensions/alphanumeric/sounds/5.ogg differ diff --git a/extensions/alphanumeric/sounds/6.ogg b/extensions/alphanumeric/sounds/6.ogg new file mode 100644 index 0000000..d1d328c Binary files /dev/null and b/extensions/alphanumeric/sounds/6.ogg differ diff --git a/extensions/alphanumeric/sounds/7.ogg b/extensions/alphanumeric/sounds/7.ogg new file mode 100644 index 0000000..ac7801b Binary files /dev/null and b/extensions/alphanumeric/sounds/7.ogg differ diff --git a/extensions/alphanumeric/sounds/8.ogg b/extensions/alphanumeric/sounds/8.ogg new file mode 100644 index 0000000..642baef Binary files /dev/null and b/extensions/alphanumeric/sounds/8.ogg differ diff --git a/extensions/alphanumeric/sounds/9.ogg b/extensions/alphanumeric/sounds/9.ogg new file mode 100644 index 0000000..f38f4e1 Binary files /dev/null and b/extensions/alphanumeric/sounds/9.ogg differ diff --git a/extensions/alphanumeric/sounds/a.ogg b/extensions/alphanumeric/sounds/a.ogg new file mode 100644 index 0000000..f026376 Binary files /dev/null and b/extensions/alphanumeric/sounds/a.ogg differ diff --git a/extensions/alphanumeric/sounds/b.ogg b/extensions/alphanumeric/sounds/b.ogg new file mode 100644 index 0000000..fe2a598 Binary files /dev/null and b/extensions/alphanumeric/sounds/b.ogg differ diff --git a/extensions/alphanumeric/sounds/c.ogg b/extensions/alphanumeric/sounds/c.ogg new file mode 100644 index 0000000..0352d75 Binary files /dev/null and b/extensions/alphanumeric/sounds/c.ogg differ diff --git a/extensions/alphanumeric/sounds/d.ogg b/extensions/alphanumeric/sounds/d.ogg new file mode 100644 index 0000000..0173723 Binary files /dev/null and b/extensions/alphanumeric/sounds/d.ogg differ diff --git a/extensions/alphanumeric/sounds/e.ogg b/extensions/alphanumeric/sounds/e.ogg new file mode 100644 index 0000000..08bf0eb Binary files /dev/null and b/extensions/alphanumeric/sounds/e.ogg differ diff --git a/extensions/alphanumeric/sounds/f.ogg b/extensions/alphanumeric/sounds/f.ogg new file mode 100644 index 0000000..ed04f99 Binary files /dev/null and b/extensions/alphanumeric/sounds/f.ogg differ diff --git a/extensions/alphanumeric/sounds/g.ogg b/extensions/alphanumeric/sounds/g.ogg new file mode 100644 index 0000000..c369761 Binary files /dev/null and b/extensions/alphanumeric/sounds/g.ogg differ diff --git a/extensions/alphanumeric/sounds/h.ogg b/extensions/alphanumeric/sounds/h.ogg new file mode 100644 index 0000000..6fbe26c Binary files /dev/null and b/extensions/alphanumeric/sounds/h.ogg differ diff --git a/extensions/alphanumeric/sounds/i.ogg b/extensions/alphanumeric/sounds/i.ogg new file mode 100644 index 0000000..0b58b88 Binary files /dev/null and b/extensions/alphanumeric/sounds/i.ogg differ diff --git a/extensions/alphanumeric/sounds/j.ogg b/extensions/alphanumeric/sounds/j.ogg new file mode 100644 index 0000000..ef76562 Binary files /dev/null and b/extensions/alphanumeric/sounds/j.ogg differ diff --git a/extensions/alphanumeric/sounds/k.ogg b/extensions/alphanumeric/sounds/k.ogg new file mode 100644 index 0000000..ade9e7b Binary files /dev/null and b/extensions/alphanumeric/sounds/k.ogg differ diff --git a/extensions/alphanumeric/sounds/l.ogg b/extensions/alphanumeric/sounds/l.ogg new file mode 100644 index 0000000..c9a78c8 Binary files /dev/null and b/extensions/alphanumeric/sounds/l.ogg differ diff --git a/extensions/alphanumeric/sounds/m.ogg b/extensions/alphanumeric/sounds/m.ogg new file mode 100644 index 0000000..41e8ee3 Binary files /dev/null and b/extensions/alphanumeric/sounds/m.ogg differ diff --git a/extensions/alphanumeric/sounds/n.ogg b/extensions/alphanumeric/sounds/n.ogg new file mode 100644 index 0000000..1650ae1 Binary files /dev/null and b/extensions/alphanumeric/sounds/n.ogg differ diff --git a/extensions/alphanumeric/sounds/o.ogg b/extensions/alphanumeric/sounds/o.ogg new file mode 100644 index 0000000..dbb8958 Binary files /dev/null and b/extensions/alphanumeric/sounds/o.ogg differ diff --git a/extensions/alphanumeric/sounds/p.ogg b/extensions/alphanumeric/sounds/p.ogg new file mode 100644 index 0000000..20fd960 Binary files /dev/null and b/extensions/alphanumeric/sounds/p.ogg differ diff --git a/extensions/alphanumeric/sounds/q.ogg b/extensions/alphanumeric/sounds/q.ogg new file mode 100644 index 0000000..f345c48 Binary files /dev/null and b/extensions/alphanumeric/sounds/q.ogg differ diff --git a/extensions/alphanumeric/sounds/r.ogg b/extensions/alphanumeric/sounds/r.ogg new file mode 100644 index 0000000..05aca8b Binary files /dev/null and b/extensions/alphanumeric/sounds/r.ogg differ diff --git a/extensions/alphanumeric/sounds/s.ogg b/extensions/alphanumeric/sounds/s.ogg new file mode 100644 index 0000000..6e61d83 Binary files /dev/null and b/extensions/alphanumeric/sounds/s.ogg differ diff --git a/extensions/alphanumeric/sounds/t.ogg b/extensions/alphanumeric/sounds/t.ogg new file mode 100644 index 0000000..0becaa7 Binary files /dev/null and b/extensions/alphanumeric/sounds/t.ogg differ diff --git a/extensions/alphanumeric/sounds/u.ogg b/extensions/alphanumeric/sounds/u.ogg new file mode 100644 index 0000000..7c0c0cc Binary files /dev/null and b/extensions/alphanumeric/sounds/u.ogg differ diff --git a/extensions/alphanumeric/sounds/v.ogg b/extensions/alphanumeric/sounds/v.ogg new file mode 100644 index 0000000..19086b6 Binary files /dev/null and b/extensions/alphanumeric/sounds/v.ogg differ diff --git a/extensions/alphanumeric/sounds/w.ogg b/extensions/alphanumeric/sounds/w.ogg new file mode 100644 index 0000000..8401ea8 Binary files /dev/null and b/extensions/alphanumeric/sounds/w.ogg differ diff --git a/extensions/alphanumeric/sounds/x.ogg b/extensions/alphanumeric/sounds/x.ogg new file mode 100644 index 0000000..a156cb1 Binary files /dev/null and b/extensions/alphanumeric/sounds/x.ogg differ diff --git a/extensions/alphanumeric/sounds/y.ogg b/extensions/alphanumeric/sounds/y.ogg new file mode 100644 index 0000000..65441dd Binary files /dev/null and b/extensions/alphanumeric/sounds/y.ogg differ diff --git a/extensions/alphanumeric/sounds/z.ogg b/extensions/alphanumeric/sounds/z.ogg new file mode 100644 index 0000000..0375f52 Binary files /dev/null and b/extensions/alphanumeric/sounds/z.ogg differ