diff --git a/core/src/core.js b/core/src/core.js index 3484ef72cee..e0b0f03eadf 100644 --- a/core/src/core.js +++ b/core/src/core.js @@ -223,6 +223,13 @@ for (const [TYPE, interpreter] of TYPES) { else element.after(show); } if (!show.id) show.id = getID(); + if (TYPE === "py") { + const canvas2D = element.getAttribute("canvas2d") || element.getAttribute("canvas"); + if (canvas2D) { + const canvas = queryTarget(document, canvas2D); + wrap.interpreter.canvas.setCanvas2D(canvas); + } + } // allows the code to retrieve the target element via // document.currentScript.target if needed diff --git a/core/src/hooks.js b/core/src/hooks.js index acad61b1d50..dbd8ca6323c 100644 --- a/core/src/hooks.js +++ b/core/src/hooks.js @@ -84,7 +84,19 @@ export const hooks = { }, worker: { /** @type {Set} */ - onReady: new SetFunction(), + onReady: new SetFunction([ + (wrap, xworker) => { + if (wrap.type === "py") { + const { interpreter } = wrap; + const element = wrap.run('from polyscript import currentScript;currentScript'); + const canvas2D = element.getAttribute("canvas2d") || element.getAttribute("canvas"); + if (canvas2D) { + const canvas = element.ownerDocument.getElementById(canvas2D); + interpreter.canvas.setCanvas2D(canvas); + } + } + } + ]), /** @type {Set} */ onBeforeRun: new SetFunction(), /** @type {Set} */ diff --git a/core/src/stdlib/pyscript.js b/core/src/stdlib/pyscript.js index 9c4550cef8d..6216cf65149 100644 --- a/core/src/stdlib/pyscript.js +++ b/core/src/stdlib/pyscript.js @@ -7,7 +7,7 @@ export default { "fetch.py": "import json,js\nfrom pyscript.util import as_bytearray\nclass _Response:\n\tdef __init__(A,response):A._response=response\n\tdef __getattr__(A,attr):return getattr(A._response,attr)\n\tasync def arrayBuffer(B):\n\t\tA=await B._response.arrayBuffer()\n\t\tif hasattr(A,'to_py'):return A.to_py()\n\t\treturn memoryview(as_bytearray(A))\n\tasync def blob(A):return await A._response.blob()\n\tasync def bytearray(A):B=await A._response.arrayBuffer();return as_bytearray(B)\n\tasync def json(A):return json.loads(await A.text())\n\tasync def text(A):return await A._response.text()\nclass _DirectResponse:\n\t@staticmethod\n\tdef setup(promise,response):A=promise;A._response=_Response(response);return A._response\n\tdef __init__(B,promise):A=promise;B._promise=A;A._response=None;A.arrayBuffer=B.arrayBuffer;A.blob=B.blob;A.bytearray=B.bytearray;A.json=B.json;A.text=B.text\n\tasync def _response(A):\n\t\tif not A._promise._response:await A._promise\n\t\treturn A._promise._response\n\tasync def arrayBuffer(A):B=await A._response();return await B.arrayBuffer()\n\tasync def blob(A):B=await A._response();return await B.blob()\n\tasync def bytearray(A):B=await A._response();return await B.bytearray()\n\tasync def json(A):B=await A._response();return await B.json()\n\tasync def text(A):B=await A._response();return await B.text()\ndef fetch(url,**B):C=js.JSON.parse(json.dumps(B));D=lambda response,*B:_DirectResponse.setup(A,response);A=js.fetch(url,C).then(D);_DirectResponse(A);return A", "ffi.py": "try:\n\timport js;from pyodide.ffi import create_proxy as _cp,to_js as _py_tjs;from_entries=js.Object.fromEntries\n\tdef _tjs(value,**A):\n\t\tB='dict_converter'\n\t\tif not hasattr(A,B):A[B]=from_entries\n\t\treturn _py_tjs(value,**A)\nexcept:from jsffi import create_proxy as _cp;from jsffi import to_js as _tjs\ncreate_proxy=_cp\nto_js=_tjs", "flatted.py": "import json as _json\nclass _Known:\n\tdef __init__(A):A.key=[];A.value=[]\nclass _String:\n\tdef __init__(A,value):A.value=value\ndef _array_keys(value):\n\tA=[];B=0\n\tfor C in value:A.append(B);B+=1\n\treturn A\ndef _object_keys(value):\n\tA=[]\n\tfor B in value:A.append(B)\n\treturn A\ndef _is_array(value):A=value;return isinstance(A,list)or isinstance(A,tuple)\ndef _is_object(value):return isinstance(value,dict)\ndef _is_string(value):return isinstance(value,str)\ndef _index(known,input,value):B=value;A=known;input.append(B);C=str(len(input)-1);A.key.append(B);A.value.append(C);return C\ndef _loop(keys,input,known,output):\n\tA=output\n\tfor B in keys:\n\t\tC=A[B]\n\t\tif isinstance(C,_String):_ref(B,input[int(C.value)],input,known,A)\n\treturn A\ndef _ref(key,value,input,known,output):\n\tB=known;A=value\n\tif _is_array(A)and not A in B:B.append(A);A=_loop(_array_keys(A),input,B,A)\n\telif _is_object(A)and not A in B:B.append(A);A=_loop(_object_keys(A),input,B,A)\n\toutput[key]=A\ndef _relate(known,input,value):\n\tB=known;A=value\n\tif _is_string(A)or _is_array(A)or _is_object(A):\n\t\ttry:return B.value[B.key.index(A)]\n\t\texcept:return _index(B,input,A)\n\treturn A\ndef _transform(known,input,value):\n\tB=known;A=value\n\tif _is_array(A):\n\t\tC=[]\n\t\tfor F in A:C.append(_relate(B,input,F))\n\t\treturn C\n\tif _is_object(A):\n\t\tD={}\n\t\tfor E in A:D[E]=_relate(B,input,A[E])\n\t\treturn D\n\treturn A\ndef _wrap(value):\n\tA=value\n\tif _is_string(A):return _String(A)\n\tif _is_array(A):\n\t\tB=0\n\t\tfor D in A:A[B]=_wrap(D);B+=1\n\telif _is_object(A):\n\t\tfor C in A:A[C]=_wrap(A[C])\n\treturn A\ndef parse(value,*C,**D):\n\tA=value;E=_json.loads(A,*C,**D);B=[]\n\tfor A in E:B.append(_wrap(A))\n\tinput=[]\n\tfor A in B:\n\t\tif isinstance(A,_String):input.append(A.value)\n\t\telse:input.append(A)\n\tA=input[0]\n\tif _is_array(A):return _loop(_array_keys(A),input,[A],A)\n\tif _is_object(A):return _loop(_object_keys(A),input,[A],A)\n\treturn A\ndef stringify(value,*D,**E):\n\tB=_Known();input=[];C=[];A=int(_index(B,input,value))\n\twhile A Promise.all(urls.map((url) => import(url)))')()\n\texcept:message='Unable to use `window` or `document` -> https://docs.pyscript.net/latest/faq/#sharedarraybuffer';globalThis.console.warn(message);window=NotSupported('pyscript.window',message);document=NotSupported('pyscript.document',message);js_import=None\n\tsync=polyscript.xworker.sync\n\tdef current_target():return polyscript.target\nelse:\n\timport _pyscript;from _pyscript import PyWorker,js_import;window=globalThis;document=globalThis.document;sync=NotSupported('pyscript.sync','pyscript.sync works only when running in a worker')\n\tdef current_target():return _pyscript.target", + "magic_js.py": "import json,sys,js as globalThis\nfrom polyscript import config as _config,js_modules\nfrom pyscript.util import NotSupported\nRUNNING_IN_WORKER=not hasattr(globalThis,'document')\nconfig=json.loads(globalThis.JSON.stringify(_config))\nif'MicroPython'in sys.version:config['type']='mpy'\nelse:config['type']='py'\nclass JSModule:\n\tdef __init__(A,name):A.name=name\n\tdef __getattr__(B,field):\n\t\tA=field\n\t\tif not A.startswith('_'):return getattr(getattr(js_modules,B.name),A)\nfor name in globalThis.Reflect.ownKeys(js_modules):sys.modules[f\"pyscript.js_modules.{name}\"]=JSModule(name)\nsys.modules['pyscript.js_modules']=js_modules\nif RUNNING_IN_WORKER:\n\timport polyscript;PyWorker=NotSupported('pyscript.PyWorker','pyscript.PyWorker works only when running in the main thread')\n\ttry:import js;window=polyscript.xworker.window;document=window.document;js.screen=window.screen;js.document=document;js_import=window.Function('return (...urls) => Promise.all(urls.map((url) => import(url)))')()\n\texcept:message='Unable to use `window` or `document` -> https://docs.pyscript.net/latest/faq/#sharedarraybuffer';globalThis.console.warn(message);window=NotSupported('pyscript.window',message);document=NotSupported('pyscript.document',message);js_import=None\n\tsync=polyscript.xworker.sync\n\tdef current_target():return polyscript.target\nelse:\n\timport _pyscript;from _pyscript import PyWorker,js_import;window=globalThis;document=globalThis.document;sync=NotSupported('pyscript.sync','pyscript.sync works only when running in a worker')\n\tdef current_target():return _pyscript.target", "media.py": "from pyscript import window\nfrom pyscript.ffi import to_js\nclass Device:\n\tdef __init__(A,device):A._dom_element=device\n\t@property\n\tdef id(self):return self._dom_element.deviceId\n\t@property\n\tdef group(self):return self._dom_element.groupId\n\t@property\n\tdef kind(self):return self._dom_element.kind\n\t@property\n\tdef label(self):return self._dom_element.label\n\tdef __getitem__(A,key):return getattr(A,key)\n\t@classmethod\n\tasync def load(E,audio=False,video=True):\n\t\tB=video;A=window.Object.new();A.audio=audio\n\t\tif isinstance(B,bool):A.video=B\n\t\telse:\n\t\t\tA.video=window.Object.new()\n\t\t\tfor C in B:setattr(A.video,C,to_js(B[C]))\n\t\tD=await window.navigator.mediaDevices.getUserMedia(A);return D\n\tasync def get_stream(A):B=A.kind.replace('input','').replace('output','');C={B:{'deviceId':{'exact':A.id}}};return await A.load(**C)\nasync def list_devices():return[Device(A)for A in await window.navigator.mediaDevices.enumerateDevices()]", "storage.py": "_C='memoryview'\n_B='bytearray'\n_A='generic'\nfrom polyscript import storage as _storage\nfrom pyscript.flatted import parse as _parse\nfrom pyscript.flatted import stringify as _stringify\ndef _to_idb(value):\n\tA=value\n\tif A is None:return _stringify(['null',0])\n\tif isinstance(A,(bool,float,int,str,list,dict,tuple)):return _stringify([_A,A])\n\tif isinstance(A,bytearray):return _stringify([_B,[A for A in A]])\n\tif isinstance(A,memoryview):return _stringify([_C,[A for A in A]])\n\traise TypeError(f\"Unexpected value: {A}\")\ndef _from_idb(value):\n\tC=value;A,B=_parse(C)\n\tif A=='null':return\n\tif A==_A:return B\n\tif A==_B:return bytearray(B)\n\tif A==_C:return memoryview(bytearray(B))\n\treturn C\nclass Storage(dict):\n\tdef __init__(B,store):A=store;super().__init__({A:_from_idb(B)for(A,B)in A.entries()});B.__store__=A\n\tdef __delitem__(A,attr):A.__store__.delete(attr);super().__delitem__(attr)\n\tdef __setitem__(B,attr,value):A=value;B.__store__.set(attr,_to_idb(A));super().__setitem__(attr,A)\n\tdef clear(A):A.__store__.clear();super().clear()\n\tasync def sync(A):await A.__store__.sync()\nasync def storage(name='',storage_class=Storage):\n\tif not name:raise ValueError('The storage name must be defined')\n\treturn storage_class(await _storage(f\"@pyscript/{name}\"))", "util.py": "import js,sys,inspect\ndef as_bytearray(buffer):\n\tA=js.Uint8Array.new(buffer);B=A.length;C=bytearray(B)\n\tfor D in range(0,B):C[D]=A[D]\n\treturn C\nclass NotSupported:\n\tdef __init__(A,name,error):object.__setattr__(A,'name',name);object.__setattr__(A,'error',error)\n\tdef __repr__(A):return f\"\"\n\tdef __getattr__(A,attr):raise AttributeError(A.error)\n\tdef __setattr__(A,attr,value):raise AttributeError(A.error)\n\tdef __call__(A,*B):raise TypeError(A.error)\ndef is_awaitable(obj):\n\tA=obj;from pyscript import config as B\n\tif B['type']=='mpy':\n\t\tif''in repr(A):return True\n\t\treturn inspect.isgeneratorfunction(A)\n\treturn inspect.iscoroutinefunction(A)", diff --git a/core/src/stdlib/pyscript/magic_js.py b/core/src/stdlib/pyscript/magic_js.py index 08e33ea7bc6..7cc6655cdf6 100644 --- a/core/src/stdlib/pyscript/magic_js.py +++ b/core/src/stdlib/pyscript/magic_js.py @@ -45,6 +45,8 @@ def __getattr__(self, field): window = polyscript.xworker.window document = window.document + # weird + not worth it as it does not work anyway + js.screen = window.screen js.document = document # this is the same as js_import on main and it lands modules on main js_import = window.Function( diff --git a/core/tests/index.html b/core/tests/index.html index 128bce2aa3f..ebbba369f5a 100644 --- a/core/tests/index.html +++ b/core/tests/index.html @@ -14,5 +14,5 @@ a:hover { opacity: 1; } - + diff --git a/core/tests/manual/game/aliens.css b/core/tests/manual/game/aliens.css new file mode 100644 index 00000000000..f7a2fc48246 --- /dev/null +++ b/core/tests/manual/game/aliens.css @@ -0,0 +1,30 @@ +/* (c) https://github.com/ryanking13/pyodide-pygame-demo/blob/main/examples/aliens.html */ +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + margin: 0; + padding: 20px; + background-color: #f4f4f4; + color: #333; +} +.demo { + background-color: #fff; + margin: 20px auto; + max-width: 1000px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + border-radius: 8px; + overflow: hidden; +} +.demo-header { + background-color: #007bff; + color: #fff; + padding: 15px 20px; + font-size: 20px; +} +.demo-content { + padding: 20px; +} + +#canvas { + margin: 0 auto; + display: block; +} diff --git a/core/tests/manual/game/aliens.py b/core/tests/manual/game/aliens.py new file mode 100644 index 00000000000..cae347d1cac --- /dev/null +++ b/core/tests/manual/game/aliens.py @@ -0,0 +1,394 @@ +""" (c) https://github.com/ryanking13/pyodide-pygame-demo/blob/main/examples/aliens.html +pygame.examples.aliens + +Shows a mini game where you have to defend against aliens. + +What does it show you about pygame? + +* pygame.sprite, the difference between Sprite and Group. +* dirty rectangle optimization for processing for speed. +* music with pygame.mixer.music, including fadeout +* sound effects with pygame.Sound +* event processing, keyboard handling, QUIT handling. +* a main loop frame limited with a game clock from the pygame.time module +* fullscreen switching. + + +Controls +-------- + +* Left and right arrows to move. +* Space bar to shoot. +* f key to toggle between fullscreen. + +""" +import asyncio +import random +import os +import pathlib + +# import basic pygame modules +import pygame + +# see if we can load more than standard BMP +if not pygame.image.get_extended(): + raise SystemExit("Sorry, extended image module required") + + +# game constants +MAX_SHOTS = 2 # most player bullets onscreen +ALIEN_ODDS = 22 # chances a new alien appears +BOMB_ODDS = 60 # chances a new bomb will drop +ALIEN_RELOAD = 12 # frames between new aliens +SCREENRECT = pygame.Rect(0, 0, 640, 480) +SCORE = 0 + + +main_dir = str(pathlib.Path(pygame.__file__).parent / "examples") + +def load_image(file): + """loads an image, prepares it for play""" + file = os.path.join(main_dir, "data", file) + try: + surface = pygame.image.load(file) + except pygame.error: + raise SystemExit(f'Could not load image "{file}" {pygame.get_error()}') + return surface.convert() + + +def load_sound(file): + """because pygame can be be compiled without mixer.""" + if not pygame.mixer: + return None + file = os.path.join(main_dir, "data", file) + try: + sound = pygame.mixer.Sound(file) + return sound + except pygame.error: + print(f"Warning, unable to load, {file}") + return None + + +# Each type of game object gets an init and an update function. +# The update function is called once per frame, and it is when each object should +# change its current position and state. +# +# The Player object actually gets a "move" function instead of update, +# since it is passed extra information about the keyboard. + + +class Player(pygame.sprite.Sprite): + """Representing the player as a moon buggy type car.""" + + speed = 10 + bounce = 24 + gun_offset = -11 + images = [] + + def __init__(self): + pygame.sprite.Sprite.__init__(self, self.containers) + self.image = self.images[0] + self.rect = self.image.get_rect(midbottom=SCREENRECT.midbottom) + self.reloading = False + self.origtop = self.rect.top + self.facing = -1 + + def move(self, direction): + if direction: + self.facing = direction + self.rect.move_ip(direction * self.speed, 0) + self.rect = self.rect.clamp(SCREENRECT) + if direction < 0: + self.image = self.images[0] + elif direction > 0: + self.image = self.images[1] + self.rect.top = self.origtop - (self.rect.left // self.bounce % 2) + + def gunpos(self): + pos = self.facing * self.gun_offset + self.rect.centerx + return pos, self.rect.top + + +class Alien(pygame.sprite.Sprite): + """An alien space ship. That slowly moves down the screen.""" + + speed = 13 + animcycle = 12 + images = [] + + def __init__(self): + pygame.sprite.Sprite.__init__(self, self.containers) + self.image = self.images[0] + self.rect = self.image.get_rect() + self.facing = random.choice((-1, 1)) * Alien.speed + self.frame = 0 + if self.facing < 0: + self.rect.right = SCREENRECT.right + + def update(self): + self.rect.move_ip(self.facing, 0) + if not SCREENRECT.contains(self.rect): + self.facing = -self.facing + self.rect.top = self.rect.bottom + 1 + self.rect = self.rect.clamp(SCREENRECT) + self.frame = self.frame + 1 + self.image = self.images[self.frame // self.animcycle % 3] + + +class Explosion(pygame.sprite.Sprite): + """An explosion. Hopefully the Alien and not the player!""" + + defaultlife = 12 + animcycle = 3 + images = [] + + def __init__(self, actor): + pygame.sprite.Sprite.__init__(self, self.containers) + self.image = self.images[0] + self.rect = self.image.get_rect(center=actor.rect.center) + self.life = self.defaultlife + + def update(self): + """called every time around the game loop. + + Show the explosion surface for 'defaultlife'. + Every game tick(update), we decrease the 'life'. + + Also we animate the explosion. + """ + self.life = self.life - 1 + self.image = self.images[self.life // self.animcycle % 2] + if self.life <= 0: + self.kill() + + +class Shot(pygame.sprite.Sprite): + """a bullet the Player sprite fires.""" + + speed = -11 + images = [] + + def __init__(self, pos): + pygame.sprite.Sprite.__init__(self, self.containers) + self.image = self.images[0] + self.rect = self.image.get_rect(midbottom=pos) + + def update(self): + """called every time around the game loop. + + Every tick we move the shot upwards. + """ + self.rect.move_ip(0, self.speed) + if self.rect.top <= 0: + self.kill() + + +class Bomb(pygame.sprite.Sprite): + """A bomb the aliens drop.""" + + speed = 9 + images = [] + + def __init__(self, alien): + pygame.sprite.Sprite.__init__(self, self.containers) + self.image = self.images[0] + self.rect = self.image.get_rect(midbottom=alien.rect.move(0, 5).midbottom) + + def update(self): + """called every time around the game loop. + + Every frame we move the sprite 'rect' down. + When it reaches the bottom we: + + - make an explosion. + - remove the Bomb. + """ + self.rect.move_ip(0, self.speed) + if self.rect.bottom >= 470: + Explosion(self) + self.kill() + + +class Score(pygame.sprite.Sprite): + """to keep track of the score.""" + + def __init__(self): + pygame.sprite.Sprite.__init__(self) + self.font = pygame.Font(None, 20) + self.font.set_italic(1) + self.color = "white" + self.lastscore = -1 + self.update() + self.rect = self.image.get_rect().move(10, 450) + + def update(self): + """We only update the score in update() when it has changed.""" + if SCORE != self.lastscore: + self.lastscore = SCORE + msg = "Score: %d" % SCORE + self.image = self.font.render(msg, 0, self.color) + + +async def main(winstyle=0): + # Initialize pygame + pygame.mixer.pre_init(44100, 32, 2, 1024) + pygame.init() + if pygame.mixer and not pygame.mixer.get_init(): + print("Warning, no sound") + pygame.mixer = None + + fullscreen = False + # Set the display mode + winstyle = 0 # |FULLSCREEN + screen = pygame.display.set_mode(SCREENRECT.size, winstyle) + + # Load images, assign to sprite classes + # (do this before the classes are used, after screen setup) + img = load_image("player1.gif") + Player.images = [img, pygame.transform.flip(img, 1, 0)] + img = load_image("explosion1.gif") + Explosion.images = [img, pygame.transform.flip(img, 1, 1)] + Alien.images = [load_image(im) for im in ("alien1.gif", "alien2.gif", "alien3.gif")] + Bomb.images = [load_image("bomb.gif")] + Shot.images = [load_image("shot.gif")] + + # decorate the game window + icon = pygame.transform.scale(Alien.images[0], (32, 32)) + pygame.display.set_icon(icon) + pygame.display.set_caption("Pygame Aliens") + pygame.mouse.set_visible(0) + + # create the background, tile the bgd image + bgdtile = load_image("background.gif") + background = pygame.Surface(SCREENRECT.size) + for x in range(0, SCREENRECT.width, bgdtile.get_width()): + background.blit(bgdtile, (x, 0)) + screen.blit(background, (0, 0)) + pygame.display.flip() + + # load the sound effects + boom_sound = load_sound("boom.wav") + shoot_sound = load_sound("car_door.wav") + if pygame.mixer: + music = os.path.join(main_dir, "data", "house_lo.wav") + pygame.mixer.music.load(music) + pygame.mixer.music.play(-1) + + # Initialize Game Groups + aliens = pygame.sprite.Group() + shots = pygame.sprite.Group() + bombs = pygame.sprite.Group() + all = pygame.sprite.RenderUpdates() + lastalien = pygame.sprite.GroupSingle() + + # assign default groups to each sprite class + Player.containers = all + Alien.containers = aliens, all, lastalien + Shot.containers = shots, all + Bomb.containers = bombs, all + Explosion.containers = all + Score.containers = all + + # Create Some Starting Values + global score + alienreload = ALIEN_RELOAD + clock = pygame.Clock() + + # initialize our starting sprites + global SCORE + player = Player() + Alien() # note, this 'lives' because it goes into a sprite group + if pygame.font: + all.add(Score()) + + # Run our main loop whilst the player is alive. + while player.alive(): + # get input + for event in pygame.event.get(): + if event.type == pygame.QUIT: + return + if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: + return + elif event.type == pygame.KEYDOWN: + if event.key == pygame.K_f: + if not fullscreen: + print("Changing to FULLSCREEN") + screen_backup = screen.copy() + screen = pygame.display.set_mode( + SCREENRECT.size, winstyle | pygame.FULLSCREEN, bestdepth + ) + screen.blit(screen_backup, (0, 0)) + else: + print("Changing to windowed mode") + screen_backup = screen.copy() + screen = pygame.display.set_mode( + SCREENRECT.size, winstyle, bestdepth + ) + screen.blit(screen_backup, (0, 0)) + pygame.display.flip() + fullscreen = not fullscreen + + keystate = pygame.key.get_pressed() + + # clear/erase the last drawn sprites + all.clear(screen, background) + + # update all the sprites + all.update() + + # handle player input + direction = keystate[pygame.K_RIGHT] - keystate[pygame.K_LEFT] + player.move(direction) + firing = keystate[pygame.K_SPACE] + if not player.reloading and firing and len(shots) < MAX_SHOTS: + Shot(player.gunpos()) + if pygame.mixer: + shoot_sound.play() + player.reloading = firing + + # Create new alien + if alienreload: + alienreload = alienreload - 1 + elif not int(random.random() * ALIEN_ODDS): + Alien() + alienreload = ALIEN_RELOAD + + # Drop bombs + if lastalien and not int(random.random() * BOMB_ODDS): + Bomb(lastalien.sprite) + + # Detect collisions between aliens and players. + for alien in pygame.sprite.spritecollide(player, aliens, 1): + if pygame.mixer: + boom_sound.play() + Explosion(alien) + Explosion(player) + SCORE = SCORE + 1 + player.kill() + + # See if shots hit the aliens. + for alien in pygame.sprite.groupcollide(aliens, shots, 1, 1).keys(): + if pygame.mixer: + boom_sound.play() + Explosion(alien) + SCORE = SCORE + 1 + + # See if alien bombs hit the player. + for bomb in pygame.sprite.spritecollide(player, bombs, 1): + if pygame.mixer: + boom_sound.play() + Explosion(player) + Explosion(bomb) + player.kill() + + # draw the scene + dirty = all.draw(screen) + pygame.display.update(dirty) + + # cap the framerate at 40fps. Also called 40HZ or 40 times per second. + await asyncio.sleep(0.025) + + if pygame.mixer: + pygame.mixer.music.fadeout(1000) + +main() diff --git a/core/tests/manual/game/index.html b/core/tests/manual/game/index.html new file mode 100644 index 00000000000..a0422a71d3d --- /dev/null +++ b/core/tests/manual/game/index.html @@ -0,0 +1,24 @@ + + + + + + + + + + + +
+
pygame.examples.aliens
+
+ +
+
+ +