diff --git a/README.md b/README.md index e13cd11..262c442 100644 --- a/README.md +++ b/README.md @@ -2912,38 +2912,45 @@ Pygame ### Example -#### Runs a simple Mario game: -```python -import collections, dataclasses, enum, io, itertools, math, pygame, random, urllib -from urllib.request import urlopen - -D = enum.Enum('D', 'n e s w') -P = collections.namedtuple('P', 'x y') -Mario = dataclasses.make_dataclass('Mario', ['rect', 'spd', 'facing_left', 'running_cycle']) -RECT_SIDE, SCR_SIDE, MAX_SPEED = 16, 25, P(x=5, y=10) -COORDS = [p for p in itertools.product(range(SCR_SIDE), repeat=2) if {*p} & {0, SCR_SIDE-1}] +\ - [(random.randint(1, SCR_SIDE-2), random.randint(2, SCR_SIDE-2)) for _ in range(62)] -FLOORS = [pygame.Rect(x*RECT_SIDE, y*RECT_SIDE, RECT_SIDE, RECT_SIDE) for x, y in COORDS] -URL = 'https://raw.githubusercontent.com/gto76/python-cheatsheet/master/web/mario_bros.png' -IMAGE = pygame.image.load(io.BytesIO(urlopen(URL).read())) -FRAMES = [IMAGE.subsurface(pygame.Rect(x*16, 0, 16, 16)) for x in range(7)] -FRAMES += [pygame.transform.flip(f, True, False) for f in FRAMES] -TILES = [IMAGE.subsurface(pygame.Rect(x*16, 0, 16, 16)) for x in range(9, 14)] +#### Runs a simple Super Mario game: +```python +import collections, dataclasses, enum, io, math, pygame, urllib.request, itertools as it +from random import randint + +P = collections.namedtuple('P', 'x y') +D = enum.Enum('D', 'n e s w') +SIZE, MAX_SPEED = 25, P(5, 10) def main(): - pygame.init() - screen = pygame.display.set_mode(2 * [SCR_SIDE * RECT_SIDE]) - mario = Mario(pygame.Rect(16, 16, 16, 16), P(0, 0), False, itertools.cycle(range(3))) - while not any(event.type == pygame.QUIT for event in pygame.event.get()): + def get_rect(x, y): + return pygame.Rect(x*16, y*16, 16, 16) + def get_images(): + url = 'https://gto76.github.io/python-cheatsheet/web/mario_bros.png' + img = pygame.image.load(io.BytesIO(urllib.request.urlopen(url).read())) + return [img.subsurface(get_rect(x, 0)) for x in range(img.get_width() // 16)] + def get_mario(): + Mario = dataclasses.make_dataclass('Mario', 'rect spd facing_left frame_cycle'.split()) + return Mario(get_rect(1, 1), P(0, 0), False, it.cycle(range(3))) + def get_tiles(): + positions = [p for p in it.product(range(SIZE), repeat=2) if {*p} & {0, SIZE-1}] + \ + [(randint(1, SIZE-2), randint(2, SIZE-2)) for _ in range(SIZE**2 // 10)] + return [get_rect(*p) for p in positions] + def get_screen(): + pygame.init() + return pygame.display.set_mode(2 * [SIZE*16]) + run(get_images(), get_mario(), get_tiles(), get_screen()) + +def run(images, mario, tiles, screen): + while all(event.type != pygame.QUIT for event in pygame.event.get()): keys = {pygame.K_UP: D.n, pygame.K_RIGHT: D.e, pygame.K_DOWN: D.s, pygame.K_LEFT: D.w} pressed = {keys.get(i, None) for i, on in enumerate(pygame.key.get_pressed()) if on} - update_speed(mario, pressed) - update_position(mario) - draw(screen, mario, pressed) + update_speed(mario, tiles, pressed) + update_position(mario, tiles) + draw(mario, tiles, screen, pressed, images) pygame.time.wait(28) -def update_speed(mario, pressed): - bounds = get_boundaries(mario.rect) +def update_speed(mario, tiles, pressed): + bounds = get_boundaries(mario.rect, tiles) x, y = mario.spd x += 2 * ((D.e in pressed) - (D.w in pressed)) x = math.copysign(abs(x) - 1, x) if x else 0 @@ -2951,36 +2958,34 @@ def update_speed(mario, pressed): speed = stop_on_collision(P(x, y), bounds) mario.spd = P(*[max(-thresh, min(thresh, s)) for thresh, s in zip(MAX_SPEED, speed)]) -def update_position(mario): - delta, old_p = P(0, 0), mario.rect.topleft +def update_position(mario, tiles): + old_p, delta = mario.rect.topleft, P(0, 0) larger_speed = max(abs(s) for s in mario.spd) for _ in range(int(larger_speed)): - mario.spd = stop_on_collision(mario.spd, get_boundaries(mario.rect)) - delta = P(*[s/larger_speed + dlt for s, dlt in zip(mario.spd, delta)]) - mario.rect.topleft = [sum(a) for a in zip(old_p, delta)] + mario.spd = stop_on_collision(mario.spd, get_boundaries(mario.rect, tiles)) + delta = P(*[a + s/larger_speed for a, s in zip(delta, mario.spd)]) + mario.rect.topleft = [sum(pair) for pair in zip(old_p, delta)] -def get_boundaries(rect): +def get_boundaries(rect, tiles): deltas = {D.n: P(0, -1), D.e: P(1, 0), D.s: P(0, 1), D.w: P(-1, 0)} - return {d for d, delta in deltas.items() if rect.move(delta).collidelist(FLOORS) != -1} + return {d for d, delta in deltas.items() if rect.move(delta).collidelist(tiles) != -1} def stop_on_collision(spd, bounds): return P(x=0 if (D.w in bounds and spd.x < 0) or (D.e in bounds and spd.x > 0) else spd.x, y=0 if (D.n in bounds and spd.y < 0) or (D.s in bounds and spd.y > 0) else spd.y) -def draw(screen, mario, pressed): +def draw(mario, tiles, screen, pressed, images): + def get_frame_index(): + if D.s not in get_boundaries(mario.rect, tiles): + return 4 + return next(mario.frame_cycle) if {D.w, D.e} & pressed else 6 screen.fill((85, 168, 255)) - mario.facing_left = mario.spd.x < 0 if mario.spd.x else mario.facing_left - screen.blit(FRAMES[get_frame_index(mario, pressed) + mario.facing_left*7], mario.rect) - for rect in FLOORS: - tile_index = 1 if {*rect.topleft} & {0, (SCR_SIDE-1)*RECT_SIDE} else 0 - screen.blit(TILES[tile_index], rect) + mario.facing_left = (D.w in pressed) if {D.e, D.w} & pressed else mario.facing_left + screen.blit(images[get_frame_index() + mario.facing_left*9], mario.rect) + for rect in tiles: + screen.blit(images[19 if {*rect.topleft} & {0, (SIZE-1)*16} else 18], rect) pygame.display.flip() -def get_frame_index(mario, pressed): - if D.s not in get_boundaries(mario.rect): - return 4 - return next(mario.running_cycle) if {D.w, D.e} & pressed else 6 - if __name__ == '__main__': main() ``` diff --git a/index.html b/index.html index 1569e88..a16967c 100644 --- a/index.html +++ b/index.html @@ -2475,36 +2475,43 @@ simpleaudio.play_buffer(samples_b, 1, -

