ya2 · news · projects · code · about

refinements (building) master
authorFlavio Calva <f.calva@gmail.com>
Thu, 4 Aug 2022 18:32:52 +0000 (19:32 +0100)
committerFlavio Calva <f.calva@gmail.com>
Thu, 4 Aug 2022 18:32:52 +0000 (19:32 +0100)
76 files changed:
ChangeLog [deleted file]
audio/__init__.py [deleted file]
audio/music.py [deleted file]
gui/__init__.py [deleted file]
gui/menu.py [deleted file]
gui/sidepanel.py [deleted file]
logics/__init__.py [deleted file]
logics/app.py [deleted file]
logics/items/__init__.py [deleted file]
logics/items/background.py [deleted file]
logics/items/basketball.py [deleted file]
logics/items/box.py [deleted file]
logics/items/domino.py [deleted file]
logics/items/item.py [deleted file]
logics/items/shelf.py [deleted file]
logics/items/teetertooter.py [deleted file]
logics/posmgr.py [deleted file]
logics/scene.py [deleted file]
logics/scenes/__init__.py [deleted file]
logics/scenes/scene_basketball.py [deleted file]
logics/scenes/scene_box.py [deleted file]
logics/scenes/scene_domino.py [deleted file]
logics/scenes/scene_domino_box.py [deleted file]
logics/scenes/scene_domino_box_basketball.py [deleted file]
logics/scenes/scene_teeter_domino_box_basketball.py [deleted file]
logics/scenes/scene_teeter_tooter.py [deleted file]
main.py
pmachines/__init__.py [new file with mode: 0644]
pmachines/app.py [new file with mode: 0755]
pmachines/audio/__init__.py [new file with mode: 0644]
pmachines/audio/music.py [new file with mode: 0644]
pmachines/gui/__init__.py [new file with mode: 0644]
pmachines/gui/menu.py [new file with mode: 0644]
pmachines/gui/sidepanel.py [new file with mode: 0644]
pmachines/items/__init__.py [new file with mode: 0644]
pmachines/items/background.py [new file with mode: 0644]
pmachines/items/basketball.py [new file with mode: 0644]
pmachines/items/box.py [new file with mode: 0644]
pmachines/items/domino.py [new file with mode: 0644]
pmachines/items/item.py [new file with mode: 0644]
pmachines/items/shelf.py [new file with mode: 0644]
pmachines/items/teetertooter.py [new file with mode: 0644]
pmachines/posmgr.py [new file with mode: 0644]
pmachines/scene.py [new file with mode: 0644]
pmachines/scenes/__init__.py [new file with mode: 0644]
pmachines/scenes/scene_basketball.py [new file with mode: 0644]
pmachines/scenes/scene_box.py [new file with mode: 0644]
pmachines/scenes/scene_domino.py [new file with mode: 0644]
pmachines/scenes/scene_domino_box.py [new file with mode: 0644]
pmachines/scenes/scene_domino_box_basketball.py [new file with mode: 0644]
pmachines/scenes/scene_teeter_domino_box_basketball.py [new file with mode: 0644]
pmachines/scenes/scene_teeter_tooter.py [new file with mode: 0644]
prj.org
setup.py
tests/functional_test.py
tests/test_functional.py
tests/ya2/build/test_build.py
tests/ya2/utils/test_gameobject.py [deleted file]
ya2/build/blend2gltf.py
ya2/build/build.py
ya2/build/images.py
ya2/build/lang.py
ya2/build/models.py
ya2/build/screenshots.py
ya2/p3d/asserts.py [new file with mode: 0644]
ya2/p3d/gfx.py
ya2/p3d/gui.py
ya2/p3d/p3d.py
ya2/patterns/gameobject.py [deleted file]
ya2/patterns/observer.py
ya2/tools/pdfsingle.py [deleted file]
ya2/utils/cursor.py
ya2/utils/dictfile.py
ya2/utils/functional.py
ya2/utils/lang.py
ya2/utils/log.py

