Py.Cafe

maartenbreddels/

breakpong-mod-by-rosa-and-jara

Pong Game - modified by Rosa and Jara

DocsPricing
  • app.py
  • requirements.txt
app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# 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)