#Pygame

Example

Runs a simple Mario game:

import collections, dataclasses, enum, io, itertools, math, pygame, random, urllib
-from urllib.request import urlopen
-
-D      = enum.Enum('D', 'n e s w')
-P      = collections.namedtuple('P', 'x y')
-Mario  = dataclasses.make_dataclass('Mario', ['rect', 'spd', 'facing_left', 'running_cycle'])
-RECT_SIDE, SCR_SIDE, MAX_SPEED = 16, 25, P(x=5, y=10)
-COORDS = [p for p in itertools.product(range(SCR_SIDE), repeat=2) if {*p} & {0, SCR_SIDE-1}] +\
-         [(random.randint(1, SCR_SIDE-2), random.randint(2, SCR_SIDE-2)) for _ in range(62)]
-FLOORS = [pygame.Rect(x*RECT_SIDE, y*RECT_SIDE, RECT_SIDE, RECT_SIDE) for x, y in COORDS]
-URL    = 'https://raw.githubusercontent.com/gto76/python-cheatsheet/master/web/mario_bros.png'
-IMAGE  = pygame.image.load(io.BytesIO(urlopen(URL).read()))
-FRAMES = [IMAGE.subsurface(pygame.Rect(x*16, 0, 16, 16)) for x in range(7)]
-FRAMES += [pygame.transform.flip(f, True, False) for f in FRAMES]
-TILES  = [IMAGE.subsurface(pygame.Rect(x*16, 0, 16, 16)) for x in range(9, 14)]
+

#Pygame

Example

Runs a simple Super Mario game:

import collections, dataclasses, enum, io, math, pygame, urllib.request, itertools as it
+from random import randint
+
+P = collections.namedtuple('P', 'x y')
+D = enum.Enum('D', 'n e s w')
+SIZE, MAX_SPEED = 25, P(5, 10)
 
 def main():