diff --git a/ChangeLog b/ChangeLog
deleted file mode 100644 (file)
index 30a7fb5..0000000
--- a/ChangeLog
+++ /dev/null
@@ -1,10 +0,0 @@
-* itch.io
-* flatpak
-* windows build
-* appimage
-* functional tests
-* first commit
-       * current rc *
-       * current *
-       * previous rc *
-       * previous *
diff --git a/audio/__init__.py b/audio/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/audio/music.py b/audio/music.py
deleted file mode 100644 (file)
index cf6c0c9..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-from os.path import dirname, exists, basename
-from platform import system
-from glob import glob
-from pathlib import Path
-from random import choice
-from logging import info
-from panda3d.core import AudioSound, Filename
-
-
-class MusicMgr:
-
-    def __init__(self, volume):
-        files = self.curr_path + 'assets/audio/music/*.ogg'
-        self._start_music(glob(files))
-        base.musicManager.setVolume(.8 * volume)
-        base.sfxManagerList[0].setVolume(volume)
-        taskMgr.add(self._on_frame, 'on frame music')
-
-    @property
-    def is_appimage(self):
-        par_path = str(Path(__file__).parent.absolute())
-        is_appimage = par_path.startswith('/tmp/.mount_Pmachi')
-        return is_appimage and par_path.endswith('/usr/bin')
-
-    @property
-    def curr_path(self):
-        if system() == 'Windows':
-            return ''
-        if exists('main.py'):
-            return ''
-        else:
-            par_path = str(Path(__file__).parent.absolute())
-        if self.is_appimage:
-            par_path = str(Path(par_path).absolute())
-        par_path += '/'
-        return par_path
-
-    def _start_music(self, files):
-        self._music = loader.load_music(choice(files))
-        info('playing music ' + self._music.get_name())
-        self._music.play()
-
-    def set_volume(self, volume):
-        base.musicManager.setVolume(.8 * volume)
-        base.sfxManagerList[0].setVolume(volume)
-
-    def _on_frame(self, task):
-        if self._music.status() == AudioSound.READY:
-            oggs = Filename(self.curr_path + 'assets/audio/music/*.ogg').to_os_specific()
-            files = glob(oggs)
-            rm_music = Filename(self.curr_path + 'assets/audio/music/' + basename(self._music.get_name())).to_os_specific()
-            # basename is needed in windows
-            files.remove(rm_music)
-            self._start_music(files)
-        return task.cont
diff --git a/gui/__init__.py b/gui/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/gui/menu.py b/gui/menu.py
deleted file mode 100644 (file)
index 335fade..0000000
+++ /dev/null
@@ -1,340 +0,0 @@
-from logging import info, debug
-from sys import platform, exit
-from os import environ, system
-from glob import glob
-from importlib import import_module
-from inspect import isclass
-from webbrowser import open_new_tab
-from xmlrpc.client import ServerProxy
-from panda3d.core import Texture, TextNode, WindowProperties, LVector2i, \
-    TextProperties, TextPropertiesManager, NodePath
-from direct.gui.DirectGui import DirectButton, DirectCheckButton, \
-    DirectOptionMenu, DirectSlider, DirectCheckButton
-from direct.gui.DirectGuiGlobals import FLAT
-from direct.gui.OnscreenText import OnscreenText
-from direct.showbase.DirectObject import DirectObject
-from ya2.utils.cursor import MouseCursor
-from ya2.p3d.p3d import LibP3d
-from logics.scene import Scene
-from panda3d.bullet import BulletWorld
-
-
-class Menu(DirectObject):
-
-    def __init__(self, fsm, lang_mgr, opt_file, music, pipeline, scenes, fun_test, pos_mgr):
-        super().__init__()
-        self._fsm = fsm
-        self._lang_mgr = lang_mgr
-        self._opt_file = opt_file
-        self._music = music
-        self._pipeline = pipeline
-        self._scenes = scenes
-        self._fun_test = fun_test
-        self._pos_mgr = pos_mgr
-        self._enforced_res = ''
-        self._cursor = MouseCursor(
-            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
-            (.01, .01))
-        self._font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
-        self._font.clear()
-        self._font.set_pixels_per_unit(60)
-        self._font.set_minfilter(Texture.FTLinearMipmapLinear)
-        self._font.set_outline((0, 0, 0, 1), .8, .2)
-        self._widgets = []
-        self._common = {
-            'scale': .12,
-            'text_font': self._font,
-            'text_fg': (.9, .9, .9, 1),
-            'relief': FLAT,
-            'frameColor': (.4, .4, .4, .14),
-            'rolloverSound': loader.load_sfx('assets/audio/sfx/rollover.ogg'),
-            'clickSound': loader.load_sfx('assets/audio/sfx/click.ogg')}
-        self._common_btn = {'frameSize': (-4.8, 4.8, -.6, 1.2)} | self._common
-        hlc = self._common_btn['frameColor']
-        hlc = (hlc[0] + .2, hlc[1] + .2, hlc[2] + .2, hlc[3] + .2)
-        self._common_opt = {
-            'item_frameColor': self._common_btn['frameColor'],
-            'popupMarker_frameColor': self._common_btn['frameColor'],
-            'item_relief': self._common_btn['relief'],
-            'item_text_font': self._common_btn['text_font'],
-            'item_text_fg': self._common_btn['text_fg'],
-            'textMayChange': 1,
-            'highlightColor': hlc,
-            'text_align': TextNode.A_center,
-        } | self._common_btn
-        f_s = self._common_opt['frameSize']
-        self._common_opt['frameSize'] = f_s[0], f_s[1] - .56, f_s[2], f_s[3]
-        self._common_slider = self._common | {
-            'range': (0, 1),
-            'thumb_frameColor': (.4, .4, .4, .4),
-            'thumb_scale': 1.6,
-            'scale': .4}
-        del self._common_slider['rolloverSound']
-        del self._common_slider['clickSound']
-        self._set_main()
-
-    def enforce_res(self, val):
-        self._enforced_res = val
-        info('enforced resolution: ' + val)
-
-    def _set_main(self):
-        self._pos_mgr.reset()
-        self._widgets = []
-        self._widgets += [DirectButton(
-            text=_('Play'), pos=(0, 1, .6), command=self.on_play,
-            **self._common_btn)]
-        self._pos_mgr.register('play', LibP3d.wdg_pos(self._widgets[-1]))
-        self._widgets += [DirectButton(
-            text=_('Options'), pos=(0, 1, .2), command=self.on_options,
-            **self._common_btn)]
-        self._pos_mgr.register('options', LibP3d.wdg_pos(self._widgets[-1]))
-        self._widgets += [DirectButton(
-            text=_('Credits'), pos=(0, 1, -.2), command=self.on_credits,
-            **self._common_btn)]
-        self._pos_mgr.register('credits', LibP3d.wdg_pos(self._widgets[-1]))
-        def btn_exit():
-            if self._fun_test:
-                ServerProxy('http://localhost:6000').destroy()
-            exit()
-        self._widgets += [DirectButton(
-            text=_('Exit'), pos=(0, 1, -.6), command=lambda: btn_exit(),
-            **self._common_btn)]
-        self._pos_mgr.register('exit', LibP3d.wdg_pos(self._widgets[-1]))
-        self._rearrange_width()
-        self.accept('enforce_resolution', self.enforce_res)
-
-    def _set_options(self):
-        self._pos_mgr.reset()
-        self._widgets = []
-        self._lang_funcs = [lambda: _('English'), lambda: _('Italian')]
-        items = [fnc() for fnc in self._lang_funcs]
-        inititem = {
-            'en': _('English'),
-            'it': _('Italian')
-        }[self._opt_file['settings']['language']]
-        btn = DirectOptionMenu(
-            text=_('Language'), items=items, initialitem=inititem,
-            pos=(0, 1, .8), command=self.on_language, **self._common_opt)
-        btn.popupMenu['frameColor'] = self._common_btn['frameColor']
-        btn.popupMenu['relief'] = self._common_btn['relief']
-        self._widgets += [btn]
-        pos_lang = LibP3d.wdg_pos(self._widgets[-1])
-        self._pos_mgr.register('languages', pos_lang)
-        pos_eng = pos_lang[0] + 300, pos_lang[1] - 57
-        pos_it = pos_lang[0] + 300, pos_lang[1] + 33
-        self._pos_mgr.register('english', pos_eng)
-        self._pos_mgr.register('italian', pos_it)
-        self._widgets += [OnscreenText(
-            _('Volume'), pos=(-.1, .55), font=self._common['text_font'],
-            scale=self._common['scale'], fg=self._common['text_fg'],
-            align=TextNode.A_right)]
-        self._widgets += [DirectSlider(
-            pos=(.5, 1, .57),
-            value=self._opt_file['settings']['volume'],
-            command=self.on_volume,
-            **self._common_slider)]
-        vol_pos = LibP3d.wdg_pos(self._widgets[-1])
-        self._pos_mgr.register('volume', vol_pos)
-        self._pos_mgr.register('volume_0', [vol_pos[0] - 153, vol_pos[1]])
-        self._slider = self._widgets[-1]
-        self._widgets += [DirectCheckButton(
-            text=_('Fullscreen'), pos=(0, 1, .3), command=self.on_fullscreen,
-            indicator_frameColor=self._common_opt['highlightColor'],
-            indicator_relief=self._common_btn['relief'],
-            indicatorValue=self._opt_file['settings']['fullscreen'],
-            **self._common_btn)]
-        self._pos_mgr.register('fullscreen', LibP3d.wdg_pos(self._widgets[-1]))
-        res = self._opt_file['settings']['resolution']
-        d_i = base.pipe.get_display_information()
-        def _res(idx):
-            return d_i.get_display_mode_width(idx), \
-                d_i.get_display_mode_height(idx)
-        resolutions = [
-            _res(idx) for idx in range(d_i.get_total_display_modes())]
-        resolutions = list(set(resolutions))
-        resolutions = sorted(resolutions)
-        resolutions = [(str(_res[0]), str(_res[1])) for _res in resolutions]
-        resolutions = ['x'.join(_res) for _res in resolutions]
-        if not res:
-            res = resolutions[-1]
-        btn = DirectOptionMenu(
-            text=_('Resolution'), items=resolutions, initialitem=res,
-            pos=(0, 1, .05), command=self.on_resolution, **self._common_opt)
-        btn.popupMenu['frameColor'] = self._common_btn['frameColor']
-        btn.popupMenu['relief'] = self._common_btn['relief']
-        self._widgets += [btn]
-        pos_res = LibP3d.wdg_pos(self._widgets[-1])
-        self._pos_mgr.register('resolutions', pos_res)  # 680 365
-        self._pos_mgr.register('res_1440x900', [pos_res[0] + 320, pos_res[1] + 75])
-        self._pos_mgr.register('res_1360x768', [pos_res[0] + 430, pos_res[1] -285])
-        self._widgets += [DirectCheckButton(
-            text=_('Antialiasing'), pos=(0, 1, -.2), command=self.on_aa,
-            indicator_frameColor=self._common_opt['highlightColor'],
-            indicator_relief=self._common_btn['relief'],
-            indicatorValue=self._opt_file['settings']['antialiasing'],
-            **self._common_btn)]
-        self._pos_mgr.register('aa', LibP3d.wdg_pos(self._widgets[-1]))
-        self._widgets += [DirectCheckButton(
-            text=_('Shadows'), pos=(0, 1, -.45), command=self.on_shadows,
-            indicator_frameColor=self._common_opt['highlightColor'],
-            indicator_relief=self._common_btn['relief'],
-            indicatorValue=self._opt_file['settings']['shadows'],
-            **self._common_btn)]
-        self._pos_mgr.register('shadows', LibP3d.wdg_pos(self._widgets[-1]))
-        self._widgets += [DirectButton(
-            text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
-            **self._common_btn)]
-        self._pos_mgr.register('back', LibP3d.wdg_pos(self._widgets[-1]))
-        self.accept('enforce_resolution', self.enforce_res)
-
-    def _set_credits(self):
-        self._pos_mgr.reset()
-        self._widgets = []
-        tp_scale = TextProperties()
-        tp_scale.set_text_scale(.64)
-        TextPropertiesManager.getGlobalPtr().setProperties('scale', tp_scale)
-        self._widgets += [OnscreenText(
-            _('Code and gfx\n  \1scale\1Flavio Calva\2\n\n\nMusic\n  \1scale\1Stefan Grossmann\2'),
-            pos=(-.9, .55), font=self._common['text_font'],
-            scale=self._common['scale'], fg=self._common['text_fg'],
-            align=TextNode.A_left)]
-        self._widgets += [DirectButton(
-            text=_('Website'), pos=(-.6, 1, .29), command=self.on_website,
-            **self._common_btn | {'scale': .08})]
-        self._widgets += [OnscreenText(
-            _('Special thanks to:\n  \1scale\1rdb\2\n  \1scale\1Luisa Tenuta\2\n  \1scale\1Damiana Ercolani\2'),
-            pos=(.1, .55), font=self._common['text_font'],
-            scale=self._common['scale'], fg=self._common['text_fg'],
-            align=TextNode.A_left)]
-        self._widgets += [DirectButton(
-            text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
-            **self._common_btn)]
-        self._pos_mgr.register('back', LibP3d.wdg_pos(self._widgets[-1]))
-        self.accept('enforce_resolution', self.enforce_res)
-
-    def on_play(self):
-        self._pos_mgr.reset()
-        self.destroy()
-        self._cursor = MouseCursor(
-            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
-            (.01, .01))
-        self._widgets = []
-        cmn = self._common_btn.copy() | {
-            'frameSize': (-2.4, 2.4, -2.4, 2.4),
-            'frameColor': (1, 1, 1, .8),
-            'text_scale': .64}
-        left = - (dx := .8) * (min(4, len(self._scenes)) - 1) / 2
-        for i, cls in enumerate(self._scenes):
-            top = .1 if len(self._scenes) < 5 else .6
-            row = 0 if i < 4 else 1
-            self._widgets += [DirectButton(
-                text=cls.name(), pos=(left + dx * (i % 4), 1, top - dx * row),
-                command=self.start, extraArgs=[cls], text_wordwrap=6,
-                frameTexture='assets/images/scenes/%s.dds' % cls.__name__,
-                **cmn)]
-            name = cls.__name__[5:].lower()
-            self._pos_mgr.register(name, LibP3d.wdg_pos(self._widgets[-1]))
-            for j in range(4):
-                tnode = self._widgets[-1].component('text%s' % j).textNode
-                height = - tnode.getLineHeight() / 2
-                height += (tnode.get_height() - tnode.get_line_height()) / 2
-                self._widgets[-1].component('text%s' % j).set_pos(0, 0, height)
-        self._widgets += [DirectButton(
-            text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
-            **self._common_btn)]
-        self._pos_mgr.register('back', LibP3d.wdg_pos(self._widgets[-1]))
-
-    def start(self, cls):
-        self._fsm.demand('Scene', cls)
-
-    def on_options(self):
-        self.destroy()
-        self._cursor = MouseCursor(
-            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
-            (.01, .01))
-        self._set_options()
-
-    def on_credits(self):
-        self.destroy()
-        self._cursor = MouseCursor(
-            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
-            (.01, .01))
-        self._set_credits()
-
-    def _rearrange_width(self):
-        max_width = 0
-        for wdg in self._widgets:
-            t_n = wdg.component('text0')
-            u_l = t_n.textNode.get_upper_left_3d()
-            l_r = t_n.textNode.get_lower_right_3d()
-            max_width = max(l_r[0] - u_l[0], max_width)
-        for wdg in self._widgets:
-            m_w = max_width / 2 + .8
-            wdg['frameSize'] = -m_w, m_w, wdg['frameSize'][2], wdg['frameSize'][3]
-
-    def on_language(self, arg):
-        lang_code = {
-            _('English'): 'en_EN',
-            _('Italian'): 'it_IT'}[arg]
-        self._lang_mgr.set_lang(lang_code)
-        self._opt_file['settings']['language'] = lang_code[:2]
-        self._opt_file.store()
-        self.on_options()
-
-    def on_volume(self):
-        self._opt_file['settings']['volume'] = self._slider['value']
-        self._music.set_volume(self._slider['value'])
-
-    def on_fullscreen(self, arg):
-        props = WindowProperties()
-        props.set_fullscreen(arg)
-        if not self._fun_test:
-            base.win.request_properties(props)
-            # if we actually switch to fullscreen during the tests then
-            # exwm inside qemu can't restore the correct resolution
-            # i may re-enable this if/when i run the tests onto a
-            # physical machine
-        self._opt_file['settings']['fullscreen'] = int(arg)
-        self._opt_file.store()
-
-    def on_resolution(self, arg):
-        info('on resolution: %s (%s)' % (arg, self._enforced_res))
-        arg = self._enforced_res or arg
-        info('set resolution: %s' % arg)
-        props = WindowProperties()
-        props.set_size(LVector2i(*[int(_res) for _res in arg.split('x')]))
-        base.win.request_properties(props)
-        self._opt_file['settings']['resolution'] = arg
-        self._opt_file.store()
-
-    def on_aa(self, arg):
-        self._pipeline.msaa_samples = 4 if arg else 1
-        debug(f'msaa: {self._pipeline.msaa_samples}')
-        self._opt_file['settings']['antialiasing'] = int(arg)
-        self._opt_file.store()
-
-    def on_shadows(self, arg):
-        self._pipeline.enable_shadows = int(arg)
-        debug(f'shadows: {self._pipeline.enable_shadows}')
-        self._opt_file['settings']['shadows'] = int(arg)
-        self._opt_file.store()
-
-    def on_website(self):
-        if platform.startswith('linux'):
-            environ['LD_LIBRARY_PATH'] = ''
-            system('xdg-open https://www.ya2.it')
-        else:
-            open_new_tab('https://www.ya2.it')
-
-    def on_back(self):
-        self._opt_file.store()
-        self.destroy()
-        self._cursor = MouseCursor(
-            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
-            (.01, .01))
-        self._set_main()
-
-    def destroy(self):
-        [wdg.destroy() for wdg in self._widgets]
-        self._cursor.destroy()
-        self.ignore('enforce_resolution')
diff --git a/gui/sidepanel.py b/gui/sidepanel.py
deleted file mode 100644 (file)
index 39afaa4..0000000
+++ /dev/null
@@ -1,132 +0,0 @@
-from textwrap import dedent
-from panda3d.core import GeomVertexData, GeomVertexFormat, Geom, \
-    GeomVertexWriter, GeomTriangles, GeomNode, Shader, Point3, Plane, Vec3
-from ya2.p3d.gfx import P3dGfxMgr
-
-
-class SidePanel:
-
-    def __init__(self, world, plane_node, top_l, bottom_r, y, items):
-        self._world = world
-        self._plane_node = plane_node
-        self._set((-1, 1), y)
-        self.update(items)
-
-    def update(self, items):
-        p_from, p_to = P3dGfxMgr.world_from_to((-1, 1))
-        for hit in self._world.ray_test_all(p_from, p_to).get_hits():
-            if hit.get_node() == self._plane_node:
-                pos = hit.get_hit_pos()
-        y = 0
-        corner = -20, 20
-        for item in items:
-            if not item._instantiated:
-                bounds = item._np.get_tight_bounds()
-                if bounds[1][1] > y:
-                    y = bounds[1][1]
-                icorner = item.get_corner()
-                icorner = P3dGfxMgr.screen_coord(icorner)
-                if icorner[0] > corner[0]:
-                    corner = icorner[0], corner[1]
-                if icorner[1] < corner[1]:
-                    corner = corner[0], icorner[1]
-        self._set((pos[0], pos[2]), y)
-        bounds = self._np.get_tight_bounds()
-        corner3d = bounds[1][0], bounds[1][1], bounds[0][2]
-        corner2d = P3dGfxMgr.screen_coord(corner3d)
-        plane = Plane(Vec3(0, 1, 0), y)
-        pos3d, near_pt, far_pt = Point3(), Point3(), Point3()
-        ar, margin = base.get_aspect_ratio(), .04
-        x = corner[0] / ar if ar >= 1 else corner[0]
-        z = corner[1] * ar if ar < 1 else corner[1]
-        x += margin / ar if ar >= 1 else margin
-        z -= margin * ar if ar < 1 else margin
-        base.camLens.extrude((x, z), near_pt, far_pt)
-        plane.intersects_line(
-            pos3d, render.get_relative_point(base.camera, near_pt),
-            render.get_relative_point(base.camera, far_pt))
-        corner_pos3d, near_pt, far_pt = Point3(), Point3(), Point3()
-        base.camLens.extrude((-1, 1), near_pt, far_pt)
-        plane.intersects_line(
-            corner_pos3d, render.get_relative_point(base.camera, near_pt),
-            render.get_relative_point(base.camera, far_pt))
-        self._np.set_pos((pos3d + corner_pos3d) / 2)
-        scale = Vec3(pos3d[0] - corner_pos3d[0], 1, corner_pos3d[2] - pos3d[2])
-        self._np.set_scale(scale)
-
-    def _set(self, pos, y):
-        if hasattr(self, '_np'):
-            self._np.remove_node()
-        vdata = GeomVertexData('quad', GeomVertexFormat.get_v3(), Geom.UHStatic)
-        vdata.setNumRows(2)
-        vertex = GeomVertexWriter(vdata, 'vertex')
-        vertex.add_data3(.5, 0, -.5)
-        vertex.add_data3(.5, 0, .5)
-        vertex.add_data3(-.5, 0, .5)
-        vertex.add_data3(-.5, 0, -.5)
-        prim = GeomTriangles(Geom.UHStatic)
-        prim.add_vertices(0, 1, 2)
-        prim.add_vertices(0, 2, 3)
-        prim.close_primitive()
-        geom = Geom(vdata)
-        geom.add_primitive(prim)
-        node = GeomNode('gnode')
-        node.add_geom(geom)
-        self._np = render.attach_new_node(node)
-        self._np.setTransparency(True)
-        self._np.set_pos(pos[0], y, pos[1])
-        vert = '''\
-            #version 130
-            uniform mat4 p3d_ModelViewProjectionMatrix;
-            in vec4 p3d_Vertex;
-            void main() {
-                gl_Position = p3d_ModelViewProjectionMatrix * p3d_Vertex; }'''
-        frag = '''\
-            #version 130
-            out vec4 p3d_FragColor;
-            void main() {
-                p3d_FragColor = vec4(.04, .04, .04, .08); }'''
-        self._np.set_shader(Shader.make(Shader.SL_GLSL, dedent(vert), dedent(frag)))
-        # mat = Material()
-        # mat.set_base_color((1, 1, 1, 1))
-        # mat.set_emission((1, 1, 1, 1))
-        # mat.set_metallic(.5)
-        # mat.set_roughness(.5)
-        # np.set_material(mat)
-        # texture_sz = 64
-        # base_color_pnm = PNMImage(texture_sz, texture_sz)
-        # base_color_pnm.fill(.1, .1, .1)
-        # base_color_pnm.add_alpha()
-        # base_color_pnm.alpha_fill(.04)
-        # base_color_tex = Texture('base color')
-        # base_color_tex.load(base_color_pnm)
-        # ts = TextureStage('base color')
-        # ts.set_mode(TextureStage.M_modulate)
-        # np.set_texture(ts, base_color_tex)
-        # emission_pnm = PNMImage(texture_sz, texture_sz)
-        # emission_pnm.fill(0, 0, 0)
-        # emission_tex = Texture('emission')
-        # emission_tex.load(emission_pnm)
-        # ts = TextureStage('emission')
-        # ts.set_mode(TextureStage.M_emission)
-        # np.set_texture(ts, emission_tex)
-        # metal_rough_pnm = PNMImage(texture_sz, texture_sz)
-        # ambient_occlusion = 1
-        # roughness = .5
-        # metallicity = .5
-        # metal_rough_pnm.fill(ambient_occlusion, roughness, metallicity)
-        # metal_rough_tex = Texture('ao metal roughness')
-        # metal_rough_tex.load(metal_rough_pnm)
-        # ts = TextureStage('ao metal roughness')
-        # ts.set_mode(TextureStage.M_selector)
-        # np.set_texture(ts, metal_rough_tex)
-        # normal_pnm = PNMImage(texture_sz, texture_sz)
-        # normal_pnm.fill(.5, .5, .1)
-        # normal_tex = Texture('normals')
-        # normal_tex.load(normal_pnm)
-        # ts = TextureStage('normals')
-        # ts.set_mode(TextureStage.M_normal)
-        # np.set_texture(ts, normal_tex)
-
-    def destroy(self):
-        self._np.remove_node()
diff --git a/logics/__init__.py b/logics/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/logics/app.py b/logics/app.py
deleted file mode 100755 (executable)
index 0ee87cc..0000000
+++ /dev/null
@@ -1,281 +0,0 @@
-import argparse
-import simplepbr
-#import gltf
-from glob import glob
-from importlib import import_module
-from inspect import isclass
-from sys import platform, argv, exit
-from logging import info, debug
-from os.path import exists
-from os import makedirs
-from panda3d.core import Filename, load_prc_file_data, AntialiasAttrib, \
-    Texture, WindowProperties, LVector2i, TextNode
-from panda3d.bullet import BulletWorld, BulletDebugNode
-from direct.showbase.ShowBase import ShowBase
-from direct.gui.OnscreenText import OnscreenText
-from direct.fsm.FSM import FSM
-from audio.music import MusicMgr
-from logics.items.background import Background
-from gui.menu import Menu
-from logics.scene import Scene
-from logics.scenes.scene_basketball import SceneBasketBall
-from logics.scenes.scene_box import SceneBox
-from logics.scenes.scene_domino_box_basketball import SceneDominoBoxBasketball
-from logics.scenes.scene_domino_box import SceneDominoBox
-from logics.scenes.scene_domino import SceneDomino
-from logics.scenes.scene_teeter_domino_box_basketball import SceneTeeterDominoBoxBasketball
-from logics.scenes.scene_teeter_tooter import SceneTeeterTooter
-from logics.posmgr import PositionMgr
-from ya2.utils.dictfile import DctFile
-from ya2.p3d.p3d import LibP3d
-from ya2.utils.lang import LangMgr
-from ya2.utils.log import LogMgr
-from ya2.utils.functional import FunctionalTest
-
-
-class MainFsm(FSM):
-
-    def __init__(self, pmachines):
-        super().__init__('Main FSM')
-        self._pmachines = pmachines
-
-    def enterMenu(self):
-        self._pmachines.on_menu_enter()
-
-    def exitMenu(self):
-        self._pmachines.on_menu_exit()
-
-    def enterScene(self, cls):
-        self._pmachines.on_scene_enter(cls)
-
-    def exitScene(self):
-        self._pmachines.on_scene_exit()
-
-
-class PmachinesApp:
-
-    scenes = [
-        SceneDomino,
-        SceneBox,
-        SceneDominoBox,
-        SceneBasketBall,
-        SceneDominoBoxBasketball,
-        SceneTeeterTooter,
-        SceneTeeterDominoBoxBasketball]
-
-    def __init__(self):
-        info('platform: %s' % platform)
-        info('exists main.py: %s' % exists('main.py'))
-        self._args = args = self._parse_args()
-        self._configure(args)
-        self.base = ShowBase()
-        self._pipeline = None
-        self.updating = args.update
-        self.version = args.version
-        self.log_mgr = LogMgr.init_cls()(self)
-        self._pos_mgr = PositionMgr()
-        self._prepare_window(args)
-        if args.update:
-            return
-        if args.functional_test:
-            self._options['settings']['volume'] = 0
-        self._music = MusicMgr(self._options['settings']['volume'])
-        self.lang_mgr = LangMgr(self._options['settings']['language'],
-                                'pmachines',
-                                'assets/locale/')
-        self._fsm = MainFsm(self)
-        if args.screenshot:
-            cls = [cls for cls in self.scenes if cls.__name__ == args.screenshot][0]
-            scene = cls(BulletWorld(), None, True, False, lambda: None, self.scenes)
-            scene.screenshot()
-            scene.destroy()
-            exit()
-        elif self._options['development']['auto_start']:
-            mod_name = 'logics.scenes.scene_' + self._options['development']['auto_start']
-            for member in import_module(mod_name).__dict__.values():
-                if isclass(member) and issubclass(member, Scene) and \
-                        member != Scene:
-                    cls = member
-            self._fsm.demand('Scene', cls)
-        else:
-            self._fsm.demand('Menu')
-        if args.functional_test or args.functional_ref:
-            FunctionalTest(args.functional_ref, self._pos_mgr)
-
-    def on_menu_enter(self):
-        self._menu_bg = Background()
-        self._menu = Menu(
-            self._fsm, self.lang_mgr, self._options, self._music,
-            self._pipeline, self.scenes, self._args.functional_test or self._args.functional_ref,
-            self._pos_mgr)
-
-    def on_home(self):
-        self._fsm.demand('Menu')
-
-    def on_menu_exit(self):
-        self._menu_bg.destroy()
-        self._menu.destroy()
-
-    def on_scene_enter(self, cls):
-        self._set_physics()
-        self._scene = cls(
-            self.world, self.on_home,
-            self._options['development']['auto_close_instructions'],
-            self._options['development']['debug_items'],
-            self.reload,
-            self.scenes,
-            self._pos_mgr)
-
-    def on_scene_exit(self):
-        self._unset_physics()
-        self._scene.destroy()
-
-    def reload(self, cls):
-        self._fsm.demand('Scene', cls)
-
-    def _configure(self, args):
-        load_prc_file_data('', 'window-title pmachines')
-        load_prc_file_data('', 'framebuffer-srgb true')
-        load_prc_file_data('', 'sync-video true')
-        if args.functional_test or args.functional_ref:
-            load_prc_file_data('', 'win-size 1360 768')
-            # otherwise it is not centered in exwm
-        # load_prc_file_data('', 'threading-model Cull/Draw')
-        # it freezes when you go to the next scene
-        if args.screenshot:
-            load_prc_file_data('', 'window-type offscreen')
-            load_prc_file_data('', 'audio-library-name null')
-
-    def _parse_args(self):
-        parser = argparse.ArgumentParser()
-        parser.add_argument('--update', action='store_true')
-        parser.add_argument('--version', action='store_true')
-        parser.add_argument('--optfile')
-        parser.add_argument('--screenshot')
-        parser.add_argument('--functional-test', action='store_true')
-        parser.add_argument('--functional-ref', action='store_true')
-        cmd_line = [arg for arg in iter(argv[1:]) if not arg.startswith('-psn_')]
-        args = parser.parse_args(cmd_line)
-        return args
-
-    def _prepare_window(self, args):
-        data_path = ''
-        if (platform.startswith('win') or platform.startswith('linux')) and (
-                not exists('main.py') or __file__.startswith('/app/bin/')):
-            # it is the deployed version for windows
-            data_path = str(Filename.get_user_appdata_directory()) + '/pmachines'
-            home = '/home/flavio'  # we must force this for wine
-            if data_path.startswith('/c/users/') and exists(home + '/.wine/'):
-                data_path = home + '/.wine/drive_' + data_path[1:]
-            info('creating dirs: %s' % data_path)
-            makedirs(data_path, exist_ok=True)
-        optfile = args.optfile if args.optfile else 'options.ini'
-        info('data path: %s' % data_path)
-        info('option file: %s' % optfile)
-        info('fixed path: %s' % LibP3d.fixpath(data_path + '/' + optfile))
-        default_opt = {
-            'settings': {
-                'volume': 1,
-                'language': 'en',
-                'fullscreen': 1,
-                'resolution': '',
-                'antialiasing': 1,
-                'shadows': 1},
-            'development': {
-                'simplepbr': 1,
-                'verbose_log': 0,
-                'physics_debug': 0,
-                'auto_start': 0,
-                'auto_close_instructions': 0,
-                'show_buffers': 0,
-                'debug_items': 0,
-                'mouse_coords': 0,
-                'fps': 0}}
-        opt_path = LibP3d.fixpath(data_path + '/' + optfile) if data_path else optfile
-        opt_exists = exists(opt_path)
-        self._options = DctFile(
-            LibP3d.fixpath(data_path + '/' + optfile) if data_path else optfile,
-            default_opt)
-        if not opt_exists:
-            self._options.store()
-        res = self._options['settings']['resolution']
-        if res:
-            res = LVector2i(*[int(_res) for _res in res.split('x')])
-        else:
-            resolutions = []
-            if not self.version:
-                d_i = base.pipe.get_display_information()
-                def _res(idx):
-                    return d_i.get_display_mode_width(idx), \
-                        d_i.get_display_mode_height(idx)
-                resolutions = [
-                    _res(idx) for idx in range(d_i.get_total_display_modes())]
-            res = sorted(resolutions)[-1]
-        fullscreen = self._options['settings']['fullscreen']
-        props = WindowProperties()
-        if args.functional_test or args.functional_ref:
-            fullscreen = False
-        else:
-            props.set_size(res)
-        props.set_fullscreen(fullscreen)
-        props.set_icon_filename('assets/images/icon/pmachines.ico')
-        if not args.screenshot and not self.version:
-            base.win.request_properties(props)
-        #gltf.patch_loader(base.loader)
-        if self._options['development']['simplepbr'] and not self.version:
-            self._pipeline = simplepbr.init(
-                use_normal_maps=True,
-                use_emission_maps=False,
-                use_occlusion_maps=True,
-                msaa_samples=4 if self._options['settings']['antialiasing'] else 1,
-                enable_shadows=int(self._options['settings']['shadows']))
-            debug(f'msaa: {self._pipeline.msaa_samples}')
-            debug(f'shadows: {self._pipeline.enable_shadows}')
-        render.setAntialias(AntialiasAttrib.MAuto)
-        self.base.set_background_color(0, 0, 0, 1)
-        self.base.disable_mouse()
-        if self._options['development']['show_buffers']:
-            base.bufferViewer.toggleEnable()
-        if self._options['development']['fps']:
-            base.set_frame_rate_meter(True)
-        #self.base.accept('window-event', self._on_win_evt)
-        self.base.accept('aspectRatioChanged', self._on_aspect_ratio_changed)
-        if self._options['development']['mouse_coords']:
-            coords_txt = OnscreenText(
-                '', parent=base.a2dTopRight, scale=0.04,
-                pos=(-.03, -.06), fg=(.9, .9, .9, 1), align=TextNode.A_right)
-            def update_coords(task):
-                txt = '%s %s' % (int(base.win.get_pointer(0).x),
-                                 int(base.win.get_pointer(0).y))
-                coords_txt['text'] = txt
-                return task.cont
-            taskMgr.add(update_coords, 'update_coords')
-
-    def _set_physics(self):
-        if self._options['development']['physics_debug']:
-            debug_node = BulletDebugNode('Debug')
-            debug_node.show_wireframe(True)
-            debug_node.show_constraints(True)
-            debug_node.show_bounding_boxes(True)
-            debug_node.show_normals(True)
-            self._debug_np = render.attach_new_node(debug_node)
-            self._debug_np.show()
-        self.world = BulletWorld()
-        self.world.set_gravity((0, 0, -9.81))
-        if self._options['development']['physics_debug']:
-            self.world.set_debug_node(self._debug_np.node())
-        def update(task):
-            dt = globalClock.get_dt()
-            self.world.do_physics(dt, 10, 1/180)
-            return task.cont
-        self._phys_tsk = taskMgr.add(update, 'update')
-
-    def _unset_physics(self):
-        if self._options['development']['physics_debug']:
-            self._debug_np.remove_node()
-        self.world = None
-        taskMgr.remove(self._phys_tsk)
-
-    def _on_aspect_ratio_changed(self):
-        if self._fsm.state == 'Scene':
-            self._scene.on_aspect_ratio_changed()
diff --git a/logics/items/__init__.py b/logics/items/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/logics/items/background.py b/logics/items/background.py
deleted file mode 100644 (file)
index 5203202..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-from itertools import product
-from panda3d.core import NodePath
-from ya2.p3d.gfx import set_srgb
-
-
-class Background:
-
-    def __init__(self):
-        self._root = NodePath('background_root')
-        self._root.reparent_to(render)
-        ncols, nrows = 16, 8
-        start_size, end_size = 5, 2.5
-        offset = 5
-        for col, row in product(range(ncols), range(nrows)):
-            model = loader.load_model('assets/models/bam/background/background.bam')
-            model.set_scale(end_size / start_size)
-            model.reparent_to(self._root)
-            total_width, total_height = end_size * ncols, end_size * nrows
-            left, bottom = -total_width/2, -total_height/2
-            model.set_pos(left + end_size * col, offset, bottom + end_size * row)
-        self._root.clear_model_nodes()
-        self._root.flatten_strong()
-        set_srgb(self._root)
-
-    def destroy(self):
-        self._root.remove_node()
diff --git a/logics/items/basketball.py b/logics/items/basketball.py
deleted file mode 100644 (file)
index 7ab294f..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-from panda3d.bullet import BulletSphereShape, BulletRigidBodyNode, BulletGhostNode
-from logics.items.item import Item
-
-
-class Basketball(Item):
-
-    def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.92, friction=.6):
-        super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/basketball/basketball.bam', .4, mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction)
-
-    def _set_shape(self, apply_scale=True):
-        self.node.add_shape(BulletSphereShape(1))
diff --git a/logics/items/box.py b/logics/items/box.py
deleted file mode 100644 (file)
index d3ee01d..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-from panda3d.bullet import BulletBoxShape, BulletRigidBodyNode, BulletGhostNode
-from logics.items.item import Item
-
-
-class Box(Item):
-
-    def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.8, model_scale=1):
-        super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/box/box.bam', mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction, model_scale=model_scale)
-
-    def _set_shape(self, apply_scale=True):
-        self.node.add_shape(BulletBoxShape((.5, .5, .5)))
-
-
-class HitStrategy:
-
-    def __init__(self, hit_by, node, world):
-        self._hit_by = hit_by
-        self._node = node
-        self._world = world
-
-    def win_condition(self):
-        for contact in self._world.contact_test(self._node).get_contacts():
-            other = contact.get_node1() if contact.get_node0() == self._node else contact.get_node0()
-            if other == self._hit_by.node:
-                return True
diff --git a/logics/items/domino.py b/logics/items/domino.py
deleted file mode 100644 (file)
index 8071e69..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-from panda3d.bullet import BulletBoxShape, BulletRigidBodyNode, BulletGhostNode
-from logics.items.item import Item, StillStrategy
-
-
-class Domino(Item):
-
-    def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.6):
-        super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/domino/domino.bam', mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction)
-
-    def _set_shape(self, apply_scale=True):
-        self.node.add_shape(BulletBoxShape((.1, .25, .5)))
-
-
-class UpStrategy(StillStrategy):
-
-    def __init__(self, np, tgt_degrees):
-        super().__init__(np)
-        self._tgt_degrees = tgt_degrees
-        self._np = np
-
-    def win_condition(self):
-        return super().win_condition() and abs(self._np.get_r()) <= self._tgt_degrees
-
-
-class DownStrategy(StillStrategy):
-
-    def __init__(self, np, tgt_degrees):
-        super().__init__(np)
-        self._tgt_degrees = tgt_degrees
-        self._np = np
-
-    def win_condition(self):
-        return self._np.get_z() < -6 or super().win_condition() and abs(self._np.get_r()) >= self._tgt_degrees
diff --git a/logics/items/item.py b/logics/items/item.py
deleted file mode 100644 (file)
index 9f2bf5d..0000000
+++ /dev/null
@@ -1,339 +0,0 @@
-from panda3d.core import CullFaceAttrib, Point3, NodePath, Point2, Texture, \
-    Plane, Vec3, BitMask32
-from panda3d.bullet import BulletBoxShape, BulletRigidBodyNode, BulletGhostNode
-from direct.gui.OnscreenText import OnscreenText
-from ya2.p3d.gfx import P3dGfxMgr, set_srgb
-
-
-class Command:
-
-    def __init__(self, pos, rot):
-        self.pos = pos
-        self.rot = rot
-
-
-class FixedStrategy:
-
-    def win_condition(self):
-        return True
-
-
-class StillStrategy:
-
-    def __init__(self, np):
-        self._np = np
-        self._positions = []
-        self._rotations = []
-
-    def win_condition(self):
-        self._positions += [self._np.get_pos()]
-        self._rotations += [self._np.get_hpr()]
-        if len(self._positions) > 30:
-            self._positions.pop(0)
-        if len(self._rotations) > 30:
-            self._rotations.pop(0)
-        if len(self._positions) < 28:
-            return
-        avg_x = sum(pos.x for pos in self._positions) / len(self._positions)
-        avg_y = sum(pos.y for pos in self._positions) / len(self._positions)
-        avg_z = sum(pos.z for pos in self._positions) / len(self._positions)
-        avg_h = sum(rot.x for rot in self._rotations) / len(self._rotations)
-        avg_p = sum(rot.y for rot in self._rotations) / len(self._rotations)
-        avg_r = sum(rot.z for rot in self._rotations) / len(self._rotations)
-        avg_pos = Point3(avg_x, avg_y, avg_z)
-        avg_rot = Point3(avg_h, avg_p, avg_r)
-        return all((pos - avg_pos).length() < .1 for pos in self._positions) and \
-            all((rot - avg_rot).length() < 1 for rot in self._rotations)
-
-
-class Item:
-
-    def __init__(self, world, plane_node, cb_inst, curr_bottom, scene_repos, model_path, model_scale=1, exp_num_contacts=1, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.5):
-        self._world = world
-        self._plane_node = plane_node
-        self._count = count
-        self._cb_inst = cb_inst
-        self._paused = False
-        self._overlapping = False
-        self._first_command = True
-        self.repos_done = False
-        self._mass = mass
-        self._pos = pos
-        self._r = r
-        self.strategy = FixedStrategy()
-        self._exp_num_contacts = exp_num_contacts
-        self._curr_bottom = curr_bottom
-        self._scene_repos = scene_repos
-        self._model_scale = model_scale
-        self._model_path = model_path
-        self._commands = []
-        self._command_idx = -1
-        self._restitution = restitution
-        self._friction = friction
-        self._positions = []
-        self._rotations = []
-        if count:
-            self.node = BulletGhostNode(self.__class__.__name__)
-        else:
-            self.node = BulletRigidBodyNode(self.__class__.__name__)
-        self._set_shape(count)
-        self._np = render.attach_new_node(self.node)
-        if count:
-            world.attach_ghost(self.node)
-        else:
-            world.attach_rigid_body(self.node)
-        self._model = loader.load_model(model_path)
-        set_srgb(self._model)
-        self._model.flatten_light()
-        self._model.reparent_to(self._np)
-        self._np.set_scale(model_scale)
-        self._np.flatten_strong()
-        if count:
-            self._set_outline_model()
-            set_srgb(self._outline_model)
-            self._model.hide(BitMask32(0x01))
-            self._outline_model.hide(BitMask32(0x01))
-        self._start_drag_pos = None
-        self._prev_rot_info = None
-        self._last_nonoverlapping_pos = None
-        self._last_nonoverlapping_rot = None
-        self._instantiated = not count
-        self.interactable = count
-        self._box_tsk = taskMgr.add(self.on_frame, 'on_frame')
-        if count:
-            self._repos()
-        else:
-            self._np.set_pos(pos)
-            self._np.set_r(r)
-
-    def _set_shape(self, apply_scale=True):
-        pass
-
-    def set_strategy(self, strategy):
-        self.strategy = strategy
-
-    def _repos(self):
-        p_from, p_to = P3dGfxMgr.world_from_to((-1, 1))
-        for hit in self._world.ray_test_all(p_from, p_to).get_hits():
-            if hit.get_node() == self._plane_node:
-                pos = hit.get_hit_pos()
-        corner = P3dGfxMgr.screen_coord(pos)
-        bounds = self._np.get_tight_bounds()
-        bounds = bounds[0] - self._np.get_pos(), bounds[1] - self._np.get_pos()
-        self._np.set_pos(pos)
-        plane = Plane(Vec3(0, 1, 0), bounds[0][1])
-        pos3d, near_pt, far_pt = Point3(), Point3(), Point3()
-        margin, ar = .04, base.get_aspect_ratio()
-        margin_x = margin / ar if ar >= 1 else margin
-        margin_z = margin * ar if ar < 1 else margin
-        top = self._curr_bottom()
-        base.camLens.extrude((-1 + margin_x, top - margin_z), near_pt, far_pt)
-        plane.intersects_line(
-            pos3d, render.get_relative_point(base.camera, near_pt),
-            render.get_relative_point(base.camera, far_pt))
-        delta = Vec3(bounds[1][0], bounds[1][1], bounds[0][2])
-        self._np.set_pos(pos3d + delta)
-        if not hasattr(self, '_txt'):
-            font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
-            font.clear()
-            font.set_pixels_per_unit(60)
-            font.set_minfilter(Texture.FTLinearMipmapLinear)
-            font.set_outline((0, 0, 0, 1), .8, .2)
-            self._txt = OnscreenText(
-                str(self._count), font=font, scale=0.06, fg=(.9, .9, .9, 1))
-        pos = self._np.get_pos() + (bounds[1][0], bounds[0][1], bounds[0][2])
-        p2d = P3dGfxMgr.screen_coord(pos)
-        self._txt['pos'] = p2d
-        self.repos_done = True
-
-    def repos_x(self, x):
-        self._np.set_x(x)
-        bounds = self._np.get_tight_bounds()
-        bounds = bounds[0] - self._np.get_pos(), bounds[1] - self._np.get_pos()
-        pos = self._np.get_pos() + (bounds[1][0], bounds[0][1], bounds[0][2])
-        p2d = P3dGfxMgr.screen_coord(pos)
-        self._txt['pos'] = p2d
-
-    def get_bottom(self):
-        bounds = self._np.get_tight_bounds()
-        bounds = bounds[0] - self._np.get_pos(), bounds[1] - self._np.get_pos()
-        pos = self._np.get_pos() + (bounds[1][0], bounds[1][1], bounds[0][2])
-        p2d = P3dGfxMgr.screen_coord(pos)
-        ar = base.get_aspect_ratio()
-        return p2d[1] if ar >= 1 else (p2d[1] * ar)
-
-    def get_corner(self):
-        bounds = self._np.get_tight_bounds()
-        return bounds[1][0], bounds[1][1], bounds[0][2]
-
-    def _set_outline_model(self):
-        self._outline_model = loader.load_model(self._model_path)
-        #clockw = CullFaceAttrib.MCullClockwise
-        #self._outline_model.set_attrib(CullFaceAttrib.make(clockw))
-        self._outline_model.set_attrib(CullFaceAttrib.make_reverse())
-        self._outline_model.reparent_to(self._np)
-        self._outline_model.set_scale(1.08)
-        self._outline_model.set_light_off()
-        self._outline_model.set_color(.4, .4, .4, 1)
-        self._outline_model.set_color_scale(.4, .4, .4, 1)
-        self._outline_model.hide()
-
-    def on_frame(self, task):
-        self._np.set_y(0)
-        return task.cont
-
-    def undo(self):
-        self._command_idx -= 1
-        self._np.set_pos(self._commands[self._command_idx].pos)
-        self._np.set_hpr(self._commands[self._command_idx].rot)
-
-    def redo(self):
-        self._command_idx += 1
-        self._np.set_pos(self._commands[self._command_idx].pos)
-        self._np.set_hpr(self._commands[self._command_idx].rot)
-
-    def play(self):
-        if not self._instantiated:
-            return
-        self._world.remove_rigid_body(self.node)
-        self.node.set_mass(self._mass)
-        self._world.attach_rigid_body(self.node)
-        self.node.set_restitution(self._restitution)
-        self.node.set_friction(self._friction)
-
-    def on_click_l(self, pos):
-        if self._paused: return
-        self._start_drag_pos = pos, self._np.get_pos()
-        loader.load_sfx('assets/audio/sfx/grab.ogg').play()
-        if not self._instantiated:
-            self._world.remove_ghost(self.node)
-            pos = self._np.get_pos()
-            self._np.remove_node()
-            self.node = BulletRigidBodyNode('box')
-            self._set_shape()
-            self._np = render.attach_new_node(self.node)
-            self._world.attach_rigid_body(self.node)
-            self._model.reparent_to(self._np)
-            self._np.set_pos(pos)
-            self._set_outline_model()
-            self._np.set_scale(self._model_scale)
-            self._model.show(BitMask32(0x01))
-            self._outline_model.hide(BitMask32(0x01))
-            self._instantiated = True
-            self._txt.destroy()
-            self._count -= 1
-            if self._count:
-                item = self.__class__(self._world, self._plane_node, self._cb_inst, self._curr_bottom, self._scene_repos, count=self._count, mass=self._mass, pos=self._pos, r=self._r)  # pylint: disable=no-value-for-parameter
-                self._cb_inst(item)
-            self._scene_repos()
-
-    def on_click_r(self, pos):
-        if self._paused or not self._instantiated: return
-        self._prev_rot_info = pos, self._np.get_pos(), self._np.get_r()
-        loader.load_sfx('assets/audio/sfx/grab.ogg').play()
-
-    def on_release(self):
-        if self._start_drag_pos or self._prev_rot_info:
-            loader.load_sfx('assets/audio/sfx/release.ogg').play()
-            self._command_idx += 1
-            self._commands = self._commands[:self._command_idx]
-            self._commands += [Command(self._np.get_pos(), self._np.get_hpr())]
-            self._first_command = False
-        self._start_drag_pos = self._prev_rot_info = None
-        if self._overlapping:
-            self._np.set_pos(self._last_nonoverlapping_pos)
-            self._np.set_hpr(self._last_nonoverlapping_rot)
-            self._outline_model.set_color(.4, .4, .4, 1)
-            self._outline_model.set_color_scale(.4, .4, .4, 1)
-            self._overlapping = False
-
-    def on_mouse_on(self):
-        if not self._paused and self.interactable:
-            self._outline_model.show()
-
-    def on_mouse_off(self):
-        if self._start_drag_pos or self._prev_rot_info: return
-        if self.interactable:
-            self._outline_model.hide()
-
-    def on_mouse_move(self, pos):
-        if self._start_drag_pos:
-            d_pos =  pos - self._start_drag_pos[0]
-            self._np.set_pos(self._start_drag_pos[1] + d_pos)
-        if self._prev_rot_info:
-            start_vec = self._prev_rot_info[0] - self._prev_rot_info[1]
-            curr_vec = pos - self._prev_rot_info[1]
-            d_angle = curr_vec.signed_angle_deg(start_vec, (0, -1, 0))
-            self._np.set_r(self._prev_rot_info[2] + d_angle)
-            self._prev_rot_info = pos, self._np.get_pos(), self._np.get_r()
-        if self._start_drag_pos or self._prev_rot_info:
-            res = self._world.contact_test(self.node)
-            nres = res.get_num_contacts()
-            if nres <= self._exp_num_contacts:
-                self._overlapping = False
-                self._outline_model.set_color(.4, .4, .4, 1)
-                self._outline_model.set_color_scale(.4, .4, .4, 1)
-            if nres > self._exp_num_contacts and not self._overlapping:
-                actual_nres = 0
-                for contact in res.get_contacts():
-                    for node in [contact.get_node0(), contact.get_node1()]:
-                        if isinstance(node, BulletRigidBodyNode) and \
-                                node != self.node:
-                            actual_nres += 1
-                if actual_nres >= 1:
-                    self._overlapping = True
-                    loader.load_sfx('assets/audio/sfx/overlap.ogg').play()
-                    self._outline_model.set_color(.9, .1, .1, 1)
-                    self._outline_model.set_color_scale(.9, .1, .1, 1)
-        if not self._overlapping:
-            self._last_nonoverlapping_pos = self._np.get_pos()
-            self._last_nonoverlapping_rot = self._np.get_hpr()
-
-    def on_aspect_ratio_changed(self):
-        if not self._instantiated:
-            self._repos()
-
-    def store_state(self):
-        self._paused = True
-        self._model.set_transparency(True)
-        self._model.set_alpha_scale(.3)
-        if hasattr(self, '_txt') and not self._txt.is_empty():
-            self._txt.set_alpha_scale(.3)
-
-    def restore_state(self):
-        self._paused = False
-        self._model.set_alpha_scale(1)
-        if hasattr(self, '_txt') and not self._txt.is_empty():
-            self._txt.set_alpha_scale(1)
-
-    def fail_condition(self):
-        if self._np.get_z() < -6:
-            return True
-        self._positions += [self._np.get_pos()]
-        self._rotations += [self._np.get_hpr()]
-        if len(self._positions) > 30:
-            self._positions.pop(0)
-        if len(self._rotations) > 30:
-            self._rotations.pop(0)
-        if len(self._positions) < 28:
-            return
-        avg_x = sum(pos.x for pos in self._positions) / len(self._positions)
-        avg_y = sum(pos.y for pos in self._positions) / len(self._positions)
-        avg_z = sum(pos.z for pos in self._positions) / len(self._positions)
-        avg_h = sum(rot.x for rot in self._rotations) / len(self._rotations)
-        avg_p = sum(rot.y for rot in self._rotations) / len(self._rotations)
-        avg_r = sum(rot.z for rot in self._rotations) / len(self._rotations)
-        avg_pos = Point3(avg_x, avg_y, avg_z)
-        avg_rot = Point3(avg_h, avg_p, avg_r)
-        return all((pos - avg_pos).length() < .1 for pos in self._positions) and \
-            all((rot - avg_rot).length() < 1 for rot in self._rotations)
-
-    def destroy(self):
-        self._np.remove_node()
-        taskMgr.remove(self._box_tsk)
-        if hasattr(self, '_txt'):
-            self._txt.destroy()
-        if not self._instantiated:
-            self._world.remove_ghost(self.node)
-        else:
-            self._world.remove_rigid_body(self.node)
diff --git a/logics/items/shelf.py b/logics/items/shelf.py
deleted file mode 100644 (file)
index b88ecd0..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-from panda3d.bullet import BulletBoxShape, BulletRigidBodyNode, BulletGhostNode
-from logics.items.item import Item
-
-
-class Shelf(Item):
-
-    def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.6):
-        super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/shelf/shelf.bam', mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction)
-
-    def _set_shape(self, apply_scale=True):
-        self.node.add_shape(BulletBoxShape((1, .5, .05)))
diff --git a/logics/items/teetertooter.py b/logics/items/teetertooter.py
deleted file mode 100644 (file)
index 7dc3349..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-from panda3d.core import TransformState
-from panda3d.bullet import BulletCylinderShape, BulletRigidBodyNode, BulletGhostNode, YUp, ZUp
-from logics.items.item import Item
-
-
-class TeeterTooter(Item):
-
-    def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.5):
-        super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/teeter_tooter/teeter_tooter.bam', exp_num_contacts=2, mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction, model_scale=.5)
-
-    def _set_shape(self, apply_scale=True):
-        scale = self._model_scale if apply_scale else 1
-        self.node.add_shape(
-            BulletCylinderShape(.1, 1.6, YUp),
-            TransformState.makePos((0, 0, scale * .36)))
-        self.node.add_shape(
-            BulletCylinderShape(.1, .7, ZUp),
-            TransformState.makePos((0, scale * .8, scale * -.1)))
-        self.node.add_shape(
-            BulletCylinderShape(.1, .7, ZUp),
-            TransformState.makePos((0, scale * -.8, scale * -.1)))
diff --git a/logics/posmgr.py b/logics/posmgr.py
deleted file mode 100644 (file)
index 980d43c..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-class PositionMgr:
-
-    def __init__(self):
-        self._pos = {}
-
-    def reset(self):
-        self._pos = {}
-
-    def register(self, wdg, pos):
-        print('register', wdg, pos)
-        self._pos[wdg] = pos
-
-    def get(self, tgt):
-        return self._pos[tgt]
diff --git a/logics/scene.py b/logics/scene.py
deleted file mode 100644 (file)
index ae168b3..0000000
+++ /dev/null
@@ -1,597 +0,0 @@
-from os.path import exists
-from os import makedirs
-from glob import glob
-from logging import debug, info
-from importlib import import_module
-from inspect import isclass
-from panda3d.core import AmbientLight, DirectionalLight, Point3, Texture, \
-    TextPropertiesManager, TextNode, Spotlight, PerspectiveLens, BitMask32
-from panda3d.bullet import BulletPlaneShape, BulletGhostNode
-from direct.gui.OnscreenImage import OnscreenImage
-from direct.gui.OnscreenText import OnscreenText
-from direct.gui.DirectGui import DirectButton, DirectFrame
-from direct.gui.DirectGuiGlobals import FLAT, DISABLED, NORMAL
-from direct.showbase.DirectObject import DirectObject
-from logics.items.background import Background
-from gui.sidepanel import SidePanel
-from ya2.utils.cursor import MouseCursor
-from ya2.p3d.gfx import P3dGfxMgr
-from ya2.p3d.p3d import LibP3d
-
-
-class Scene(DirectObject):
-
-    def __init__(self, world, exit_cb, auto_close_instr, dbg_items, reload_cb, scenes, pos_mgr):
-        super().__init__()
-        self._world = world
-        self._exit_cb = exit_cb
-        self._dbg_items = dbg_items
-        self._reload_cb = reload_cb
-        self._pos_mgr = pos_mgr
-        self._pos_mgr.reset()
-        self._scenes = scenes
-        self._enforce_res = ''
-        self.accept('enforce_res', self.enforce_res)
-        self._set_camera()
-        self._cursor = MouseCursor(
-            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
-            (.01, .01))
-        self._set_gui()
-        self._set_lights()
-        self._set_input()
-        self._set_mouse_plane()
-        self.items = []
-        self.reset()
-        self._state = 'init'
-        self._paused = False
-        self._item_active = None
-        if auto_close_instr:
-            self.__store_state()
-            self.__restore_state()
-        else:
-            self._set_instructions()
-        self._bg = Background()
-        self._side_panel = SidePanel(world, self._mouse_plane_node, (-5, 4), (-3, 1), 1, self.items)
-        self._scene_tsk = taskMgr.add(self.on_frame, 'on_frame')
-
-    @staticmethod
-    def name():
-        return ''
-
-    def _instr_txt(self):
-        return ''
-
-    def _set_items(self):
-        self.items = []
-
-    def screenshot(self, task=None):
-        tex = Texture('screenshot')
-        buffer = base.win.make_texture_buffer('screenshot', 512, 512, tex, True )
-        cam = base.make_camera(buffer)
-        cam.reparent_to(render)
-        cam.node().get_lens().set_fov(base.camLens.get_fov())
-        cam.set_pos(0, -20, 0)
-        cam.look_at(0, 0, 0)
-        import simplepbr
-        simplepbr.init(
-            window=buffer,
-            camera_node=cam,
-            use_normal_maps=True,
-            use_emission_maps=False,
-            use_occlusion_maps=True,
-            msaa_samples=4,
-            enable_shadows=True)
-        base.graphicsEngine.renderFrame()
-        base.graphicsEngine.renderFrame()
-        fname = self.__class__.__name__
-        if not exists('assets/images/scenes'):
-            makedirs('assets/images/scenes')
-        buffer.save_screenshot('assets/images/scenes/%s.png' % fname)
-        # img = DirectButton(
-        #     frameTexture=buffer.get_texture(), relief=FLAT,
-        #     frameSize=(-.2, .2, -.2, .2))
-        return buffer.get_texture()
-
-    def current_bottom(self):
-        curr_bottom = 1
-        for item in self.items:
-            if item.repos_done:
-                curr_bottom = min(curr_bottom, item.get_bottom())
-        return curr_bottom
-
-    def reset(self):
-        [itm.destroy() for itm in self.items]
-        self._set_items()
-        self._state = 'init'
-        self._commands = []
-        self._command_idx = 0
-        if hasattr(self, '_success_txt'):
-            self._success_txt.destroy()
-            del self._success_txt
-        self.__right_btn['state'] = NORMAL
-
-    def enforce_res(self, val):
-        self._enforce_res = val
-        info('enforce res: ' + val)
-
-    def destroy(self):
-        self.ignore('enforce_res')
-        self._unset_gui()
-        self._unset_lights()
-        self._unset_input()
-        self._unset_mouse_plane()
-        [itm.destroy() for itm in self.items]
-        self._bg.destroy()
-        self._side_panel.destroy()
-        self._cursor.destroy()
-        taskMgr.remove(self._scene_tsk)
-        if hasattr(self, '_success_txt'):
-            self._success_txt.destroy()
-
-    def _set_camera(self):
-        base.camera.set_pos(0, -20, 0)
-        base.camera.look_at(0, 0, 0)
-
-    def __load_img_btn(self, path, col):
-        img = OnscreenImage('assets/images/buttons/%s.dds' % path)
-        img.set_transparency(True)
-        img.set_color(col)
-        img.detach_node()
-        return img
-
-    def _set_gui(self):
-        def load_images_btn(path, col):
-            colors = {
-                'gray': [
-                    (.6, .6, .6, 1),  # ready
-                    (1, 1, 1, 1), # press
-                    (.8, .8, .8, 1), # rollover
-                    (.4, .4, .4, .4)],
-                'green': [
-                    (.1, .68, .1, 1),
-                    (.1, 1, .1, 1),
-                    (.1, .84, .1, 1),
-                    (.4, .1, .1, .4)]}[col]
-            return [self.__load_img_btn(path, col) for col in colors]
-        abl, abr = base.a2dBottomLeft, base.a2dBottomRight
-        btn_info = [
-            ('home', self.on_home, NORMAL, abl, 'gray'),
-            ('information', self._set_instructions, NORMAL, abl, 'gray'),
-            ('right', self.on_play, NORMAL, abr, 'green'),
-            #('next', self.on_next, DISABLED, abr, 'gray'),
-            #('previous', self.on_prev, DISABLED, abr, 'gray'),
-            #('rewind', self.reset, NORMAL, abr, 'gray')
-        ]
-        num_l = num_r = 0
-        btns = []
-        for binfo in btn_info:
-            imgs = load_images_btn(binfo[0], binfo[4])
-            if binfo[3] == base.a2dBottomLeft:
-                sign, num = 1, num_l
-                num_l += 1
-            else:
-                sign, num = -1, num_r
-                num_r += 1
-            fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
-            btn = DirectButton(
-                image=imgs, scale=.05, pos=(sign * (.06 + .11 * num), 1, .06),
-                parent=binfo[3], command=binfo[1], state=binfo[2], relief=FLAT,
-                frameColor=fcols[0] if binfo[2] == NORMAL else fcols[1],
-                rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
-                clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
-            btn.set_transparency(True)
-            self._pos_mgr.register(binfo[0], LibP3d.wdg_pos(btn))
-            btns += [btn]
-        self.__home_btn, self.__info_btn, self.__right_btn = btns
-        # , self.__next_btn, self.__prev_btn, self.__rewind_btn
-        if self._dbg_items:
-            self._info_txt = OnscreenText(
-                '', parent=base.a2dTopRight, scale=0.04,
-                pos=(-.03, -.06), fg=(.9, .9, .9, 1), align=TextNode.A_right)
-
-    def _unset_gui(self):
-        btns = [
-            self.__home_btn, self.__info_btn, self.__right_btn,
-            #self.__next_btn, self.__prev_btn, self.__rewind_btn
-        ]
-        [btn.destroy() for btn in btns]
-        if self._dbg_items:
-            self._info_txt.destroy()
-
-    def _set_spotlight(self, name, pos, look_at, color, shadows=False):
-        light = Spotlight(name)
-        if shadows:
-            light.setLens(PerspectiveLens())
-        light_np = render.attach_new_node(light)
-        light_np.set_pos(pos)
-        light_np.look_at(look_at)
-        light.set_color(color)
-        render.set_light(light_np)
-        return light_np
-
-    def _set_lights(self):
-        alight = AmbientLight('alight')  # for ao
-        alight.set_color((.15, .15, .15, 1))
-        self._alnp = render.attach_new_node(alight)
-        render.set_light(self._alnp)
-        self._key_light = self._set_spotlight(
-            'key light', (-5, -80, 5), (0, 0, 0), (2.8, 2.8, 2.8, 1))
-        self._shadow_light = self._set_spotlight(
-            'key light', (-5, -80, 5), (0, 0, 0), (.58, .58, .58, 1), True)
-        self._shadow_light.node().set_shadow_caster(True, 2048, 2048)
-        self._shadow_light.node().get_lens().set_film_size(2048, 2048)
-        self._shadow_light.node().get_lens().set_near_far(1, 256)
-        self._shadow_light.node().set_camera_mask(BitMask32(0x01))
-
-    def _unset_lights(self):
-        for light in [self._alnp, self._key_light, self._shadow_light]:
-            render.clear_light(light)
-            light.remove_node()
-
-    def _set_input(self):
-        self.accept('mouse1', self.on_click_l)
-        self.accept('mouse1-up', self.on_release)
-        self.accept('mouse3', self.on_click_r)
-        self.accept('mouse3-up', self.on_release)
-
-    def _unset_input(self):
-        for evt in ['mouse1', 'mouse1-up', 'mouse3', 'mouse3-up']:
-            self.ignore(evt)
-
-    def _set_mouse_plane(self):
-        shape = BulletPlaneShape((0, -1, 0), 0)
-        #self._mouse_plane_node = BulletRigidBodyNode('mouse plane')
-        self._mouse_plane_node = BulletGhostNode('mouse plane')
-        self._mouse_plane_node.addShape(shape)
-        #np = render.attachNewNode(self._mouse_plane_node)
-        #self._world.attachRigidBody(self._mouse_plane_node)
-        self._world.attach_ghost(self._mouse_plane_node)
-
-    def _unset_mouse_plane(self):
-        self._world.remove_ghost(self._mouse_plane_node)
-
-    def _get_hits(self):
-        if not base.mouseWatcherNode.has_mouse(): return []
-        p_from, p_to = P3dGfxMgr.world_from_to(base.mouseWatcherNode.get_mouse())
-        return self._world.ray_test_all(p_from, p_to).get_hits()
-
-    def _update_info(self, item):
-        txt = ''
-        if item:
-            txt = '%.3f %.3f\n%.3f°' % (
-                item._np.get_x(), item._np.get_z(), item._np.get_r())
-        self._info_txt['text'] = txt
-
-    def _on_click(self, method):
-        if self._paused:
-            return
-        for hit in self._get_hits():
-            if hit.get_node() == self._mouse_plane_node:
-                pos = hit.get_hit_pos()
-        for hit in self._get_hits():
-            for item in [i for i in self.items if hit.get_node() == i.node and i.interactable]:
-                if not self._item_active:
-                    self._item_active = item
-                getattr(item, method)(pos)
-                img = 'move' if method == 'on_click_l' else 'rotate'
-                if not (img == 'rotate' and not item._instantiated):
-                    self._cursor.set_image('assets/images/buttons/%s.dds' % img)
-
-    def on_click_l(self):
-        self._on_click('on_click_l')
-
-    def on_click_r(self):
-        self._on_click('on_click_r')
-
-    def on_release(self):
-        if self._item_active and not self._item_active._first_command:
-            self._commands = self._commands[:self._command_idx]
-            self._commands += [self._item_active]
-            self._command_idx += 1
-            #self.__prev_btn['state'] = NORMAL
-            #fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
-            #self.__prev_btn['frameColor'] = fcols[0]
-            #if self._item_active._command_idx == len(self._item_active._commands) - 1:
-            #    self.__next_btn['state'] = DISABLED
-            #    self.__next_btn['frameColor'] = fcols[1]
-        self._item_active = None
-        [item.on_release() for item in self.items]
-        self._cursor.set_image('assets/images/buttons/arrowUpLeft.dds')
-
-    def repos(self):
-        for item in self.items:
-            item.repos_done = False
-        self.items = sorted(self.items, key=lambda itm: itm.__class__.__name__)
-        [item.on_aspect_ratio_changed() for item in self.items]
-        self._side_panel.update(self.items)
-        max_x = -float('inf')
-        for item in self.items:
-            if not item._instantiated:
-                max_x = max(item._np.get_x(), max_x)
-        for item in self.items:
-            if not item._instantiated:
-                item.repos_x(max_x)
-
-    def on_aspect_ratio_changed(self):
-        self.repos()
-
-    def _win_condition(self):
-        pass
-
-    def _fail_condition(self):
-        return all(itm.fail_condition() for itm in self.items) and not self._paused and self._state == 'playing'
-
-    def on_frame(self, task):
-        hits = self._get_hits()
-        pos = None
-        for hit in self._get_hits():
-            if hit.get_node() == self._mouse_plane_node:
-                pos = hit.get_hit_pos()
-        hit_nodes = [hit.get_node() for hit in hits]
-        if self._item_active:
-            items_hit = [self._item_active]
-        else:
-            items_hit = [itm for itm in self.items if itm.node in hit_nodes]
-        items_no_hit = [itm for itm in self.items if itm not in items_hit]
-        [itm.on_mouse_on() for itm in items_hit]
-        [itm.on_mouse_off() for itm in items_no_hit]
-        if pos and self._item_active:
-            self._item_active.on_mouse_move(pos)
-        if self._dbg_items:
-            self._update_info(items_hit[0] if items_hit else None)
-        if self._win_condition():
-            self._set_fail() if self._enforce_res == 'fail' else self._set_win()
-        elif self._state == 'playing' and self._fail_condition():
-            self._set_win() if self._enforce_res == 'win' else self._set_fail()
-        if any(itm._overlapping for itm in self.items):
-            self._cursor.cursor_img.img.set_color(.9, .1, .1, 1)
-        else:
-            self._cursor.cursor_img.img.set_color(.9, .9, .9, 1)
-        return task.cont
-
-    def cb_inst(self, item):
-        self.items += [item]
-
-    def on_play(self):
-        self._state = 'playing'
-        #self.__prev_btn['state'] = DISABLED
-        #self.__next_btn['state'] = DISABLED
-        self.__right_btn['state'] = DISABLED
-        [itm.play() for itm in self.items]
-
-    def on_next(self):
-        self._commands[self._command_idx].redo()
-        self._command_idx += 1
-        fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
-        #self.__prev_btn['state'] = NORMAL
-        #self.__prev_btn['frameColor'] = fcols[0]
-        more_commands = self._command_idx < len(self._commands)
-        #self.__next_btn['state'] = NORMAL if more_commands else DISABLED
-        #self.__next_btn['frameColor'] = fcols[0] if more_commands else fcols[1]
-
-    def on_prev(self):
-        self._command_idx -= 1
-        self._commands[self._command_idx].undo()
-        fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
-        #self.__next_btn['state'] = NORMAL
-        #self.__next_btn['frameColor'] = fcols[0]
-        #self.__prev_btn['state'] = NORMAL if self._command_idx else DISABLED
-        #self.__prev_btn['frameColor'] = fcols[0] if self._command_idx else fcols[1]
-
-    def on_home(self):
-        self._exit_cb()
-
-    def _set_instructions(self):
-        self._paused = True
-        self.__store_state()
-        mgr = TextPropertiesManager.get_global_ptr()
-        for name in ['mouse_l', 'mouse_r']:
-            graphic = OnscreenImage('assets/images/buttons/%s.dds' % name)
-            graphic.set_scale(.5)
-            graphic.get_texture().set_minfilter(Texture.FTLinearMipmapLinear)
-            graphic.get_texture().set_anisotropic_degree(2)
-            mgr.set_graphic(name, graphic)
-            graphic.set_z(-.2)
-            graphic.set_transparency(True)
-            graphic.detach_node()
-        frm = DirectFrame(frameColor=(.4, .4, .4, .06),
-                          frameSize=(-.6, .6, -.3, .3))
-        font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
-        font.clear()
-        font.set_pixels_per_unit(60)
-        font.set_minfilter(Texture.FTLinearMipmapLinear)
-        font.set_outline((0, 0, 0, 1), .8, .2)
-        self._txt = OnscreenText(
-            self._instr_txt(), parent=frm, font=font, scale=0.06,
-            fg=(.9, .9, .9, 1), align=TextNode.A_left)
-        u_l = self._txt.textNode.get_upper_left_3d()
-        l_r = self._txt.textNode.get_lower_right_3d()
-        w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
-        btn_scale = .05
-        mar = .06  # margin
-        z = h / 2 - font.get_line_height() * self._txt['scale'][1]
-        z += (btn_scale + 2 * mar) / 2
-        self._txt['pos'] = -w / 2, z
-        u_l = self._txt.textNode.get_upper_left_3d()
-        l_r = self._txt.textNode.get_lower_right_3d()
-        c_l_r = l_r[0], l_r[1], l_r[2] - 2 * mar - btn_scale
-        fsz = u_l[0] - mar, l_r[0] + mar, c_l_r[2] - mar, u_l[2] + mar
-        frm['frameSize'] = fsz
-        colors = [
-            (.6, .6, .6, 1),  # ready
-            (1, 1, 1, 1), # press
-            (.8, .8, .8, 1), # rollover
-            (.4, .4, .4, .4)]
-        imgs = [self.__load_img_btn('exitRight', col) for col in colors]
-        btn = DirectButton(
-            image=imgs, scale=btn_scale,
-            pos=(l_r[0] - btn_scale, 1, l_r[2] - mar - btn_scale),
-            parent=frm, command=self.__on_close_instructions, extraArgs=[frm],
-            relief=FLAT, frameColor=(.6, .6, .6, .08),
-            rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
-            clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
-        btn.set_transparency(True)
-        self._pos_mgr.register('close_instructions', LibP3d.wdg_pos(btn))
-
-    def _set_win(self):
-        loader.load_sfx('assets/audio/sfx/success.ogg').play()
-        self._paused = True
-        self.__store_state()
-        frm = DirectFrame(frameColor=(.4, .4, .4, .06),
-                          frameSize=(-.6, .6, -.3, .3))
-        font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
-        font.clear()
-        font.set_pixels_per_unit(60)
-        font.set_minfilter(Texture.FTLinearMipmapLinear)
-        font.set_outline((0, 0, 0, 1), .8, .2)
-        self._txt = OnscreenText(
-            _('You win!'),
-            parent=frm,
-            font=font, scale=0.2,
-            fg=(.9, .9, .9, 1))
-        u_l = self._txt.textNode.get_upper_left_3d()
-        l_r = self._txt.textNode.get_lower_right_3d()
-        w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
-        btn_scale = .05
-        mar = .06  # margin
-        z = h / 2 - font.get_line_height() * self._txt['scale'][1]
-        z += (btn_scale + 2 * mar) / 2
-        self._txt['pos'] = 0, z
-        u_l = self._txt.textNode.get_upper_left_3d()
-        l_r = self._txt.textNode.get_lower_right_3d()
-        c_l_r = l_r[0], l_r[1], l_r[2] - 2 * mar - btn_scale
-        fsz = u_l[0] - mar, l_r[0] + mar, c_l_r[2] - mar, u_l[2] + mar
-        frm['frameSize'] = fsz
-        colors = [
-            (.6, .6, .6, 1),  # ready
-            (1, 1, 1, 1), # press
-            (.8, .8, .8, 1), # rollover
-            (.4, .4, .4, .4)]
-        imgs = [self.__load_img_btn('home', col) for col in colors]
-        btn = DirectButton(
-            image=imgs, scale=btn_scale,
-            pos=(-2.8 * btn_scale, 1, l_r[2] - mar - btn_scale),
-            parent=frm, command=self._on_end_home, extraArgs=[frm],
-            relief=FLAT, frameColor=(.6, .6, .6, .08),
-            rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
-            clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
-        btn.set_transparency(True)
-        self._pos_mgr.register('home_win', LibP3d.wdg_pos(btn))
-        imgs = [self.__load_img_btn('rewind', col) for col in colors]
-        btn = DirectButton(
-            image=imgs, scale=btn_scale,
-            pos=(0, 1, l_r[2] - mar - btn_scale),
-            parent=frm, command=self._on_restart, extraArgs=[frm],
-            relief=FLAT, frameColor=(.6, .6, .6, .08),
-            rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
-            clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
-        self._pos_mgr.register('replay', LibP3d.wdg_pos(btn))
-        btn.set_transparency(True)
-        enabled = self._scenes.index(self.__class__) < len(self._scenes) - 1
-        if enabled:
-            next_scene = self._scenes[self._scenes.index(self.__class__) + 1]
-        else:
-            next_scene = None
-        imgs = [self.__load_img_btn('right', col) for col in colors]
-        btn = DirectButton(
-            image=imgs, scale=btn_scale,
-            pos=(2.8 * btn_scale, 1, l_r[2] - mar - btn_scale),
-            parent=frm, command=self._on_next_scene,
-            extraArgs=[frm, next_scene], relief=FLAT,
-            frameColor=(.6, .6, .6, .08),
-            rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
-            clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
-        btn['state'] = NORMAL if enabled else DISABLED
-        self._pos_mgr.register('next', LibP3d.wdg_pos(btn))
-        btn.set_transparency(True)
-
-    def _set_fail(self):
-        loader.load_sfx('assets/audio/sfx/success.ogg').play()
-        self._paused = True
-        self.__store_state()
-        frm = DirectFrame(frameColor=(.4, .4, .4, .06),
-                          frameSize=(-.6, .6, -.3, .3))
-        font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
-        font.clear()
-        font.set_pixels_per_unit(60)
-        font.set_minfilter(Texture.FTLinearMipmapLinear)
-        font.set_outline((0, 0, 0, 1), .8, .2)
-        self._txt = OnscreenText(
-            _('You have failed!'),
-            parent=frm,
-            font=font, scale=0.2,
-            fg=(.9, .9, .9, 1))
-        u_l = self._txt.textNode.get_upper_left_3d()
-        l_r = self._txt.textNode.get_lower_right_3d()
-        w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
-        btn_scale = .05
-        mar = .06  # margin
-        z = h / 2 - font.get_line_height() * self._txt['scale'][1]
-        z += (btn_scale + 2 * mar) / 2
-        self._txt['pos'] = 0, z
-        u_l = self._txt.textNode.get_upper_left_3d()
-        l_r = self._txt.textNode.get_lower_right_3d()
-        c_l_r = l_r[0], l_r[1], l_r[2] - 2 * mar - btn_scale
-        fsz = u_l[0] - mar, l_r[0] + mar, c_l_r[2] - mar, u_l[2] + mar
-        frm['frameSize'] = fsz
-        colors = [
-            (.6, .6, .6, 1),  # ready
-            (1, 1, 1, 1), # press
-            (.8, .8, .8, 1), # rollover
-            (.4, .4, .4, .4)]
-        imgs = [self.__load_img_btn('home', col) for col in colors]
-        btn = DirectButton(
-            image=imgs, scale=btn_scale,
-            pos=(-2.8 * btn_scale, 1, l_r[2] - mar - btn_scale),
-            parent=frm, command=self._on_end_home, extraArgs=[frm],
-            relief=FLAT, frameColor=(.6, .6, .6, .08),
-            rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
-            clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
-        self._pos_mgr.register('home_win', LibP3d.wdg_pos(btn))
-        btn.set_transparency(True)
-        imgs = [self.__load_img_btn('rewind', col) for col in colors]
-        btn = DirectButton(
-            image=imgs, scale=btn_scale,
-            pos=(0, 1, l_r[2] - mar - btn_scale),
-            parent=frm, command=self._on_restart, extraArgs=[frm],
-            relief=FLAT, frameColor=(.6, .6, .6, .08),
-            rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
-            clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
-        self._pos_mgr.register('replay', LibP3d.wdg_pos(btn))
-        btn.set_transparency(True)
-
-    def _on_restart(self, frm):
-        self.__on_close_instructions(frm)
-        self.reset()
-
-    def _on_end_home(self, frm):
-        self.__on_close_instructions(frm)
-        self.on_home()
-
-    def _on_next_scene(self, frm, scene):
-        self.__on_close_instructions(frm)
-        self._reload_cb(scene)
-
-    def __store_state(self):
-        btns = [
-            self.__home_btn, self.__info_btn, self.__right_btn,
-            #self.__next_btn, self.__prev_btn, self.__rewind_btn
-        ]
-        self.__btn_state = [btn['state'] for btn in btns]
-        for btn in btns:
-            btn['state'] = DISABLED
-        [itm.store_state() for itm in self.items]
-
-    def __restore_state(self):
-        btns = [
-            self.__home_btn, self.__info_btn, self.__right_btn,
-            #self.__next_btn, self.__prev_btn, self.__rewind_btn
-        ]
-        for btn, state in zip(btns, self.__btn_state):
-            btn['state'] = state
-        [itm.restore_state() for itm in self.items]
-        self._paused = False
-
-    def __on_close_instructions(self, frm):
-        frm.remove_node()
-        self.__restore_state()
diff --git a/logics/scenes/__init__.py b/logics/scenes/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/logics/scenes/scene_basketball.py b/logics/scenes/scene_basketball.py
deleted file mode 100644 (file)
index f90e683..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-from logics.scene import Scene
-from logics.items.box import Box
-from logics.items.shelf import Shelf
-from logics.items.domino import Domino, UpStrategy, DownStrategy
-from logics.items.basketball import Basketball
-from logics.items.teetertooter import TeeterTooter
-
-
-class SceneBasketBall(Scene):
-
-    @staticmethod
-    def name():
-        return _('Basket ball')
-
-    def _set_items(self):
-        self.items = []
-        #self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=5, count=2)]
-        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=9)]
-        self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=1)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, .21))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, .21))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, -1.45))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, -1.45))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-4.45, 0, -3.18), r=27, restitution=1)]
-        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-5.45, 0, -3.18), restitution=1)]
-        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(3.78, 0, -1.45))]
-        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=9)]
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.61, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.06, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.91, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.73, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.57, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 30))
-        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.61, 0, .73), r=37)]
-        #self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.06, 0, .78))]
-        #self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.91, 0, .78))]
-        #self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.73, 0, .78))]
-        #self.items[-1].set_strategy(UpStrategy(self.items[-1]._np, 30))
-        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.57, 0, .78))]
-        #self.items[-1].set_strategy(UpStrategy(self.items[-1]._np, 30))
-        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-
-    def _instr_txt(self):
-        txt = _('Scene: ') + self.name() + '\n\n'
-        txt += _('Goal: you must hit every domino piece\n\n')
-        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
-                'keep \5mouse_r\5 pressed to rotate an item')
-        return txt
-
-    def _win_condition(self):
-        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/logics/scenes/scene_box.py b/logics/scenes/scene_box.py
deleted file mode 100644 (file)
index aed3b73..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-from logics.scene import Scene
-from logics.items.box import Box, HitStrategy
-from logics.items.shelf import Shelf
-from logics.items.domino import Domino
-from logics.items.basketball import Basketball
-from logics.items.teetertooter import TeeterTooter
-
-
-class SceneBox(Scene):
-
-    @staticmethod
-    def name():
-        return _('Box')
-
-    def _set_items(self):
-        self.items = []
-        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(.46, 0, -3.95))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(4.43, 0, -3.95))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-1.29, 0, .26), r=28.45)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(2.15, 0, -1.49), r=28.45)]
-        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-1.55, 0, 1.23), friction=.4)]
-        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(4.38, 0, -3.35))]
-        self.items[-1].set_strategy(HitStrategy(self.items[-2], self.items[-1].node, self.items[-1]._world))
-        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=2)]
-        #self.items += [TargetDomino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-1.14, 0, -.04), tgt_degrees=60)]
-        #self.items += [TargetDomino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.49, 0, -.04), tgt_degrees=60)]
-        #self.items += [TargetDomino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.94, 0, -.04), tgt_degrees=60)]
-        #self.items += [TargetDomino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.55, 0, -.04), tgt_degrees=60)]
-        #self.items += [TargetDomino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.09, 0, -.04), tgt_degrees=88)]
-        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-
-    def _instr_txt(self):
-        txt = _('Scene: ') + self.name() + '\n\n'
-        txt += _('Goal: the left box must hit the right box\n\n')
-        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
-                'keep \5mouse_r\5 pressed to rotate an item')
-        return txt
-
-    def _win_condition(self):
-        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/logics/scenes/scene_domino.py b/logics/scenes/scene_domino.py
deleted file mode 100644 (file)
index fa14aa5..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-from logics.scene import Scene
-from logics.items.box import Box
-from logics.items.shelf import Shelf
-from logics.items.domino import Domino, DownStrategy
-from logics.items.basketball import Basketball
-from logics.items.teetertooter import TeeterTooter
-
-
-class SceneDomino(Scene):
-
-    @staticmethod
-    def name():
-        return _('Domino')
-
-    def _set_items(self):
-        self.items = []
-        #self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-1.2, 0, -.6))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.2, 0, -.6))]
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=2)]
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-1.14, 0, -.04))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 60))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.49, 0, -.04))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 60))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.94, 0, -.04))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 60))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.55, 0, -.04))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 60))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.09, 0, -.04))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 88))
-        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-
-    def _instr_txt(self):
-        txt = _('Scene: ') + self.name() + '\n\n'
-        txt += _('Goal: every domino piece must fall\n\n')
-        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
-                'keep \5mouse_r\5 pressed to rotate an item')
-        return txt
-
-    def _win_condition(self):
-        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/logics/scenes/scene_domino_box.py b/logics/scenes/scene_domino_box.py
deleted file mode 100644 (file)
index bbdbb26..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-from logics.scene import Scene
-from logics.items.box import Box
-from logics.items.shelf import Shelf
-from logics.items.domino import Domino, UpStrategy, DownStrategy
-from logics.items.basketball import Basketball
-from logics.items.teetertooter import TeeterTooter
-
-
-class SceneDominoBox(Scene):
-
-    @staticmethod
-    def name():
-        return _('Domino and box')
-
-    def _set_items(self):
-        self.items = []
-        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=5, count=2)]
-        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=9)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, .21))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, .21))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, -1.45))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, -1.45))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(3.78, 0, -1.45))]
-        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=9)]
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.61, 0, -.94), r=37)]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.06, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.91, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.73, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.57, 0, -.89))]
-        self.items[-1].set_strategy(UpStrategy(self.items[-1]._np, 30))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.61, 0, .73), r=37)]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.06, 0, .78))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.91, 0, .78))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.73, 0, .78))]
-        self.items[-1].set_strategy(UpStrategy(self.items[-1]._np, 30))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.57, 0, .78))]
-        self.items[-1].set_strategy(UpStrategy(self.items[-1]._np, 30))
-        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-
-    def _instr_txt(self):
-        txt = _('Scene: ') + self.name() + '\n\n'
-        txt += _('Goal: only the last piece of each row must be up\n\n')
-        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
-                'keep \5mouse_r\5 pressed to rotate an item')
-        return txt
-
-    def _win_condition(self):
-        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/logics/scenes/scene_domino_box_basketball.py b/logics/scenes/scene_domino_box_basketball.py
deleted file mode 100644 (file)
index 6181478..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-from logics.scene import Scene
-from logics.items.box import Box
-from logics.items.shelf import Shelf
-from logics.items.domino import Domino, UpStrategy, DownStrategy
-from logics.items.basketball import Basketball
-from logics.items.teetertooter import TeeterTooter
-
-
-class SceneDominoBoxBasketball(Scene):
-
-    @staticmethod
-    def name():
-        return _('Domino, box and basket ball')
-
-    def _set_items(self):
-        self.items = []
-        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=1, mass=5)]
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=1)]
-        self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.3, 1, 2.5))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, -1.45))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, -1.45))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(3.78, 0, -1.45))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.48, 0, .38), r=-90)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(2.62, 0, .05))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(4.88, 0, .05))]
-        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=9)]
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.68, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.35, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(3.08, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(3.78, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(4.53, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-
-    def _instr_txt(self):
-        txt = _('Scene: ') + self.name() + '\n\n'
-        txt += _('Goal: every domino piece must be hit\n\n')
-        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
-                'keep \5mouse_r\5 pressed to rotate an item')
-        return txt
-
-    def _win_condition(self):
-        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/logics/scenes/scene_teeter_domino_box_basketball.py b/logics/scenes/scene_teeter_domino_box_basketball.py
deleted file mode 100644 (file)
index 75d361c..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-from logics.scene import Scene
-from logics.items.box import Box
-from logics.items.shelf import Shelf
-from logics.items.domino import Domino, UpStrategy, DownStrategy
-from logics.items.basketball import Basketball
-from logics.items.teetertooter import TeeterTooter
-
-
-class SceneTeeterDominoBoxBasketball(Scene):
-
-    @staticmethod
-    def name():
-        return _('Teeter tooter, domino, box and basket ball')
-
-    def _set_items(self):
-        self.items = []
-        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=3, count=2, friction=1)]
-        self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(.98, 1, 1.02))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-6.24, 0, -1.45))]
-        self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-6.24, 0, -1.20))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=1, r=24.60, friction=1, pos=(-6.15, 0, -.93))]
-        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=.3, friction=1, model_scale=.5, pos=(-5.38, 0, -.93), r=24.60)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(5.37, 0, -.78))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(7.48, 0, -.78))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(4.74, 0, -1.95))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(6.88, 0, -1.95))]
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=1, pos=(4.83, 0, -1.39))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=1, pos=(5.67, 0, -1.39))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=1, pos=(6.59, 0, -1.39))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(.53, 0, -1.95), restitution=.95)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(2.63, 0, -1.95), restitution=.95)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-3.65, 0, 1.05), r=28, friction=0)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-1.27, 0, 1.72), restitution=.95)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(.88, 0, 1.72), restitution=.95)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-1.67, 0, .55), restitution=.95)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(.52, 0, .55), restitution=.95)]
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=.5, pos=(-1.73, 0, 1.11))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=.5, pos=(-.97, 0, 1.11))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=.5, pos=(-.1, 0, 1.11))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-
-    def _instr_txt(self):
-        txt = _('Scene: ') + self.name() + '\n\n'
-        txt += _('Goal: every domino piece must be hit\n\n')
-        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
-                'keep \5mouse_r\5 pressed to rotate an item')
-        return txt
-
-    def _win_condition(self):
-        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/logics/scenes/scene_teeter_tooter.py b/logics/scenes/scene_teeter_tooter.py
deleted file mode 100644 (file)
index 3a748ea..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-from logics.scene import Scene
-from logics.items.box import Box
-from logics.items.shelf import Shelf
-from logics.items.domino import Domino, UpStrategy, DownStrategy
-from logics.items.basketball import Basketball
-from logics.items.teetertooter import TeeterTooter
-
-
-class SceneTeeterTooter(Scene):
-
-    @staticmethod
-    def name():
-        return _('Teeter tooter')
-
-    def _set_items(self):
-        self.items = []
-        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=3, count=1, friction=1)]
-        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=5, count=2)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-2.76, 0, -1.45))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, -1.45))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(2.27, 0, -.28))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(4.38, 0, -.28))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, -1.45))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(3.78, 0, -1.45))]
-        self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-2.74, 0, -1.20))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=1, r=-25.30, friction=1, pos=(-2.78, 0, -.93))]
-        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=.2, friction=1, model_scale=.5, pos=(-3.61, 0, -.99), r=-25.30)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.25, 0, -.57), r=52)]
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.73, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.57, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(3.50, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-
-    def _instr_txt(self):
-        txt = _('Scene: ') + self.name() + '\n\n'
-        txt += _('Goal: you must hit every domino piece\n\n')
-        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
-                'keep \5mouse_r\5 pressed to rotate an item')
-        return txt
-
-    def _win_condition(self):
-        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/main.py b/main.py
index 00d9b9cc67c1e45ea1a98c8d5eb5d6daba5c2d8a..b27b586a7ba862ee0306651d7a818bab338e1b59 100644 (file)
--- a/main.py
+++ b/main.py
@@ -6,7 +6,7 @@ if '--version' in argv:
     load_prc_file_data('', 'window-type none')
 from os.path import exists
 from traceback import print_exc
