Exemple #1
0
def refresh_screen(scr, cline, cp, offset, blink=False):

    global curcol
    curcol = 1 if curcol == 3 else 3

    if not scr:
        scr = pew.Pix()

    if blink:
        scr.pixel(0, cp, curcol)
    else:
        dt = (cline + "        ")[offset:offset + 8]
        for i in range(8):
            scr.pixel(0, i, curcol if cp == i else 1)
            try:
                b = MORSE.get(dt[i], 0)
            except IndexError:
                b = MORSE.get(" ")
            for j, v in enumerate(to_beep(b)):
                if v > 0:
                    v += 1
                scr.pixel(STARTPOS + j, i, v)

    if offset > 0:
        scr.pixel(7, 0, curcol)
    else:
        scr.pixel(7, 0, 0)
    if len(cline) - 8 > offset:
        scr.pixel(7, 7, curcol)
    else:
        scr.pixel(7, 7, 0)
Exemple #2
0
def main():
    pew.init()
    screen = pew.Pix()

    while True:
        screen.box(0, 0, 0)
        board = Board(screen)
        board.draw()

        while not pew.keys():
            pew.tick(0.25)

        while True:
            pew.tick(0.25)

            keys = pew.keys()
            if keys & pew.K_UP:
                board.move_paddle(board.left_paddle, Direction.UP)
            if keys & pew.K_DOWN:
                board.move_paddle(board.left_paddle, Direction.DOWN)
            if keys & pew.K_O:
                board.move_paddle(board.right_paddle, Direction.UP)
            if keys & pew.K_X:
                board.move_paddle(board.right_paddle, Direction.DOWN)

            try:
                board.refresh()
            except BallOut:
                break
Exemple #3
0
 def __init__(self):
     pew.init()
     self.screen = pew.Pix()
     self.cursorx, self.cursory = 4, 4
     self.counter = 0
     self.pressing = False
     self.main_loop()
Exemple #4
0
    def run(self):
        screen = pew.Pix()
        apple = Apple(self._snake)
        apple.paint(screen)

        while True:
            self.paint(screen)
            pew.show(screen)
            pew.tick(1 / self._game_speed)

            self._keyboard.update(pew.keys())
            x = (self._snake[-1][0] + self._x_delta) % 8
            y = (self._snake[-1][1] + self._y_delta) % 8

            if (x, y) in self._snake:
                return False

            if len(self._snake) >= self._win_length:
                return True

            self._snake.append((x, y))

            if x == apple.x_location and y == apple.y_location:
                apple.rub_out(screen)
                apple = Apple(self._snake)
                apple.paint(screen)
                self._game_speed += 0.2
            else:
                self.rub_out(screen, 0)
def board_to_pix(board):
    """
    Returns board converted to pew.Pix.
    """
    pix = pew.Pix()
    for x, y in board:
        pix.pixel(x, y, 3)
    return pix
Exemple #6
0
    def print(string):
        _screen = pew.Pix()
        _text = pew.Pix.from_text(string, 3, 0)

        for dx in range(-8, _text.width):
            _screen.blit(_text, -dx, 1)
            pew.show(_screen)
            pew.tick(1 / 8)
Exemple #7
0
 def __init__(self):
     self.screen = pew.Pix()
     # Game stats
     self.player = (__PLAYER_STARTING_X, __PLAYER_STARTING_Y)
     self.old_player = (__PLAYER_STARTING_X, __PLAYER_STARTING_Y)
     self.reset_game_logic()
     self.game_speed = __STARTING_SPEED
     self.speed_factor = __DEFAULT_SPEED_FACTOR
     # Visuals
     self.title = pew.Pix.from_text("AcidRain")
     self.game_over = pew.Pix.from_text("Game Over!")
Exemple #8
0
def update(values, previous):
    output = pew.Pix()
    for (x_off, y_off), (shift, rot) in zip(OFFSETS, values):
        for y in range(4):
            for x in range(4):
                a, b = x, y
                for i in range(rot):
                    a, b = 3 - b, a
                b = (b + shift) % 4
                output.pixel(a + x_off, b + y_off,
                             previous.pixel(x + x_off, y + y_off))
    return output
Exemple #9
0
def hint(lt):
    if lt:
        b = MORSE.get(lt, 0)
        if b:
            scr = pew.Pix()
            scr.blit(pew.Pix.from_text(lt, color=3), 2, 0)
            for j, v in enumerate(to_beep(b)):
                if v > 1:
                    v = 3
                scr.pixel(STARTPOS + j, 7, v)
            pew.show(scr)
            pew.tick(1)
Exemple #10
0
def make_image(state):
    blocks = []
    for num in state:
        blocks.append(make_block(num))

    image = pew.Pix()

    for i in range(2):
        for j in range(4):
            tmp = blocks[2 * i][j] + blocks[2 * i + 1][j]
            for x in range(8):
                image.pixel(x, 4 * i + j, tmp[x])

    return image