-    pygame.init()
-    screen = pygame.display.set_mode(2 * [SCR_SIDE * RECT_SIDE])
-    mario = Mario(pygame.Rect(16, 16, 16, 16), P(0, 0), False, itertools.cycle(range(3)))
-    while not any(event.type == pygame.QUIT for event in pygame.event.get()):
+    def get_rect(x, y):
+        return pygame.Rect(x*16, y*16, 16, 16)
+    def get_images():
+        url = 'https://gto76.github.io/python-cheatsheet/web/mario_bros.png'
+        img = pygame.image.load(io.BytesIO(urllib.request.urlopen(url).read()))
+        return [img.subsurface(get_rect(x, 0)) for x in range(img.get_width() // 16)]
+    def get_mario():
+        Mario = dataclasses.make_dataclass('Mario', 'rect spd facing_left frame_cycle'.split())
+        return Mario(get_rect(1, 1), P(0, 0), False, it.cycle(range(3)))
+    def get_tiles():
+        positions = [p for p in it.product(range(SIZE), repeat=2) if {*p} & {0, SIZE-1}] + \
+            [(randint(1, SIZE-2), randint(2, SIZE-2)) for _ in range(SIZE**2 // 10)]
+        return [get_rect(*p) for p in positions]
+    def get_screen():
+        pygame.init()
+        return pygame.display.set_mode(2 * [SIZE*16])
+    run(get_images(), get_mario(), get_tiles(), get_screen())
+
+def run(images, mario, tiles, screen):
+    while all(event.type != pygame.QUIT for event in pygame.event.get()):
         keys = {pygame.K_UP: D.n, pygame.K_RIGHT: D.e, pygame.K_DOWN: D.s, pygame.K_LEFT: D.w}
         pressed = {keys.get(i, None) for i, on in enumerate(pygame.key.get_pressed()) if on}
-        update_speed(mario, pressed)
-        update_position(mario)
-        draw(screen, mario, pressed)
+        update_speed(mario, tiles, pressed)
+        update_position(mario, tiles)
+        draw(mario, tiles, screen, pressed, images)
         pygame.time.wait(28)
 
-def update_speed(mario, pressed):
-    bounds = get_boundaries(mario.rect)
+def update_speed(mario, tiles, pressed):
+    bounds = get_boundaries(mario.rect, tiles)
     x, y = mario.spd
     x += 2 * ((D.e in pressed) - (D.w in pressed))
     x = math.copysign(abs(x) - 1, x) if x else 0
@@ -2512,36 +2519,34 @@ TILES  = [IMAGE.subsurface(pygame.Rect(x*16, for thresh, s in zip(MAX_SPEED, speed)])
 
-def update_position(mario):
-    delta, old_p = P(0, 0), mario.rect.topleft
+def update_position(mario, tiles):
+    old_p, delta = mario.rect.topleft, P(0, 0)
     larger_speed = max(abs(s) for s in mario.spd)
     for _ in range(int(larger_speed)):
-        mario.spd = stop_on_collision(mario.spd, get_boundaries(mario.rect))
-        delta = P(*[s/larger_speed + dlt for s, dlt in zip(mario.spd, delta)])
-        mario.rect.topleft = [sum(a) for a in zip(old_p, delta)]
+        mario.spd = stop_on_collision(mario.spd, get_boundaries(mario.rect, tiles))
+        delta = P(*[a + s/larger_speed for a, s in zip(delta, mario.spd)])
+        mario.rect.topleft = [sum(pair) for pair in zip(old_p, delta)]
 
-def get_boundaries(rect):
+def get_boundaries(rect, tiles):
     deltas = {D.n: P(0, -1), D.e: P(1, 0), D.s: P(0, 1), D.w: P(-1, 0)}
-    return {d for d, delta in deltas.items() if rect.move(delta).collidelist(FLOORS) != -1}
+    return {d for d, delta in deltas.items() if rect.move(delta).collidelist(tiles) != -1}
 
 def stop_on_collision(spd, bounds):
     return P(x=0 if (D.w in bounds and spd.x < 0) or (D.e in bounds and spd.x > 0) else spd.x,
              y=0 if (D.n in bounds and spd.y < 0) or (D.s in bounds and spd.y > 0) else spd.y)
 
-def draw(screen, mario, pressed):
+def draw(mario, tiles, screen, pressed, images):
+    def get_frame_index():
+        if D.s not in get_boundaries(mario.rect, tiles):
+            return 4
+        return next(mario.frame_cycle) if {D.w, D.e} & pressed else 6
     screen.fill((85, 168, 255))
-    mario.facing_left = mario.spd.x < 0 if mario.spd.x else mario.facing_left
-    screen.blit(FRAMES[get_frame_index(mario, pressed) + mario.facing_left*7], mario.rect)
-    for rect in FLOORS:
-        tile_index = 1 if {*rect.topleft} & {0, (SCR_SIDE-1)*RECT_SIDE} else 0
-        screen.blit(TILES[tile_index], rect)
+    mario.facing_left = (D.w in pressed) if {D.e, D.w} & pressed else mario.facing_left
+    screen.blit(images[get_frame_index() + mario.facing_left*9], mario.rect)
+    for rect in tiles:
+        screen.blit(images[19 if {*rect.topleft} & {0, (SIZE-1)*16} else 18], rect)
     pygame.display.flip()
 
-def get_frame_index(mario, pressed):
-    if D.s not in get_boundaries(mario.rect):
-        return 4
-    return next(mario.running_cycle) if {D.w, D.e} & pressed else 6
-
 if __name__ == '__main__':
     main()