-from logics.app import PmachinesApp
+from pmachines.app import PmachinesApp
 from p3d_appimage import AppImageBuilder
 
 if __name__ == '__main__' or exists('main.pyo'):
diff --git a/pmachines/__init__.py b/pmachines/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/pmachines/app.py b/pmachines/app.py
new file mode 100755 (executable)
index 0000000..a0ed4ed
--- /dev/null
@@ -0,0 +1,311 @@
+import argparse
+import simplepbr
+#import gltf
+from glob import glob
+from importlib import import_module
+from inspect import isclass
+from sys import platform, argv, exit
+from logging import info, debug
+from os.path import exists
+from os import makedirs
+from multiprocessing import cpu_count
+from panda3d.core import Filename, load_prc_file_data, AntialiasAttrib, \
+    Texture, WindowProperties, LVector2i, TextNode
+from panda3d.bullet import BulletWorld, BulletDebugNode
+from direct.showbase.ShowBase import ShowBase
+from direct.gui.OnscreenText import OnscreenText
+from direct.fsm.FSM import FSM
+from pmachines.audio.music import MusicMgr
+from pmachines.items.background import Background
+from pmachines.gui.menu import Menu
+from pmachines.scene import Scene
+from pmachines.scenes.scene_basketball import SceneBasketBall
+from pmachines.scenes.scene_box import SceneBox
+from pmachines.scenes.scene_domino_box_basketball import SceneDominoBoxBasketball
+from pmachines.scenes.scene_domino_box import SceneDominoBox
+from pmachines.scenes.scene_domino import SceneDomino
+from pmachines.scenes.scene_teeter_domino_box_basketball import SceneTeeterDominoBoxBasketball
+from pmachines.scenes.scene_teeter_tooter import SceneTeeterTooter
+from pmachines.posmgr import PositionMgr
+from ya2.utils.dictfile import DctFile
+from ya2.p3d.p3d import LibP3d
+from ya2.utils.lang import LangMgr
+from ya2.utils.log import LogMgr
+from ya2.utils.functional import FunctionalTest
+from ya2.p3d.asserts import assert_threads, assert_tasks, assert_render3d, \
+    assert_render2d, assert_aspect2d, assert_events, assert_buffers
+
+
+class MainFsm(FSM):
+
+    def __init__(self, pmachines):
+        super().__init__('Main FSM')
+        self._pmachines = pmachines
+
+    def enterMenu(self):
+        self._pmachines.on_menu_enter()
+
+    def exitMenu(self):
+        self._pmachines.on_menu_exit()
+        self.__do_asserts()
+
+    def enterScene(self, cls):
+        self._pmachines.on_scene_enter(cls)
+
+    def exitScene(self):
+        self._pmachines.on_scene_exit()
+        self.__do_asserts()
+
+    def __do_asserts(self):
+        args = self._pmachines._args
+        if not LibP3d.runtime() or args.functional_test or args.functional_ref:
+            assert_threads()
+            assert_tasks()
+            assert_render3d()
+            assert_render2d()
+            assert_aspect2d()
+            assert_events()
+            assert_buffers()
+
+
+class PmachinesApp:
+
+    scenes = [
+        SceneDomino,
+        SceneBox,
+        SceneDominoBox,
+        SceneBasketBall,
+        SceneDominoBoxBasketball,
+        SceneTeeterTooter,
+        SceneTeeterDominoBoxBasketball]
+
+    def __init__(self):
+        info('platform: %s' % platform)
+        info('exists main.py: %s' % exists('main.py'))
+        self._args = args = self._parse_args()
+        self._configure(args)
+        self.base = ShowBase()
+        self._pipeline = None
+        self.updating = args.update
+        self.version = args.version
+        self.log_mgr = LogMgr.init_cls()()
+        self._pos_mgr = PositionMgr()
+        self._prepare_window(args)
+        if args.update:
+            return
+        if args.functional_test:
+            self._options['settings']['volume'] = 0
+        self._music = MusicMgr(self._options['settings']['volume'])
+        self.lang_mgr = LangMgr(self._options['settings']['language'],
+                                'pmachines',
+                                'assets/locale/')
+        self._fsm = MainFsm(self)
+        if args.screenshot:
+            cls = [cls for cls in self.scenes if cls.__name__ == args.screenshot][0]
+            scene = cls(BulletWorld(), None, True, False, lambda: None, self.scenes)
+            scene.screenshot()
+            scene.destroy()
+            exit()
+        elif self._options['development']['auto_start']:
+            mod_name = 'pmachines.scenes.scene_' + self._options['development']['auto_start']
+            for member in import_module(mod_name).__dict__.values():
+                if isclass(member) and issubclass(member, Scene) and \
+                        member != Scene:
+                    cls = member
+            self._fsm.demand('Scene', cls)
+        else:
+            self._fsm.demand('Menu')
+        if args.functional_test or args.functional_ref:
+            FunctionalTest(args.functional_ref, self._pos_mgr)
+        if not LibP3d.runtime() or args.functional_test or args.functional_ref:
+            self.__fps_lst = []
+            taskMgr.do_method_later(1.0, self.__assert_fps, 'assert_fps')
+
+    def on_menu_enter(self):
+        self._menu_bg = Background()
+        self._menu = Menu(
+            self._fsm, self.lang_mgr, self._options, self._music,
+            self._pipeline, self.scenes, self._args.functional_test or self._args.functional_ref,
+            self._pos_mgr)
+
+    def on_home(self):
+        self._fsm.demand('Menu')
+
+    def on_menu_exit(self):
+        self._menu_bg.destroy()
+        self._menu.destroy()
+
+    def on_scene_enter(self, cls):
+        self._set_physics()
+        self._scene = cls(
+            self.world, self.on_home,
+            self._options['development']['auto_close_instructions'],
+            self._options['development']['debug_items'],
+            self.reload,
+            self.scenes,
+            self._pos_mgr,
+            self._args.functional_test or self._args.functional_ref,
+            self._options['development']['mouse_coords'])
+
+    def on_scene_exit(self):
+        self._unset_physics()
+        self._scene.destroy()
+
+    def reload(self, cls):
+        self._fsm.demand('Scene', cls)
+
+    def _configure(self, args):
+        load_prc_file_data('', 'window-title pmachines')
+        load_prc_file_data('', 'framebuffer-srgb true')
+        load_prc_file_data('', 'sync-video true')
+        if args.functional_test or args.functional_ref:
+            load_prc_file_data('', 'win-size 1360 768')
+            # otherwise it is not centered in exwm
+        # load_prc_file_data('', 'threading-model Cull/Draw')
+        # it freezes when you go to the next scene
+        if args.screenshot:
+            load_prc_file_data('', 'window-type offscreen')
+            load_prc_file_data('', 'audio-library-name null')
+
+    def _parse_args(self):
+        parser = argparse.ArgumentParser()
+        parser.add_argument('--update', action='store_true')
+        parser.add_argument('--version', action='store_true')
+        parser.add_argument('--optfile')
+        parser.add_argument('--screenshot')
+        parser.add_argument('--functional-test', action='store_true')
+        parser.add_argument('--functional-ref', action='store_true')
+        cmd_line = [arg for arg in iter(argv[1:]) if not arg.startswith('-psn_')]
+        args = parser.parse_args(cmd_line)
+        return args
+
+    def _prepare_window(self, args):
+        data_path = ''
+        if (platform.startswith('win') or platform.startswith('linux')) and (
+                not exists('main.py') or __file__.startswith('/app/bin/')):
+            # it is the deployed version for windows
+            data_path = str(Filename.get_user_appdata_directory()) + '/pmachines'
+            home = '/home/flavio'  # we must force this for wine
+            if data_path.startswith('/c/users/') and exists(home + '/.wine/'):
+                data_path = home + '/.wine/drive_' + data_path[1:]
+            info('creating dirs: %s' % data_path)
+            makedirs(data_path, exist_ok=True)
+        optfile = args.optfile if args.optfile else 'options.ini'
+        info('data path: %s' % data_path)
+        info('option file: %s' % optfile)
+        info('fixed path: %s' % LibP3d.fixpath(data_path + '/' + optfile))
+        default_opt = {
+            'settings': {
+                'volume': 1,
+                'language': 'en',
+                'fullscreen': 1,
+                'resolution': '',
+                'antialiasing': 1,
+                'shadows': 1},
+            'development': {
+                'simplepbr': 1,
+                'verbose_log': 0,
+                'physics_debug': 0,
+                'auto_start': 0,
+                'auto_close_instructions': 0,
+                'show_buffers': 0,
+                'debug_items': 0,
+                'mouse_coords': 0,
+                'fps': 0}}
+        opt_path = LibP3d.fixpath(data_path + '/' + optfile) if data_path else optfile
+        opt_exists = exists(opt_path)
+        self._options = DctFile(
+            LibP3d.fixpath(data_path + '/' + optfile) if data_path else optfile,
+            default_opt)
+        if not opt_exists:
+            self._options.store()
+        res = self._options['settings']['resolution']
+        if res:
+            res = LVector2i(*[int(_res) for _res in res.split('x')])
+        else:
+            resolutions = []
+            if not self.version:
+                d_i = base.pipe.get_display_information()
+                def _res(idx):
+                    return d_i.get_display_mode_width(idx), \
+                        d_i.get_display_mode_height(idx)
+                resolutions = [
+                    _res(idx) for idx in range(d_i.get_total_display_modes())]
+                res = sorted(resolutions)[-1]
+        fullscreen = self._options['settings']['fullscreen']
+        props = WindowProperties()
+        if args.functional_test or args.functional_ref:
+            fullscreen = False
+        elif not self.version:
+            props.set_size(res)
+        props.set_fullscreen(fullscreen)
+        props.set_icon_filename('assets/images/icon/pmachines.ico')
+        if not args.screenshot and not self.version:
+            base.win.request_properties(props)
+        #gltf.patch_loader(base.loader)
+        if self._options['development']['simplepbr'] and not self.version:
+            self._pipeline = simplepbr.init(
+                use_normal_maps=True,
+                use_emission_maps=False,
+                use_occlusion_maps=True,
+                msaa_samples=4 if self._options['settings']['antialiasing'] else 1,
+                enable_shadows=int(self._options['settings']['shadows']))
+            debug(f'msaa: {self._pipeline.msaa_samples}')
+            debug(f'shadows: {self._pipeline.enable_shadows}')
+        render.setAntialias(AntialiasAttrib.MAuto)
+        self.base.set_background_color(0, 0, 0, 1)
+        self.base.disable_mouse()
+        if self._options['development']['show_buffers']:
+            base.bufferViewer.toggleEnable()
+        if self._options['development']['fps']:
+            base.set_frame_rate_meter(True)
+        #self.base.accept('window-event', self._on_win_evt)
+        self.base.accept('aspectRatioChanged', self._on_aspect_ratio_changed)
+        if self._options['development']['mouse_coords']:
+            coords_txt = OnscreenText(
+                '', parent=base.a2dTopRight, scale=0.04,
+                pos=(-.03, -.06), fg=(.9, .9, .9, 1), align=TextNode.A_right)
+            def update_coords(task):
+                txt = '%s %s' % (int(base.win.get_pointer(0).x),
+                                 int(base.win.get_pointer(0).y))
+                coords_txt['text'] = txt
+                return task.cont
+            taskMgr.add(update_coords, 'update_coords')
+
+    def _set_physics(self):
+        if self._options['development']['physics_debug']:
+            debug_node = BulletDebugNode('Debug')
+            debug_node.show_wireframe(True)
+            debug_node.show_constraints(True)
+            debug_node.show_bounding_boxes(True)
+            debug_node.show_normals(True)
+            self._debug_np = render.attach_new_node(debug_node)
+            self._debug_np.show()
+        self.world = BulletWorld()
+        self.world.set_gravity((0, 0, -9.81))
+        if self._options['development']['physics_debug']:
+            self.world.set_debug_node(self._debug_np.node())
+        def update(task):
+            dt = globalClock.get_dt()
+            self.world.do_physics(dt, 10, 1/180)
+            return task.cont
+        self._phys_tsk = taskMgr.add(update, 'update')
+
+    def _unset_physics(self):
+        if self._options['development']['physics_debug']:
+            self._debug_np.remove_node()
+        self.world = None
+        taskMgr.remove(self._phys_tsk)
+
+    def _on_aspect_ratio_changed(self):
+        if self._fsm.state == 'Scene':
+            self._scene.on_aspect_ratio_changed()
+
+    def __assert_fps(self, task):
+        if len(self.__fps_lst) > 3:
+            self.__fps_lst.pop(0)
+        self.__fps_lst += [globalClock.average_frame_rate]
+        if len(self.__fps_lst) == 4:
+            fps_threshold = 55 if cpu_count() >= 4 else 25
+            assert(any(fps > fps_threshold for fps in self.__fps_lst), 'low fps %s' % self.__fps_lst)
+        return task.again
diff --git a/pmachines/audio/__init__.py b/pmachines/audio/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/pmachines/audio/music.py b/pmachines/audio/music.py
new file mode 100644 (file)
index 0000000..47779a2
--- /dev/null
@@ -0,0 +1,58 @@
+from os.path import exists, basename
+from platform import system
+from glob import glob
+from pathlib import Path
+from random import choice
+from logging import info
+from panda3d.core import AudioSound, Filename
+
+
+class MusicMgr:
+
+    def __init__(self, volume):
+        files = self.curr_path + 'assets/audio/music/*.ogg'
+        self._start_music(glob(files))
+        base.musicManager.setVolume(.8 * volume)
+        base.sfxManagerList[0].setVolume(volume)
+        taskMgr.add(self._on_frame, 'on frame music')
+
+    @property
+    def is_appimage(self):
+        par_path = str(Path(__file__).parent.absolute())
+        is_appimage = par_path.startswith('/tmp/.mount_Pmachi')
+        return is_appimage and par_path.endswith('/usr/bin')
+
+    @property
+    def curr_path(self):
+        if system() == 'Windows':
+            return ''
+        if exists('main.py'):
+            return ''
+        else:
+            par_path = str(Path(__file__).parent.absolute())
+        if self.is_appimage:
+            par_path = str(Path(par_path).absolute())
+        par_path += '/'
+        return par_path
+
+    def _start_music(self, files):
+        self._music = loader.load_music(choice(files))
+        info('playing music ' + self._music.get_name())
+        self._music.play()
+
+    def set_volume(self, volume):
+        base.musicManager.setVolume(.8 * volume)
+        base.sfxManagerList[0].setVolume(volume)
+
+    def _on_frame(self, task):
+        if self._music.status() == AudioSound.READY:
+            oggs = Filename(
+                self.curr_path + 'assets/audio/music/*.ogg').to_os_specific()
+            files = glob(oggs)
+            rm_music = Filename(
+                self.curr_path + 'assets/audio/music/' +
+                basename(self._music.get_name())).to_os_specific()
+            # basename is needed in windows
+            files.remove(rm_music)
+            self._start_music(files)
+        return task.cont
diff --git a/pmachines/gui/__init__.py b/pmachines/gui/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/pmachines/gui/menu.py b/pmachines/gui/menu.py
new file mode 100644 (file)
index 0000000..8ec41a3
--- /dev/null
@@ -0,0 +1,368 @@
+from logging import info, debug
+from sys import platform, exit
+from os import environ, system
+from webbrowser import open_new_tab
+from xmlrpc.client import ServerProxy
+from panda3d.core import Texture, TextNode, WindowProperties, LVector2i, \
+    TextProperties, TextPropertiesManager, NodePath
+from direct.gui.DirectGui import DirectButton, DirectCheckButton, \
+    DirectOptionMenu, DirectSlider
+from direct.gui.DirectGuiGlobals import FLAT
+from direct.gui.OnscreenText import OnscreenText
+from direct.showbase.DirectObject import DirectObject
+from ya2.utils.cursor import MouseCursor
+from ya2.p3d.p3d import LibP3d
+
+
+class DirectOptionMenuTest(DirectOptionMenu):
+
+    def __init__(self, parent=None, **kw):
+        DirectOptionMenu.__init__(self, parent, **kw)
+        self.initialiseoptions(DirectOptionMenuTest)
+
+    def showPopupMenu(self, event=None):
+        super().showPopupMenu(event)
+        self._show_cb([self.component(cmp) for cmp in self.components()])
+
+
+
+class Menu(DirectObject):
+
+    def __init__(self, fsm, lang_mgr, opt_file, music, pipeline, scenes,
+                 fun_test, pos_mgr):
+        super().__init__()
+        self._fsm = fsm
+        self._lang_mgr = lang_mgr
+        self._opt_file = opt_file
+        self._music = music
+        self._pipeline = pipeline
+        self._scenes = scenes
+        self._fun_test = fun_test
+        self._pos_mgr = pos_mgr
+        self._enforced_res = ''
+        self._cursor = MouseCursor(
+            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04),
+            (.5, .5, .5, 1), (.01, .01))
+        self._font = base.loader.load_font(
+            'assets/fonts/Hanken-Book.ttf')
+        self._font.clear()
+        self._font.set_pixels_per_unit(60)
+        self._font.set_minfilter(Texture.FTLinearMipmapLinear)
+        self._font.set_outline((0, 0, 0, 1), .8, .2)
+        self._widgets = []
+        self._common = {
+            'scale': .12,
+            'text_font': self._font,
+            'text_fg': (.9, .9, .9, 1),
+            'relief': FLAT,
+            'frameColor': (.4, .4, .4, .14),
+            'rolloverSound': loader.load_sfx(
+                'assets/audio/sfx/rollover.ogg'),
+            'clickSound': loader.load_sfx(
+                'assets/audio/sfx/click.ogg')}
+        self._common_btn = {'frameSize': (-4.8, 4.8, -.6, 1.2)} | self._common
+        hlc = self._common_btn['frameColor']
+        hlc = (hlc[0] + .2, hlc[1] + .2, hlc[2] + .2, hlc[3] + .2)
+        self._common_opt = {
+            'item_frameColor': self._common_btn['frameColor'],
+            'popupMarker_frameColor': self._common_btn['frameColor'],
+            'item_relief': self._common_btn['relief'],
+            'item_text_font': self._common_btn['text_font'],
+            'item_text_fg': self._common_btn['text_fg'],
+            'textMayChange': 1,
+            'highlightColor': hlc,
+            'text_align': TextNode.A_center,
+        } | self._common_btn
+        f_s = self._common_opt['frameSize']
+        self._common_opt['frameSize'] = f_s[0], f_s[1] - .56, f_s[2], f_s[3]
+        self._common_slider = self._common | {
+            'range': (0, 1),
+            'thumb_frameColor': (.4, .4, .4, .4),
+            'thumb_scale': 1.6,
+            'scale': .4}
+        del self._common_slider['rolloverSound']
+        del self._common_slider['clickSound']
+        self._set_main()
+
+    def enforce_res(self, val):
+        self._enforced_res = val
+        info('enforced resolution: ' + val)
+
+    def _set_main(self):
+        self._pos_mgr.reset()
+        self._widgets = []
+        self._widgets += [DirectButton(
+            text=_('Play'), pos=(0, 1, .6), command=self.on_play,
+            **self._common_btn)]
+        self._pos_mgr.register('play', LibP3d.wdg_pos(self._widgets[-1]))
+        self._widgets += [DirectButton(
+            text=_('Options'), pos=(0, 1, .2), command=self.on_options,
+            **self._common_btn)]
+        self._pos_mgr.register('options', LibP3d.wdg_pos(self._widgets[-1]))
+        self._widgets += [DirectButton(
+            text=_('Credits'), pos=(0, 1, -.2), command=self.on_credits,
+            **self._common_btn)]
+        self._pos_mgr.register('credits', LibP3d.wdg_pos(self._widgets[-1]))
+
+        def btn_exit():
+            if self._fun_test:
+                ServerProxy('http://localhost:7000').destroy()
+            exit()
+        self._widgets += [DirectButton(
+            text=_('Exit'), pos=(0, 1, -.6), command=lambda: btn_exit(),
+            **self._common_btn)]
+        self._pos_mgr.register('exit', LibP3d.wdg_pos(self._widgets[-1]))
+        self._rearrange_width()
+        self.accept('enforce_resolution', self.enforce_res)
+
+    def _set_options(self):
+        self._pos_mgr.reset()
+        self._widgets = []
+        self._lang_funcs = [lambda: _('English'), lambda: _('Italian')]
+        items = [fnc() for fnc in self._lang_funcs]
+        inititem = {
+            'en': _('English'),
+            'it': _('Italian')
+        }[self._opt_file['settings']['language']]
+
+        def lang_cb(comps):
+            self._pos_mgr.remove(['english', 'italian'])
+            for lng, btn in zip(['english', 'italian'], comps):
+                pos = LibP3d.wdg_pos(btn)
+                self._pos_mgr.register(lng, (pos[0] + 5, pos[1]))
+        btn = DirectOptionMenuTest(
+            text=_('Language'), items=items, initialitem=inititem,
+            pos=(0, 1, .8), command=self.on_language, **self._common_opt)
+        btn._show_cb = lang_cb
+        btn.popupMenu['frameColor'] = self._common_btn['frameColor']
+        btn.popupMenu['relief'] = self._common_btn['relief']
+        self._widgets += [btn]
+        pos_lang = LibP3d.wdg_pos(self._widgets[-1])
+        self._pos_mgr.register('languages', pos_lang)
+        self._widgets += [OnscreenText(
+            _('Volume'), pos=(-.1, .55), font=self._common['text_font'],
+            scale=self._common['scale'], fg=self._common['text_fg'],
+            align=TextNode.A_right)]
+        self._widgets += [DirectSlider(
+            pos=(.5, 1, .57),
+            value=self._opt_file['settings']['volume'],
+            command=self.on_volume,
+            **self._common_slider)]
+        vol_pos = LibP3d.wdg_pos(self._widgets[-1])
+        self._pos_mgr.register('volume', vol_pos)
+        np_left = NodePath('left_slider')
+        np_left.set_pos(self._widgets[-1].get_net_transform().get_pos())
+        np_left.set_x(np_left.get_x() - self._widgets[-1].get_scale()[0])
+        lpos = LibP3d.wdg_pos(np_left)
+        self._pos_mgr.register('volume_0', lpos)
+        self._slider = self._widgets[-1]
+        self._widgets += [DirectCheckButton(
+            text=_('Fullscreen'), pos=(0, 1, .3), command=self.on_fullscreen,
+            indicator_frameColor=self._common_opt['highlightColor'],
+            indicator_relief=self._common_btn['relief'],
+            indicatorValue=self._opt_file['settings']['fullscreen'],
+            **self._common_btn)]
+        self._pos_mgr.register('fullscreen', LibP3d.wdg_pos(self._widgets[-1]))
+        res = self._opt_file['settings']['resolution']
+        d_i = base.pipe.get_display_information()
+        def _res(idx):
+            return d_i.get_display_mode_width(idx), \
+                d_i.get_display_mode_height(idx)
+        resolutions = [
+            _res(idx) for idx in range(d_i.get_total_display_modes())]
+        resolutions = list(set(resolutions))
+        resolutions = sorted(resolutions)
+        resolutions = [(str(_res[0]), str(_res[1])) for _res in resolutions]
+        resolutions = ['x'.join(_res) for _res in resolutions]
+        if not res:
+            res = resolutions[-1]
+
+        def res_cb(comps):
+            self._pos_mgr.remove(['res_1440x900', 'res_1360x768'])
+            for tgt_res in ['1440x900', '1360x768']:
+                for btn in comps:
+                    if btn['text'] == tgt_res:
+                        pos = LibP3d.wdg_pos(btn)
+                        self._pos_mgr.register('res_' + tgt_res, (pos[0] + 5, pos[1]))
+        btn = DirectOptionMenuTest(
+            text=_('Resolution'), items=resolutions, initialitem=res,
+            pos=(0, 1, .05), command=self.on_resolution, **self._common_opt)
+        btn._show_cb = res_cb
+        btn.popupMenu['frameColor'] = self._common_btn['frameColor']
+        btn.popupMenu['relief'] = self._common_btn['relief']
+        self._widgets += [btn]
+        pos_res = LibP3d.wdg_pos(self._widgets[-1])
+        self._pos_mgr.register('resolutions', pos_res)  # 680 365
+        self._pos_mgr.register('res_1440x900', [pos_res[0] + 320, pos_res[1] + 75])
+        self._pos_mgr.register('res_1360x768', [pos_res[0] + 430, pos_res[1] -285])
+        self._widgets += [DirectCheckButton(
+            text=_('Antialiasing'), pos=(0, 1, -.2), command=self.on_aa,
+            indicator_frameColor=self._common_opt['highlightColor'],
+            indicator_relief=self._common_btn['relief'],
+            indicatorValue=self._opt_file['settings']['antialiasing'],
+            **self._common_btn)]
+        self._pos_mgr.register('aa', LibP3d.wdg_pos(self._widgets[-1]))
+        self._widgets += [DirectCheckButton(
+            text=_('Shadows'), pos=(0, 1, -.45), command=self.on_shadows,
+            indicator_frameColor=self._common_opt['highlightColor'],
+            indicator_relief=self._common_btn['relief'],
+            indicatorValue=self._opt_file['settings']['shadows'],
+            **self._common_btn)]
+        self._pos_mgr.register('shadows', LibP3d.wdg_pos(self._widgets[-1]))
+        self._widgets += [DirectButton(
+            text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
+            **self._common_btn)]
+        self._pos_mgr.register('back', LibP3d.wdg_pos(self._widgets[-1]))
+        self.accept('enforce_resolution', self.enforce_res)
+
+    def _set_credits(self):
+        self._pos_mgr.reset()
+        self._widgets = []
+        tp_scale = TextProperties()
+        tp_scale.set_text_scale(.64)
+        TextPropertiesManager.getGlobalPtr().setProperties('scale', tp_scale)
+        self._widgets += [OnscreenText(
+            _('Code and gfx\n  \1scale\1Flavio Calva\2\n\n\nMusic\n  \1scale\1Stefan Grossmann\2'),
+            pos=(-.9, .55), font=self._common['text_font'],
+            scale=self._common['scale'], fg=self._common['text_fg'],
+            align=TextNode.A_left)]
+        self._widgets += [DirectButton(
+            text=_('Website'), pos=(-.6, 1, .29), command=self.on_website,
+            **self._common_btn | {'scale': .08})]
+        self._widgets += [OnscreenText(
+            _('Special thanks to:\n  \1scale\1rdb\2\n  \1scale\1Luisa Tenuta\2\n  \1scale\1Damiana Ercolani\2'),
+            pos=(.1, .55), font=self._common['text_font'],
+            scale=self._common['scale'], fg=self._common['text_fg'],
+            align=TextNode.A_left)]
+        self._widgets += [DirectButton(
+            text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
+            **self._common_btn)]
+        self._pos_mgr.register('back', LibP3d.wdg_pos(self._widgets[-1]))
+        self.accept('enforce_resolution', self.enforce_res)
+
+    def on_play(self):
+        self._pos_mgr.reset()
+        self.destroy()
+        self._cursor = MouseCursor(
+            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
+            (.01, .01))
+        self._widgets = []
+        cmn = self._common_btn.copy() | {
+            'frameSize': (-2.4, 2.4, -2.4, 2.4),
+            'frameColor': (1, 1, 1, .8),
+            'text_scale': .64}
+        left = - (dx := .8) * (min(4, len(self._scenes)) - 1) / 2
+        for i, cls in enumerate(self._scenes):
+            top = .1 if len(self._scenes) < 5 else .6
+            row = 0 if i < 4 else 1
+            self._widgets += [DirectButton(
+                text=cls.name(), pos=(left + dx * (i % 4), 1, top - dx * row),
+                command=self.start, extraArgs=[cls], text_wordwrap=6,
+                frameTexture='assets/images/scenes/%s.dds' % cls.__name__,
+                **cmn)]
+            name = cls.__name__[5:].lower()
+            self._pos_mgr.register(name, LibP3d.wdg_pos(self._widgets[-1]))
+            for j in range(4):
+                tnode = self._widgets[-1].component('text%s' % j).textNode
+                height = - tnode.getLineHeight() / 2
+                height += (tnode.get_height() - tnode.get_line_height()) / 2
+                self._widgets[-1].component('text%s' % j).set_pos(0, 0, height)
+        self._widgets += [DirectButton(
+            text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
+            **self._common_btn)]
+        self._pos_mgr.register('back', LibP3d.wdg_pos(self._widgets[-1]))
+
+    def start(self, cls):
+        self._fsm.demand('Scene', cls)
+
+    def on_options(self):
+        self.destroy()
+        self._cursor = MouseCursor(
+            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
+            (.01, .01))
+        self._set_options()
+
+    def on_credits(self):
+        self.destroy()
+        self._cursor = MouseCursor(
+            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
+            (.01, .01))
+        self._set_credits()
+
+    def _rearrange_width(self):
+        max_width = 0
+        for wdg in self._widgets:
+            t_n = wdg.component('text0')
+            u_l = t_n.textNode.get_upper_left_3d()
+            l_r = t_n.textNode.get_lower_right_3d()
+            max_width = max(l_r[0] - u_l[0], max_width)
+        for wdg in self._widgets:
+            m_w = max_width / 2 + .8
+            wdg['frameSize'] = -m_w, m_w, wdg['frameSize'][2], wdg['frameSize'][3]
+
+    def on_language(self, arg):
+        lang_code = {
+            _('English'): 'en_EN',
+            _('Italian'): 'it_IT'}[arg]
+        self._lang_mgr.set_lang(lang_code)
+        self._opt_file['settings']['language'] = lang_code[:2]
+        self._opt_file.store()
+        self.on_options()
+
+    def on_volume(self):
+        self._opt_file['settings']['volume'] = self._slider['value']
+        self._music.set_volume(self._slider['value'])
+
+    def on_fullscreen(self, arg):
+        props = WindowProperties()
+        props.set_fullscreen(arg)
+        if not self._fun_test:
+            base.win.request_properties(props)
+            # if we actually switch to fullscreen during the tests then
+            # exwm inside qemu can't restore the correct resolution
+            # i may re-enable this if/when i run the tests onto a
+            # physical machine
+        self._opt_file['settings']['fullscreen'] = int(arg)
+        self._opt_file.store()
+
+    def on_resolution(self, arg):
+        info('on resolution: %s (%s)' % (arg, self._enforced_res))
+        arg = self._enforced_res or arg
+        info('set resolution: %s' % arg)
+        props = WindowProperties()
+        props.set_size(LVector2i(*[int(_res) for _res in arg.split('x')]))
+        base.win.request_properties(props)
+        self._opt_file['settings']['resolution'] = arg
+        self._opt_file.store()
+
+    def on_aa(self, arg):
+        self._pipeline.msaa_samples = 4 if arg else 1
+        debug(f'msaa: {self._pipeline.msaa_samples}')
+        self._opt_file['settings']['antialiasing'] = int(arg)
+        self._opt_file.store()
+
+    def on_shadows(self, arg):
+        self._pipeline.enable_shadows = int(arg)
+        debug(f'shadows: {self._pipeline.enable_shadows}')
+        self._opt_file['settings']['shadows'] = int(arg)
+        self._opt_file.store()
+
+    def on_website(self):
+        if platform.startswith('linux'):
+            environ['LD_LIBRARY_PATH'] = ''
+            system('xdg-open https://www.ya2.it')
+        else:
+            open_new_tab('https://www.ya2.it')
+
+    def on_back(self):
+        self._opt_file.store()
+        self.destroy()
+        self._cursor = MouseCursor(
+            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
+            (.01, .01))
+        self._set_main()
+
+    def destroy(self):
+        [wdg.destroy() for wdg in self._widgets]
+        self._cursor.destroy()
+        self.ignore('enforce_resolution')
diff --git a/pmachines/gui/sidepanel.py b/pmachines/gui/sidepanel.py
new file mode 100644 (file)
index 0000000..39afaa4
--- /dev/null
@@ -0,0 +1,132 @@
+from textwrap import dedent
+from panda3d.core import GeomVertexData, GeomVertexFormat, Geom, \
+    GeomVertexWriter, GeomTriangles, GeomNode, Shader, Point3, Plane, Vec3
+from ya2.p3d.gfx import P3dGfxMgr
+
+
+class SidePanel:
+
+    def __init__(self, world, plane_node, top_l, bottom_r, y, items):
+        self._world = world
+        self._plane_node = plane_node
+        self._set((-1, 1), y)
+        self.update(items)
+
+    def update(self, items):
+        p_from, p_to = P3dGfxMgr.world_from_to((-1, 1))
+        for hit in self._world.ray_test_all(p_from, p_to).get_hits():
+            if hit.get_node() == self._plane_node:
+                pos = hit.get_hit_pos()
+        y = 0
+        corner = -20, 20
+        for item in items:
+            if not item._instantiated:
+                bounds = item._np.get_tight_bounds()
+                if bounds[1][1] > y:
+                    y = bounds[1][1]
+                icorner = item.get_corner()
+                icorner = P3dGfxMgr.screen_coord(icorner)
+                if icorner[0] > corner[0]:
+                    corner = icorner[0], corner[1]
+                if icorner[1] < corner[1]:
+                    corner = corner[0], icorner[1]
+        self._set((pos[0], pos[2]), y)
+        bounds = self._np.get_tight_bounds()
+        corner3d = bounds[1][0], bounds[1][1], bounds[0][2]
+        corner2d = P3dGfxMgr.screen_coord(corner3d)
+        plane = Plane(Vec3(0, 1, 0), y)
+        pos3d, near_pt, far_pt = Point3(), Point3(), Point3()
+        ar, margin = base.get_aspect_ratio(), .04
+        x = corner[0] / ar if ar >= 1 else corner[0]
+        z = corner[1] * ar if ar < 1 else corner[1]
+        x += margin / ar if ar >= 1 else margin
+        z -= margin * ar if ar < 1 else margin
+        base.camLens.extrude((x, z), near_pt, far_pt)
+        plane.intersects_line(
+            pos3d, render.get_relative_point(base.camera, near_pt),
+            render.get_relative_point(base.camera, far_pt))
+        corner_pos3d, near_pt, far_pt = Point3(), Point3(), Point3()
+        base.camLens.extrude((-1, 1), near_pt, far_pt)
+        plane.intersects_line(
+            corner_pos3d, render.get_relative_point(base.camera, near_pt),
+            render.get_relative_point(base.camera, far_pt))
+        self._np.set_pos((pos3d + corner_pos3d) / 2)
+        scale = Vec3(pos3d[0] - corner_pos3d[0], 1, corner_pos3d[2] - pos3d[2])
+        self._np.set_scale(scale)
+
+    def _set(self, pos, y):
+        if hasattr(self, '_np'):
+            self._np.remove_node()
+        vdata = GeomVertexData('quad', GeomVertexFormat.get_v3(), Geom.UHStatic)
+        vdata.setNumRows(2)
+        vertex = GeomVertexWriter(vdata, 'vertex')
+        vertex.add_data3(.5, 0, -.5)
+        vertex.add_data3(.5, 0, .5)
+        vertex.add_data3(-.5, 0, .5)
+        vertex.add_data3(-.5, 0, -.5)
+        prim = GeomTriangles(Geom.UHStatic)
+        prim.add_vertices(0, 1, 2)
+        prim.add_vertices(0, 2, 3)
+        prim.close_primitive()
+        geom = Geom(vdata)
+        geom.add_primitive(prim)
+        node = GeomNode('gnode')
+        node.add_geom(geom)
+        self._np = render.attach_new_node(node)
+        self._np.setTransparency(True)
+        self._np.set_pos(pos[0], y, pos[1])
+        vert = '''\
+            #version 130
+            uniform mat4 p3d_ModelViewProjectionMatrix;
+            in vec4 p3d_Vertex;
+            void main() {
+                gl_Position = p3d_ModelViewProjectionMatrix * p3d_Vertex; }'''
+        frag = '''\
+            #version 130
+            out vec4 p3d_FragColor;
+            void main() {
+                p3d_FragColor = vec4(.04, .04, .04, .08); }'''
+        self._np.set_shader(Shader.make(Shader.SL_GLSL, dedent(vert), dedent(frag)))
+        # mat = Material()
+        # mat.set_base_color((1, 1, 1, 1))
+        # mat.set_emission((1, 1, 1, 1))
+        # mat.set_metallic(.5)
+        # mat.set_roughness(.5)
+        # np.set_material(mat)
+        # texture_sz = 64
+        # base_color_pnm = PNMImage(texture_sz, texture_sz)
+        # base_color_pnm.fill(.1, .1, .1)
+        # base_color_pnm.add_alpha()
+        # base_color_pnm.alpha_fill(.04)
+        # base_color_tex = Texture('base color')
+        # base_color_tex.load(base_color_pnm)
+        # ts = TextureStage('base color')
+        # ts.set_mode(TextureStage.M_modulate)
+        # np.set_texture(ts, base_color_tex)
+        # emission_pnm = PNMImage(texture_sz, texture_sz)
+        # emission_pnm.fill(0, 0, 0)
+        # emission_tex = Texture('emission')
+        # emission_tex.load(emission_pnm)
+        # ts = TextureStage('emission')
+        # ts.set_mode(TextureStage.M_emission)
+        # np.set_texture(ts, emission_tex)
+        # metal_rough_pnm = PNMImage(texture_sz, texture_sz)
+        # ambient_occlusion = 1
+        # roughness = .5
+        # metallicity = .5
+        # metal_rough_pnm.fill(ambient_occlusion, roughness, metallicity)
+        # metal_rough_tex = Texture('ao metal roughness')
+        # metal_rough_tex.load(metal_rough_pnm)
+        # ts = TextureStage('ao metal roughness')
+        # ts.set_mode(TextureStage.M_selector)
+        # np.set_texture(ts, metal_rough_tex)
+        # normal_pnm = PNMImage(texture_sz, texture_sz)
+        # normal_pnm.fill(.5, .5, .1)
+        # normal_tex = Texture('normals')
+        # normal_tex.load(normal_pnm)
+        # ts = TextureStage('normals')
+        # ts.set_mode(TextureStage.M_normal)
+        # np.set_texture(ts, normal_tex)
+
+    def destroy(self):
+        self._np.remove_node()
diff --git a/pmachines/items/__init__.py b/pmachines/items/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/pmachines/items/background.py b/pmachines/items/background.py
new file mode 100644 (file)
index 0000000..5203202
--- /dev/null
@@ -0,0 +1,26 @@
+from itertools import product
+from panda3d.core import NodePath
+from ya2.p3d.gfx import set_srgb
+
+
+class Background:
+
+    def __init__(self):
+        self._root = NodePath('background_root')
+        self._root.reparent_to(render)
+        ncols, nrows = 16, 8
+        start_size, end_size = 5, 2.5
+        offset = 5
+        for col, row in product(range(ncols), range(nrows)):
+            model = loader.load_model('assets/models/bam/background/background.bam')
+            model.set_scale(end_size / start_size)
+            model.reparent_to(self._root)
+            total_width, total_height = end_size * ncols, end_size * nrows
+            left, bottom = -total_width/2, -total_height/2
+            model.set_pos(left + end_size * col, offset, bottom + end_size * row)
+        self._root.clear_model_nodes()
+        self._root.flatten_strong()
+        set_srgb(self._root)
+
+    def destroy(self):
+        self._root.remove_node()
diff --git a/pmachines/items/basketball.py b/pmachines/items/basketball.py
new file mode 100644 (file)
index 0000000..1063379
--- /dev/null
@@ -0,0 +1,11 @@
+from panda3d.bullet import BulletSphereShape, BulletRigidBodyNode, BulletGhostNode
+from pmachines.items.item import Item
+
+
+class Basketball(Item):
+
+    def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.92, friction=.6):
+        super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/basketball/basketball.bam', .4, mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction)
+
+    def _set_shape(self, apply_scale=True):
+        self.node.add_shape(BulletSphereShape(1))
diff --git a/pmachines/items/box.py b/pmachines/items/box.py
new file mode 100644 (file)
index 0000000..dd25992
--- /dev/null
@@ -0,0 +1,25 @@
+from panda3d.bullet import BulletBoxShape, BulletRigidBodyNode, BulletGhostNode
+from pmachines.items.item import Item
+
+
+class Box(Item):
+
+    def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.8, model_scale=1):
+        super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/box/box.bam', mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction, model_scale=model_scale)
+
+    def _set_shape(self, apply_scale=True):
+        self.node.add_shape(BulletBoxShape((.5, .5, .5)))
+
+
+class HitStrategy:
+
+    def __init__(self, hit_by, node, world):
+        self._hit_by = hit_by
+        self._node = node
+        self._world = world
+
+    def win_condition(self):
+        for contact in self._world.contact_test(self._node).get_contacts():
+            other = contact.get_node1() if contact.get_node0() == self._node else contact.get_node0()
+            if other == self._hit_by.node:
+                return True
diff --git a/pmachines/items/domino.py b/pmachines/items/domino.py
new file mode 100644 (file)
index 0000000..333305d
--- /dev/null
@@ -0,0 +1,33 @@
+from panda3d.bullet import BulletBoxShape, BulletRigidBodyNode, BulletGhostNode
+from pmachines.items.item import Item, StillStrategy
+
+
+class Domino(Item):
+
+    def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.6):
+        super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/domino/domino.bam', mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction)
+
+    def _set_shape(self, apply_scale=True):
+        self.node.add_shape(BulletBoxShape((.1, .25, .5)))
+
+
+class UpStrategy(StillStrategy):
+
+    def __init__(self, np, tgt_degrees):
+        super().__init__(np)
+        self._tgt_degrees = tgt_degrees
+        self._np = np
+
+    def win_condition(self):
+        return super().win_condition() and abs(self._np.get_r()) <= self._tgt_degrees
+
+
+class DownStrategy(StillStrategy):
+
+    def __init__(self, np, tgt_degrees):
+        super().__init__(np)
+        self._tgt_degrees = tgt_degrees
+        self._np = np
+
+    def win_condition(self):
+        return self._np.get_z() < -6 or super().win_condition() and abs(self._np.get_r()) >= self._tgt_degrees
diff --git a/pmachines/items/item.py b/pmachines/items/item.py
new file mode 100644 (file)
index 0000000..9f2bf5d
--- /dev/null
@@ -0,0 +1,339 @@
+from panda3d.core import CullFaceAttrib, Point3, NodePath, Point2, Texture, \
+    Plane, Vec3, BitMask32
+from panda3d.bullet import BulletBoxShape, BulletRigidBodyNode, BulletGhostNode
+from direct.gui.OnscreenText import OnscreenText
+from ya2.p3d.gfx import P3dGfxMgr, set_srgb
+
+
+class Command:
+
+    def __init__(self, pos, rot):
+        self.pos = pos
+        self.rot = rot
+
+
+class FixedStrategy:
+
+    def win_condition(self):
+        return True
+
+
+class StillStrategy:
+
+    def __init__(self, np):
+        self._np = np
+        self._positions = []
+        self._rotations = []
+
+    def win_condition(self):
+        self._positions += [self._np.get_pos()]
+        self._rotations += [self._np.get_hpr()]
+        if len(self._positions) > 30:
+            self._positions.pop(0)
+        if len(self._rotations) > 30:
+            self._rotations.pop(0)
+        if len(self._positions) < 28:
+            return
+        avg_x = sum(pos.x for pos in self._positions) / len(self._positions)
+        avg_y = sum(pos.y for pos in self._positions) / len(self._positions)
+        avg_z = sum(pos.z for pos in self._positions) / len(self._positions)
+        avg_h = sum(rot.x for rot in self._rotations) / len(self._rotations)
+        avg_p = sum(rot.y for rot in self._rotations) / len(self._rotations)
+        avg_r = sum(rot.z for rot in self._rotations) / len(self._rotations)
+        avg_pos = Point3(avg_x, avg_y, avg_z)
+        avg_rot = Point3(avg_h, avg_p, avg_r)
+        return all((pos - avg_pos).length() < .1 for pos in self._positions) and \
+            all((rot - avg_rot).length() < 1 for rot in self._rotations)
+
+
+class Item:
+
+    def __init__(self, world, plane_node, cb_inst, curr_bottom, scene_repos, model_path, model_scale=1, exp_num_contacts=1, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.5):
+        self._world = world
+        self._plane_node = plane_node
+        self._count = count
+        self._cb_inst = cb_inst
+        self._paused = False
+        self._overlapping = False
+        self._first_command = True
+        self.repos_done = False
+        self._mass = mass
+        self._pos = pos
+        self._r = r
+        self.strategy = FixedStrategy()
+        self._exp_num_contacts = exp_num_contacts
+        self._curr_bottom = curr_bottom
+        self._scene_repos = scene_repos
+        self._model_scale = model_scale
+        self._model_path = model_path
+        self._commands = []
+        self._command_idx = -1
+        self._restitution = restitution
+        self._friction = friction
+        self._positions = []
+        self._rotations = []
+        if count:
+            self.node = BulletGhostNode(self.__class__.__name__)
+        else:
+            self.node = BulletRigidBodyNode(self.__class__.__name__)
+        self._set_shape(count)
+        self._np = render.attach_new_node(self.node)
+        if count:
+            world.attach_ghost(self.node)
+        else:
+            world.attach_rigid_body(self.node)
+        self._model = loader.load_model(model_path)
+        set_srgb(self._model)
+        self._model.flatten_light()
+        self._model.reparent_to(self._np)
+        self._np.set_scale(model_scale)
+        self._np.flatten_strong()
+        if count:
+            self._set_outline_model()
+            set_srgb(self._outline_model)
+            self._model.hide(BitMask32(0x01))
+            self._outline_model.hide(BitMask32(0x01))
+        self._start_drag_pos = None
+        self._prev_rot_info = None
+        self._last_nonoverlapping_pos = None
+        self._last_nonoverlapping_rot = None
+        self._instantiated = not count
+        self.interactable = count
+        self._box_tsk = taskMgr.add(self.on_frame, 'on_frame')
+        if count:
+            self._repos()
+        else:
+            self._np.set_pos(pos)
+            self._np.set_r(r)
+
+    def _set_shape(self, apply_scale=True):
+        pass
+
+    def set_strategy(self, strategy):
+        self.strategy = strategy
+
+    def _repos(self):
+        p_from, p_to = P3dGfxMgr.world_from_to((-1, 1))
+        for hit in self._world.ray_test_all(p_from, p_to).get_hits():
+            if hit.get_node() == self._plane_node:
+                pos = hit.get_hit_pos()
+        corner = P3dGfxMgr.screen_coord(pos)
+        bounds = self._np.get_tight_bounds()
+        bounds = bounds[0] - self._np.get_pos(), bounds[1] - self._np.get_pos()
+        self._np.set_pos(pos)
+        plane = Plane(Vec3(0, 1, 0), bounds[0][1])
+        pos3d, near_pt, far_pt = Point3(), Point3(), Point3()
+        margin, ar = .04, base.get_aspect_ratio()
+        margin_x = margin / ar if ar >= 1 else margin
+        margin_z = margin * ar if ar < 1 else margin
+        top = self._curr_bottom()
+        base.camLens.extrude((-1 + margin_x, top - margin_z), near_pt, far_pt)
+        plane.intersects_line(
+            pos3d, render.get_relative_point(base.camera, near_pt),
+            render.get_relative_point(base.camera, far_pt))
+        delta = Vec3(bounds[1][0], bounds[1][1], bounds[0][2])
+        self._np.set_pos(pos3d + delta)
+        if not hasattr(self, '_txt'):
+            font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
+            font.clear()
+            font.set_pixels_per_unit(60)
+            font.set_minfilter(Texture.FTLinearMipmapLinear)
+            font.set_outline((0, 0, 0, 1), .8, .2)
+            self._txt = OnscreenText(
+                str(self._count), font=font, scale=0.06, fg=(.9, .9, .9, 1))
+        pos = self._np.get_pos() + (bounds[1][0], bounds[0][1], bounds[0][2])
+        p2d = P3dGfxMgr.screen_coord(pos)
+        self._txt['pos'] = p2d
+        self.repos_done = True
+
+    def repos_x(self, x):
+        self._np.set_x(x)
+        bounds = self._np.get_tight_bounds()
+        bounds = bounds[0] - self._np.get_pos(), bounds[1] - self._np.get_pos()
+        pos = self._np.get_pos() + (bounds[1][0], bounds[0][1], bounds[0][2])
+        p2d = P3dGfxMgr.screen_coord(pos)
+        self._txt['pos'] = p2d
+
+    def get_bottom(self):
+        bounds = self._np.get_tight_bounds()
+        bounds = bounds[0] - self._np.get_pos(), bounds[1] - self._np.get_pos()
+        pos = self._np.get_pos() + (bounds[1][0], bounds[1][1], bounds[0][2])
+        p2d = P3dGfxMgr.screen_coord(pos)
+        ar = base.get_aspect_ratio()
+        return p2d[1] if ar >= 1 else (p2d[1] * ar)
+
+    def get_corner(self):
+        bounds = self._np.get_tight_bounds()
+        return bounds[1][0], bounds[1][1], bounds[0][2]
+
+    def _set_outline_model(self):
+        self._outline_model = loader.load_model(self._model_path)
+        #clockw = CullFaceAttrib.MCullClockwise
+        #self._outline_model.set_attrib(CullFaceAttrib.make(clockw))
+        self._outline_model.set_attrib(CullFaceAttrib.make_reverse())
+        self._outline_model.reparent_to(self._np)
+        self._outline_model.set_scale(1.08)
+        self._outline_model.set_light_off()
+        self._outline_model.set_color(.4, .4, .4, 1)
+        self._outline_model.set_color_scale(.4, .4, .4, 1)
+        self._outline_model.hide()
+
+    def on_frame(self, task):
+        self._np.set_y(0)
+        return task.cont
+
+    def undo(self):
+        self._command_idx -= 1
+        self._np.set_pos(self._commands[self._command_idx].pos)
+        self._np.set_hpr(self._commands[self._command_idx].rot)
+
+    def redo(self):
+        self._command_idx += 1
+        self._np.set_pos(self._commands[self._command_idx].pos)
+        self._np.set_hpr(self._commands[self._command_idx].rot)
+
+    def play(self):
+        if not self._instantiated:
+            return
+        self._world.remove_rigid_body(self.node)
+        self.node.set_mass(self._mass)
+        self._world.attach_rigid_body(self.node)
+        self.node.set_restitution(self._restitution)
+        self.node.set_friction(self._friction)
+
+    def on_click_l(self, pos):
+        if self._paused: return
+        self._start_drag_pos = pos, self._np.get_pos()
+        loader.load_sfx('assets/audio/sfx/grab.ogg').play()
+        if not self._instantiated:
+            self._world.remove_ghost(self.node)
+            pos = self._np.get_pos()
+            self._np.remove_node()
+            self.node = BulletRigidBodyNode('box')
+            self._set_shape()
+            self._np = render.attach_new_node(self.node)
+            self._world.attach_rigid_body(self.node)
+            self._model.reparent_to(self._np)
+            self._np.set_pos(pos)
+            self._set_outline_model()
+            self._np.set_scale(self._model_scale)
+            self._model.show(BitMask32(0x01))
+            self._outline_model.hide(BitMask32(0x01))
+            self._instantiated = True
+            self._txt.destroy()
+            self._count -= 1
+            if self._count:
+                item = self.__class__(self._world, self._plane_node, self._cb_inst, self._curr_bottom, self._scene_repos, count=self._count, mass=self._mass, pos=self._pos, r=self._r)  # pylint: disable=no-value-for-parameter
+                self._cb_inst(item)
+            self._scene_repos()
+
+    def on_click_r(self, pos):
+        if self._paused or not self._instantiated: return
+        self._prev_rot_info = pos, self._np.get_pos(), self._np.get_r()
+        loader.load_sfx('assets/audio/sfx/grab.ogg').play()
+
+    def on_release(self):
+        if self._start_drag_pos or self._prev_rot_info:
+            loader.load_sfx('assets/audio/sfx/release.ogg').play()
+            self._command_idx += 1
+            self._commands = self._commands[:self._command_idx]
+            self._commands += [Command(self._np.get_pos(), self._np.get_hpr())]
+            self._first_command = False
+        self._start_drag_pos = self._prev_rot_info = None
+        if self._overlapping:
+            self._np.set_pos(self._last_nonoverlapping_pos)
+            self._np.set_hpr(self._last_nonoverlapping_rot)
+            self._outline_model.set_color(.4, .4, .4, 1)
+            self._outline_model.set_color_scale(.4, .4, .4, 1)
+            self._overlapping = False
+
+    def on_mouse_on(self):
+        if not self._paused and self.interactable:
+            self._outline_model.show()
+
+    def on_mouse_off(self):
+        if self._start_drag_pos or self._prev_rot_info: return
+        if self.interactable:
+            self._outline_model.hide()
+
+    def on_mouse_move(self, pos):
+        if self._start_drag_pos:
+            d_pos =  pos - self._start_drag_pos[0]
+            self._np.set_pos(self._start_drag_pos[1] + d_pos)
+        if self._prev_rot_info:
+            start_vec = self._prev_rot_info[0] - self._prev_rot_info[1]
+            curr_vec = pos - self._prev_rot_info[1]
+            d_angle = curr_vec.signed_angle_deg(start_vec, (0, -1, 0))
+            self._np.set_r(self._prev_rot_info[2] + d_angle)
+            self._prev_rot_info = pos, self._np.get_pos(), self._np.get_r()
+        if self._start_drag_pos or self._prev_rot_info:
+            res = self._world.contact_test(self.node)
+            nres = res.get_num_contacts()
+            if nres <= self._exp_num_contacts:
+                self._overlapping = False
+                self._outline_model.set_color(.4, .4, .4, 1)
+                self._outline_model.set_color_scale(.4, .4, .4, 1)
+            if nres > self._exp_num_contacts and not self._overlapping:
+                actual_nres = 0
+                for contact in res.get_contacts():
+                    for node in [contact.get_node0(), contact.get_node1()]:
+                        if isinstance(node, BulletRigidBodyNode) and \
+                                node != self.node:
+                            actual_nres += 1
+                if actual_nres >= 1:
+                    self._overlapping = True
+                    loader.load_sfx('assets/audio/sfx/overlap.ogg').play()
+                    self._outline_model.set_color(.9, .1, .1, 1)
+                    self._outline_model.set_color_scale(.9, .1, .1, 1)
+        if not self._overlapping:
+            self._last_nonoverlapping_pos = self._np.get_pos()
+            self._last_nonoverlapping_rot = self._np.get_hpr()
+
+    def on_aspect_ratio_changed(self):
+        if not self._instantiated:
+            self._repos()
+
+    def store_state(self):
+        self._paused = True
+        self._model.set_transparency(True)
+        self._model.set_alpha_scale(.3)
+        if hasattr(self, '_txt') and not self._txt.is_empty():
+            self._txt.set_alpha_scale(.3)
+
+    def restore_state(self):
+        self._paused = False
+        self._model.set_alpha_scale(1)
+        if hasattr(self, '_txt') and not self._txt.is_empty():
+            self._txt.set_alpha_scale(1)
+
+    def fail_condition(self):
+        if self._np.get_z() < -6:
+            return True
+        self._positions += [self._np.get_pos()]
+        self._rotations += [self._np.get_hpr()]
+        if len(self._positions) > 30:
+            self._positions.pop(0)
+        if len(self._rotations) > 30:
+            self._rotations.pop(0)
+        if len(self._positions) < 28:
+            return
+        avg_x = sum(pos.x for pos in self._positions) / len(self._positions)
+        avg_y = sum(pos.y for pos in self._positions) / len(self._positions)
+        avg_z = sum(pos.z for pos in self._positions) / len(self._positions)
+        avg_h = sum(rot.x for rot in self._rotations) / len(self._rotations)
+        avg_p = sum(rot.y for rot in self._rotations) / len(self._rotations)
+        avg_r = sum(rot.z for rot in self._rotations) / len(self._rotations)
+        avg_pos = Point3(avg_x, avg_y, avg_z)
+        avg_rot = Point3(avg_h, avg_p, avg_r)
+        return all((pos - avg_pos).length() < .1 for pos in self._positions) and \
+            all((rot - avg_rot).length() < 1 for rot in self._rotations)
+
+    def destroy(self):
+        self._np.remove_node()
+        taskMgr.remove(self._box_tsk)
+        if hasattr(self, '_txt'):
+            self._txt.destroy()
+        if not self._instantiated:
+            self._world.remove_ghost(self.node)
+        else:
+            self._world.remove_rigid_body(self.node)
diff --git a/pmachines/items/shelf.py b/pmachines/items/shelf.py
new file mode 100644 (file)
index 0000000..2710dd6
--- /dev/null
@@ -0,0 +1,11 @@
+from panda3d.bullet import BulletBoxShape, BulletRigidBodyNode, BulletGhostNode
+from pmachines.items.item import Item
+
+
+class Shelf(Item):
+
+    def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.6):
+        super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/shelf/shelf.bam', mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction)
+
+    def _set_shape(self, apply_scale=True):
+        self.node.add_shape(BulletBoxShape((1, .5, .05)))
diff --git a/pmachines/items/teetertooter.py b/pmachines/items/teetertooter.py
new file mode 100644 (file)
index 0000000..20adfb9
--- /dev/null
@@ -0,0 +1,21 @@
+from panda3d.core import TransformState
+from panda3d.bullet import BulletCylinderShape, BulletRigidBodyNode, BulletGhostNode, YUp, ZUp
+from pmachines.items.item import Item
+
+
+class TeeterTooter(Item):
+
+    def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.5):
+        super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/teeter_tooter/teeter_tooter.bam', exp_num_contacts=2, mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction, model_scale=.5)
+
+    def _set_shape(self, apply_scale=True):
+        scale = self._model_scale if apply_scale else 1
+        self.node.add_shape(
+            BulletCylinderShape(.1, 1.6, YUp),
+            TransformState.makePos((0, 0, scale * .36)))
+        self.node.add_shape(
+            BulletCylinderShape(.1, .7, ZUp),
+            TransformState.makePos((0, scale * .8, scale * -.1)))
+        self.node.add_shape(
+            BulletCylinderShape(.1, .7, ZUp),
+            TransformState.makePos((0, scale * -.8, scale * -.1)))
diff --git a/pmachines/posmgr.py b/pmachines/posmgr.py
new file mode 100644 (file)
index 0000000..077bd2a
--- /dev/null
@@ -0,0 +1,19 @@
+class PositionMgr:
+
+    def __init__(self):
+        self._pos = {}
+
+    def reset(self):
+        self._pos = {}
+
+    def register(self, wdg, pos):
+        # print('register', wdg, pos)
+        self._pos[wdg] = pos
+
+    def get(self, tgt):
+        return self._pos[tgt]
+
+    def remove(self, elements):
+        for element in elements:
+            if element in self._pos:
+                del self._pos[element]
diff --git a/pmachines/scene.py b/pmachines/scene.py
new file mode 100644 (file)
index 0000000..ae0f759
--- /dev/null
@@ -0,0 +1,643 @@
+from os.path import exists
+from os import makedirs
+from glob import glob
+from logging import debug, info
+from importlib import import_module
+from inspect import isclass
+from panda3d.core import AmbientLight, DirectionalLight, Point3, Texture, \
+    TextPropertiesManager, TextNode, Spotlight, PerspectiveLens, BitMask32, NodePath
+from panda3d.bullet import BulletPlaneShape, BulletGhostNode
+from direct.gui.OnscreenImage import OnscreenImage
+from direct.gui.OnscreenText import OnscreenText
+from direct.gui.DirectGui import DirectButton, DirectFrame
+from direct.gui.DirectGuiGlobals import FLAT, DISABLED, NORMAL
+from direct.showbase.DirectObject import DirectObject
+from pmachines.items.background import Background
+from pmachines.gui.sidepanel import SidePanel
+from ya2.utils.cursor import MouseCursor
+from ya2.p3d.gfx import P3dGfxMgr
+from ya2.p3d.p3d import LibP3d
+
+
+class Scene(DirectObject):
+
+    def __init__(self, world, exit_cb, auto_close_instr, dbg_items, reload_cb, scenes, pos_mgr, testing, mouse_coords):
+        super().__init__()
+        self._world = world
+        self._exit_cb = exit_cb
+        self._testing = testing
+        self._mouse_coords = mouse_coords
+        self._dbg_items = dbg_items
+        self._reload_cb = reload_cb
+        self._pos_mgr = pos_mgr
+        self._pos_mgr.reset()
+        self._scenes = scenes
+        self._start_evt_time = None
+        self._enforce_res = ''
+        self.accept('enforce_res', self.enforce_res)
+        self._set_camera()
+        self._cursor = MouseCursor(
+            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
+            (.01, .01))
+        self._set_gui()
+        self._set_lights()
+        self._set_input()
+        self._set_mouse_plane()
+        self.items = []
+        self._test_items = []
+        self.reset()
+        self._state = 'init'
+        self._paused = False
+        self._item_active = None
+        if auto_close_instr:
+            self.__store_state()
+            self.__restore_state()
+        else:
+            self._set_instructions()
+        self._bg = Background()
+        self._side_panel = SidePanel(world, self._mouse_plane_node, (-5, 4), (-3, 1), 1, self.items)
+        self._scene_tsk = taskMgr.add(self.on_frame, 'on_frame')
+
+    @staticmethod
+    def name():
+        return ''
+
+    def _instr_txt(self):
+        return ''
+
+    def _set_items(self):
+        self.items = []
+        self._test_items = []
+
+    def screenshot(self, task=None):
+        tex = Texture('screenshot')
+        buffer = base.win.make_texture_buffer('screenshot', 512, 512, tex, True )
+        cam = base.make_camera(buffer)
+        cam.reparent_to(render)
+        cam.node().get_lens().set_fov(base.camLens.get_fov())
+        cam.set_pos(0, -20, 0)
+        cam.look_at(0, 0, 0)
+        import simplepbr
+        simplepbr.init(
+            window=buffer,
+            camera_node=cam,
+            use_normal_maps=True,
+            use_emission_maps=False,
+            use_occlusion_maps=True,
+            msaa_samples=4,
+            enable_shadows=True)
+        base.graphicsEngine.renderFrame()
+        base.graphicsEngine.renderFrame()
+        fname = self.__class__.__name__
+        if not exists('assets/images/scenes'):
+            makedirs('assets/images/scenes')
+        buffer.save_screenshot('assets/images/scenes/%s.png' % fname)
+        # img = DirectButton(
+        #     frameTexture=buffer.get_texture(), relief=FLAT,
+        #     frameSize=(-.2, .2, -.2, .2))
+        return buffer.get_texture()
+
+    def current_bottom(self):
+        curr_bottom = 1
+        for item in self.items:
+            if item.repos_done:
+                curr_bottom = min(curr_bottom, item.get_bottom())
+        return curr_bottom
+
+    def reset(self):
+        [itm.destroy() for itm in self.items]
+        [itm.remove_node() for itm in self._test_items]
+        self.items = []
+        self._test_items = []
+        self._set_items()
+        self._set_test_items()
+        self._state = 'init'
+        self._commands = []
+        self._command_idx = 0
+        self._start_evt_time = None
+        if hasattr(self, '_success_txt'):
+            self._success_txt.destroy()
+            del self._success_txt
+        self.__right_btn['state'] = NORMAL
+
+    def enforce_res(self, val):
+        self._enforce_res = val
+        info('enforce res: ' + val)
+
+    def destroy(self):
+        self.ignore('enforce_res')
+        self._unset_gui()
+        self._unset_lights()
+        self._unset_input()
+        self._unset_mouse_plane()
+        [itm.destroy() for itm in self.items]
+        [itm.remove_node() for itm in self._test_items]
+        self._bg.destroy()
+        self._side_panel.destroy()
+        self._cursor.destroy()
+        taskMgr.remove(self._scene_tsk)
+        if hasattr(self, '_success_txt'):
+            self._success_txt.destroy()
+
+    def _set_camera(self):
+        base.camera.set_pos(0, -20, 0)
+        base.camera.look_at(0, 0, 0)
+
+    def __load_img_btn(self, path, col):
+        img = OnscreenImage('assets/images/buttons/%s.dds' % path)
+        img.set_transparency(True)
+        img.set_color(col)
+        img.detach_node()
+        return img
+
+    def _set_gui(self):
+        def load_images_btn(path, col):
+            colors = {
+                'gray': [
+                    (.6, .6, .6, 1),  # ready
+                    (1, 1, 1, 1), # press
+                    (.8, .8, .8, 1), # rollover
+                    (.4, .4, .4, .4)],
+                'green': [
+                    (.1, .68, .1, 1),
+                    (.1, 1, .1, 1),
+                    (.1, .84, .1, 1),
+                    (.4, .1, .1, .4)]}[col]
+            return [self.__load_img_btn(path, col) for col in colors]
+        abl, abr = base.a2dBottomLeft, base.a2dBottomRight
+        btn_info = [
+            ('home', self.on_home, NORMAL, abl, 'gray'),
+            ('information', self._set_instructions, NORMAL, abl, 'gray'),
+            ('right', self.on_play, NORMAL, abr, 'green'),
+            #('next', self.on_next, DISABLED, abr, 'gray'),
+            #('previous', self.on_prev, DISABLED, abr, 'gray'),
+            #('rewind', self.reset, NORMAL, abr, 'gray')
+        ]
+        num_l = num_r = 0
+        btns = []
+        for binfo in btn_info:
+            imgs = load_images_btn(binfo[0], binfo[4])
+            if binfo[3] == base.a2dBottomLeft:
+                sign, num = 1, num_l
+                num_l += 1
+            else:
+                sign, num = -1, num_r
+                num_r += 1
+            fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
+            btn = DirectButton(
+                image=imgs, scale=.05, pos=(sign * (.06 + .11 * num), 1, .06),
+                parent=binfo[3], command=binfo[1], state=binfo[2], relief=FLAT,
+                frameColor=fcols[0] if binfo[2] == NORMAL else fcols[1],
+                rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
+                clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
+            btn.set_transparency(True)
+            self._pos_mgr.register(binfo[0], LibP3d.wdg_pos(btn))
+            btns += [btn]
+        self.__home_btn, self.__info_btn, self.__right_btn = btns
+        # , self.__next_btn, self.__prev_btn, self.__rewind_btn
+        if self._dbg_items:
+            self._info_txt = OnscreenText(
+                '', parent=base.a2dTopRight, scale=0.04,
+                pos=(-.03, -.06), fg=(.9, .9, .9, 1), align=TextNode.A_right)
+        if self._mouse_coords:
+            self._coords_txt = OnscreenText(
+                '', parent=base.a2dTopRight, scale=0.04,
+                pos=(-.03, -.12), fg=(.9, .9, .9, 1), align=TextNode.A_right)
+            def update_coords(task):
+                pos = None
+                for hit in self._get_hits():
+                    if hit.get_node() == self._mouse_plane_node:
+                        pos = hit.get_hit_pos()
+                if pos:
+                    txt = '%s %s' % (round(pos.x, 3),
+                                     round(pos.z, 3))
+                    self._coords_txt['text'] = txt
+                return task.cont
+            self._coords_tsk = taskMgr.add(update_coords, 'update_coords')
+
+    def _unset_gui(self):
+        btns = [
+            self.__home_btn, self.__info_btn, self.__right_btn,
+            #self.__next_btn, self.__prev_btn, self.__rewind_btn
+        ]
+        [btn.destroy() for btn in btns]
+        if self._dbg_items:
+            self._info_txt.destroy()
+        if self._mouse_coords:
+            taskMgr.remove(self._coords_tsk)
+            self._coords_txt.destroy()
+
+    def _set_spotlight(self, name, pos, look_at, color, shadows=False):
+        light = Spotlight(name)
+        if shadows:
+            light.setLens(PerspectiveLens())
+        light_np = render.attach_new_node(light)
+        light_np.set_pos(pos)
+        light_np.look_at(look_at)
+        light.set_color(color)
+        render.set_light(light_np)
+        return light_np
+
+    def _set_lights(self):
+        alight = AmbientLight('alight')  # for ao
+        alight.set_color((.15, .15, .15, 1))
+        self._alnp = render.attach_new_node(alight)
+        render.set_light(self._alnp)
+        self._key_light = self._set_spotlight(
+            'key light', (-5, -80, 5), (0, 0, 0), (2.8, 2.8, 2.8, 1))
+        self._shadow_light = self._set_spotlight(
+            'key light', (-5, -80, 5), (0, 0, 0), (.58, .58, .58, 1), True)
+        self._shadow_light.node().set_shadow_caster(True, 2048, 2048)
+        self._shadow_light.node().get_lens().set_film_size(2048, 2048)
+        self._shadow_light.node().get_lens().set_near_far(1, 256)
+        self._shadow_light.node().set_camera_mask(BitMask32(0x01))
+
+    def _unset_lights(self):
+        for light in [self._alnp, self._key_light, self._shadow_light]:
+            render.clear_light(light)
+            light.remove_node()
+
+    def _set_input(self):
+        self.accept('mouse1', self.on_click_l)
+        self.accept('mouse1-up', self.on_release)
+        self.accept('mouse3', self.on_click_r)
+        self.accept('mouse3-up', self.on_release)
+
+    def _unset_input(self):
+        for evt in ['mouse1', 'mouse1-up', 'mouse3', 'mouse3-up']:
+            self.ignore(evt)
+
+    def _set_mouse_plane(self):
+        shape = BulletPlaneShape((0, -1, 0), 0)
+        #self._mouse_plane_node = BulletRigidBodyNode('mouse plane')
+        self._mouse_plane_node = BulletGhostNode('mouse plane')
+        self._mouse_plane_node.addShape(shape)
+        #np = render.attachNewNode(self._mouse_plane_node)
+        #self._world.attachRigidBody(self._mouse_plane_node)
+        self._world.attach_ghost(self._mouse_plane_node)
+
+    def _unset_mouse_plane(self):
+        self._world.remove_ghost(self._mouse_plane_node)
+
+    def _get_hits(self):
+        if not base.mouseWatcherNode.has_mouse(): return []
+        p_from, p_to = P3dGfxMgr.world_from_to(base.mouseWatcherNode.get_mouse())
+        return self._world.ray_test_all(p_from, p_to).get_hits()
+
+    def _update_info(self, item):
+        txt = ''
+        if item:
+            txt = '%.3f %.3f\n%.3f°' % (
+                item._np.get_x(), item._np.get_z(), item._np.get_r())
+        self._info_txt['text'] = txt
+
+    def _on_click(self, method):
+        if self._paused:
+            return
+        for hit in self._get_hits():
+            if hit.get_node() == self._mouse_plane_node:
+                pos = hit.get_hit_pos()
+        for hit in self._get_hits():
+            for item in [i for i in self.items if hit.get_node() == i.node and i.interactable]:
+                if not self._item_active:
+                    self._item_active = item
+                getattr(item, method)(pos)
+                img = 'move' if method == 'on_click_l' else 'rotate'
+                if not (img == 'rotate' and not item._instantiated):
+                    self._cursor.set_image('assets/images/buttons/%s.dds' % img)
+
+    def on_click_l(self):
+        self._on_click('on_click_l')
+
+    def on_click_r(self):
+        self._on_click('on_click_r')
+
+    def on_release(self):
+        if self._item_active and not self._item_active._first_command:
+            self._commands = self._commands[:self._command_idx]
+            self._commands += [self._item_active]
+            self._command_idx += 1
+            #self.__prev_btn['state'] = NORMAL
+            #fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
+            #self.__prev_btn['frameColor'] = fcols[0]
+            #if self._item_active._command_idx == len(self._item_active._commands) - 1:
+            #    self.__next_btn['state'] = DISABLED
+            #    self.__next_btn['frameColor'] = fcols[1]
+        self._item_active = None
+        [item.on_release() for item in self.items]
+        self._cursor.set_image('assets/images/buttons/arrowUpLeft.dds')
+
+    def repos(self):
+        for item in self.items:
+            item.repos_done = False
+        self.items = sorted(self.items, key=lambda itm: itm.__class__.__name__)
+        [item.on_aspect_ratio_changed() for item in self.items]
+        self._side_panel.update(self.items)
+        max_x = -float('inf')
+        for item in self.items:
+            if not item._instantiated:
+                max_x = max(item._np.get_x(), max_x)
+        for item in self.items:
+            if not item._instantiated:
+                item.repos_x(max_x)
+
+    def on_aspect_ratio_changed(self):
+        self.repos()
+
+    def _win_condition(self):
+        pass
+
+    def _fail_condition(self):
+        return all(itm.fail_condition() for itm in self.items) and not self._paused and self._state == 'playing'
+
+    def on_frame(self, task):
+        hits = self._get_hits()
+        pos = None
+        for hit in self._get_hits():
+            if hit.get_node() == self._mouse_plane_node:
+                pos = hit.get_hit_pos()
+        hit_nodes = [hit.get_node() for hit in hits]
+        if self._item_active:
+            items_hit = [self._item_active]
+        else:
+            items_hit = [itm for itm in self.items if itm.node in hit_nodes]
+        items_no_hit = [itm for itm in self.items if itm not in items_hit]
+        [itm.on_mouse_on() for itm in items_hit]
+        [itm.on_mouse_off() for itm in items_no_hit]
+        if pos and self._item_active:
+            self._item_active.on_mouse_move(pos)
+        if self._dbg_items:
+            self._update_info(items_hit[0] if items_hit else None)
+        if self._win_condition():
+            self._start_evt_time = None
+            self._set_fail() if self._enforce_res == 'fail' else self._set_win()
+        elif self._state == 'playing' and self._fail_condition():
+            self._start_evt_time = None
+            self._set_win() if self._enforce_res == 'win' else self._set_fail()
+        elif self._testing and self._start_evt_time and globalClock.getFrameTime() - self._start_evt_time > 5.0:
+            self._start_evt_time = None
+            self._set_win() if self._enforce_res == 'win' else self._set_fail()
+        if any(itm._overlapping for itm in self.items):
+            self._cursor.cursor_img.img.set_color(.9, .1, .1, 1)
+        else:
+            self._cursor.cursor_img.img.set_color(.9, .9, .9, 1)
+        return task.cont
+
+    def cb_inst(self, item):
+        self.items += [item]
+
+    def on_play(self):
+        self._state = 'playing'
+        #self.__prev_btn['state'] = DISABLED
+        #self.__next_btn['state'] = DISABLED
+        self.__right_btn['state'] = DISABLED
+        [itm.play() for itm in self.items]
+        self._start_evt_time = globalClock.getFrameTime()
+
+    def on_next(self):
+        self._commands[self._command_idx].redo()
+        self._command_idx += 1
+        fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
+        #self.__prev_btn['state'] = NORMAL
+        #self.__prev_btn['frameColor'] = fcols[0]
+        more_commands = self._command_idx < len(self._commands)
+        #self.__next_btn['state'] = NORMAL if more_commands else DISABLED
+        #self.__next_btn['frameColor'] = fcols[0] if more_commands else fcols[1]
+
+    def on_prev(self):
+        self._command_idx -= 1
+        self._commands[self._command_idx].undo()
+        fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
+        #self.__next_btn['state'] = NORMAL
+        #self.__next_btn['frameColor'] = fcols[0]
+        #self.__prev_btn['state'] = NORMAL if self._command_idx else DISABLED
+        #self.__prev_btn['frameColor'] = fcols[0] if self._command_idx else fcols[1]
+
+    def on_home(self):
+        self._exit_cb()
+
+    def _set_instructions(self):
+        self._paused = True
+        self.__store_state()
+        mgr = TextPropertiesManager.get_global_ptr()
+        for name in ['mouse_l', 'mouse_r']:
+            graphic = OnscreenImage('assets/images/buttons/%s.dds' % name)
+            graphic.set_scale(.5)
+            graphic.get_texture().set_minfilter(Texture.FTLinearMipmapLinear)
+            graphic.get_texture().set_anisotropic_degree(2)
+            mgr.set_graphic(name, graphic)
+            graphic.set_z(-.2)
+            graphic.set_transparency(True)
+            graphic.detach_node()
+        frm = DirectFrame(frameColor=(.4, .4, .4, .06),
+                          frameSize=(-.6, .6, -.3, .3))
+        font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
+        font.clear()
+        font.set_pixels_per_unit(60)
+        font.set_minfilter(Texture.FTLinearMipmapLinear)
+        font.set_outline((0, 0, 0, 1), .8, .2)
+        self._txt = OnscreenText(
+            self._instr_txt(), parent=frm, font=font, scale=0.06,
+            fg=(.9, .9, .9, 1), align=TextNode.A_left)
+        u_l = self._txt.textNode.get_upper_left_3d()
+        l_r = self._txt.textNode.get_lower_right_3d()
+        w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
+        btn_scale = .05
+        mar = .06  # margin
+        z = h / 2 - font.get_line_height() * self._txt['scale'][1]
+        z += (btn_scale + 2 * mar) / 2
+        self._txt['pos'] = -w / 2, z
+        u_l = self._txt.textNode.get_upper_left_3d()
+        l_r = self._txt.textNode.get_lower_right_3d()
+        c_l_r = l_r[0], l_r[1], l_r[2] - 2 * mar - btn_scale
+        fsz = u_l[0] - mar, l_r[0] + mar, c_l_r[2] - mar, u_l[2] + mar
+        frm['frameSize'] = fsz
+        colors = [
+            (.6, .6, .6, 1),  # ready
+            (1, 1, 1, 1), # press
+            (.8, .8, .8, 1), # rollover
+            (.4, .4, .4, .4)]
+        imgs = [self.__load_img_btn('exitRight', col) for col in colors]
+        btn = DirectButton(
+            image=imgs, scale=btn_scale,
+            pos=(l_r[0] - btn_scale, 1, l_r[2] - mar - btn_scale),
+            parent=frm, command=self.__on_close_instructions, extraArgs=[frm],
+            relief=FLAT, frameColor=(.6, .6, .6, .08),
+            rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
+            clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
+        btn.set_transparency(True)
+        self._pos_mgr.register('close_instructions', LibP3d.wdg_pos(btn))
+
+    def _set_win(self):
+        loader.load_sfx('assets/audio/sfx/success.ogg').play()
+        self._paused = True
+        self.__store_state()
+        frm = DirectFrame(frameColor=(.4, .4, .4, .06),
+                          frameSize=(-.6, .6, -.3, .3))
+        font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
+        font.clear()
+        font.set_pixels_per_unit(60)
+        font.set_minfilter(Texture.FTLinearMipmapLinear)
+        font.set_outline((0, 0, 0, 1), .8, .2)
+        self._txt = OnscreenText(
+            _('You win!'),
+            parent=frm,
+            font=font, scale=0.2,
+            fg=(.9, .9, .9, 1))
+        u_l = self._txt.textNode.get_upper_left_3d()
+        l_r = self._txt.textNode.get_lower_right_3d()
+        w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
+        btn_scale = .05
+        mar = .06  # margin
+        z = h / 2 - font.get_line_height() * self._txt['scale'][1]
+        z += (btn_scale + 2 * mar) / 2
+        self._txt['pos'] = 0, z
+        u_l = self._txt.textNode.get_upper_left_3d()
+        l_r = self._txt.textNode.get_lower_right_3d()
+        c_l_r = l_r[0], l_r[1], l_r[2] - 2 * mar - btn_scale
+        fsz = u_l[0] - mar, l_r[0] + mar, c_l_r[2] - mar, u_l[2] + mar
+        frm['frameSize'] = fsz
+        colors = [
+            (.6, .6, .6, 1),  # ready
+            (1, 1, 1, 1), # press
+            (.8, .8, .8, 1), # rollover
+            (.4, .4, .4, .4)]
+        imgs = [self.__load_img_btn('home', col) for col in colors]
+        btn = DirectButton(
+            image=imgs, scale=btn_scale,
+            pos=(-2.8 * btn_scale, 1, l_r[2] - mar - btn_scale),
+            parent=frm, command=self._on_end_home, extraArgs=[frm],
+            relief=FLAT, frameColor=(.6, .6, .6, .08),
+            rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
+            clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
+        btn.set_transparency(True)
+        self._pos_mgr.register('home_win', LibP3d.wdg_pos(btn))
+        imgs = [self.__load_img_btn('rewind', col) for col in colors]
+        btn = DirectButton(
+            image=imgs, scale=btn_scale,
+            pos=(0, 1, l_r[2] - mar - btn_scale),
+            parent=frm, command=self._on_restart, extraArgs=[frm],
+            relief=FLAT, frameColor=(.6, .6, .6, .08),
+            rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
+            clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
+        self._pos_mgr.register('replay', LibP3d.wdg_pos(btn))
+        btn.set_transparency(True)
+        enabled = self._scenes.index(self.__class__) < len(self._scenes) - 1
+        if enabled:
+            next_scene = self._scenes[self._scenes.index(self.__class__) + 1]
+        else:
+            next_scene = None
+        imgs = [self.__load_img_btn('right', col) for col in colors]
+        btn = DirectButton(
+            image=imgs, scale=btn_scale,
+            pos=(2.8 * btn_scale, 1, l_r[2] - mar - btn_scale),
+            parent=frm, command=self._on_next_scene,
+            extraArgs=[frm, next_scene], relief=FLAT,
+            frameColor=(.6, .6, .6, .08),
+            rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
+            clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
+        btn['state'] = NORMAL if enabled else DISABLED
+        self._pos_mgr.register('next', LibP3d.wdg_pos(btn))
+        btn.set_transparency(True)
+
+    def _set_fail(self):
+        loader.load_sfx('assets/audio/sfx/success.ogg').play()
+        self._paused = True
+        self.__store_state()
+        frm = DirectFrame(frameColor=(.4, .4, .4, .06),
+                          frameSize=(-.6, .6, -.3, .3))
+        font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
+        font.clear()
+        font.set_pixels_per_unit(60)
+        font.set_minfilter(Texture.FTLinearMipmapLinear)
+        font.set_outline((0, 0, 0, 1), .8, .2)
+        self._txt = OnscreenText(
+            _('You have failed!'),
+            parent=frm,
+            font=font, scale=0.2,
+            fg=(.9, .9, .9, 1))
+        u_l = self._txt.textNode.get_upper_left_3d()
+        l_r = self._txt.textNode.get_lower_right_3d()
+        w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
+        btn_scale = .05
+        mar = .06  # margin
+        z = h / 2 - font.get_line_height() * self._txt['scale'][1]
+        z += (btn_scale + 2 * mar) / 2
+        self._txt['pos'] = 0, z
+        u_l = self._txt.textNode.get_upper_left_3d()
+        l_r = self._txt.textNode.get_lower_right_3d()
+        c_l_r = l_r[0], l_r[1], l_r[2] - 2 * mar - btn_scale
+        fsz = u_l[0] - mar, l_r[0] + mar, c_l_r[2] - mar, u_l[2] + mar
+        frm['frameSize'] = fsz
+        colors = [
+            (.6, .6, .6, 1),  # ready
+            (1, 1, 1, 1), # press
+            (.8, .8, .8, 1), # rollover
+            (.4, .4, .4, .4)]
+        imgs = [self.__load_img_btn('home', col) for col in colors]
+        btn = DirectButton(
+            image=imgs, scale=btn_scale,
+            pos=(-2.8 * btn_scale, 1, l_r[2] - mar - btn_scale),
+            parent=frm, command=self._on_end_home, extraArgs=[frm],
+            relief=FLAT, frameColor=(.6, .6, .6, .08),
+            rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
+            clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
+        self._pos_mgr.register('home_win', LibP3d.wdg_pos(btn))
+        btn.set_transparency(True)
+        imgs = [self.__load_img_btn('rewind', col) for col in colors]
+        btn = DirectButton(
+            image=imgs, scale=btn_scale,
+            pos=(0, 1, l_r[2] - mar - btn_scale),
+            parent=frm, command=self._on_restart, extraArgs=[frm],
+            relief=FLAT, frameColor=(.6, .6, .6, .08),
+            rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
+            clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
+        self._pos_mgr.register('replay', LibP3d.wdg_pos(btn))
+        btn.set_transparency(True)
+
+    def _on_restart(self, frm):
+        self.__on_close_instructions(frm)
+        self.reset()
+
+    def _on_end_home(self, frm):
+        self.__on_close_instructions(frm)
+        self.on_home()
+
+    def _on_next_scene(self, frm, scene):
+        self.__on_close_instructions(frm)
+        self._reload_cb(scene)
+
+    def __store_state(self):
+        btns = [
+            self.__home_btn, self.__info_btn, self.__right_btn,
+            #self.__next_btn, self.__prev_btn, self.__rewind_btn
+        ]
+        self.__btn_state = [btn['state'] for btn in btns]
+        for btn in btns:
+            btn['state'] = DISABLED
+        [itm.store_state() for itm in self.items]
+
+    def __restore_state(self):
+        btns = [
+            self.__home_btn, self.__info_btn, self.__right_btn,
+            #self.__next_btn, self.__prev_btn, self.__rewind_btn
+        ]
+        for btn, state in zip(btns, self.__btn_state):
+            btn['state'] = state
+        [itm.restore_state() for itm in self.items]
+        self._paused = False
+
+    def __on_close_instructions(self, frm):
+        frm.remove_node()
+        self.__restore_state()
+
+    def _set_test_items(self):
+        def frame_after(task):
+            self._define_test_items()
+            for itm in self._test_items:
+                self._pos_mgr.register(itm.name, P3dGfxMgr.pos2d_p2d(itm))
+        taskMgr.doMethodLater(.01, frame_after, 'frame after')
+
+    def _set_test_item(self, name, pos):
+        self._test_items += [NodePath(name)]
+        self._test_items[-1].set_pos(pos[0], 0, pos[1])
diff --git a/pmachines/scenes/__init__.py b/pmachines/scenes/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/pmachines/scenes/scene_basketball.py b/pmachines/scenes/scene_basketball.py
new file mode 100644 (file)
index 0000000..9fe3020
--- /dev/null
@@ -0,0 +1,63 @@
+from pmachines.scene import Scene
+from pmachines.items.box import Box
+from pmachines.items.shelf import Shelf
+from pmachines.items.domino import Domino, UpStrategy, DownStrategy
+from pmachines.items.basketball import Basketball
+from pmachines.items.teetertooter import TeeterTooter
+
+
+class SceneBasketBall(Scene):
+
+    @staticmethod
+    def name():
+        return _('Basket ball')
+
+    def _set_items(self):
+        #self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=5, count=2)]
+        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=9)]
+        self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=1)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, .21))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, .21))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, -1.45))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, -1.45))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-4.45, 0, -3.18), r=27, restitution=1)]
+        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-5.45, 0, -3.18), restitution=1)]
+        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(3.78, 0, -1.45))]
+        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=9)]
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.61, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.06, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.91, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.73, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.57, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 30))
+        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.61, 0, .73), r=37)]
+        #self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.06, 0, .78))]
+        #self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.91, 0, .78))]
+        #self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.73, 0, .78))]
+        #self.items[-1].set_strategy(UpStrategy(self.items[-1]._np, 30))
+        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.57, 0, .78))]
+        #self.items[-1].set_strategy(UpStrategy(self.items[-1]._np, 30))
+        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+
+    def _define_test_items(self):
+        self._pos_mgr.register('drag_start_0', (55, 50))
+        self._set_test_item('drag_stop_0', (-.42, 1.03))
+        self._set_test_item('drag_stop_1', (-4.19, 4.66))
+
+    def _instr_txt(self):
+        txt = _('Scene: ') + self.name() + '\n\n'
+        txt += _('Goal: you must hit every domino piece\n\n')
+        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
+                'keep \5mouse_r\5 pressed to rotate an item')
+        return txt
+
+    def _win_condition(self):
+        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/pmachines/scenes/scene_box.py b/pmachines/scenes/scene_box.py
new file mode 100644 (file)
index 0000000..8fb6c2a
--- /dev/null
@@ -0,0 +1,48 @@
+from pmachines.scene import Scene
+from pmachines.items.box import Box, HitStrategy
+from pmachines.items.shelf import Shelf
+from pmachines.items.domino import Domino
+from pmachines.items.basketball import Basketball
+from pmachines.items.teetertooter import TeeterTooter
+
+
+class SceneBox(Scene):
+
+    @staticmethod
+    def name():
+        return _('Box')
+
+    def _set_items(self):
+        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(.46, 0, -3.95))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(4.43, 0, -3.95))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-1.29, 0, .26), r=28.45)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(2.15, 0, -1.49), r=28.45)]
+        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-1.55, 0, 1.23), friction=.4)]
+        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(4.38, 0, -3.35))]
+        self.items[-1].set_strategy(HitStrategy(self.items[-2], self.items[-1].node, self.items[-1]._world))
+        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=2)]
+        #self.items += [TargetDomino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-1.14, 0, -.04), tgt_degrees=60)]
+        #self.items += [TargetDomino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.49, 0, -.04), tgt_degrees=60)]
+        #self.items += [TargetDomino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.94, 0, -.04), tgt_degrees=60)]
+        #self.items += [TargetDomino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.55, 0, -.04), tgt_degrees=60)]
+        #self.items += [TargetDomino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.09, 0, -.04), tgt_degrees=88)]
+        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+
+    def _define_test_items(self):
+        self._pos_mgr.register('drag_start_0', (65, 60))
+        self._set_test_item('drag_stop_0', (.42, -3.29))
+        self._set_test_item('drag_stop_1', (.42, -2.18))
+        self._set_test_item('drag_stop_2', (.35, -1.06))
+
+    def _instr_txt(self):
+        txt = _('Scene: ') + self.name() + '\n\n'
+        txt += _('Goal: the left box must hit the right box\n\n')
+        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
+                'keep \5mouse_r\5 pressed to rotate an item')
+        return txt
+
+    def _win_condition(self):
+        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/pmachines/scenes/scene_domino.py b/pmachines/scenes/scene_domino.py
new file mode 100644 (file)
index 0000000..e5a65ba
--- /dev/null
@@ -0,0 +1,52 @@
+from panda3d.core import NodePath
+from pmachines.scene import Scene
+from pmachines.items.box import Box
+from pmachines.items.shelf import Shelf
+from pmachines.items.domino import Domino, DownStrategy
+from pmachines.items.basketball import Basketball
+from pmachines.items.teetertooter import TeeterTooter
+from ya2.p3d.gfx import P3dGfxMgr
+
+
+class SceneDomino(Scene):
+
+    @staticmethod
+    def name():
+        return _('Domino')
+
+    def _set_items(self):
+        #self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-1.2, 0, -.6))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.2, 0, -.6))]
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=2)]
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-1.14, 0, -.04))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 60))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.49, 0, -.04))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 60))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.94, 0, -.04))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 60))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.55, 0, -.04))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 60))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.09, 0, -.04))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 88))
+        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+
+    def _define_test_items(self):
+        self._pos_mgr.register('drag_start_0', (35, 60))
+        self._set_test_item('drag_stop_0', (-1.82, .06))
+        self._set_test_item('drag_stop_1', (.49, .06))
+        self._set_test_item('drag_stop_2', (-1.54, .06))
+        self._set_test_item('drag_start_1', (-1.54, .4))
+        self._set_test_item('drag_stop_3', (-1.05, .4))
+
+    def _instr_txt(self):
+        txt = _('Scene: ') + self.name() + '\n\n'
+        txt += _('Goal: every domino piece must fall\n\n')
+        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
+                'keep \5mouse_r\5 pressed to rotate an item')
+        return txt
+
+    def _win_condition(self):
+        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/pmachines/scenes/scene_domino_box.py b/pmachines/scenes/scene_domino_box.py
new file mode 100644 (file)
index 0000000..08f0325
--- /dev/null
@@ -0,0 +1,61 @@
+from pmachines.scene import Scene
+from pmachines.items.box import Box
+from pmachines.items.shelf import Shelf
+from pmachines.items.domino import Domino, UpStrategy, DownStrategy
+from pmachines.items.basketball import Basketball
+from pmachines.items.teetertooter import TeeterTooter
+
+
+class SceneDominoBox(Scene):
+
+    @staticmethod
+    def name():
+        return _('Domino and box')
+
+    def _set_items(self):
+        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=5, count=2)]
+        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=9)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, .21))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, .21))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, -1.45))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, -1.45))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(3.78, 0, -1.45))]
+        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=9)]
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.61, 0, -.94), r=37)]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.06, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.91, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.73, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.57, 0, -.89))]
+        self.items[-1].set_strategy(UpStrategy(self.items[-1]._np, 30))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.61, 0, .73), r=37)]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.06, 0, .78))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.91, 0, .78))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.73, 0, .78))]
+        self.items[-1].set_strategy(UpStrategy(self.items[-1]._np, 30))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.57, 0, .78))]
+        self.items[-1].set_strategy(UpStrategy(self.items[-1]._np, 30))
+        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+
+    def _define_test_items(self):
+        self._pos_mgr.register('drag_start_0', (65, 60))
+        self._set_test_item('drag_stop_0', (3.21, -.78))
+        self._set_test_item('drag_stop_1', (3.21, .33))
+        self._set_test_item('drag_stop_2', (2.16, 1.87))
+
+    def _instr_txt(self):
+        txt = _('Scene: ') + self.name() + '\n\n'
+        txt += _('Goal: only the last piece of each row must be up\n\n')
+        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
+                'keep \5mouse_r\5 pressed to rotate an item')
+        return txt
+
+    def _win_condition(self):
+        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/pmachines/scenes/scene_domino_box_basketball.py b/pmachines/scenes/scene_domino_box_basketball.py
new file mode 100644 (file)
index 0000000..ab470c7
--- /dev/null
@@ -0,0 +1,58 @@
+from pmachines.scene import Scene
+from pmachines.items.box import Box
+from pmachines.items.shelf import Shelf
+from pmachines.items.domino import Domino, UpStrategy, DownStrategy
+from pmachines.items.basketball import Basketball
+from pmachines.items.teetertooter import TeeterTooter
+
+
+class SceneDominoBoxBasketball(Scene):
+
+    @staticmethod
+    def name():
+        return _('Domino, box and basket ball')
+
+    def _set_items(self):
+        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=1, mass=5)]
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=1)]
+        self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.3, 1, 2.5))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, -1.45))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, -1.45))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(3.78, 0, -1.45))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.48, 0, .38), r=-90)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(2.62, 0, .05))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(4.88, 0, .05))]
+        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=9)]
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.68, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.35, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(3.08, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(3.78, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(4.53, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+
+    def _define_test_items(self):
+        self._pos_mgr.register('drag_start_0', (65, 60))
+        self._pos_mgr.register('drag_start_1', (30, 60))
+        self._set_test_item('drag_stop_0', (-1.4, -.78))
+        self._set_test_item('drag_stop_1', (-1.26, .2))
+        self._set_test_item('drag_stop_2', (-.28, -.78))
+        self._set_test_item('drag_start_2', (-.28, -.57))
+        self._set_test_item('drag_stop_3', (-.77, -.57))
+        self._set_test_item('drag_start_3', (-.28, -.85))
+        self._set_test_item('drag_stop_4', (-.42, -.85))
+
+    def _instr_txt(self):
+        txt = _('Scene: ') + self.name() + '\n\n'
+        txt += _('Goal: every domino piece must be hit\n\n')
+        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
+                'keep \5mouse_r\5 pressed to rotate an item')
+        return txt
+
+    def _win_condition(self):
+        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/pmachines/scenes/scene_teeter_domino_box_basketball.py b/pmachines/scenes/scene_teeter_domino_box_basketball.py
new file mode 100644 (file)
index 0000000..39a670b
--- /dev/null
@@ -0,0 +1,61 @@
+from pmachines.scene import Scene
+from pmachines.items.box import Box
+from pmachines.items.shelf import Shelf
+from pmachines.items.domino import Domino, UpStrategy, DownStrategy
+from pmachines.items.basketball import Basketball
+from pmachines.items.teetertooter import TeeterTooter
+
+
+class SceneTeeterDominoBoxBasketball(Scene):
+
+    @staticmethod
+    def name():
+        return _('Teeter tooter, domino, box and basket ball')
+
+    def _set_items(self):
+        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=3, count=2, friction=1)]
+        self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(.98, 1, 1.02))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-6.24, 0, -1.45))]
+        self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-6.24, 0, -1.20))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=1, r=24.60, friction=1, pos=(-6.15, 0, -.93))]
+        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=.3, friction=1, model_scale=.5, pos=(-5.38, 0, -.93), r=24.60)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(5.37, 0, -.78))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(7.48, 0, -.78))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(4.74, 0, -1.95))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(6.88, 0, -1.95))]
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=1, pos=(4.83, 0, -1.39))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=1, pos=(5.67, 0, -1.39))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=1, pos=(6.59, 0, -1.39))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(.53, 0, -1.95), restitution=.95)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(2.63, 0, -1.95), restitution=.95)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-3.65, 0, 1.05), r=28, friction=0)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-1.27, 0, 1.72), restitution=.95)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(.88, 0, 1.72), restitution=.95)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-1.67, 0, .55), restitution=.95)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(.52, 0, .55), restitution=.95)]
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=.5, pos=(-1.73, 0, 1.11))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=.5, pos=(-.97, 0, 1.11))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=.5, pos=(-.1, 0, 1.11))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+
+    def _define_test_items(self):
+        self._pos_mgr.register('drag_start_0', (60, 60))
+        self._set_test_item('drag_stop_0', (-7.33, 4.24))
+        self._set_test_item('drag_stop_1', (-7.12, 4.24))
+        self._set_test_item('drag_start_1', (-6.77, 4.66))
+        self._set_test_item('drag_stop_2', (-6.77, 4.24))
+
+    def _instr_txt(self):
+        txt = _('Scene: ') + self.name() + '\n\n'
+        txt += _('Goal: every domino piece must be hit\n\n')
+        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
+                'keep \5mouse_r\5 pressed to rotate an item')
+        return txt
+
+    def _win_condition(self):
+        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/pmachines/scenes/scene_teeter_tooter.py b/pmachines/scenes/scene_teeter_tooter.py
new file mode 100644 (file)
index 0000000..47debd3
--- /dev/null
@@ -0,0 +1,52 @@
+from pmachines.scene import Scene
+from pmachines.items.box import Box
+from pmachines.items.shelf import Shelf
+from pmachines.items.domino import Domino, UpStrategy, DownStrategy
+from pmachines.items.basketball import Basketball
+from pmachines.items.teetertooter import TeeterTooter
+
+
+class SceneTeeterTooter(Scene):
+
+    @staticmethod
+    def name():
+        return _('Teeter tooter')
+
+    def _set_items(self):
+        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=3, count=1, friction=1)]
+        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=5, count=2)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-2.76, 0, -1.45))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, -1.45))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(2.27, 0, -.28))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(4.38, 0, -.28))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, -1.45))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(3.78, 0, -1.45))]
+        self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-2.74, 0, -1.20))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=1, r=-25.30, friction=1, pos=(-2.78, 0, -.93))]
+        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=.2, friction=1, model_scale=.5, pos=(-3.61, 0, -.99), r=-25.30)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.25, 0, -.57), r=52)]
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.73, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.57, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(3.50, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+
+    def _define_test_items(self):
+        self._pos_mgr.register('drag_start_0', (60, 60))
+        self._set_test_item('drag_stop_0', (-2.65, 1.18))
+        self._set_test_item('drag_stop_1', (-2.65, 3.27))
+        self._set_test_item('drag_start_1', (-2.3, 3.75))
+        self._set_test_item('drag_stop_2', (-2.5, 3.66))
+
+    def _instr_txt(self):
+        txt = _('Scene: ') + self.name() + '\n\n'
+        txt += _('Goal: you must hit every domino piece\n\n')
+        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
+                'keep \5mouse_r\5 pressed to rotate an item')
+        return txt
+
+    def _win_condition(self):
+        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/prj.org b/prj.org
index 97eef1a7162f61445f2e7846a46732025b15117b..7dbc6011cdb089302966e5086637ca760654c7d9 100644 (file)
--- a/prj.org
+++ b/prj.org
@@ -1,17 +1,11 @@
 #+STARTUP: indent