Exemple #11
0
def play():

    sc = pew.Pix()
    keys = pew.keys()

    player_x = 1
    p = Player(x=player_x)
    o = Obstacle()

    score = 0
    player_collided = False

    # Game loop
    while not keys & pew.K_X and not player_collided:
        # Parse events
        p.parse_keys(keys)
        if o.get_pos() == player_x:
            if p.collides(o.get_collisions()):
                player_collided = True
        elif o.get_pos() == player_x - 1:
            score += 1

        p.fall()

        score_array = get_score_array(score)[::-1]
        for i in range(len(score_array)):
            sc.pixel(7 - i, 0, 3 * score_array[i])

        p.blit(sc)
        o.blit(sc)

        pew.show(sc)
        pew.tick(1 / 2)

        # Clear last player position
        p.blit(sc, clear=True)
        o.blit(sc, clear=True)
        o.move()
        p.clear_jump()
        keys = pew.keys()

    if p.collides(o.get_collisions()):
        # Display score
        print(f"Game ended with a score of {score}!!")
    else:
        print("Quit game.")
Exemple #12
0
def show_text(t):
    if not t.strip():
        return
    scr = pew.Pix()
    rpt = False
    text = pew.Pix.from_text(t, color=3)
    while True:
        for dx in range(-8, text.width):
            scr.blit(text, -dx, 1)
            pew.show(scr)
            keys = pew.keys()
            if keys:
                if keys & pew.K_X:
                    return
                if keys & pew.K_O:
                    pew.tick(1 / 24)
                    rpt = True
            else:
                pew.tick(1 / 6)
        if not rpt:
            return
Exemple #13
0
def loop():
    screen = pew.Pix()

    # Run until user input
    while not pew.keys():
        current_time = time.localtime()
        h = current_time[3] % 12
        m = current_time[4] // 5
        s = current_time[5] // 5

        # Paint pixels
        enable_hand(screen, h, 1)
        enable_hand(screen, m, 2)
        enable_hand(screen, s, 3)

        # Update display
        pew.show(screen)
        # Wait until next iteration
        pew.tick(1 / 2)

        # Clear pixels
        disable_hand(screen, h)
        disable_hand(screen, m)
        disable_hand(screen, s)
