# on crash, click Settings->Restart system (due to an issue with pygame-ce + pycafe)
# from https://github.com/tank-king/BreakPong
# https://tank-king.itch.io/breakpong runs faster, but has a smaller screen
import solara
import numpy as np
import pygame
import asyncio
import time
data = solara.reactive(None)
import config
width = config.screen_width
height = config.screen_height
player_pos = pygame.Vector2(width / 4, height / 2)
screen = None
canvas = None
if not hasattr(pygame, "events"):
pygame.events = []
def addEventListener(name, callback, flags):
pygame.events.append((name, callback))
print(name, callback)
### BOILERPLATE START ###
def setup():
global screen, canvas
import js
canvas = js.OffscreenCanvas.new(width, height)
canvas.id = "canvas" # emscripten wants this?
canvas.style = js.Object.new() # otherwise we'll get a style.cursor error
js.pyodide.canvas.setCanvas2D(canvas)
# emscripten assumes globalThis.screen to exist
js.screen = {
"width": width,
"height": height,
}
pygame.display.init()
# we define a mock globalThis.window, and to
# avoid 'eventHandler.target.addEventListener is not a function'
# we put in this mock addEventListener
print("adding event hander")
js.window.addEventListener = addEventListener#js.Function.new()
# monkey patch document
js.document = js.Object.new()
js.document.querySelector = lambda x: js.window
js.document.fullscreenEnabled = False
js.document.addEventListener = js.Function.new()
screen = pygame.display.set_mode((width, height))
# pygame.mixer.init()
def transfer_image():
# read off a pixel from the canvas
context = canvas.getContext("2d")
# can possibly be faster?
mem_obj = context.getImageData(0, 0, width, height).data.to_py()
np_array = np.frombuffer(mem_obj, dtype=np.uint8)
np_array.shape = (height, width, 4)
data.value = np_array
# ideally, all the above boilerplate goes away
### BOILERPLATE END ###
print("setup")
setup()
import sys
import pygame
from menu import *
from game import Game
from config import *
from utils import *
pygame.init()
screen = pygame.display.set_mode((screen_width, screen_height))#, pygame.RESIZABLE | pygame.SCALED)
pygame.display.set_caption('BreakPong')
clock = pygame.time.Clock()
mode = 'home'
first_time_playing = True
async def main_game():
global mode, first_time_playing
g = Game()
game_won = GameWonMenu()
game_lost = GameLostMenu()
home = Home()
guide = Guide()
frame = 0
t1 = time.time()
while True:
events = pygame.event.get()
print(events)
for e in events:
if e.type == pygame.QUIT:
sys.exit(0)
screen.fill((0, 0, 10))
if mode == 'game':
result = g.update(events)
g.draw(screen)
if result is not None:
if result:
mode = 'gamewon'
SoundManager.play('win')
else:
mode = 'gamelost'
SoundManager.play('game_over')
elif mode == 'gamewon':
result = game_won.update(events)
game_won.draw(screen)
if result is not None:
if result == 'Home':
mode = 'home'
else:
sys.exit(0)
elif mode == 'gamelost':
result = game_lost.update(events)
game_lost.draw(screen)
if result is not None:
if result == 'Retry':
g = Game()
mode = 'game'
elif result == 'Home':
mode = 'home'
else:
sys.exit(0)
elif mode == 'home':
result = home.update(events)
home.draw(screen)
if result is not None:
if result == 'Play':
if first_time_playing:
mode = 'guide'
else:
g = Game()
mode = 'game'
elif result == 'Help':
guide = Guide()
mode = 'guide'
first_time_playing = False
else:
sys.exit(0)
elif mode == 'guide':
result = guide.update(events)
guide.draw(screen)
if result is not None:
if result:
if first_time_playing:
mode = 'game'
first_time_playing = False
else:
mode = 'home'
# screen.blit(text(str(clock.get_fps().__int__()), 50, 'white'), (0, 0))
pygame.display.update()
transfer_image()
clock.tick(FPS)
await asyncio.sleep(0)
frame += 1
max_frames = 20
if (frame % max_frames) == 0:
t2 = time.time()
fps = max_frames/(t2-t1)
print("fps", fps)
t1 = t2
# print("go")
# main_game()
reset_counter = solara.reactive(0)
def reset():
reset_counter.value += 1
import reacton
import ipyvue
from typing import cast
from reacton.core import get_render_context
import js
def use_events(
el, events
):
# to avoid add_event_handler having a stale reference to callback
events_ref = reacton.use_ref(events)
events_ref.current = events
def add_event_handler():
vue_widget = cast(ipyvue.VueWidget, reacton.core.get_widget(el))
# we are basically copying the logic from
# reacton.core._event_handler_exception_wrapper
rc = get_render_context()
context = rc.context
assert context is not None
def handler(widget, event_name, data):
try:
# print(event_name, data)
for name, callback in events_ref.current:
# create a fake event object
data_js = js.Object.new()
for key, value in data.items():
setattr(data_js, key, value)
data_js.preventDefault = js.Function.new()
if name == event_name:
callback(data_js)
except Exception as e:
assert context is not None
# because widgets don't have a context, but are a child of a component
# we add it to exceptions_children, not exception_self
# this allows a component to catch the exception of a direct child
context.exceptions_children.append(e)
rc.force_update()
eventnames = set([name for name, callback in events])
print(eventnames)
for name in eventnames:
print("add", name)
vue_widget.on_event(name, handler)
def cleanup():
for name in eventnames:
vue_widget.on_event(name, handler, remove=True)
return cleanup
reacton.use_effect(add_event_handler, [events])
@solara.component
def Page():
solara.Button(label=f"Restart", on_click=reset, outlined=True, color="primary")
with solara.Div(attributes={'tabIndex': 0}) as div:
solara.lab.use_task(main_game, dependencies=[reset_counter.value])
if data.value is not None:
solara.Image(data.value)
# print(pygame.events)
use_events(div, pygame.events)