-#+TODO: INBOX BACKLOG READY RED CODE L10N REFACTOR LINT GREEN BUILD CHANGELOG BLOG | DONE
+#+TODO: BACKLOG READY CODE L10N UNITTEST BUILD BLOG | DONE
 #+CATEGORY: pmachines
 #+TAGS: bug(b) calendar(c) waiting(w)
 
-* RED functional tests for performance (frame rate)
-* READY functional tests for "cleaning" i.e. at the end of the states verify:
-- [ ] active threads
-- [ ] active tasks
-- [ ] current nodepaths (render3d)
-- [ ] current nodepaths (render2d)
-- [ ] current nodepaths (render3d)
-- [ ] current accepting events
-- [ ] current buffers
+* CODE create builds and update website/itch.io
+* READY refactoring (recurring task)
+* BACKLOG rename pmachines/ to logics/ (business logics)
 * BACKLOG intro animation (from target item to start position)
 * BACKLOG buttons of the scenes enabled sequentially
 - [ ] each scene has a version
 - [ ] put an "update" if id is saved and versions are different
 * BACKLOG actions: rewind, prev, next
 * BACKLOG teeter-tooter with constraints (real teeter tooter), magnet, road cone, bucket
+* BACKLOG (when python 3.11) use toml in place of ini
 * BACKLOG (when panda3d provides it) android build (test with the emulator of android studio)
 * BACKLOG (when itch.io's client waiting works with wine) functional tests for windows-itch.io
 * calendar                                                         :calendar:
 ** publish post q1; reschedule
 SCHEDULED: <2023-03-18 Sat +1y>
 ** publish post q2; reschedule
-SCHEDULED: <2022-06-18 Sat +1y>
+SCHEDULED: <2023-06-16 Fri +1y>
 ** publish post q3; reschedule
 SCHEDULED: <2022-09-16 Fri +1y>
 ** publish post q4; reschedule
index 24a302b020ba50e64961959c9a891f6a12498cbd..6133f04189f8c0ce105272241506af7ebe12e4b1 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -11,16 +11,16 @@ from setuptools import setup, Command
 from setuptools.command.develop import develop
 from multiprocessing import cpu_count
 from direct.dist.commands import bdist_apps
-from ya2.build.build import branch, files, ver, files, bld_dpath
+from ya2.build.build import _branch, files, _version
 #from ya2.build.docs import bld_docs
 from ya2.build.models import ModelsBuilder
 from ya2.build.images import bld_images
 from ya2.build.screenshots import bld_screenshots
 from ya2.build.lang import LanguageBuilder
 from p3d_appimage import AppImageBuilder
-from p3d_flatpak import FlatpakBuilder
+#from p3d_flatpak import FlatpakBuilder
 import ya2.utils.log  # so logging's info/debug are logged
-from logics.app import PmachinesApp
+from pmachines.app import PmachinesApp
 
 
 appname = longname = 'pmachines'
@@ -126,21 +126,21 @@ class BDistAppsCmd(bdist_apps):
         system(cmd)
         bdist_apps.run(self)
         if not self.nolinux:
-            hbranch = {'master': 'alpha', 'rc': 'rc', 'stable': ''}[branch]
+            hbranch = {'master': 'alpha', 'rc': 'rc', 'stable': ''}[_branch()]
             AppImageBuilder(self).build(longname, hbranch,
                                         'https://www.ya2.it/downloads/')
-            fbranch = {'master': 'alpha', 'rc': 'rc', 'stable': 'stable'}[branch]
-            bld = FlatpakBuilder(
-                self,
-                'it.ya2.Pmachines',
-                '/home/flavio/builders/pmachines_builder/flatpak',
-                'D43B6D401912B520B6805FCC8E019E6340E3BAB5',
-                '/home/flavio/builders/pmachines_builder/gpg',
-                'https://www.ya2.it/flatpak',
-                ['options*.ini'],
-                fbranch,
-                ['assets'])
-            bld.build()
+            fbranch = {'master': 'alpha', 'rc': 'rc', 'stable': 'stable'}[_branch()]
+            bld = FlatpakBuilder(
+                self,
+                'it.ya2.Pmachines',
+                '/home/flavio/builders/pmachines_builder/flatpak',
+                'D43B6D401912B520B6805FCC8E019E6340E3BAB5',
+                '/home/flavio/builders/pmachines_builder/gpg',
+                'https://www.ya2.it/flatpak',
+                ['options*.ini'],
+                fbranch,
+                ['assets'])
+            bld.build()
 
 
 if __name__ == '__main__':
@@ -155,7 +155,7 @@ if __name__ == '__main__':
     package_data_dirs = {'simplepbr': [('simplepbr/shaders*', '', {})]}
     setup(
         name=appname,
-        version=ver,
+        version=_version(),
         cmdclass={
             'develop': DevelopPyCmd,
             # 'docs': DocsCmd,
index 9aa1d8ac045095e9721da214449c8d8404fa3df0..46dbaeab04fa09b24ea508b424559d66abeba7f4 100644 (file)
@@ -7,10 +7,10 @@
 from panda3d.core import load_prc_file_data
 load_prc_file_data('', 'window-type none')
 import datetime
+from pathlib import Path
 from time import sleep
 from os import getcwd, system
 from xmlrpc.client import ServerProxy
-from logging import debug, info
 from pathlib import Path
 from shutil import rmtree
 from os import makedirs
@@ -20,27 +20,45 @@ from sys import exit, argv
 from panda3d.core import Filename
 from direct.showbase.ShowBase import ShowBase
 from direct.gui.OnscreenText import OnscreenText
-from ya2.patterns.gameobject import GameObject
 from ya2.build.build import _branch
+import asyncio
+import xmlrpc
+from logging import basicConfig, debug, info, INFO, DEBUG, getLogger, StreamHandler, Formatter, FileHandler
+
+
+basicConfig(level=DEBUG, format='(evt-test) %(asctime)s.%(msecs)03d %(message)s', datefmt='%H:%M:%S')
+getLogger().setLevel(DEBUG)  # it doesn't work otherwise
 
 
-class FunctionalTest(GameObject):
+class FunctionalTest:
 
-    screenshot_time = 1.2
-    evt_time = 1.0
-    drag_time = 1.0
+    screenshot_time = 2.0
+    evt_time = 2.0
+    drag_time = 2.0
     start_time = 2
 
     def __init__(self, idx, offset):
+        debug('creating FunctionalTest (%s)' % id(self))
         super().__init__()
+
+        timestamp = datetime.datetime.now().strftime('%y%m%d%H%M%S')
+        logFormatter = Formatter('%(asctime)s.%(msecs)03d %(message)s', datefmt='%H:%M:%S')
+        path = str(Path.home()) + '/.local/share/pmachines/logs/evt/'
+        if not exists(path):
+            makedirs(path)
+        lpath = path + 'evt_%s.log' % timestamp
+        fileHandler = FileHandler(lpath)
+        fileHandler.setFormatter(logFormatter)
+        getLogger().addHandler(fileHandler)
+
         info('test idx: %s' % idx)
         self._offset = offset
-        sleep(5)
-        self._proxy = ServerProxy('http://localhost:6000')
+        sleep(12)
+        self._proxy = ServerProxy('http://localhost:7000')
         self._curr_time = 0
         self._tasks = []
         self._prev_time = 0
-        taskMgr.add(self.on_frame_unpausable, 'on-frame-unpausable')
+        #taskMgr.add(self.on_frame_unpausable, 'on-frame-unpausable')
         self._proxy.set_idx(idx)
         self._do_screenshots(idx)
 
@@ -50,408 +68,468 @@ class FunctionalTest(GameObject):
         self._proxy.screenshot(name)
         info('screenshot %s' % name)
 
-    def _screenshot(self, time, name):
-        self._tasks += [(
-            self._curr_time + time,
-            lambda: self._do_screenshot(name),
-            'screenshot: %s' % name)]
-        self._curr_time += time
+    async def _screenshot(self, time, name):
+        #self._tasks += [(
+        #    self._curr_time + time,
+        #    lambda: self._do_screenshot(name),
+        #    'screenshot: %s' % name)]
+        #self._curr_time += time
+        await asyncio.sleep(time)
+        self._do_screenshot(name)
 
-    def __mouse_click(self, tgt, btn):
+    async def __mouse_click(self, tgt, btn):
+        await asyncio.sleep(.5)
         offset_x = int((1920 - 1360) / 2) #+ 1  # xfce decorations
         offset_y = int((1080 - 768) / 2) #+ 24 + self._offset  # xfce decorations
         btn = 3 if btn == 'right' else 1
         pos = self._proxy.get_pos(tgt)
-        print(tgt, pos)
         system('xdotool mousemove %s %s' % (offset_x + pos[0], offset_y + pos[1]))
-        def click(task):
-            system('xdotool click %s' % btn)
-        taskMgr.do_method_later(.28, click, 'click')
+        #def click(task):
+        #    system('xdotool click %s' % tgt)
+        #    print('CLICK %s' % str(btn))
+        #taskMgr.do_method_later(.28, click, 'click')
+        await asyncio.sleep(.5)
+        system('xdotool click %s' % btn)
 
-    def __mouse_drag(self, start, end, btn):
+    async def __mouse_drag(self, start, end, btn):
+        await asyncio.sleep(.5)
         offset_x = int((1920 - 1360) / 2) #+ 1  # xfce decorations
         offset_y = int((1080 - 768) / 2) #+ 24 + self._offset  # xfce decorations
         btn = 3 if btn == 'right' else 1
+        start = self._proxy.get_pos(start)
+        end = self._proxy.get_pos(end)
         system('xdotool mousemove %s %s' % (offset_x + start[0], offset_y + start[1]))
-        def mousedown(task):
-            system('xdotool mousedown %s' % btn)
-            def mousemove(task):
-                system('xdotool mousemove %s %s' % (offset_x + end[0], offset_y + end[1]))
-                def mouseup(task):
-                    system('xdotool mouseup %s' % btn)
-                taskMgr.do_method_later(.28, mouseup, 'mouseup')
-            taskMgr.do_method_later(.28, mousemove, 'mousemove')
-        taskMgr.do_method_later(.28, mousedown, 'mousedown')
+        await asyncio.sleep(.5)
+        system('xdotool mousedown %s' % btn)
+        await asyncio.sleep(.5)
+        system('xdotool mousemove %s %s' % (offset_x + end[0], offset_y + end[1]))
+        await asyncio.sleep(.5)
+        system('xdotool mouseup %s' % btn)
+        # def mousedown(task):
+        #     system('xdotool mousedown %s' % btn)
+        #     def mousemove(task):
+        #         system('xdotool mousemove %s %s' % (offset_x + end[0], offset_y + end[1]))
+        #         def mouseup(task):
+        #             system('xdotool mouseup %s' % btn)
+        #         taskMgr.do_method_later(.28, mouseup, 'mouseup')
+        #     taskMgr.do_method_later(.28, mousemove, 'mousemove')
+        # taskMgr.do_method_later(.28, mousedown, 'mousedown')
 
-    def _event(self, time, evt, mouse_args=None):
+    async def _event(self, time, evt, mouse_args=None):
+        await asyncio.sleep(time)
         if evt == 'mouseclick':
-            cback = lambda: self.__mouse_click(*mouse_args)
+            #cback = lambda: self.__mouse_click(*mouse_args)
+            await self.__mouse_click(*mouse_args)
         elif evt == 'mousedrag':
-            cback = lambda: self.__mouse_drag(*mouse_args)
-        self._tasks += [(
-            self._curr_time + time,
-            cback,
-            'event: %s' % evt)]
-        self._curr_time += time
+            #cback = lambda: self.__mouse_drag(*mouse_args)
+            await self.__mouse_drag(*mouse_args)
+        #self._tasks += [(
+        #    self._curr_time + time,
+        #    cback,
+        #    'event: %s' % evt)]
+        #self._curr_time += time
 
-    def _enforce_res(self, time, res):
-        def cback():
-            self._proxy.enforce_res(res)
-            info('enforce_res %s' % res)
-        self._tasks += [(
-            self._curr_time + time,
-            cback,
-            'enforce res: %s' % res)]
-        self._curr_time += time
+    async def _enforce_res(self, time, res):
+        await asyncio.sleep(time)
+        self._proxy.enforce_res(res)
+        #def cback():
+        #    self._proxy.enforce_res(res)
+        #    info('enforce_res %s' % res)
+        #self._tasks += [(
+        #    self._curr_time + time,
+        #    cback,
+        #    'enforce res: %s' % res)]
+        #self._curr_time += time
 
-    def _enforce_resolution(self, time, res):
-        def cback():
-            self._proxy.enforce_resolution(res)
-            info('enforce_resolution %s (send)' % res)
-        self._tasks += [(
-            self._curr_time + time,
-            cback,
-            'enforce resolution: %s' % res)]
-        self._curr_time += time
+    async def _enforce_resolution(self, time, res):
+        await asyncio.sleep(time)
+        self._proxy.enforce_resolution(res)
+        #def cback():
+        #    self._proxy.enforce_resolution(res)
+        #    info('enforce_resolution %s (send)' % res)
+        #self._tasks += [(
+        #    self._curr_time + time,
+        #    cback,
+        #    'enforce resolution: %s' % res)]
+        #self._curr_time += time
 
-    def _verify(self):
-        def __verify():
-            self._proxy.verify()
-            info('verify')
-        self._tasks += [(
-            self._curr_time + 3,
-            lambda: __verify(),
-            'verify')]
-        self._curr_time += 3
+    async def _verify(self, time):
+        await asyncio.sleep(time)
+        self._proxy.verify()
+        info('verify')
+        #def __verify():
+        #    self._proxy.verify()
+        #    info('verify')
+        #self._tasks += [(
+        #    self._curr_time + 3,
+        #    lambda: __verify(),
+        #    'verify')]
+        #self._curr_time += 3
 
-    def _exit(self):
-        def do_exit():
+    async def _exit(self, time):
+        await asyncio.sleep(time)
+        try:
             self._proxy.close()
-            exit()
-        self._tasks += [(
-            self._curr_time + 3,
-            lambda: do_exit(),
-            'exit')]
+        except (ConnectionRefusedError, xmlrpc.client.Fault) as e:  # the other part has been closed with the exit button
+            debug('already closed (%s)' % e)
+        debug('destroying FunctionalTest (%s)' % id(self))
+        exit()
+        #def do_exit():
+        #    try:
+        #        self._proxy.close()
+        #    except (ConnectionRefusedError, xmlrpc.client.Fault) as e:  # the other part has been closed with the exit button
+        #        debug('already closed (%s)' % e)
+        #    debug('destroying FunctionalTest (%s)' % id(self))
+        #    exit()
+        #self._tasks += [(
+        #    self._curr_time + 3,
+        #    lambda: do_exit(),
+        #    'exit')]
 
-    def on_frame_unpausable(self, task):
-        for tsk in self._tasks:
-            #if self._prev_time <= tsk[0] < self.eng.event.unpaused_time:
-            if self._prev_time <= tsk[0] < globalClock.getFrameTime():
-                debug('%s %s' % (tsk[0], tsk[2]))
-                tsk[1]()
-        self._prev_time = globalClock.getFrameTime()  # self.eng.event.unpaused_time
-        return task.cont
+    def on_frame_unpausable(self, task):
+        for tsk in self._tasks:
+            #if self._prev_time <= tsk[0] < self.eng.event.unpaused_time:
+            if self._prev_time <= tsk[0] < globalClock.getFrameTime():
+                debug('%s %s' % (tsk[0], tsk[2]))
+                tsk[1]()
+        self._prev_time = globalClock.getFrameTime()  # self.eng.event.unpaused_time
+        return task.cont
 
-    def _do_screenshots_1(self):
+    async def _do_screenshots_1(self):
         info('_do_screenshots_1')
-        self._screenshot(FunctionalTest.start_time, 'main_menu')
-        self._do_screenshots_credits()
-        self._do_screenshots_options()
-        self._do_screenshots_exit()
+        await self._screenshot(FunctionalTest.start_time, 'main_menu')
+        await self._do_screenshots_credits()
+        await self._do_screenshots_options()
+        await self._do_screenshots_exit()
 
-    def _do_screenshots_credits(self):
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 450), 'left'])
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['credits', 'left'])
-        self._screenshot(FunctionalTest.screenshot_time, 'credits_menu')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 680), 'left'])
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['back', 'left'])
-        self._screenshot(FunctionalTest.screenshot_time, 'main_menu_back_from_credits')
+    async def _do_screenshots_credits(self):
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 450), 'left'])
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['credits', 'left'])
+        await self._screenshot(FunctionalTest.screenshot_time, 'credits_menu')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 680), 'left'])
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['back', 'left'])
+        await self._screenshot(FunctionalTest.screenshot_time, 'main_menu_back_from_credits')
 