Exemple #14
0
def main():
    screen = pew.Pix()
    r1 = -1
    r2 = -1
    stop = False
    stopped = 0
    blend = bytearray(16)
    t1r = t1g = t2r = t2g = 0
    while stopped != 3:
        stop = stop or pew.keys()
        if r1 < 10:
            if stop:
                stopped |= 1
                cx1 = 500
                r1 = 10
                dr1 = 20
            else:
                cx1 = random.getrandbits(7)
                cy1 = random.getrandbits(7)
                dx1 = random.getrandbits(4) - 8
                dy1 = random.getrandbits(4) - 11
                r1 = 10
                dr1 = random.getrandbits(4) + 5
                t1r = random.getrandbits(8) % 23
                t1g = 7
                if t1r >= 16:
                    t1g = 22 - t1r
                    t1r = 15
                for a1 in range(4):
                    for a2 in range(4):
                        blend[(a1 << 2) | a2] = 0x80 | ((
                            (3 * a1 * t1r +
                             (3 - a1) * a2 * t2r + 4) // 9) << 3) | (
                                 (3 * a1 * t1g + (3 - a1) * a2 * t2g + 4) // 9)
        if r2 < 10:
            if stop:
                stopped |= 2
                cx2 = 500
                r2 = 10
                dr2 = 20
            else:
                cx2 = random.getrandbits(7)
                cy2 = random.getrandbits(7)
                dx2 = random.getrandbits(4) - 8
                dy2 = random.getrandbits(4) - 11
                r2 = 10
                dr2 = random.getrandbits(4) + 5
                t2r = random.getrandbits(8) % 23
                t2g = 7
                if t2r >= 16:
                    t2g = 22 - t2r
                    t2r = 15
                for a1 in range(4):
                    for a2 in range(4):
                        blend[(a1 << 2) | a2] = 0x80 | ((
                            (3 * a1 * t1r +
                             (3 - a1) * a2 * t2r + 4) // 9) << 3) | (
                                 (3 * a1 * t1g + (3 - a1) * a2 * t2g + 4) // 9)
        r = r1 >> 1
        l1 = r - 5
        l1 *= l1
        m1 = r * r
        u1 = r + 5
        u1 *= u1
        r = r2 >> 1
        l2 = r - 5
        l2 *= l2
        m2 = r * r
        u2 = r + 5
        u2 *= u2
        for y in range(8):
            for x in range(8):
                rx = (x << 4) - cx1
                ry = (y << 4) - cy1
                rr1 = rx * rx + ry * ry
                rx = (x << 4) - cx2
                ry = (y << 4) - cy2
                rr2 = rx * rx + ry * ry
                screen.pixel(
                    x, y,
                    blend[((3 if rr1 < l1 else
                            2 if rr1 < m1 else 1 if rr1 < u1 else 0) << 2) |
                          (3 if rr2 < l2 else
                           2 if rr2 < m2 else 1 if rr2 < u2 else 0)])
        r1 += dr1
        dr1 -= 1
        cx1 += dx1
        cy1 += dy1
        r2 += dr2
        dr2 -= 1
        cx2 += dx2
        cy2 += dy2
        pew.show(screen)
        pew.tick(0.05)
Exemple #15
0
# 6 Sided die - graphic display = press X for next number

from random import randint  # import random integer function

import pew  # Import pewpew library

pew.init()  # Initlise library

screen = pew.Pix()  # Create blsnk screen

# Define pattern for 1 dot
score1 = pew.Pix.from_iter((
    (0, 0, 0, 0, 0, 0, 0, 0),
    (0, 0, 0, 0, 0, 0, 0, 0),
    (0, 0, 0, 0, 0, 0, 0, 0),
    (0, 0, 0, 1, 1, 0, 0, 0),
    (0, 0, 0, 1, 1, 0, 0, 0),
    (0, 0, 0, 0, 0, 0, 0, 0),
    (0, 0, 0, 0, 0, 0, 0, 0),
    (0, 0, 0, 0, 0, 0, 0, 0),
))

# Define pattern for 2 dots
score2 = pew.Pix.from_iter((
    (0, 0, 0, 0, 0, 0, 0, 0),
    (0, 1, 1, 0, 0, 0, 0, 0),
    (0, 1, 1, 0, 0, 0, 0, 0),
    (0, 0, 0, 0, 0, 0, 0, 0),
    (0, 0, 0, 0, 0, 0, 0, 0),
    (0, 0, 0, 0, 0, 1, 1, 0),
    (0, 0, 0, 0, 0, 1, 1, 0),
Exemple #16
0
# Othello, copyright 2019 Christian Walther

import pew
try:
    import random
except ImportError:
    import urandom as random

import os
if 'PewPew 10.' in os.uname().machine:
    screen2 = pew.Pix()

    def show(p):
        for i in range(len(p.buffer)):
            c = p.buffer[i]
            screen2.buffer[i] = c ^ (c >> 1)
        pew.show(screen2)
else:
    show = pew.show


def lookup(board, x, y):
    return 0 if x < 0 or y < 0 or x >= 8 or y >= 8 else board[y * 8 + x]


def move(board, x, y, color):
    if lookup(board, x, y) != 0:
        return None
    turned = False
    newboard = bytearray(board)
    newboard[8 * y + x] = color
Exemple #17
0
#CC written by Joost Markerink (March 2020) http://joostmarkerink.nl
import pew
import time
pew.init()
screen = pew.Pix()
drawing = pew.Pix()
cursorx = 3
cursory = 4
prevkeys = pew.keys()
paint = 1
editing = True
willEdit = False
xbuttontime = 0
eventTime = 0
isClear = True
splash = [
    0b11111111, 0b10000001, 0b10111101, 0b10100101, 0b10111101, 0b10100001,
    0b10000001, 0b11111111
]

for y in range(8):
    for x in range(8):
        screen.pixel(x, y, (splash[y] >> (7 - x)) & 1)
pew.show(screen)
pew.tick(2.4)


def fill(sx, sy, fill_value):
    orig_value = drawing.pixel(sx, sy)
    stack = set(((sx, sy), ))
    if fill_value == orig_value:
Exemple #18
0
def run():

    cos = [
        4, 4, 3, 3, 2, 1, 0, -1, -2, -3, -3, -4, -4, -4, -3, -3, -2, -1, 0, 1,
        2, 3, 3, 4
    ]
    sin = cos[18:] + cos[:18]

    with open('m3dlevel.bmp', 'rb') as f:
        f.seek(54)
        textures = f.read(64)
        textures = bytes(textures[4 * i] for i in range(16))
        level = f.read()

    def bresenham(ax, ay, bx, by):
        x = ax
        y = ay
        dx = bx - ax
        if dx < 0:
            dx = -dx
            ix = -1
        else:
            ix = 1
        dy = by - ay
        if dy < 0:
            dy = -dy
            iy = -1
        else:
            iy = 1
        dx <<= 1
        dy <<= 1
        sx = dx
        sy = dy
        if dx >= dy:
            sx >>= 1
            while x != bx:
                if sx < sy:
                    y += iy
                    sx += dx
                x += ix
                sy += dy
                yield x, y
        else:
            sy >>= 1
            while y != by:
                if sy < sx:
                    x += ix
                    sy += dy
                y += iy
                sx += dx
                yield x, y

    def lookup(x, y):
        b = level[(y << (_LOG_LEVEL_W - 1 - 2)) & (((1 << _LOG_LEVEL_H) - 1) <<
                                                   (_LOG_LEVEL_W - 1)) |
                  ((x >> 3) & ((1 << (_LOG_LEVEL_W - 1)) - 1))]
        return (b & 0xF) if (x & 4) else (b >> 4)

    pew.init()
    screen = pew.Pix()

    x = (1 << (_LOG_LEVEL_W + 1))
    y = (1 << (_LOG_LEVEL_H + 1))
    nx = None
    b = 6
    sb = sin[b]
    cb = cos[b]

    while pew.keys():
        pew.tick(0.1)
    while True:
        keys = pew.keys()
        if keys & pew.K_X:
            break
        if keys & pew.K_RIGHT:
            if keys & pew.K_O:
                nx = x + sb
                ny = y - cb
            else:
                b = (b + 23) % 24
                sb = sin[b]
                cb = cos[b]
                #print('xy:', x, y, 'b:', b)
        if keys & pew.K_LEFT:
            if keys & pew.K_O:
                nx = x - sb
                ny = y + cb
            else:
                b = (b + 1) % 24
                sb = sin[b]
                cb = cos[b]
                #print('xy:', x, y, 'b:', b)
        if keys & pew.K_UP:
            nx = x + cb
            ny = y + sb
        if keys & pew.K_DOWN:
            nx = x - cb
            ny = y - sb
        if nx is not None and lookup(nx, ny) == 0:
            x = nx
            y = ny
            nx = None
            #print('xy:', x, y, 'b:', b)

        rx = x + 16 * cb - 14 * sb
        ry = y + 16 * sb + 14 * cb
        for c in range(8):
            p = 0
            for bx, by in bresenham(x, y, rx, ry):
                p = lookup(bx, by)
                if p != 0:
                    bxy = max(abs(bx - x), abs(by - y))
                    rxy = max(abs(rx - x), abs(ry - y))
                    for r in range(8):
                        t = (7 * rxy - 8 * bxy * (5 - 2 * r)) // (4 * rxy) - 1
                        screen.pixel(
                            c, r, 0 if t < 0 else 73 - 8 * r if t >= 4 else
                            ((textures[p] >>
                              (t << 1)) & 3) + 4 * (bxy * 15 // rxy))
                    #if keys != 0:
                    #	print(rx, ',', ry, ':', bx, ',', by, '=', p, 'z', 64*bxy/rxy)
                    break
            else:
                for r in range(8):
                    screen.pixel(c, r, 0 if r < 3 else 73 - 8 * r)

            rx += 4 * sb
            ry -= 4 * cb

        pew.show(screen)
        pew.tick(0.06)
Exemple #19
0
# Simple moving dot from random start position
import pew
from random import randint

pew.init()  # intialise device
screen = pew.Pix()  # create empty screen image

x = randint(0, 7)  # random intial x position
y = randint(0, 7)  # random intial y position

dx = 1  # initial x step
dy = 1  # initial y step

while True:
    screen.pixel(x, y, 0)  #make previuos pixel not lit
    if x < 0 or x > 7:  #if next x position is off screen
        dx = -dx  #reverse its direction
    if y < 0 or y > 7:  #if next y position is off screen
        dy = -dy  #reverse its direction
    x += dx  #update x position
    y += dy  #update y position
    screen.pixel(x, y, 2)  #plot next pixel
    pew.show(screen)  #update screen
    pew.tick(1 / 12)  #pause approx 1/2 second
Exemple #20
0
 def __init__(self):
     self.screens = pew.Pix(), pew.Pix()
     self.x = 1
     self.y = 1
Exemple #21
0
def new_scene():
    return pew.Pix()
Exemple #22
0
 def __init__(self):
     pew.init()
     self.screen = pew.Pix()
     self.main_loop()
Exemple #23
0
 def print_dot(self):
     self.screen = pew.Pix()
     self.screen.box(x=0, y=0, color=self.color_animation(), width=self.size, height=self.size)
Exemple #24
0
import struct
import usb_hid
import pew

pew.init()
screen = pew.Pix()
for gamepad in usb_hid.devices:
    if gamepad.usage_page == 0x01 and gamepad.usage == 0x05:
        break
else:
    raise RuntimeError("Gamepad HID device not found")
report = bytearray(6)

while True:
    buttons = pew.keys()
    report_buttons = 0

    if buttons & pew.K_O:
        screen.pixel(6, 3, 3)
        report_buttons |= 0x01
    else:
        screen.pixel(6, 3, 1)

    if buttons & pew.K_X:
        screen.pixel(6, 5, 3)
        report_buttons |= 0x02
    else:
        screen.pixel(6, 5, 1)

    if buttons & pew.K_UP:
        y = -127
Exemple #25
0
        for x in range(brick.width):
            if (brick.pixel(x, y)
                    and board.pixel(brick_x + x + 1, brick_y + y + 3)):
                return True
    return False


def debounce():
    for i in range(100):
        pew.tick(1 / 100)
        if not pew.keys():
            return


pew.init()
screen = pew.Pix(width=8, height=8)
screen.box(color=2, x=6, y=0, width=2, height=8)
next_brick = BRICKS[qRand(3)]
board = pew.Pix(width=8, height=12)
board.box(color=1)
board.box(color=0, x=1, y=0, width=6, height=11)

while True:
    brick = next_brick
    next_brick = BRICKS[qRand(3)]  # 0-7
    screen.box(color=0, x=6, y=0, width=2, height=5)
    screen.blit(next_brick, dx=6, dy=0)
    brick_x = 2
    brick_y = -3
    while True:
        if is_colliding(board, brick, brick_x, brick_y):
Exemple #26
0
import pew  # setting up tools for the pewpew
import random
from microqiskit import QuantumCircuit, simulate  # setting up tools for quantum

pew.init()  # initialize the game engine...
screen = pew.Pix()  # ...and the screen

qc = QuantumCircuit(
    2, 2)  # create an empty circuit with two qubits and two output bits
qc.h(0)
qc.cx(0, 1)

# create circuits with the required measurements, so we can add them in easily
meas = QuantumCircuit(2, 2)
meas.measure(0, 0)
meas.measure(1, 1)

# loop over the squares centered on (1,2), (6,2) (1,4) and (6,4) and make all dim
for (X, Y) in [(1, 4), (6, 4)]:
    for dX in [+1, 0, -1]:
        for dY in [+1, 0, -1]:
            screen.pixel(X + dX, Y + dY, 2)
pew.show(screen)

for (X, Y) in [(1, 4), (6, 4)]:
    screen.pixel(X, Y, 0)  # turn off the center pixels of the squares

old_keys = 0
while True:  # loop which checks for user input and responds

    # look for and act upon key presses
def main():

    # -- animations ----

    def blink(row):
        while len(animations) > 1:
            yield
        while True:
            yield
            for x, y in row:
                screen.pixel(x, y + 2, 0)
            yield

    def drop(color, x, y):
        for i in range(1, y):
            screen.pixel(x, y, 0)
            screen.pixel(x, i, color)
            yield

    # -- initialization ----

    pew.init()
    screen = pew.Pix()
    board = pew.Pix(7, 6)
    cursor = 3
    opcursor = 3
    turn = 1
    prevk = 0b111111
    won = False
    animations = []

    with open('four-name', 'rb') as f:
        myname = f.read()
    lobbyprefix = b'fourinarow/lobby/'
    lobbytopic = lobbyprefix + myname
    joinprefix = b'fourinarow/join/'
    jointopic = joinprefix + myname
    client = mqtt.MQTTClient('', 'mqtt.kolleegium.ch')
    client.set_last_will(lobbytopic, b'', True)
    client.connect()
    try:

        # -- lobby initialization ----

        client.publish(lobbytopic, b'1', True)
        joined = None
        lobby = set()
        lobbylist = ['>exit']
        menu = menugen(screen, lobbylist)

        def onMessageLobby(topic, message):
            nonlocal joined, mycolor
            if topic.startswith(lobbyprefix):
                username = topic[len(lobbyprefix):]
                if message:
                    lobby.add(username)
                    screen.box(1, 0, 7, 8, 1)
                else:
                    lobby.discard(username)
                    screen.box(2, 0, 7, 8, 1)
                lobbylist[:-1] = [
                    str(n, 'ascii') for n in lobby if n != myname
                ]
            elif topic == jointopic:
                joined = message
                mycolor = 2

        client.set_callback(onMessageLobby)
        client.subscribe(lobbyprefix + b'+')
        client.subscribe(jointopic)

        # -- lobby loop ----

        for selected in menu:
            client.check_msg()
            if joined:
                break
            pew.show(screen)
            pew.tick(1 / 24)
        else:
            if selected < len(lobbylist) - 1:
                joined = bytes(lobbylist[selected], 'ascii')
                client.publish(joinprefix + joined, myname)
                mycolor = 1
        client.publish(lobbytopic, b'', True)
        screen.box(0)
        pew.show(screen)

        if not joined:
            return

        # -- game initialization ----

        mygameprefix = b'fourinarow/game/' + myname + b'/'
        mycursortopic = mygameprefix + b'cursor'
        mydroptopic = mygameprefix + b'drop'
        opgameprefix = b'fourinarow/game/' + joined + b'/'
        opcursortopic = opgameprefix + b'cursor'
        opdroptopic = opgameprefix + b'drop'

        def move(cursor):
            nonlocal won, turn
            y = 0
            while y < 6 and board.pixel(cursor, y) == 0:
                y += 1
            if y != 0:
                board.pixel(cursor, y - 1, turn)
                animations.append(drop(turn, cursor, y + 1))
                won = check(board)
                if won:
                    animations.append(blink(won))
                turn = 3 - turn

        def onMessageGame(topic, message):
            nonlocal opcursor
            if topic == opcursortopic and len(message) == 1:
                opcursor = message[0]
            elif topic == opdroptopic and len(
                    message) == 1 and turn == 3 - mycolor:
                move(message[0])

        client.set_callback(onMessageGame)
        client.subscribe(opgameprefix + b'#')
        client.publish(mycursortopic, bytes((cursor, )), True)

        # -- game loop ----

        while True:

            # -- input handling ----

            k = pew.keys()
            if not won:
                if k & pew.K_LEFT:
                    if cursor > 0:
                        cursor -= 1
                        client.publish(mycursortopic, bytes((cursor, )), True)
                if k & pew.K_RIGHT:
                    if cursor < 6:
                        cursor += 1
                        client.publish(mycursortopic, bytes((cursor, )), True)
                if k & ~prevk & (pew.K_DOWN | pew.K_O
                                 | pew.K_X) and turn == mycolor:
                    move(cursor)
                    client.publish(mydroptopic, bytes((cursor, )), False)
            else:
                if prevk == 0 and k != 0 and len(animations) == 1:
                    return
            prevk = k

            client.check_msg()

            # -- drawing ----

            screen.box(0, 0, 0, 8, 2)
            if not won:
                screen.pixel(cursor, 0, mycolor)
                screen.pixel(opcursor, 0,
                             3 if cursor == opcursor else 3 - mycolor)
                if turn == mycolor:
                    screen.pixel(7, 1, turn)
            screen.blit(board, 0, 2)
            for i in range(len(animations) - 1, -1, -1):
                try:
                    next(animations[i])
                except StopIteration:
                    del animations[i]
            pew.show(screen)
            pew.tick(0.15)

    finally:
        client.publish(lobbytopic, b'', True)
        client.disconnect()
def main():

    # -- animations ----

    # these functions need access to `screen`, which is a local variable of the main() function, so they must be defined locally as well (or, alternatively, they could take it passed as an argument)

    # Generator function that takes a winning row in the format returned by
    # check() and makes it blink by alternatingly doing nothing (leaving the
    # pixels in their original color) and overwriting them with black.
    def blink(row):
        # as long as a drop animation is still running, do nothing
        while len(animations) > 1:
            yield
        # infinite loop, the blinking does not end by itself
        while True:
            # odd iterations: do nothing -> colored pixels
            yield
            # even iterations: black pixels
            for x, y in row:
                # x, y are in board coordinates, add 2 to convert to screen coordinates
                screen.pixel(x, y + 2, 0)
            yield

    # Generator function that takes the color and final position of a piece and
    # animates it dropping from the top down to that position.
    # Drawn over a board where the piece is already in the final position, so
    # - the final pixel must be erased
    # - the animation ends one before the final position
    def drop(color, x, y):
        # start at 1 (because the cursor was already at 0) and run up to and excluding y
        for i in range(1, y):
            # erase final position with black
            screen.pixel(x, y, 0)
            # draw at current position
            screen.pixel(x, i, color)
            yield

    # -- initialization ----

    pew.init()
    # the framebuffer
    screen = pew.Pix()
    # the board
    board = pew.Pix(7, 6)
    # x coordinate of my cursor
    cursor = 3
    # x coordinate of opponent's cursor
    opcursor = 3
    # color value of whose turn it is (1=green, 2=red)
    turn = 1
    # keys pressed in the previous game loop iteration (pew.keys() value),
    # initialized to "all keys pressed" in binary so that there cannot be a
    # rising edge in the first iteration
    prevk = 0b111111
    # whether the game has ended, expressed by the return value of check():
    # either False or the winning row, which counts as "true" becaues it is a
    # non-empty sequence
    won = False
    # a list of generators that implement any currently running animations
    animations = []

    # read the player name from the configuration file
    # open for reading ('r'), in binary mode ('b') because we want the name as bytes, not as a string
    with open('four-name', 'rb') as f:
        myname = f.read()
    # various common parts of MQTT topics as bytes
    lobbyprefix = b'fourinarow/lobby/'
    lobbytopic = lobbyprefix + myname
    joinprefix = b'fourinarow/join/'
    jointopic = joinprefix + myname
    # set up the MQTT client object
    client = mqtt.MQTTClient('', 'mqtt.kolleegium.ch')
    # last will is the "leaving the lobby" message
    client.set_last_will(lobbytopic, b'', True)
    client.connect()
    # Whatever happens from now on, whether we exit by an error or by a
    # deliberate return, we want to close the connection in the end. Use a
    # try-finally statement for that.
    try:

        # -- lobby initialization ----

        # I am present
        client.publish(lobbytopic, b'1', True)
        # the name of the player who joined us or whom we joined, currently nobody
        joined = None
        # all the player names in the lobby, as a set so we can easily add and remove them by value without getting duplicates
        lobby = set()
        # the lobby as a list for the menu, which can't directly take a set, and with the additional "exit" entry at the end
        lobbylist = ['>exit']
        # create the menu generator, it keeps a reference to the list and will automatically pick up changes to it
        menu = menugen(screen, lobbylist)

        # callback for handling incoming MQTT messages while we're in the lobby
        def onMessageLobby(topic, message):
            # declare variables from the outer context that we want to assign to, otherwise assignment would create them as local variables of this function
            # access to other outer variables such as lobby or screen is read-only and needs no declaration
            nonlocal joined, mycolor
            # messages about players arriving and leaving
            if topic.startswith(lobbyprefix):
                username = topic[len(lobbyprefix):]
                # message is b'', which counts as false, or non-empty (expected b'1'), which counts as true
                if message:
                    lobby.add(username)
                    # flash a green bar at the bottom to indicate arrival
                    # this works because onMessageLobby is called from client.check_msg(), which occurs after drawing the menu (in `for selected in menu`) but before `pew.show(screen)`
                    screen.box(1, 0, 7, 8, 1)
                else:
                    # use discard(), not remove() to avoid an exception if the name is not there
                    # (it should, but we have no control over what messages others send us)
                    lobby.discard(username)
                    # red bar at the bottom to indicate departure
                    screen.box(2, 0, 7, 8, 1)
                # update the list form of the lobby by
                # - transforming the elements of the set form using a list comprehension (they are bytes but the menu wants strings)
                # - inserting them using a slice index that replaces everything but the ">exit" item at the end
                # it's important that we modify this list in place, not create a totally new list, because this list is the one the menu generator has a reference to
                lobbylist[:-1] = [
                    str(n, 'ascii') for n in lobby if n != myname
                ]
            # messages about someone joining us
            elif topic == jointopic:
                # message content is the name of the other player
                joined = message
                # the joined player (us) gets red
                mycolor = 2

        client.set_callback(onMessageLobby)
        # subscribe to all topics 1 level deep in the lobby (= user names)
        client.subscribe(lobbyprefix + b'+')
        client.subscribe(jointopic)

        # -- lobby loop ----

        # repeatedly poke the menu generator, which draws the menu and handles buttons, until the user selects an entry - conveniently done by a for loop
        # assigns the returned index to `selected` every time - we don't need it during the loop, we only need the value from the last iteration afterwards
        for selected in menu:
            # while in the menu, we also repeatedly need to check for incoming MQTT messages - this calls onMessageLobby if there is any, which may set joined
            client.check_msg()
            if joined:
                # leave the for loop
                break
            pew.show(screen)
            # this is the frame rate expected by the menu for an appropriate animation speed
            pew.tick(1 / 24)
        # we can leave the loop above in two ways:
        # 1. when the menu generator ends, which is when the local user has selected an emtry from the menu
        # 2. by the `break` statement when someone else has sent us a join message
        # the `else` block of a `for` statement is executed in case 1 but not in case 2
        else:
            # if selected == len(lobbylist) - 1, the user selected ">exit", otherwise another player
            if selected < len(lobbylist) - 1:
                # selected someone to join, look up who by their index, convert from string to bytes, and send them a join message
                joined = bytes(lobbylist[selected], 'ascii')
                client.publish(joinprefix + joined, myname)
                # the joining player gets green
                mycolor = 1
        # in any case (whether we joined someone, were joined, or are exiting), we now leave the lobby
        client.publish(lobbytopic, b'', True)
        # clear the menu from the screen
        screen.box(0)
        pew.show(screen)

        # if the user chose ">exit", we're done, return from the main() function
        if not joined:
            # (the `finally` block at the end will still be executed because we're jumping out from inside the `try` block)
            return

        # -- game initialization ----

        # more MQTT topics
        mygameprefix = b'fourinarow/game/' + myname + b'/'
        mycursortopic = mygameprefix + b'cursor'
        mydroptopic = mygameprefix + b'drop'
        opgameprefix = b'fourinarow/game/' + joined + b'/'
        opcursortopic = opgameprefix + b'cursor'
        opdroptopic = opgameprefix + b'drop'

        # Execute a move by dropping a piece of whose turn it is at the given column.
        def move(cursor):
            nonlocal won, turn
            # determine the topmost occupied (or beyond-the-bottom) place in the column by iterating from the top
            y = 0
            while y < 6 and board.pixel(cursor, y) == 0:
                y += 1
            # now either y == 6 (all were free) or place y was occupied, in both cases y-1 is the desired free place
            # unless the whole column was full (y == 0)
            if y != 0:
                # place the piece in the final position
                board.pixel(cursor, y - 1, turn)
                # start the drop animation - doesn't draw anything yet, just sets up the generator that will draw when poked
                animations.append(drop(turn, cursor, y + 1))
                # check for winning rows
                won = check(board)
                # won is either False or a non-empty sequence that counts as true
                if won:
                    # start the blink animation
                    animations.append(blink(won))
                # reverse the turn: 1 -> 2, 2 -> 1
                turn = 3 - turn

        # callback for handling incoming MQTT messages while we're in the game
        def onMessageGame(topic, message):
            nonlocal opcursor
            # input validation: check length, otherwise if someone sends us an empty message we crash with an IndexError on the following line
            if topic == opcursortopic and len(message) == 1:
                opcursor = message[0]
            # input validation: the opponent is only allowed to make a move when it is their turn
            elif topic == opdroptopic and len(
                    message) == 1 and turn == 3 - mycolor:
                # opponent's move
                move(message[0])

        client.set_callback(onMessageGame)
        # subscribe to all of the opponent's game topics (cursor and drop)
        client.subscribe(opgameprefix + b'#')
        # initial update so the opponent knows where my cursor is from the start
        # convert number to one-element bytes by packing it into an intermediate tuple (needs trailing comma to distinguish from grouping parentheses)
        client.publish(mycursortopic, bytes((cursor, )), True)

        # -- game loop ----

        while True:

            # -- input handling ----

            k = pew.keys()
            # key handling is different depending on whether the game is running or over
            if not won:
                # check for bits in k using the bitwise AND operator - the result is zero or nonzero, which count as false or true
                if k & pew.K_LEFT:
                    # move cursor left if possible and publish the new position
                    if cursor > 0:
                        cursor -= 1
                        client.publish(mycursortopic, bytes((cursor, )), True)
                if k & pew.K_RIGHT:
                    # move cursor right if possible and publish the new position
                    if cursor < 6:
                        cursor += 1
                        client.publish(mycursortopic, bytes((cursor, )), True)
                # drop only if the respective key was not pressed in the last iteration, otherwise we would repeatedly drop while the key is held down (edge detection)
                if k & ~prevk & (pew.K_DOWN | pew.K_O
                                 | pew.K_X) and turn == mycolor:
                    # my move
                    move(cursor)
                    client.publish(mydroptopic, bytes((cursor, )), False)
            else:
                # when the game is over, exit on a key press - several conditions to check:
                # - the first time we're getting here, the key that dropped the final piece may still be pressed - do nothing until all keys have been up in the previous iteration
                # - do nothing until the drop animation has completed and only the blink animation (which is endless) remains
                if prevk == 0 and k != 0 and len(animations) == 1:
                    return
            # save the pressed keys for the next iteration to detect edges
            prevk = k

            # check for incoming messages, which may execute an opponent's move via onMessageGame
            client.check_msg()

            # -- drawing ----

            # clear previous cursor, dropping piece, and turn indicator
            screen.box(0, 0, 0, 8, 2)
            if not won:
                # draw two cursors - if they overlap, in orange, otherwise in their respective color
                screen.pixel(cursor, 0, mycolor)
                screen.pixel(opcursor, 0,
                             3 if cursor == opcursor else 3 - mycolor)
                # turn indicator
                if turn == mycolor:
                    screen.pixel(7, 1, turn)
            # draw the board (in unanimated state)
            screen.blit(board, 0, 2)
            # poke all active animations to draw one iteration each
            # we'll be removing items from the list while iterating over it, so we need to do it backwards to avoid skipping items
            # start at the last position (len-1), continue to 0 inclusively which is -1 exclusively, in steps of -1
            for i in range(len(animations) - 1, -1, -1):
                try:
                    # next() pokes, it raises StopIteration when the generator is exhausted (animation is over)
                    next(animations[i])
                except StopIteration:
                    # remove completed animations
                    del animations[i]
            # done drawing into the framebuffer, send it to the display
            pew.show(screen)
            # wait until it's time for the next frame
            # 0.15 seconds is an appropriate frame time for our animations and key repeating - increase it if you still find it hard to move the cursor by exactly one pixel
            # drawing at a faster rate would require more complex key repeat handling
            pew.tick(0.15)

        # end of game loop

    finally:
        # however we exited from the try block, by an error or by a deliberate return, leave the lobby and close the connection
        client.publish(lobbytopic, b'', True)
        client.disconnect()