-    def _do_screenshots_options(self):
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 300), 'left'])
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['options', 'left'])
-        self._screenshot(FunctionalTest.screenshot_time, 'options_menu')
+    async def _do_screenshots_options(self):
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 300), 'left'])
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['options', 'left'])
+        await self._screenshot(FunctionalTest.screenshot_time, 'options_menu')
         # languages
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 60), 'left'])
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['languages', 'left'])
-        self._screenshot(FunctionalTest.screenshot_time, 'open_languages')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(980, 120), 'left'])
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['italian', 'left'])
-        self._screenshot(FunctionalTest.screenshot_time, 'options_menu_italian')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 60), 'left'])
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['languages', 'left'])
+        await self._screenshot(FunctionalTest.screenshot_time, 'open_languages')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(980, 120), 'left'])
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['italian', 'left'])
+        await self._screenshot(FunctionalTest.screenshot_time, 'options_menu_italian')
         # volume
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(740, 163), 'left'])
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['volume', 'left'])
-        self._screenshot(FunctionalTest.screenshot_time, 'options_menu_drag_1')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(740, 163), 'left'])
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['volume', 'left'])
+        await self._screenshot(FunctionalTest.screenshot_time, 'options_menu_drag_1')
         # antialiasing
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 440), 'left'])
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['aa', 'left'])
-        self._screenshot(FunctionalTest.screenshot_time, 'antialiasing_no')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 440), 'left'])
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['aa', 'left'])
+        await self._screenshot(FunctionalTest.screenshot_time, 'antialiasing_no')
         # shadows
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 540), 'left'])
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['shadows', 'left'])
-        self._screenshot(FunctionalTest.screenshot_time, 'shadows_no')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 540), 'left'])
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['shadows', 'left'])
+        await self._screenshot(FunctionalTest.screenshot_time, 'shadows_no')
         # test aa and shadows
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 680), 'left'])  # back
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['back', 'left'])  # back
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 140), 'left'])  # play
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['play', 'left'])  # play
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(230, 160), 'left'])  # domino
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['domino', 'left'])  # domino
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(900, 490), 'left'])  # close instructions
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['close_instructions', 'left'])  # close instructions
-        self._screenshot(FunctionalTest.screenshot_time, 'aa_no_shadows_no')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(25, 740), 'left'])  # home
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['home', 'left'])  # home
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 680), 'left'])  # back
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['back', 'left'])  # back
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 140), 'left'])  # play
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['play', 'left'])  # play
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(230, 160), 'left'])  # domino
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['domino', 'left'])  # domino
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(900, 490), 'left'])  # close instructions
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['close_instructions', 'left'])  # close instructions
+        await self._screenshot(FunctionalTest.screenshot_time, 'aa_no_shadows_no')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(25, 740), 'left'])  # home
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['home', 'left'])  # home
 
-    def _do_screenshots_restore_options(self):
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 300), 'left'])
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['options', 'left'])
-        self._screenshot(FunctionalTest.screenshot_time, 'options_menu_restored')
+    async def _do_screenshots_restore_options(self):
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 300), 'left'])
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['options', 'left'])
+        await self._screenshot(FunctionalTest.screenshot_time, 'options_menu_restored')
         # languages
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 60), 'left'])
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['languages', 'left'])
-        self._screenshot(FunctionalTest.screenshot_time, 'open_languages_restored')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(980, 20), 'left'])
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['english', 'left'])
-        self._screenshot(FunctionalTest.screenshot_time, 'options_menu_english')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 60), 'left'])
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['languages', 'left'])
+        await self._screenshot(FunctionalTest.screenshot_time, 'open_languages_restored')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(980, 20), 'left'])
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['english', 'left'])
+        await self._screenshot(FunctionalTest.screenshot_time, 'options_menu_english')
         # volume
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(719, 163), 'left'])
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['volume_0', 'left'])
-        self._screenshot(FunctionalTest.screenshot_time, 'options_menu_drag_2')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(719, 163), 'left'])
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['volume_0', 'left'])
+        await self._screenshot(FunctionalTest.screenshot_time, 'options_menu_drag_2')
         # fullscreen
         # the first one is because of the windowed mode in test
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 250), 'left'])
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['fullscreen', 'left'])
-        self._screenshot(FunctionalTest.screenshot_time, 'fullscreen')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 250), 'left'])
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['fullscreen', 'left'])
-        self._screenshot(FunctionalTest.screenshot_time, 'fullscreen')
-        #self._event(8 + FunctionalTest.evt_time, 'mouseclick', [(440, 120), 'left'])
-        #self._event(8 + FunctionalTest.evt_time, 'mouseclick', [(680, 250), 'left'])
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['fullscreen', 'left'])
-        self._screenshot(8 + FunctionalTest.screenshot_time, 'back_from_fullscreen')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 250), 'left'])
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['fullscreen', 'left'])
+        await self._screenshot(FunctionalTest.screenshot_time, 'fullscreen')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 250), 'left'])
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['fullscreen', 'left'])
+        await self._screenshot(FunctionalTest.screenshot_time, 'fullscreen')
+        #await self._event(8 + FunctionalTest.evt_time, 'mouseclick', [(440, 120), 'left'])
+        #await self._event(8 + FunctionalTest.evt_time, 'mouseclick', [(680, 250), 'left'])
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['fullscreen', 'left'])
+        await self._screenshot(8 + FunctionalTest.screenshot_time, 'back_from_fullscreen')
         # resolution
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 340), 'left'])
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['resolutions', 'left'])
-        self._screenshot(FunctionalTest.screenshot_time, 'resolutions')
-        self._enforce_resolution(FunctionalTest.evt_time, '1440x900')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(1000, 440), 'left'])
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['res_1440x900', 'left'])
-        self._screenshot(FunctionalTest.screenshot_time, '1440x900')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(740, 400), 'left'])
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['resolutions', 'left'])
-        self._screenshot(FunctionalTest.screenshot_time, 'resolutions_2')
-        self._enforce_resolution(FunctionalTest.evt_time, '1360x768')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(1110, 80), 'left'])
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['res_1360x768', 'left'])
-        self._screenshot(FunctionalTest.screenshot_time, '1360x768')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 340), 'left'])
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['resolutions', 'left'])
+        await self._screenshot(FunctionalTest.screenshot_time, 'resolutions')
+        await self._enforce_resolution(FunctionalTest.evt_time, '1440x900')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(1000, 440), 'left'])
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['res_1440x900', 'left'])
+        await self._screenshot(FunctionalTest.screenshot_time, '1440x900')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(740, 400), 'left'])
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['resolutions', 'left'])
+        await self._screenshot(FunctionalTest.screenshot_time, 'resolutions_2')
+        await self._enforce_resolution(FunctionalTest.evt_time, '1360x768')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(1110, 80), 'left'])
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['res_1360x768', 'left'])
+        await self._screenshot(FunctionalTest.screenshot_time, '1360x768')
         # antialiasing
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 440), 'left'])
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['aa', 'left'])
-        self._screenshot(FunctionalTest.screenshot_time, 'antialiasing_yes')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 440), 'left'])
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['aa', 'left'])
+        await self._screenshot(FunctionalTest.screenshot_time, 'antialiasing_yes')
         # shadows
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 540), 'left'])
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['shadows', 'left'])
-        self._screenshot(FunctionalTest.screenshot_time, 'shadows_yes')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 680), 'left'])  # back
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['back', 'left'])
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 540), 'left'])
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['shadows', 'left'])
+        await self._screenshot(FunctionalTest.screenshot_time, 'shadows_yes')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 680), 'left'])  # back
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['back', 'left'])
 
-    def _do_screenshots_play(self):
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 140), 'left'])  # play
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['play', 'left'])
-        self._screenshot(FunctionalTest.screenshot_time, 'play_menu')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 680), 'left'])  # back
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['back', 'left'])
-        self._screenshot(FunctionalTest.screenshot_time, 'back_from_play')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 140), 'left'])  # play
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['play', 'left'])
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(230, 160), 'left'])  # domino scene
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['domino', 'left'])
-        self._screenshot(FunctionalTest.screenshot_time, 'scene_domino_instructions')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(850, 490), 'left'])  # close instructions
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['close_instructions', 'left'])
-        self._screenshot(FunctionalTest.screenshot_time, 'scene_domino')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(25, 740), 'left'])  # home
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['home', 'left'])
-        self._screenshot(FunctionalTest.screenshot_time, 'home_back_from_scene')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 140), 'left'])  # play
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['play', 'left'])
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(230, 160), 'left'])  # domino
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['domino', 'left'])
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(850, 490), 'left'])  # close instructions
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['close_instructions', 'left'])
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(70, 740), 'left'])  # info
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['information', 'left'])
-        self._screenshot(FunctionalTest.screenshot_time, 'info')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(850, 490), 'left'])  # close instructions
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['close_instructions', 'left'])
-        # self._event(FunctionalTest.drag_time, 'mousedrag', [(35, 60), (430, 280), 'left'])  # drag a piece
-        # self._screenshot(FunctionalTest.screenshot_time, 'domino_dragged')
-        # self._event(FunctionalTest.evt_time, 'mouseclick', [(1220, 740), 'left'])  # rewind
-        # self._screenshot(FunctionalTest.screenshot_time, 'rewind')
-        self._event(FunctionalTest.drag_time, 'mousedrag', [(35, 60), (550, 380), 'left'])  # drag a piece
-        # self._event(FunctionalTest.drag_time, 'mousedrag', [(35, 60), (715, 380), 'left'])  # drag a piece
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(1340, 740), 'left'])  # play
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['right', 'left'])  # play
-        self._screenshot(16 + FunctionalTest.screenshot_time, 'fail_domino')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(630, 450), 'left'])  # home
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['home_win', 'left'])  # home
-        self._screenshot(FunctionalTest.screenshot_time, 'home_back_from_fail')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 140), 'left'])  # play
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['play', 'left'])  # play
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(230, 160), 'left'])  # domino
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['domino', 'left'])
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(850, 490), 'left'])  # close instructions
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['close_instructions', 'left'])
-        self._event(FunctionalTest.drag_time, 'mousedrag', [(35, 60), (550, 380), 'left'])  # drag a piece
-        self._event(FunctionalTest.drag_time, 'mousedrag', [(35, 60), (715, 380), 'left'])  # drag a piece
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(1340, 740), 'left'])  # play
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['right', 'left'])  # play
-        self._screenshot(16 + FunctionalTest.screenshot_time, 'fail_domino_2')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 450), 'left'])  # replay
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['replay', 'left'])  # play
-        self._event(FunctionalTest.drag_time, 'mousedrag', [(35, 60), (570, 380), 'left'])  # drag a piece
-        self._event(FunctionalTest.drag_time, 'mousedrag', [(570, 355), (605, 355), 'right'])  # rotate the piece
-        self._event(FunctionalTest.drag_time, 'mousedrag', [(35, 60), (715, 380), 'left'])  # drag a piece
-        self._enforce_res(FunctionalTest.evt_time, 'win')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(1340, 740), 'left'])  # play
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['right', 'left'])  # play
-        self._screenshot(16 + FunctionalTest.screenshot_time, 'win_domino')
-        self._enforce_res(FunctionalTest.evt_time, '')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(735, 450), 'left'])  # next
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['next', 'left'])  # play
-        self._screenshot(FunctionalTest.screenshot_time, 'scene_box')
+    async def _do_screenshots_play(self):
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 140), 'left'])  # play
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['play', 'left'])
+        await self._screenshot(FunctionalTest.screenshot_time, 'play_menu')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 680), 'left'])  # back
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['back', 'left'])
+        await self._screenshot(FunctionalTest.screenshot_time, 'back_from_play')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 140), 'left'])  # play
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['play', 'left'])
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(230, 160), 'left'])  # domino scene
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['domino', 'left'])
+        await self._screenshot(FunctionalTest.screenshot_time, 'scene_domino_instructions')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(850, 490), 'left'])  # close instructions
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['close_instructions', 'left'])
+        await self._screenshot(FunctionalTest.screenshot_time, 'scene_domino')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(25, 740), 'left'])  # home
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['home', 'left'])
+        await self._screenshot(FunctionalTest.screenshot_time, 'home_back_from_scene')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 140), 'left'])  # play
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['play', 'left'])
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(230, 160), 'left'])  # domino
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['domino', 'left'])
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(850, 490), 'left'])  # close instructions
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['close_instructions', 'left'])
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(70, 740), 'left'])  # info
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['information', 'left'])
+        await self._screenshot(FunctionalTest.screenshot_time, 'info')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(850, 490), 'left'])  # close instructions
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['close_instructions', 'left'])
+        # await self._event(FunctionalTest.drag_time, 'mousedrag', [(35, 60), (430, 280), 'left'])  # drag a piece
+        # await self._screenshot(FunctionalTest.screenshot_time, 'domino_dragged')
+        # await self._event(FunctionalTest.evt_time, 'mouseclick', [(1220, 740), 'left'])  # rewind
+        # await self._screenshot(FunctionalTest.screenshot_time, 'rewind')
+        #await self._event(FunctionalTest.drag_time, 'mousedrag', [(35, 60), (550, 380), 'left'])  # drag a piece
+        await self._event(FunctionalTest.drag_time, 'mousedrag', ['drag_start_0', 'drag_stop_0', 'left'])  # drag a piece
+        # await self._event(FunctionalTest.drag_time, 'mousedrag', [(35, 60), (715, 380), 'left'])  # drag a piece
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(1340, 740), 'left'])  # play
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['right', 'left'])  # play
+        await self._screenshot(16 + FunctionalTest.screenshot_time, 'fail_domino')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(630, 450), 'left'])  # home
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['home_win', 'left'])  # home
+        await self._screenshot(FunctionalTest.screenshot_time, 'home_back_from_fail')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 140), 'left'])  # play
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['play', 'left'])  # play
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(230, 160), 'left'])  # domino
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['domino', 'left'])
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(850, 490), 'left'])  # close instructions
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['close_instructions', 'left'])
+        #await self._event(FunctionalTest.drag_time, 'mousedrag', [(35, 60), (550, 380), 'left'])  # drag a piece
+        await self._event(FunctionalTest.drag_time, 'mousedrag', ['drag_start_0', 'drag_stop_0', 'left'])  # drag a piece
+        #await self._event(FunctionalTest.drag_time, 'mousedrag', [(35, 60), (715, 380), 'left'])  # drag a piece
+        await self._event(FunctionalTest.drag_time, 'mousedrag', ['drag_start_0', 'drag_stop_1', 'left'])  # drag a piece  .49 .06
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(1340, 740), 'left'])  # play
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['right', 'left'])  # play
+        await self._screenshot(16 + FunctionalTest.screenshot_time, 'fail_domino_2')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 450), 'left'])  # replay
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['replay', 'left'])  # play
+        #await self._event(FunctionalTest.drag_time, 'mousedrag', [(35, 60), (570, 380), 'left'])  # drag a piece -1.54 .06
+        await self._event(FunctionalTest.drag_time, 'mousedrag', ['drag_start_0', 'drag_stop_2', 'left'])  # drag a piece -1.54 .06
+        #await self._event(FunctionalTest.drag_time, 'mousedrag', [(570, 355), (605, 355), 'right'])  # rotate the piece -1.05 .4
+        await self._event(FunctionalTest.drag_time, 'mousedrag', ['drag_start_1', 'drag_stop_3', 'right'])  # rotate the piece -1.54 .4 -1.05 .4
+        #await self._event(FunctionalTest.drag_time, 'mousedrag', [(35, 60), (715, 380), 'left'])  # drag a piece
+        await self._event(FunctionalTest.drag_time, 'mousedrag', ['drag_start_0', 'drag_stop_0', 'left'])  # drag a piece
+        await self._enforce_res(FunctionalTest.evt_time, 'win')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(1340, 740), 'left'])  # play
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['right', 'left'])  # play
+        await self._screenshot(16 + FunctionalTest.screenshot_time, 'win_domino')
+        await self._enforce_res(FunctionalTest.evt_time, '')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(735, 450), 'left'])  # next
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['next', 'left'])  # play
+        await self._screenshot(FunctionalTest.screenshot_time, 'scene_box')
         # scene 2
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(880, 490), 'left'])  # close instructions
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['close_instructions', 'left'])
-        self._event(FunctionalTest.drag_time, 'mousedrag', [(65, 60), (710, 620), 'left'])  # drag a box
-        self._event(FunctionalTest.drag_time, 'mousedrag', [(65, 60), (710, 540), 'left'])  # drag a box
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(1340, 740), 'left'])  # play
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['right', 'left'])
-        self._screenshot(16 + FunctionalTest.screenshot_time, 'fail_box')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 450), 'left'])  # replay
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['replay', 'left'])
-        self._event(FunctionalTest.drag_time, 'mousedrag', [(65, 60), (710, 620), 'left'])  # drag a box
-        self._event(FunctionalTest.drag_time, 'mousedrag', [(65, 60), (710, 540), 'left'])  # drag a box
-        self._event(FunctionalTest.drag_time, 'mousedrag', [(65, 60), (705, 460), 'left'])  # drag a box
-        self._enforce_res(FunctionalTest.evt_time, 'win')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(1340, 740), 'left'])  # play
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['right', 'left'])
-        self._screenshot(16 + FunctionalTest.screenshot_time, 'win_box')
-        self._enforce_res(FunctionalTest.evt_time, '')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(735, 450), 'left'])  # next
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['next', 'left'])
-        self._screenshot(FunctionalTest.screenshot_time, 'scene_box_domino')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(880, 490), 'left'])  # close instructions
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['close_instructions', 'left'])
+        #await self._event(FunctionalTest.drag_time, 'mousedrag', [(65, 60), (710, 620), 'left'])  # drag a box .42 -3.29
+        await self._event(FunctionalTest.drag_time, 'mousedrag', ['drag_start_0', 'drag_stop_0', 'left'])  # drag a box .42 -3.29
+        #await self._event(FunctionalTest.drag_time, 'mousedrag', [(65, 60), (710, 540), 'left'])  # drag a box .42 -2.18
+        await self._event(FunctionalTest.drag_time, 'mousedrag', ['drag_start_0', 'drag_stop_1', 'left'])  # drag a box .42 -2.18
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(1340, 740), 'left'])  # play
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['right', 'left'])
+        await self._screenshot(16 + FunctionalTest.screenshot_time, 'fail_box')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 450), 'left'])  # replay
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['replay', 'left'])
+        #await self._event(FunctionalTest.drag_time, 'mousedrag', [(65, 60), (710, 620), 'left'])  # drag a box
+        await self._event(FunctionalTest.drag_time, 'mousedrag', ['drag_start_0', 'drag_stop_0', 'left'])  # drag a box
+       # await self._event(FunctionalTest.drag_time, 'mousedrag', [(65, 60), (710, 540), 'left'])  # drag a box
+        await self._event(FunctionalTest.drag_time, 'mousedrag', ['drag_start_0', 'drag_stop_1', 'left'])  # drag a box
+       # await self._event(FunctionalTest.drag_time, 'mousedrag', [(65, 60), (705, 460), 'left'])  # drag a box .35 -1.06
+        await self._event(FunctionalTest.drag_time, 'mousedrag', ['drag_start_0', 'drag_stop_2', 'left'])  # drag a box .35 -1.06
+        await self._enforce_res(FunctionalTest.evt_time, 'win')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(1340, 740), 'left'])  # play
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['right', 'left'])
+        await self._screenshot(16 + FunctionalTest.screenshot_time, 'win_box')
+        await self._enforce_res(FunctionalTest.evt_time, '')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(735, 450), 'left'])  # next
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['next', 'left'])
+        await self._screenshot(FunctionalTest.screenshot_time, 'scene_box_domino')
         # scene 3
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(930, 485), 'left'])  # close instructions
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['close_instructions', 'left'])
-        self._event(FunctionalTest.drag_time, 'mousedrag', [(65, 60), (910, 440), 'left'])  # drag a box
-        self._event(FunctionalTest.drag_time, 'mousedrag', [(65, 60), (910, 360), 'left'])  # drag a box
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(1340, 740), 'left'])  # play
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['right', 'left'])
-        self._screenshot(16 + FunctionalTest.screenshot_time, 'fail_box_domino')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 450), 'left'])  # replay
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['replay', 'left'])
-        self._event(FunctionalTest.drag_time, 'mousedrag', [(65, 60), (910, 440), 'left'])  # drag a box
-        self._event(FunctionalTest.drag_time, 'mousedrag', [(65, 60), (835, 250), 'left'])  # drag a box
-        self._enforce_res(FunctionalTest.evt_time, 'win')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(1340, 740), 'left'])  # play
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['right', 'left'])
-        self._screenshot(16 + FunctionalTest.screenshot_time, 'win_box_domino')
-        self._enforce_res(FunctionalTest.evt_time, '')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(735, 450), 'left'])  # next
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['next', 'left'])
-        self._screenshot(FunctionalTest.screenshot_time, 'scene_basketball')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(930, 485), 'left'])  # close instructions
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['close_instructions', 'left'])
+        #await self._event(FunctionalTest.drag_time, 'mousedrag', [(65, 60), (910, 440), 'left'])  # drag a box 3.21 -.78
+        await self._event(FunctionalTest.drag_time, 'mousedrag', ['drag_start_0', 'drag_stop_0', 'left'])  # drag a box 3.21 -.78
+        #await self._event(FunctionalTest.drag_time, 'mousedrag', [(65, 60), (910, 360), 'left'])  # drag a box 3.21 .33
+        await self._event(FunctionalTest.drag_time, 'mousedrag', ['drag_start_0', 'drag_stop_1', 'left'])  # drag a box 3.21 .33
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(1340, 740), 'left'])  # play
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['right', 'left'])
+        await self._screenshot(16 + FunctionalTest.screenshot_time, 'fail_box_domino')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 450), 'left'])  # replay
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['replay', 'left'])
+        #await self._event(FunctionalTest.drag_time, 'mousedrag', [(65, 60), (910, 440), 'left'])  # drag a box
+        await self._event(FunctionalTest.drag_time, 'mousedrag', ['drag_start_0', 'drag_stop_0', 'left'])  # drag a box
+        #await self._event(FunctionalTest.drag_time, 'mousedrag', [(65, 60), (835, 250), 'left'])  # drag a box 2.16 1.87
+        await self._event(FunctionalTest.drag_time, 'mousedrag', ['drag_start_0', 'drag_stop_2', 'left'])  # drag a box 2.16 1.87
+        await self._enforce_res(FunctionalTest.evt_time, 'win')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(1340, 740), 'left'])  # play
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['right', 'left'])
+        await self._screenshot(16 + FunctionalTest.screenshot_time, 'win_box_domino')
+        await self._enforce_res(FunctionalTest.evt_time, '')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(735, 450), 'left'])  # next
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['next', 'left'])
+        await self._screenshot(FunctionalTest.screenshot_time, 'scene_basketball')
         # scene 4
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(870, 490), 'left'])  # close instructions
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['close_instructions', 'left'])
-        self._event(FunctionalTest.drag_time, 'mousedrag', [(55, 50), (650, 310), 'left'])  # drag a ball
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(1340, 740), 'left'])  # play
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['right', 'left'])
-        self._screenshot(16 + FunctionalTest.screenshot_time, 'fail_basketball')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 450), 'left'])  # replay
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['replay', 'left'])
-        self._event(FunctionalTest.drag_time, 'mousedrag', [(55, 50), (380, 50), 'left'])  # drag a ball
-        self._enforce_res(FunctionalTest.evt_time, 'win')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(1340, 740), 'left'])  # play
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['right', 'left'])
-        self._screenshot(16 + FunctionalTest.screenshot_time, 'win_basketball')
-        self._enforce_res(FunctionalTest.evt_time, '')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(735, 450), 'left'])  # next
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['next', 'left'])
-        self._screenshot(FunctionalTest.screenshot_time, 'scene_domino_box_basketball')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(870, 490), 'left'])  # close instructions
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['close_instructions', 'left'])
+        #await self._event(FunctionalTest.drag_time, 'mousedrag', [(55, 50), (650, 310), 'left'])  # drag a ball -.42 1.03
+        await self._event(FunctionalTest.drag_time, 'mousedrag', ['drag_start_0', 'drag_stop_0', 'left'])  # drag a ball -.42 1.03
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(1340, 740), 'left'])  # play
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['right', 'left'])
+        await self._screenshot(16 + FunctionalTest.screenshot_time, 'fail_basketball')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 450), 'left'])  # replay
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['replay', 'left'])
+        #await self._event(FunctionalTest.drag_time, 'mousedrag', [(55, 50), (380, 50), 'left'])  # drag a ball -4.19 4.66
+        await self._event(FunctionalTest.drag_time, 'mousedrag', ['drag_start_0', 'drag_stop_1', 'left'])  # drag a ball -4.19 4.66
+        await self._enforce_res(FunctionalTest.evt_time, 'win')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(1340, 740), 'left'])  # play
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['right', 'left'])
+        await self._screenshot(16 + FunctionalTest.screenshot_time, 'win_basketball')
+        await self._enforce_res(FunctionalTest.evt_time, '')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(735, 450), 'left'])  # next
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['next', 'left'])
+        await self._screenshot(FunctionalTest.screenshot_time, 'scene_domino_box_basketball')
         # scene 5
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(865, 490), 'left'])  # close instructions
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['close_instructions', 'left'])
-        self._event(FunctionalTest.drag_time, 'mousedrag', [(65, 60), (580, 440), 'left'])  # drag a box
-        self._event(FunctionalTest.drag_time, 'mousedrag', [(30, 60), (590, 370), 'left'])  # drag a piece
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(1340, 740), 'left'])  # play
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['right', 'left'])
-        self._screenshot(16 + FunctionalTest.screenshot_time, 'fail_domino_box_basketball')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 450), 'left'])  # replay
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['replay', 'left'])
-        self._event(FunctionalTest.drag_time, 'mousedrag', [(65, 60), (580, 440), 'left'])  # drag a box
-        self._event(FunctionalTest.drag_time, 'mousedrag', [(30, 60), (660, 440), 'left'])  # drag a piece
-        self._event(FunctionalTest.drag_time, 'mousedrag', [(660, 425), (625, 425), 'right'])  # rotate a piece
-        self._event(FunctionalTest.drag_time, 'mousedrag', [(660, 435), (650, 445), 'left'])  # drag a piece
-        self._enforce_res(FunctionalTest.evt_time, 'win')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(1340, 740), 'left'])  # play
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['right', 'left'])
-        self._screenshot(16 + FunctionalTest.screenshot_time, 'win_domino_box_basketball')
-        self._enforce_res(FunctionalTest.evt_time, '')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(735, 450), 'left'])  # next
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['next', 'left'])
-        self._screenshot(FunctionalTest.screenshot_time, 'scene_teeter_tooter')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(865, 490), 'left'])  # close instructions
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['close_instructions', 'left'])
+        #await self._event(FunctionalTest.drag_time, 'mousedrag', [(65, 60), (580, 440), 'left'])  # drag a box -1.4 -.78
+        await self._event(FunctionalTest.drag_time, 'mousedrag', ['drag_start_0', 'drag_stop_0', 'left'])  # drag a box -1.4 -.78
+        #await self._event(FunctionalTest.drag_time, 'mousedrag', [(30, 60), (590, 370), 'left'])  # drag a piece -1.26 .2
+        await self._event(FunctionalTest.drag_time, 'mousedrag', ['drag_start_1', 'drag_stop_1', 'left'])  # drag a piece -1.26 .2
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(1340, 740), 'left'])  # play
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['right', 'left'])
+        await self._screenshot(16 + FunctionalTest.screenshot_time, 'fail_domino_box_basketball')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 450), 'left'])  # replay
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['replay', 'left'])
+        #await self._event(FunctionalTest.drag_time, 'mousedrag', [(65, 60), (580, 440), 'left'])  # drag a box
+        await self._event(FunctionalTest.drag_time, 'mousedrag', ['drag_start_0', 'drag_stop_0', 'left'])  # drag a box
+        #await self._event(FunctionalTest.drag_time, 'mousedrag', [(30, 60), (660, 440), 'left'])  # drag a piece -.28 -.78
+        await self._event(FunctionalTest.drag_time, 'mousedrag', ['drag_start_1', 'drag_stop_2', 'left'])  # drag a piece -.28 -.78
+        #await self._event(FunctionalTest.drag_time, 'mousedrag', [(660, 425), (625, 425), 'right'])  # rotate a piece -.28 -.57 -.77 -.57
+        await self._event(FunctionalTest.drag_time, 'mousedrag', ['drag_start_2', 'drag_stop_3', 'right'])  # rotate a piece -.28 -.57 -.77 -.57
+        #await self._event(FunctionalTest.drag_time, 'mousedrag', [(660, 435), (650, 445), 'left'])  # drag a piece -.28 -.85 -.42 -.85
+        await self._event(FunctionalTest.drag_time, 'mousedrag', ['drag_start_3', 'drag_stop_4', 'left'])  # drag a piece -.28 -.85 -.42 -.85
+        await self._enforce_res(FunctionalTest.evt_time, 'win')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(1340, 740), 'left'])  # play
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['right', 'left'])
+        await self._screenshot(16 + FunctionalTest.screenshot_time, 'win_domino_box_basketball')
+        await self._enforce_res(FunctionalTest.evt_time, '')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(735, 450), 'left'])  # next
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['next', 'left'])
+        await self._screenshot(FunctionalTest.screenshot_time, 'scene_teeter_tooter')
         # scene 6
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(870, 485), 'left'])  # close instructions
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['close_instructions', 'left'])
-        self._event(FunctionalTest.drag_time, 'mousedrag', [(60, 60), (490, 300), 'left'])  # drag a box
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(1340, 740), 'left'])  # play
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['right', 'left'])
-        self._screenshot(16 + FunctionalTest.screenshot_time, 'fail_teeter_tooter')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 450), 'left'])  # replay
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['replay', 'left'])
-        self._event(FunctionalTest.drag_time, 'mousedrag', [(60, 60), (490, 150), 'left'])  # drag a box
-        self._event(FunctionalTest.drag_time, 'mousedrag', [(515, 115), (515, 122), 'right'])  # rotate a box
-        self._enforce_res(FunctionalTest.evt_time, 'win')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(1340, 740), 'left'])  # play
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['right', 'left'])
-        self._screenshot(16 + FunctionalTest.screenshot_time, 'win_teeter_tooter')
-        self._enforce_res(FunctionalTest.evt_time, '')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(735, 450), 'left'])  # next
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['next', 'left'])
-        self._screenshot(FunctionalTest.screenshot_time, 'scene_teeter_domino_box_basketball')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(870, 485), 'left'])  # close instructions
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['close_instructions', 'left'])
+        #await self._event(FunctionalTest.drag_time, 'mousedrag', [(60, 60), (490, 300), 'left'])  # drag a box -2.65 1.18
+        await self._event(FunctionalTest.drag_time, 'mousedrag', ['drag_start_0', 'drag_stop_0', 'left'])  # drag a box -2.65 1.18
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(1340, 740), 'left'])  # play
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['right', 'left'])
+        await self._screenshot(16 + FunctionalTest.screenshot_time, 'fail_teeter_tooter')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 450), 'left'])  # replay
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['replay', 'left'])
+        #await self._event(FunctionalTest.drag_time, 'mousedrag', [(60, 60), (490, 150), 'left'])  # drag a box -2.65 3.27
+        await self._event(FunctionalTest.drag_time, 'mousedrag', ['drag_start_0', 'drag_stop_1', 'left'])  # drag a box -2.65 3.27
+        #await self._event(FunctionalTest.drag_time, 'mousedrag', [(515, 115), (515, 122), 'right'])  # rotate a box -2.3 3.75 -2.5 3.66
+        await self._event(FunctionalTest.drag_time, 'mousedrag', ['drag_start_1', 'drag_stop_2', 'right'])  # rotate a box -2.3 3.75 -2.5 3.66
+        await self._enforce_res(FunctionalTest.evt_time, 'win')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(1340, 740), 'left'])  # play
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['right', 'left'])
+        await self._screenshot(16 + FunctionalTest.screenshot_time, 'win_teeter_tooter')
+        await self._enforce_res(FunctionalTest.evt_time, '')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(735, 450), 'left'])  # next
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['next', 'left'])
+        await self._screenshot(FunctionalTest.screenshot_time, 'scene_teeter_domino_box_basketball')
         # scene 7
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(930, 485), 'left'])  # close instructions
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['close_instructions', 'left'])
-        self._event(FunctionalTest.drag_time, 'mousedrag', [(60, 60), (155, 180), 'left'])  # drag a box
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(1340, 740), 'left'])  # play
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['right', 'left'])
-        self._screenshot(16 + FunctionalTest.screenshot_time, 'fail_teeter_domino_box_basketball')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 450), 'left'])  # replay
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['replay', 'left'])
-        self._event(FunctionalTest.drag_time, 'mousedrag', [(60, 60), (170, 80), 'left'])  # drag a box
-        self._event(FunctionalTest.drag_time, 'mousedrag', [(195, 50), (195, 80), 'right'])  # rotate a box
-        self._enforce_res(FunctionalTest.evt_time, 'win')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(1340, 740), 'left'])  # play
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['right', 'left'])
-        self._screenshot(16 + FunctionalTest.screenshot_time, 'win_teeter_domino_box_basketball')
-        self._enforce_res(FunctionalTest.evt_time, '')
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(630, 450), 'left'])  # home
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['home_win', 'left'])
-        self._screenshot(FunctionalTest.screenshot_time, 'home_from_play')
-
-    def _exit(self):
-        self._tasks += [(
-            self._curr_time + 3,
-            lambda: exit(),
-            'exit')]
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(930, 485), 'left'])  # close instructions
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['close_instructions', 'left'])
+        #await self._event(FunctionalTest.drag_time, 'mousedrag', [(60, 60), (155, 180), 'left'])  # drag a box -7.33 4.24
+        await self._event(FunctionalTest.drag_time, 'mousedrag', ['drag_start_0', 'drag_stop_0', 'left'])  # drag a box -7.33 4.24
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(1340, 740), 'left'])  # play
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['right', 'left'])
+        await self._screenshot(16 + FunctionalTest.screenshot_time, 'fail_teeter_domino_box_basketball')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 450), 'left'])  # replay
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['replay', 'left'])
+        #await self._event(FunctionalTest.drag_time, 'mousedrag', [(60, 60), (170, 80), 'left'])  # drag a box -7.12 4.24
+        await self._event(FunctionalTest.drag_time, 'mousedrag', ['drag_start_0', 'drag_stop_1', 'left'])  # drag a box -7.12 4.24
+        #await self._event(FunctionalTest.drag_time, 'mousedrag', [(195, 50), (195, 80), 'right'])  # rotate a box -6.77 4.66 -6.77 4.24
+        await self._event(FunctionalTest.drag_time, 'mousedrag', ['drag_start_1', 'drag_stop_2', 'right'])  # rotate a box -6.77 4.66 -6.77 4.24
+        await self._enforce_res(FunctionalTest.evt_time, 'win')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(1340, 740), 'left'])  # play
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['right', 'left'])
+        await self._screenshot(16 + FunctionalTest.screenshot_time, 'win_teeter_domino_box_basketball')
+        await self._enforce_res(FunctionalTest.evt_time, '')
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(630, 450), 'left'])  # home
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['home_win', 'left'])
+        await self._screenshot(FunctionalTest.screenshot_time, 'home_from_play')
 
-    def _do_screenshots_exit(self):
-        self._verify()
-        #self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 600), 'left'])
-        self._event(FunctionalTest.evt_time, 'mouseclick', ['exit', 'left'])
-        self._exit()
+    async def _do_screenshots_exit(self):
+        await self._verify(FunctionalTest.evt_time)
+        #await self._event(FunctionalTest.evt_time, 'mouseclick', [(680, 600), 'left'])
+        await self._event(FunctionalTest.evt_time, 'mouseclick', ['exit', 'left'])
+        await self._exit(FunctionalTest.evt_time)
 
-    def _do_screenshots_2(self):
+    async def _do_screenshots_2(self):
         info('_do_screenshots_2')
-        self._screenshot(FunctionalTest.start_time, 'main_menu_2')
-        self._do_screenshots_restore_options()
-        self._do_screenshots_play()
-        self._do_screenshots_exit()
+        await self._screenshot(FunctionalTest.start_time, 'main_menu_2')
+        await self._do_screenshots_restore_options()
+        await self._do_screenshots_play()
+        await self._do_screenshots_exit()
 
     def _do_screenshots(self, idx):
-        [self._do_screenshots_1, self._do_screenshots_2][int(idx) - 1]()
+        asyncio.run(
+            [self._do_screenshots_1, self._do_screenshots_2][int(idx) - 1]()
+        )
 
 
 class TestApp(ShowBase):
index fbd89e0341bef4eebdc8a26c60480ad21492088f..def76b5c6c0003efabc5f2c601580d922f1467bc 100644 (file)
@@ -11,12 +11,17 @@ from time import sleep
 from os import system, remove, environ
 from os.path import exists, basename, join
 from glob import glob
+from subprocess import PIPE, run, Popen
 from panda3d.core import Filename
-from ya2.build.build import exec_cmd, _branch, ver
+from ya2.build.build import exec_cmd, _branch, _version
 
 
 class FunctionalTests(TestCase):
 
+    def __init__(self, *args, **kwargs):
+        super(FunctionalTests, self).__init__(*args, **kwargs)
+        self.__itchio_updated = False
+
     def __clean(self):
         paths = [
             'tests/functional',
@@ -61,19 +66,41 @@ class FunctionalTests(TestCase):
 
     def __similar_images(self, ref_img, tst_img):
         cmd = 'magick compare -metric NCC %s %s diff.png' % (ref_img, tst_img)
-        res = exec_cmd(cmd)
+        res = exec_cmd(cmd, False)
         if exists('diff.png'): remove('diff.png')
         print('compare %s %s: %s' % (ref_img, tst_img, res))
         return float(res) > .8
 
-    def __test_template(self, cmd, path):
+    def __test_template(self, app_cmd, test_cmds, path):
         if environ.get('FUNCTIONAL') != '1':
             self.skipTest('skipped functional tests')
         self.__clean()
         self.__awake()
         system('amixer sset Master 0%')
-        ret = system(cmd)
-        self.assertEqual(ret, 0, 'error while executing ' + cmd)
+        #ret = system(cmd)
+        #proc = run(cmd, shell=True, stdout=PIPE, stderr=PIPE, universal_newlines=True, check=check)
+        #return proc.stdout.strip() + proc.stderr.strip()
+        for test_cmd in test_cmds:
+            sleep(8)
+            info('launching ' + app_cmd)
+            print('launching ' + app_cmd)
+            p_app = Popen(app_cmd, shell=True, stdout=PIPE, stderr=PIPE, universal_newlines=True)
+            sleep(30)
+            info('launching ' + test_cmd)
+            print('launching ' + test_cmd)
+            p_test = Popen(test_cmd, shell=True, stdout=PIPE, stderr=PIPE, universal_newlines=True)
+            p_app_out, p_app_err = p_app.communicate()
+            t_out, t_err = p_test.communicate()
+            info('output (%s): %s' % (app_cmd, p_app_out))
+            info('error (%s): %s' % (app_cmd, p_app_err))
+            info('output (%s): %s' % (test_cmd, t_out))
+            info('error (%s): %s' % (test_cmd, t_err))
+            print('output (%s): %s' % (app_cmd, p_app_out))
+            print('error (%s): %s' % (app_cmd, p_app_err))
+            print('output (%s): %s' % (test_cmd, t_out))
+            print('error (%s): %s' % (test_cmd, t_err))
+            self.assertEqual(p_app.returncode, 0, 'error while executing ' + app_cmd)
+            self.assertEqual(p_test.returncode, 0, 'error while executing ' + test_cmd)
         files = glob(str(Path.home()) + '/.local/share/pmachines/tests/functional_ref_%s/*.png' % _branch())
         self.assertGreater(len(files), 1)
         for fname in files:
@@ -91,50 +118,57 @@ class FunctionalTests(TestCase):
 
     def test_code(self):
         info('test_code')
+        if environ.get('FUNCTIONALPOST') == '1':
+            self.skipTest('skipped functional-post test: test_code')
         self.__test_template(
-            'timeout 720s ~/venv/bin/python main.py --functional-test & '
-            'timeout 720s ~/venv/bin/python -m tests.functional_test.py 1; sleep 5; '
-            'timeout 720s ~/venv/bin/python main.py --functional-test & '
-            'timeout 720s ~/venv/bin/python -m tests.functional_test.py 2',
+            '/home/flavio/venv/bin/python main.py --functional-test',
+            ['/home/flavio/venv/bin/python -m tests.functional_test.py 1',
+             'timeout 7200s ~/venv/bin/python -m tests.functional_test.py 2'],
             str(Path.home()) + '/.local/share/pmachines/tests/functional/')
 
     def test_appimage(self):
         info('test_appimage')
+        if environ.get('FUNCTIONALPOST') == '1':
+            self.skipTest('skipped functional-post test: test_appimage')
         bld_branch = {'master': 'alpha', 'rc': 'rc', 'stable': 'stable'}[_branch()]
         bld_branch = '' if bld_branch == 'stable' else ('-' + bld_branch)
         self.__test_template(
-            'timeout 720s ./dist/Pmachines%s-x86_64.AppImage --functional-test & '
-            'timeout 720s ~/venv/bin/python -m tests.functional_test.py 1; sleep 5; '
-            'timeout 720s ./dist/Pmachines%s-x86_64.AppImage --functional-test & ' % (bld_branch, bld_branch) +
-            'timeout 720s ~/venv/bin/python -m tests.functional_test.py 2',
+            'timeout 7200s ./dist/Pmachines%s-x86_64.AppImage --functional-test' % bld_branch,
+            ['timeout 7200s ~/venv/bin/python -m tests.functional_test.py 1',
+             'timeout 7200s ~/venv/bin/python -m tests.functional_test.py 2'],
             str(Path.home()) + '/.local/share/pmachines/tests/functional/')
 
-    def test_flatpak(self):
-        info('test_flatpak')
-        if environ.get('FUNCTIONALPOST') != '1':
-            self.skipTest('skipped functional-post tests')
-        bld_branch = {'master': 'alpha', 'rc': 'rc', 'stable': 'stable'}[_branch()]
-        cmd = 'flatpak update --user -y it.ya2.Pmachines//%s' % bld_branch
-        info('executing: %s' % cmd)
-        #system(cmd)
-        fout = exec_cmd(cmd)
-        info('executed: %s' % cmd)
-        info(fout)
-        self.__test_template(
-            'timeout 720s flatpak run it.ya2.Pmachines//%s --functional-test & '
-            'timeout 720s ~/venv/bin/python -m tests.functional_test.py 1; sleep 5; '
-            'timeout 720s flatpak run it.ya2.Pmachines//%s --functional-test & ' % (bld_branch, bld_branch) +
-            'timeout 720s ~/venv/bin/python -m tests.functional_test.py 2',
-            str(Path.home()) + '/.var/app/it.ya2.Pmachines/data/pmachines/tests/functional/')
+    def test_flatpak(self):
+        info('test_flatpak')
+        if environ.get('FUNCTIONALPOST') != '1':
+            self.skipTest('skipped functional-post tests')
+        bld_branch = {'master': 'alpha', 'rc': 'rc', 'stable': 'stable'}[_branch()]
+        cmd = 'flatpak update --user -y it.ya2.Pmachines//%s' % bld_branch
+        info('executing: %s' % cmd)
+        #system(cmd)
+        fout = exec_cmd(cmd)
+        info('executed: %s' % cmd)
+        info(fout)
+        self.__test_template(
+            'timeout 720s flatpak run it.ya2.Pmachines//%s --functional-test & '
+            'timeout 720s ~/venv/bin/python -m tests.functional_test.py 1; sleep 5; '
+            'timeout 720s flatpak run it.ya2.Pmachines//%s --functional-test & ' % (bld_branch, bld_branch) +
+            'timeout 720s ~/venv/bin/python -m tests.functional_test.py 2',
+            str(Path.home()) + '/.var/app/it.ya2.Pmachines/data/pmachines/tests/functional/')
 
     def __update_itchio(self):
+        info('update_itchio::itchio_updated::cond: %s' % str(self.__itchio_updated))
+        if self.__itchio_updated:
+            return
         system('/home/flavio/.itch/itch')
-        sleep(5)
+        sleep(120)
         system('xdotool mousemove 860 620')
-        sleep(1)
+        sleep(3)
         system('xdotool click 1')
         sleep(300)
         system('killall itch')
+        self.__itchio_updated = True
+        info('update_itchio::itchio_updated::done: %s' % str(self.__itchio_updated))
 
     def test_itchio(self):
         info('test_itchio')
@@ -144,21 +178,21 @@ class FunctionalTests(TestCase):
             return
         self.__update_itchio()
         self.__test_template(
-            'timeout 720s /home/flavio/.config/itch/apps/pmachines/pmachines --functional-test & '
-            'timeout 720s ~/venv/bin/python -m tests.functional_test.py 1; sleep 5; '
-            'timeout 720s /home/flavio/.config/itch/apps/pmachines/pmachines --functional-test & '
-            'timeout 720s ~/venv/bin/python -m tests.functional_test.py 2',
+            'timeout 7200s /home/flavio/.config/itch/apps/pmachines/pmachines --functional-test',
+            ['timeout 7200s ~/venv/bin/python -m tests.functional_test.py 1',
+             'timeout 7200s ~/venv/bin/python -m tests.functional_test.py 2'],
             str(Path.home()) + '/.local/share/pmachines/tests/functional/')
 
     def test_windows(self):
         info('test_windows')
+        if environ.get('FUNCTIONALPOST') == '1':
+            self.skipTest('skipped functional-post test: test_windows')
         system('pkill -f "pmachines.exe"')
         abspath = str(Path(__file__).parent.parent) + '/build/win_amd64/pmachines.exe'
         self.__test_template(
-            'timeout 720s wine %s --functional-test & ' % abspath +
-            'timeout 720s ~/venv/bin/python -m tests.functional_test.py 1; sleep 5; '
-            'timeout 720s wine %s --functional-test & ' % abspath +
-            'timeout 720s ~/venv/bin/python -m tests.functional_test.py 2',
+            'timeout 7200s wine %s --functional-test' % abspath,
+            ['timeout 7200s ~/venv/bin/python -m tests.functional_test.py 1',
+             'timeout 7200s ~/venv/bin/python -m tests.functional_test.py 2'],
             str(Path.home()) + '/.wine/drive_c/users/flavio/AppData/Local/pmachines/tests/functional/')
 
     def test_versions(self):
@@ -171,26 +205,26 @@ class FunctionalTests(TestCase):
         for line in lines:
             if line.strip().split()[0] == _branch():
                 commit = line.strip().split()[1][:7]
-        _ver = ver
+        __ver = _version()
         if _branch() == 'stable':
             with open('/home/flavio/builders/pmachines_builder/pmachines/assets/version.txt') as fver:
-                _ver = fver.read().strip() + '-'
-        exp = '%s-%s' % (_ver, commit)
+                __ver = fver.read().strip() + '-'
+        exp = '%s-%s' % (__ver, commit)
         cmds = [
-            ('timeout 720s ./build/manylinux2010_x86_64/pmachines --version', str(Filename.get_user_appdata_directory()) + '/pmachines/obs_version.txt'),
-            ('timeout 720s ./dist/Pmachines-%s-x86_64.AppImage --version' % bld_branch, str(Filename.get_user_appdata_directory()) + '/pmachines/obs_version.txt'),
-            ('timeout 720s wine ./build/win_amd64/pmachines.exe --version', '/home/flavio/.wine/drive_c/users/flavio/AppData/Local/pmachines/obs_version.txt')
+            ('timeout 7200s ./build/manylinux2010_x86_64/pmachines --version', str(Filename.get_user_appdata_directory()) + '/pmachines/obs_version.txt'),
+            ('timeout 7200s ./dist/Pmachines-%s-x86_64.AppImage --version' % bld_branch, str(Filename.get_user_appdata_directory()) + '/pmachines/obs_version.txt'),
+            ('timeout 7200s wine ./build/win_amd64/pmachines.exe --version', '/home/flavio/.wine/drive_c/users/flavio/AppData/Local/pmachines/obs_version.txt')
         ]
         if environ.get('FUNCTIONALPOST') == '1':
             if _branch() == 'master':
                 self.__update_itchio()
-                cmds += [('timeout 720s /home/flavio/.config/itch/apps/pmachines/pmachines --version', str(Filename.get_user_appdata_directory()) + '/pmachines/obs_version.txt')]
-            cmds += [('timeout 720s flatpak run it.ya2.Pmachines//%s --version' % bld_branch, '/home/flavio/.var/app/it.ya2.Pmachines/data/pmachines/obs_version.txt')]
-        info('executing flatpak update --user -y it.ya2.Pmachines//%s' % bld_branch)
-        #system('flatpak update -y --user it.ya2.Pmachines//%s' % bld_branch)
-        fout = exec_cmd('flatpak update -y --user it.ya2.Pmachines//%s' % bld_branch)
-        info('executed flatpak update --user -y it.ya2.Pmachines//%s' % bld_branch)
-        info(fout)
+                cmds += [('timeout 7200s /home/flavio/.config/itch/apps/pmachines/pmachines --version', str(Filename.get_user_appdata_directory()) + '/pmachines/obs_version.txt')]
+            # cmds += [('timeout 7200s flatpak run it.ya2.Pmachines//%s --version' % bld_branch, '/home/flavio/.var/app/it.ya2.Pmachines/data/pmachines/obs_version.txt')]
+        info('executing flatpak update --user -y it.ya2.Pmachines//%s' % bld_branch)
+        # #system('flatpak update -y --user it.ya2.Pmachines//%s' % bld_branch)
+        fout = exec_cmd('flatpak update -y --user it.ya2.Pmachines//%s' % bld_branch)
+        info('executed flatpak update --user -y it.ya2.Pmachines//%s' % bld_branch)
+        info(fout)
         for cmd in cmds:
             if exists(cmd[1]):
                 remove(cmd[1])
index 6713addb2fdc745f99605cdcb58bddac13c127fc..5546cf1cafb28abd37fa2f8c75ef8ca1bd36d656 100644 (file)
@@ -1,13 +1,14 @@
 '''Unit tests for lib.build.'''
 from pathlib import Path
 import sys
-if '' in sys.path: sys.path.remove('')
+if '' in sys.path:
+    sys.path.remove('')
 sys.path.append(str(Path(__file__).parent.parent.parent.parent))
 from os import getcwd, makedirs, walk
 from os.path import basename
 from shutil import rmtree
 from unittest import TestCase
-from re import compile
+import re
 from ya2.build.build import InsideDir, files, exec_cmd, _branch, _version, \
     to_be_built
 
@@ -18,13 +19,16 @@ class BuildTests(TestCase):
         makedirs('test_get_files/a')
         makedirs('test_get_files/b')
         makedirs('test_get_files/c')
-        with open('test_get_files/a/c.ext1', 'w') as ftest:
+        with open('test_get_files/a/c.ext1', 'w', encoding='utf8') as ftest:
             ftest.write('0123456789')
-        with open('test_get_files/a/d.ext2', 'w'): pass
-        with open('test_get_files/b/e.ext2', 'w') as ftest:
+        with open('test_get_files/a/d.ext2', 'w', encoding='utf8'):
+            pass
+        with open('test_get_files/b/e.ext2', 'w', encoding='utf8') as ftest:
             ftest.write('0123456789')
-        with open('test_get_files/b/f.ext3', 'w'): pass
-        with open('test_get_files/c/g.ext2', 'w'): pass
+        with open('test_get_files/b/f.ext3', 'w', encoding='utf8'):
+            pass
+        with open('test_get_files/c/g.ext2', 'w', encoding='utf8'):
+            pass
 
     def tearDown(self):
         rmtree('test_get_files')
@@ -39,8 +43,8 @@ class BuildTests(TestCase):
         patterns = [
             "^0a[0-9]+$",
             "^0rc[0-9]+$",
-            "^0\.[0-9]+$"]
-        compiled = [compile(pattern) for pattern in patterns]
+            r"^0\.[0-9]+$"]
+        compiled = [re.compile(pattern) for pattern in patterns]
         matches = [pattern.match(_version()) for pattern in compiled]
         self.assertTrue(any(matches))
 
@@ -61,8 +65,8 @@ class BuildTests(TestCase):
 
     def test_to_be_built(self):
         tgt = 'test_get_files/tgt.txt'
-        with open('test_get_files/src.txt', 'w') as fsrc:
+        with open('test_get_files/src.txt', 'w', encoding='utf8') as fsrc:
             fsrc.write('src')
-        with open(tgt, 'w') as ftgt:
+        with open(tgt, 'w', encoding='utf8') as ftgt:
             ftgt.write('tgt')
         self.assertTrue(to_be_built(tgt, ['test_get_files/src.txt']))
diff --git a/tests/ya2/utils/test_gameobject.py b/tests/ya2/utils/test_gameobject.py
deleted file mode 100644 (file)
index 58cae86..0000000
+++ /dev/null
@@ -1,228 +0,0 @@
-from pathlib import Path
-import sys
-if '' in sys.path: sys.path.remove('')
-sys.path.append(str(Path(__file__).parent.parent.parent))
-from unittest.mock import patch
-from unittest import TestCase
-from panda3d.core import loadPrcFileData
-#from ya2.engine.engine import Engine
-from ya2.patterns.gameobject import AiColleague, AudioColleague, EventColleague, \
-    FsmColleague, GameObject, GfxColleague, GuiColleague, LogicColleague, \
-    PhysColleague, Colleague
-
-
-class AiTests(TestCase):
-
-    def setUp(self):
-        loadPrcFileData('', 'window-type none')
-        loadPrcFileData('', 'audio-library-name null')
-
-    def tearDown(self):
-        pass
-        #self.engine.destroy()
-
-    def test_init(self):
-        #self.engine = Engine()
-        game_obj = GameObject()
-        ai = AiColleague(game_obj)
-        self.assertIsInstance(ai, AiColleague)
-
-
-class AudioTests(TestCase):
-
-    def setUp(self):
-        loadPrcFileData('', 'window-type none')
-        loadPrcFileData('', 'audio-library-name null')
-
-    def tearDown(self):
-        pass
-        #self.engine.destroy()
-
-    def test_init(self):
-        #self.engine = Engine()
-        game_obj = GameObject()
-        audio = AudioColleague(game_obj)
-        self.assertIsInstance(audio, AudioColleague)
-
-
-class ColleagueTests(TestCase):
-
-    def setUp(self):
-        loadPrcFileData('', 'window-type none')
-        loadPrcFileData('', 'audio-library-name null')
-
-    def tearDown(self):
-        pass
-        #self.engine.destroy()
-
-    def test_init(self):
-        #self.engine = Engine()
-        game_obj = GameObject()
-        colleague = Colleague(game_obj)
-        self.assertIsInstance(colleague, Colleague)
-
-
-class EventTests(TestCase):
-
-    def setUp(self):
-        loadPrcFileData('', 'window-type none')
-        loadPrcFileData('', 'audio-library-name null')
-
-    def tearDown(self):
-        pass
-        #self.engine.destroy()
-
-    def test_init(self):
-        #self.engine = Engine()
-        game_obj = GameObject()
-        event = EventColleague(game_obj)
-        self.assertIsInstance(event, EventColleague)
-
-
-class FsmTests(TestCase):
-
-    def setUp(self):
-        loadPrcFileData('', 'window-type none')
-        loadPrcFileData('', 'audio-library-name null')
-
-    def tearDown(self):
-        pass
-        #self.engine.destroy()
-
-    def test_init(self):
-        #self.engine = Engine()
-        game_obj = GameObject()
-        fsm = FsmColleague(game_obj)
-        self.assertIsInstance(fsm, FsmColleague)
-
-
-class GfxTests(TestCase):
-
-    def setUp(self):
-        loadPrcFileData('', 'window-type none')
-        loadPrcFileData('', 'audio-library-name null')
-
-    def tearDown(self):
-        pass
-        #self.engine.destroy()
-
-    def test_init(self):
-        #self.engine = Engine()
-        game_obj = GameObject()
-        gfx = GfxColleague(game_obj)
-        self.assertIsInstance(gfx, GfxColleague)
-
-
-class GuiTests(TestCase):
-
-    def setUp(self):
-        loadPrcFileData('', 'window-type none')
-        loadPrcFileData('', 'audio-library-name null')
-
-    def tearDown(self):
-        pass
-        #self.engine.destroy()
-
-    def test_init(self):
-        pass
-        #self.engine = Engine()
-        game_obj = GameObject()
-        gui = GuiColleague(game_obj)
-        self.assertIsInstance(gui, GuiColleague)
-
-
-class LogicTests(TestCase):
-
-    def setUp(self):
-        loadPrcFileData('', 'window-type none')
-        loadPrcFileData('', 'audio-library-name null')
-
-    def tearDown(self):
-        pass
-        #self.engine.destroy()
-
-    def test_init(self):
-        pass
-        #self.engine = Engine()
-        game_obj = GameObject()
-        logic = LogicColleague(game_obj)
-        self.assertIsInstance(logic, LogicColleague)
-
-
-class PhysicsTests(TestCase):
-
-    def setUp(self):
-        loadPrcFileData('', 'window-type none')
-        loadPrcFileData('', 'audio-library-name null')
-
-    def tearDown(self):
-        pass
-        #self.engine.destroy()
-
-    def test_init(self):
-        pass
-        #self.engine = Engine()
-        game_obj = GameObject()
-        phys = PhysColleague(game_obj)
-        self.assertIsInstance(phys, PhysColleague)
-
-
-class GameObjectInstance(GameObject):
-
-    def __init__(self):
-        GameObject.__init__(self)
-        self.fsm = FsmColleague(self)
-        self.event = EventColleague(self)
-        self.ai = AiColleague(self)
-        self.phys = PhysColleague(self)
-        self.audio = AudioColleague(self)
-        self.logic = LogicColleague(self)
-        self.gui = GuiColleague(self)
-        self.gfx = GfxColleague(self)
-
-    def destroy(self):
-        self.fsm.destroy()
-        self.event.destroy()
-        self.ai.destroy()
-        self.phys.destroy()
-        self.audio.destroy()
-        self.logic.destroy()
-        self.gui.destroy()
-        self.gfx.destroy()
-
-
-class GameObjectTests(TestCase):
-
-    def setUp(self):
-        loadPrcFileData('', 'window-type none')
-        loadPrcFileData('', 'audio-library-name null')
-
-    def tearDown(self):
-        pass
-        #self.engine.destroy()
-
-    @patch.object(GfxColleague, 'destroy')
-    @patch.object(GuiColleague, 'destroy')
-    @patch.object(LogicColleague, 'destroy')
-    @patch.object(AudioColleague, 'destroy')
-    @patch.object(PhysColleague, 'destroy')
-    @patch.object(AiColleague, 'destroy')
-    @patch.object(EventColleague, 'destroy')
-    @patch.object(FsmColleague, 'destroy')
-    def test_init(
-            self, mock_fsm_destroy, mock_event_destroy, mock_ai_destroy,
-            mock_phys_destroy, mock_audio_destroy, mock_logic_destroy,
-            mock_gui_destroy, mock_gfx_destroy):
-        #self.engine = Engine()
-        mock_event_destroy.__name__ = 'destroy'
-        game_obj = GameObjectInstance()
-        self.assertIsInstance(game_obj, GameObject)
-        game_obj.destroy()
-        assert mock_fsm_destroy.called
-        assert mock_event_destroy.called
-        assert mock_ai_destroy.called
-        assert mock_phys_destroy.called
-        assert mock_audio_destroy.called
-        assert mock_logic_destroy.called
-        assert mock_gui_destroy.called
-        assert mock_gfx_destroy.called
index b43f09f6238dadc192feb9651eb1a96850c891ed..565946335796ddfd1e8d63b7aca1e430edd4bb1e 100644 (file)
@@ -1,4 +1,6 @@
-import bpy, sys, os
+import bpy
+import sys
+import os
 
 filepath = os.path.abspath(sys.argv[-1])
 bpy.ops.export_scene.gltf(
index 420a7eb7f63ae633dd22c29045f5ca825ef799e9..f67345d4c2bb4bcfd7835507a937347c37074c0a 100644 (file)
@@ -1,55 +1,55 @@
 '''Tools for making the builds.'''
 from os import walk, chdir, getcwd
-from os.path import join, getsize, exists, dirname, getmtime, sep
-from subprocess import Popen, PIPE, run
-from logging import debug
+from os.path import join, exists, dirname, getmtime, sep
+from subprocess import PIPE, run
 from time import strftime
 from pathlib import Path
 from hashlib import md5
 
 
-#TODO refactor: make class BuilderTools
+# TODO refactor: make class BuilderTools
 
 
-def exec_cmd(cmd):
+def exec_cmd(cmd, check=True):
     '''Synchronously executes a command and returns its output.'''
-    #ret = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True).communicate()
-    #return ret[0].decode('utf-8').strip()
-    proc = run(cmd, shell=True, stdout=PIPE, stderr=PIPE, universal_newlines=True)
+    # ret = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True).communicate()
+    # return ret[0].decode('utf-8').strip()
+    proc = run(cmd, shell=True, stdout=PIPE, stderr=PIPE, universal_newlines=True, check=check)
     return proc.stdout.strip() + proc.stderr.strip()
 
 
 def _branch():
     '''Returns the current branch.'''
     git_branch = exec_cmd('git symbolic-ref HEAD').split('/')[-1].strip()
-    print('git_branch result: %s' % git_branch)
+    print(f'git_branch result: {git_branch}')
     branches = ['master', 'rc', 'stable']
     if git_branch in branches:
-        print('git_branch: %s' % git_branch)
+        print(f'git_branch: {git_branch}')
         return git_branch
     root = str(Path(dirname(dirname(__file__))).parent) + '/'
     if 'itch' in __file__.split(sep):
         root = str(Path(dirname(__file__))) + '/'
     if __file__ == '/app/bin/pmachines':  # flatpak
         root = '/app/bin/'
-    for branch in branches:
+    for __branch in branches:
         try:
-            print('try: %s' % root + 'assets/bld_version.txt')
-            with open(root + 'assets/bld_version.txt') as fver:
-                ver = fver.read()
-                print('ver: %s' % ver)
-            #if branch in ver:
+            print(f'try: {root}' + 'assets/bld_version.txt')
+            with open(root + 'assets/bld_version.txt', encoding='utf8') as fver:
+                __ver = fver.read()
+                print(f'ver: {__ver}')
+            # if __branch in __ver:
             b2c = {'master': 'a', 'rc': 'r', 'stable': '.'}
-            if ver[1] == b2c[branch]:
-                return branch
+            if __ver[1] == b2c[__branch]:
+                return __branch
         except FileNotFoundError:
-            print('file not found: %s' % root + 'assets/bld_version.txt')
+            print(f'file not found: {root}' + 'assets/bld_version.txt')
+    return ''
 
 
 def _commit():
     '''Returns the current branch.'''
     git_commit = exec_cmd('git rev-parse HEAD')[:7]
-    print('git_commit result: %s' % git_commit)
+    print(f'git_commit result: {git_commit}')
     if not git_commit.startswith("Can't r"):
         return git_commit
     root = str(Path(dirname(dirname(__file__))).parent) + '/'
@@ -58,13 +58,14 @@ def _commit():
     if __file__ == '/app/bin/pmachines':  # flatpak
         root = '/app/bin/'
     try:
-        print('try: %s' % root + 'assets/bld_version.txt')
-        with open(root + 'assets/bld_version.txt') as fver:
-            ver = fver.read()
-            print('ver: %s' % ver)
-            return ver.split('-')[1]
+        print(f'try: {root}' + 'assets/bld_version.txt')
+        with open(root + 'assets/bld_version.txt', encoding='utf8') as fver:
+            __ver = fver.read()
+            print(f'ver: {__ver}')
+            return __ver.split('-')[1]
     except FileNotFoundError:
-        print('file not found: %s' % root + 'assets/bld_version.txt')
+        print(f'file not found: {root}' + 'assets/bld_version.txt')
+    return ''
 
 
 def _version():
@@ -76,20 +77,20 @@ def _version():
     if _branch() == 'stable':
         pref, _ver = '', ''
         if exists(root + 'assets/version.txt'):
-            with open(root + 'assets/version.txt') as fver:
+            with open(root + 'assets/version.txt', encoding='utf8') as fver:
                 pref = fver.read().strip() + '-'  # + _branch() + '-'
                 _ver = fver.read().strip()
         ret_ver = _ver or ('0.' + day)
     else:
-        #try:  we want an error!
+        # try:  we want an error!
         pref = {'master': 'a', 'rc': 'rc', '': 'runtime'}[_branch()]
-        #except KeyError:
+        # except KeyError:
         #    pref = 'notfound'
-        ret_ver = '0%s%s' % (pref, day)
+        ret_ver = f'0{pref}{day}'
         pref = ret_ver
     bld_ver = pref + '-' + _commit()
     try:
-        with open(root + 'assets/bld_version.txt', 'w') as fver:
+        with open(root + 'assets/bld_version.txt', 'w', encoding='utf8') as fver:
             fver.write(bld_ver)
     except OSError:
         print("we can't write inside flatpaks, but we don't need it")
@@ -112,30 +113,30 @@ def __files_ext(fnames, _extensions):
 
 def __to_be_built_single(src, tgt):
     if getmtime(tgt) > getmtime(src):
-        print('%s is newer than %s: do not build' % (tgt, src))
+        print(f'{tgt} is newer than {src}: do not build')
         return False
-    with open(src, 'rb') as f:
-        src_content = f.read()
-    with open(tgt, 'rb') as f:
-        tgt_content = f.read()
+    with open(src, 'rb') as fsrc:
+        src_content = fsrc.read()
+    with open(tgt, 'rb') as ftgt:
+        tgt_content = ftgt.read()
     hash_src = md5(src_content).hexdigest()
     hash_tgt = md5(tgt_content).hexdigest()
     cache = {}
     lines = []
     if exists('hash_cache.txt'):
-        with open('hash_cache.txt') as f:
-            lines = f.readlines()
+        with open('hash_cache.txt', encoding='utf8') as fhash:
+            lines = fhash.readlines()
     for line in lines:
         line_spl = line.split()
-        hash = line_spl[-1]
+        _hash = line_spl[-1]
         fname = ' '.join(line_spl[:-1])
-        cache[fname] = hash
+        cache[fname] = _hash
     if src in cache and tgt in cache:
         if hash_src == cache[src] and \
            hash_tgt == cache[tgt]:
-            print('%s and %s are in the cache: do not build' % (tgt, src))
+            print(f'{tgt} and {src} are in the cache: do not build')
             return False
-    print('%s and %s are not up-to-date: building...' % (src, tgt))
+    print(f'{src} and {tgt} are not up-to-date: building...')
     return True
 
 
@@ -161,11 +162,11 @@ class InsideDir:
         chdir(self.old_dir)
 
 
-bld_dpath = 'build/'
-branch = _branch()
-ver = _version()
-win_fpath = '{dst_dir}{appname}-%s-windows.exe' % branch
-#osx_fpath = '{dst_dir}{appname}-%s-osx.zip' % branch
-#flatpak_fpath = '{dst_dir}{appname}-%s-flatpak' % branch
-appimage_fpath = '{dst_dir}{appname}-%s-appimage' % branch
-#docs_fpath = '{dst_dir}{appname}-%s-docs.tar.gz' % branch
+bld_dpath = 'build/'
+branch = _branch()
+ver = _version()
+# win_fpath = '{dst_dir}{appname}-' + f'{branch}-windows.exe'
+# osx_fpath = '{dst_dir}{appname}-%s-osx.zip' % branch
+# flatpak_fpath = '{dst_dir}{appname}-%s-flatpak' % branch
+# appimage_fpath = '{dst_dir}{appname}-' + f'{branch}-appimage'
+# docs_fpath = '{dst_dir}{appname}-%s-docs.tar.gz' % branch
index 839960a1be3922e8c74085e543850c8c5355cf46..42f2d46204f111bb5470419a0682472ef37e565c 100644 (file)
@@ -1,9 +1,7 @@
-from os.path import dirname
-from sys import executable
 from ya2.build.mtprocesser import ProcesserMgr
 
 
-#TODO refactor: make class ImagesBuilder
+# TODO refactor: make class ImagesBuilder
 
 
 def bld_images(files, cores):
@@ -14,5 +12,5 @@ def bld_images(files, cores):
 
 def __bld_img(fname_mp_mgr):
     fname, mp_mgr = fname_mp_mgr
-    curr_path = dirname(__file__)
+    curr_path = dirname(__file__)
     mp_mgr.add('convert "%s" "%s"' % (fname, fname[:-3] + 'dds'))
index 3d58730baaf7ff4f0d97ac31efe2d4f60bd15ad5..b18d19fc8976e7cb3830ab8b6eef3e6a5821a628 100644 (file)
@@ -38,7 +38,8 @@ class LanguageBuilder:
         lng_dir = lng_base_dir + lng + '/LC_MESSAGES/'
         if not exists('assets/locale/po/' + lng + '.po'):
             lines_to_fix = ['CHARSET/UTF-8', 'ENCODING/8bit']
-            [LanguageBuilder.__fix_line(line, lng_dir, appname) for line in lines_to_fix]
+            [LanguageBuilder.__fix_line(line, lng_dir, appname)
+             for line in lines_to_fix]
             copy(lng_dir + appname + '.pot', lng_dir + appname + '.po')
         return lng_dir
 
@@ -59,7 +60,8 @@ class LanguageBuilder:
         cmd = cmd.format(lng_dir=lng_dir, lng_code=lng_code, appname=appname,
                          tgt_path=tgt_path)
         system(cmd)
-        copy(lng_dir + appname + 'merge.po', 'assets/locale/po/%s.po' % lng_code)
+        copy(lng_dir + appname + 'merge.po',
+             'assets/locale/po/%s.po' % lng_code)
         poname_tmpl = '{lng_dir}{appname}merge.po'
         remove(poname_tmpl.format(lng_dir=lng_dir, appname=appname))
 
index bf9f473fbf334573445c707f74d5071cbae3082e..dd2ef8ba7040a2f0f4edda5f59a058f04103ef51 100644 (file)
@@ -1,12 +1,10 @@
 '''Provides tools for building models.'''
 from logging import info
 from os import system, walk, makedirs
-from os.path import exists, basename, dirname
+from os.path import exists, dirname
 from multiprocessing import Pool
-from glob import glob
 from hashlib import md5
-from shutil import copyfile, move, rmtree
-from ya2.build.mtprocesser import ProcesserMgr
+from shutil import copyfile, rmtree
 from ya2.build.build import to_be_built
 
 
@@ -20,15 +18,16 @@ class ModelsBuilder():
         ones.'''
         args = []
         for root, _, fnames in walk(blend_path):
-            for fname in [fname for fname in fnames if fname.endswith('.blend')]:
+            blends = [fname for fname in fnames if fname.endswith('.blend')]
+            for fname in blends:
                 if '/prototypes/' not in root:
                     args += [(root, fname)]
         with Pool() as p:
             p.starmap(self._export_blend, args)
         # caching is broken now: it is based on the previous
         # non-multiprocessing approach
-        # i should evaluate if fix it or just change the blend_path for building
-        # only specific models
+        # i should evaluate if fix it or just change the blend_path for
+        # building only specific models
         cache, lines = [], []
         if exists('hash_cache.txt'):
             with open('hash_cache.txt') as fhash:
@@ -57,7 +56,7 @@ class ModelsBuilder():
         new_dir = root.replace('assets/models/blend/', pgltf)
         rmtree(new_dir, ignore_errors=True)
         makedirs(new_dir)
-        #files_before = [basename(gname) for gname in glob('./*')]
+        # files_before = [basename(gname) for gname in glob('./*')]
         cmd = 'blender %s --background --python ya2/build/blend2gltf.py '
         cmd += '-- %s.gltf'
         cmd = cmd % (_fname, new_dir + '/' + fname[:-6])
@@ -67,15 +66,15 @@ class ModelsBuilder():
             return False
         system(cmd)
         self._cache_files += [_fname, gltf_name]
-        #files_after = [basename(gname) for gname in glob('./*')]
-        #new_files = [nnm for nnm in files_after if nnm not in files_before]
-        #new_dir = root.replace('assets/models/blend/', pgltf)
-        #rmtree(new_dir, ignore_errors=True)
-        #makedirs(new_dir)
-        #for mname in new_files:
-        #    new_name = '%s/%s' % (new_dir, mname)
-        #    move(mname, new_name)
-        #    info('move %s %s' % (mname, new_name))
+        # files_after = [basename(gname) for gname in glob('./*')]
+        # new_files = [nnm for nnm in files_after if nnm not in files_before]
+        # new_dir = root.replace('assets/models/blend/', pgltf)
+        # rmtree(new_dir, ignore_errors=True)
+        # makedirs(new_dir)
+        # for mname in new_files:
+        #     new_name = '%s/%s' % (new_dir, mname)
+        #     move(mname, new_name)
+        #     info('move %s %s' % (mname, new_name))
         # # blender rewrites metal files: let's restore them
         # metal_files = [fnm for fnm in glob(new_dir + '/*') if 'metal' in fnm]
         # for metal_file in metal_files:
@@ -112,7 +111,12 @@ class ModelsBuilder():
             makedirs(dirname(tgt), exist_ok=True)
             info('convert %s %s' % (dep, tgt))
             system('convert %s %s' % (dep, tgt))
-        rpl = lambda lin: lin.replace('.png', '.dds').replace('.jpg', '.dds').replace('/png', '/dds').replace('/jpg', '/dds')
+
+        def rpl(lin):
+            lin = lin.replace('.png', '.dds')
+            lin = lin.replace('.jpg', '.dds')
+            lin = lin.replace('/png', '/dds')
+            return lin.replace('/jpg', '/dds')
         with open(gltf_name, 'w') as fgltf:
             fgltf.write(''.join([rpl(line) for line in lines]))
         makedirs(dirname(bam_name), exist_ok=True)
index 964cc595e0e726bcbdcd3402d54e4b88e0d87a51..426e5885e88119e354a10e5ce28a3cd22fb346b2 100644 (file)
@@ -1,9 +1,5 @@
 from os import system
-from glob import glob
-from importlib import import_module
-from inspect import isclass
 from multiprocessing import Pool
-from logics.scene import Scene
 
 
 def do_screenshot(cls):
diff --git a/ya2/p3d/asserts.py b/ya2/p3d/asserts.py
new file mode 100644 (file)
index 0000000..27cf265
--- /dev/null
@@ -0,0 +1,69 @@
+from logging import info, debug
+import threading
+
+def assert_render3d():
+    preserve = ['camera']
+    for child in render.children:
+        if child.name not in preserve:
+            render.ls()
+            msg = 'unexpected render3d node: %s' % child.name
+            raise Exception(msg)
+
+def assert_aspect2d():
+    preserve = [
+        'a2dBackground', 'a2dTopCenter', 'a2dTopCenterNS',
+        'a2dBottomCenter', 'a2dBottomCenterNS', 'a2dLeftCenter',
+        'a2dLeftCenterNS', 'a2dRightCenter', 'a2dRightCenterNS',
+        'a2dTopLeft', 'a2dTopLeftNS', 'a2dTopRight',
+        'a2dTopRightNS', 'a2dBottomLeft', 'a2dBottomLeftNS',
+        'a2dBottomRight', 'a2dBottomRightNS', 'test_txt']
+    for child in aspect2d.children:
+        if child.name not in preserve and not child.has_python_tag('preserve'):
+            aspect2d.ls()
+            msg = 'unexpected aspect2d node: %s' % child.name
+            raise Exception(msg)
+
+def assert_render2d():
+    for child in render2d.children:
+        preserve = ['aspect2d', 'pixel2d', 'camera2d']
+        if child.name not in preserve and not child.has_python_tag('preserve'):
+            render2d.ls()
+            msg = 'unexpected render2d node: %s' % child.name
+            raise Exception(msg)
+
+def assert_events():
+    preserve = ['window-event', 'window-closed', 'async_loader_0',
+                'render-texture-targets-changed', 'aspectRatioChanged']
+    for evt in messenger.getEvents():
+        if evt not in preserve:
+            if (acc := messenger.who_accepts(evt)):
+                for key, _acc in acc.items():
+                    if _acc[2]:
+                        print('the event %s accepted by <%s, %s> is persistent' % (evt, key, _acc[0]))
+                    else:
+                        msg = 'unexpected event: %s, %s' % (evt, str(acc))
+                        raise Exception(msg)
+
+def assert_tasks():
+    preserve = [
+        'ivalLoop', 'garbageCollectStates', 'collisionLoop',
+        'igLoop', 'audioLoop', 'resetPrevTransform', 'dataLoop',
+        'eventManager', 'simplepbr update', 'on frame music',
+        'assert_fps']
+    for task in taskMgr.getTasks() + taskMgr.getDoLaters():
+        if task.name not in preserve and not hasattr(task, 'preserve'):
+            msg = 'unexpected task: %s' % task.name
+            raise Exception(msg)
+
+def assert_buffers():
+    pass
+    #for buffer in RenderToTexture.buffers:
+    #    raise Error()
+
+def assert_threads():
+    thr_names = [thread.name for thread in threading.enumerate()]
+    preserve = ['MainThread', 'rpc_server']
+    for thr_name in thr_names:
+        if thr_name not in preserve:
+            msg = 'unexpected thread: %s' % thr_name
+            raise Exception(msg)
index 56f6a8704b15c78eec2292c6a59d998693354daf..d0871ba0d7c5b9ac3d00fd805ae2c576bb5e9953 100755 (executable)
@@ -1,13 +1,8 @@
 import datetime
-from logging import debug, info
+from logging import info
 from os import getcwd
-from os.path import exists, dirname
-from panda3d.core import get_model_path, AntialiasAttrib, PandaNode, \
-    LightRampAttrib, Camera, OrthographicLens, NodePath, OmniBoundingVolume, \
-    AmbientLight as P3DAmbientLight, Spotlight as P3DSpotlight, Point2, \
-    Point3, Texture
-from direct.filter.CommonFilters import CommonFilters
-from direct.actor.Actor import Actor
+from panda3d.core import AntialiasAttrib, PandaNode, LightRampAttrib, \
+    NodePath, Point2, Point3, Texture
 # from ya2.lib.p3d.p3d import LibP3d
 
 
@@ -62,34 +57,34 @@ def set_srgb(model):
 
 class P3dGfxMgr:
 
-    def __init__(self, model_path, antialiasing, shaders, srgb):
-        self.root = P3dNode(render)
-        self.__srgb = srgb
-        self.callbacks = {}
-        self.filters = None
-        get_model_path().append_directory(model_path)
-        if LibP3d.runtime():
-            root_dir = LibP3d.p3dpath(dirname(__file__))
-            paths = [root_dir + '/' + model_path, root_dir]
-            list(map(get_model_path().append_directory, paths))
-        render.set_shader_auto()
-        # render.set_two_sided(True)  # it breaks shadows
-        if antialiasing: render.set_antialias(AntialiasAttrib.MAuto)
-        if shaders and base.win:
-            self.filters = CommonFilters(base.win, base.cam)
-
-    def load_model(self, filename, callback=None, anim=None):
-        ext = '.bam' if exists(filename + '.bam') else ''
-        if anim:
-            anim_dct = {'anim': filename + '-Anim' + ext}
-            node = P3dNode(self.set_srgb(Actor(filename + ext, anim_dct)))
-        elif callback:
-            callb = lambda model: callback(P3dNode(self.set_srgb(model)))
-            node = loader.loadModel(filename + ext, callback=callb)
-        else:
-            node = P3dNode(self.set_srgb(
-                loader.loadModel(LibP3d.p3dpath(filename + ext))))
-        return node
+    def __init__(self, model_path, antialiasing, shaders, srgb):
+        self.root = P3dNode(render)
+        self.__srgb = srgb
+        self.callbacks = {}
+        self.filters = None
+        get_model_path().append_directory(model_path)
+        if LibP3d.runtime():
+            root_dir = LibP3d.p3dpath(dirname(__file__))
+            paths = [root_dir + '/' + model_path, root_dir]
+            list(map(get_model_path().append_directory, paths))
+        render.set_shader_auto()
+        # render.set_two_sided(True)  # it breaks shadows
+        if antialiasing: render.set_antialias(AntialiasAttrib.MAuto)
+        if shaders and base.win:
+            self.filters = CommonFilters(base.win, base.cam)
+
+    def load_model(self, filename, callback=None, anim=None):
+        ext = '.bam' if exists(filename + '.bam') else ''
+        if anim:
+            anim_dct = {'anim': filename + '-Anim' + ext}
+            node = P3dNode(self.set_srgb(Actor(filename + ext, anim_dct)))
+        elif callback:
+            callb = lambda model: callback(P3dNode(self.set_srgb(model)))
+            node = loader.loadModel(filename + ext, callback=callb)
+        else:
+            node = P3dNode(self.set_srgb(
+                loader.loadModel(LibP3d.p3dpath(filename + ext))))
+        return node
 
     def set_srgb(self, model):
         if self.__srgb:
@@ -105,7 +100,8 @@ class P3dGfxMgr:
         aa_not_none = render.get_antialias() != AntialiasAttrib.MNone
         if render.has_antialias() and aa_not_none:
             render.clear_antialias()
-        else: render.set_antialias(AntialiasAttrib.MAuto, 1)
+        else:
+            render.set_antialias(AntialiasAttrib.MAuto, 1)
 
     def set_toon(self):
         tmp_node = NodePath(PandaNode('temp node'))
@@ -115,7 +111,8 @@ class P3dGfxMgr:
         self.filters.set_cartoon_ink(separation=1)
 
     def set_bloom(self):
-        if not base.win: return
+        if not base.win:
+            return
         self.filters.setBloom(
             blend=(.3, .4, .3, 0), mintrigger=.6, maxtrigger=1.0, desat=.6,
             intensity=1.0, size='medium')
@@ -123,10 +120,22 @@ class P3dGfxMgr:
 
     @staticmethod
     def pos2d(node):
-        p3d = base.cam.get_relative_point(node.node, Point3(0, 0, 0))
+        p3d = base.cam.get_relative_point(node, Point3(0, 0, 0))
         p2d = Point2()
         return p2d if base.camLens.project(p3d, p2d) else None
 
+    @staticmethod
+    def pos2d_p2d(node):
+        p3d = base.cam.get_relative_point(node, Point3(0, 0, 0))
+        p2d = Point2()
+        ret = p2d if base.camLens.project(p3d, p2d) else None
+        if ret:
+            n = NodePath('tmp')
+            rpos = (ret[0], 0, ret[1])
+            n.set_pos(rpos)
+            pos = n.get_pos(pixel2d)
+            return int(round(pos[0])), int(round(-pos[2]))
+
     @staticmethod
     def screen_coord(pos):
         new_node = NodePath('temp')
@@ -136,15 +145,18 @@ class P3dGfxMgr:
         coord2d = Point2()
         base.camLens.project(coord3d, coord2d)
         coord_r2d = Point3(coord2d[0], 0, coord2d[1])
-        coord_a2d = base.aspect2d.get_relative_point(render2d, coord_r2d)
+        coord_a2d = base.aspect2d.get_relative_point(
+            render2d, coord_r2d)
         return coord_a2d[0], coord_a2d[2]
 
     @staticmethod
     def world_from_to(pos):
         p_from, p_to = Point3(), Point3()    # in camera coordinates
         base.camLens.extrude(pos, p_from, p_to)
-        p_from = render.get_relative_point(base.cam, p_from)  # global coords
-        p_to = render.get_relative_point(base.cam, p_to)  # global coords
+        p_from = render.get_relative_point(
+            base.cam, p_from)  # global coords
+        p_to = render.get_relative_point(
+            base.cam, p_to)  # global coords
         return p_from, p_to
 
     @property
@@ -153,29 +165,34 @@ class P3dGfxMgr:
 
     def screenshot(self, path=None):
         time = datetime.datetime.now().strftime('%y%m%d%H%M%S')
-        #res = base.win.save_screenshot(Filename(path or ("yocto%s.png" % time)))
-        #debug('screenshot %s (%s)' % (path or ("yocto%s.png" % time), res))
-        res = base.screenshot(path or ("pmachines%s.png" % time), False)
-        info('screenshot %s (%s; %s)' % (path or ("pmachines%s.png" % time), res, getcwd()))
+        # res = base.win.save_screenshot(
+        #     Filename(path or ("yocto%s.png" % time)))
+        # debug('screenshot %s (%s)' % (path or ("yocto%s.png" % time), res))
+        res = base.screenshot(
+            path or ("pmachines%s.png" % time), False)
+        info('screenshot %s (%s; %s)' % (path or ("pmachines%s.png" % time),
+                                         res, getcwd()))
 
     @staticmethod
-    def enable_shader(): render.set_shader_auto()
+    def enable_shader():
+        render.set_shader_auto()
 
     @staticmethod
-    def disable_shader(): render.set_shader_off()
+    def disable_shader():
+        render.set_shader_off()
 
     @staticmethod
     def print_stats(two_d=True, three_d=True, analyze=True, ls=True):
         '''Print graphics stats. They use standard output (from p3d).'''
         info = []
         if two_d and analyze:
-            info +=[('render2d.analyze', base.render2d.analyze)]
+            info += [('render2d.analyze', base.render2d.analyze)]
         if three_d and analyze:
-            info +=[('render.analyze', base.render.analyze)]
+            info += [('render.analyze', base.render.analyze)]
         if two_d and ls:
-            info +=[('render2d.ls', base.render2d.ls)]
+            info += [('render2d.ls', base.render2d.ls)]
         if three_d and ls:
-            info +=[('render.ls', base.render.ls)]
+            info += [('render.ls', base.render.ls)]
         for elm in info:
             print('\n\n#####\n%s()' % elm[0])
             elm[1]()
@@ -292,7 +309,8 @@ class P3dGfxMgr:
 #         if model: return self.__get_pandanode(model)
 
 #     def optimize(self):
-#         self.node.prepare_scene(base.win.get_gsg())  # crash with texture.set_format
+#         # crash with texture.set_format
+#         self.node.prepare_scene(base.win.get_gsg())
 #         self.node.premunge_scene(base.win.get_gsg())
 
 #     def hide(self, mask=None):
index 46d2a21d8a3c15d76d465574ea8d52cd8fa483e6..70381abb8bf2615370a2850e02270d5dd31b0348 100755 (executable)
@@ -1,19 +1,6 @@
 from inspect import getmro
-from panda3d.core import TextNode, Texture
-from direct.gui.DirectGuiGlobals import FLAT, ENTER, EXIT, DISABLED, NORMAL, \
-    B1PRESS
-from direct.showbase.DirectObject import DirectObject
-from direct.gui.DirectButton import DirectButton
-from direct.gui.DirectCheckButton import DirectCheckButton
-from direct.gui.DirectOptionMenu import DirectOptionMenu
+from direct.gui.DirectGuiGlobals import ENTER, EXIT
 from direct.gui.OnscreenImage import OnscreenImage
-from direct.gui.DirectSlider import DirectSlider
-from direct.gui.DirectEntry import DirectEntry, ENTRY_FOCUS_STATE
-from direct.gui.DirectLabel import DirectLabel
-from direct.gui.DirectFrame import DirectFrame
-from direct.gui.OnscreenText import OnscreenText
-from direct.gui.DirectScrolledFrame import DirectScrolledFrame
-from ya2.patterns.observer import Subject
 # from ya2.lib.ivals import Seq, Wait, PosIval, Func
 
 
@@ -41,31 +28,33 @@ class CommonBase:
         clsname = self.__class__.__name__ + 'Widget'
         self.__class__ = type(clsname, (self.__class__, par_cls), {})
         self.init(self)
-        if not hasattr(self, 'bind'): return
+        if not hasattr(self, 'bind'):
+            return
         bind_args = [(ENTER, self.on_wdg_enter), (EXIT, self.on_wdg_exit)]
         list(map(lambda args: self.bind(*args), bind_args))
 
-    def set_enter_transition(self):
-        start_pos = self.get_pos()
-        pos = self.pos - (3.6, 0)
-        self.set_pos((pos.x, 1, pos.y))
-        Seq(
-            Wait(abs(pos.y - 1) / 4),
-            PosIval(self.get_np(), .5, start_pos)
-        ).start()
-
-    def set_exit_transition(self, destroy):
-        start_pos = self.get_pos()
-        end_pos = (self.pos.x + 3.6, 1, self.pos.y)
-        seq = Seq(
-            Wait(abs(self.pos.y - 1) / 4),
-            PosIval(self.get_np(), .5, end_pos),
-            Func(self.destroy if destroy else self.hide))
-        if not destroy: seq += Func(self.set_pos, start_pos)
-        seq.start()
+    def set_enter_transition(self):
+        start_pos = self.get_pos()
+        pos = self.pos - (3.6, 0)
+        self.set_pos((pos.x, 1, pos.y))
+        Seq(
+            Wait(abs(pos.y - 1) / 4),
+            PosIval(self.get_np(), .5, start_pos)
+        ).start()
+
+    def set_exit_transition(self, destroy):
+        start_pos = self.get_pos()
+        end_pos = (self.pos.x + 3.6, 1, self.pos.y)
+        seq = Seq(
+            Wait(abs(self.pos.y - 1) / 4),
+            PosIval(self.get_np(), .5, end_pos),
+            Func(self.destroy if destroy else self.hide))
+        if not destroy: seq += Func(self.set_pos, start_pos)
+        seq.start()
 
     def translate(self):
-        if hasattr(self, 'bind_transl'): self.wdg['text'] = self.bind_transl
+        if hasattr(self, 'bind_transl'):
+            self.wdg['text'] = self.bind_transl
 
 
 class P3dImg(CommonBase):
@@