Appendiks til grafiske brukergrensesnitt

Innholdet på denne siden vil ikke komme på eksamen. Vi publiserer det her slik at dem som ønsker får se flere muligheter som finnes i uib_inf100_graphics.event_app -rammeverket.

Denne siden er appendiks til notatene om grafiske brukergrensesnitt.


Innebygde tastaturbindinger
from uib_inf100_graphics.event_app import run_app

def app_started(app):
    app.counter = 0

def timer_fired(app):
    app.counter += 1

def redraw_all(app, canvas):
    canvas.create_text(200,  40, text='Tastaturbindinger', font='Arial 20')
    canvas.create_text(200,  80, text='Trykk ctrl-p for pause')
    canvas.create_text(200, 120, text='Trykk ctrl-s for å ta skjermbilde')
    canvas.create_text(200, 260, text='Trykk ctrl-q for å lukke')
    canvas.create_text(200, 200, text='Trykk ctrl-x for å avslutte alt')
    canvas.create_text(200, 240, text=f'{app.counter}')

run_app(width=400, height=300) # ctrl-q fortsetter videre, ctrl-x gjør ikke
run_app(width=600, height=400) 
Illustrasjon av programmet over
Brukerhendelser (kontroller-funksjoner)
from uib_inf100_graphics.event_app import run_app

def app_started(app): 
    app.messages = ['app_started']

def app_stopped(app):
    app.messages.append('app_stopped')
    print('app_stopped!')

def key_pressed(app, event):
    app.messages.append('key_pressed: ' + event.key)

def key_released(app, event):
    app.messages.append('key_released: ' + event.key)

def mouse_pressed(app, event):
    app.messages.append(f'mouse_pressed {(event.x, event.y)}')

def mouse_released(app, event):
    app.messages.append(f'mouse_released {(event.x, event.y)}')

def mouse_moved(app, event):
    app.messages.append(f'mouse_moved {(event.x, event.y)}')

def mouse_dragged(app, event):
    app.messages.append(f'mouse_dragged {(event.x, event.y)}')

def size_changed(app):
    app.messages.append(f'size_changed {(app.width, app.height)}')

def redraw_all(app, canvas):
    font = 'Arial 20 bold'
    canvas.create_text(app.width/2, 30, text='Brukerhendelser', font=font)
    n = min(10, len(app.messages))
    i0 = len(app.messages) - n
    for i in range(i0, len(app.messages)):
        canvas.create_text(
            app.width / 2, 
            100 + 50 * (i - i0),
            text=f'#{i}: {app.messages[i]}',
            font=font
        )

run_app(width=600, height=600)
Dialogbokser og popup

Rammeverket uib_inf100_graphics bygger på og er en forenklet versjon av tkinter. Dette gjør det er mulig å integrere komponenter som for eksempel dialogbokser og meldingsbokser fra tkinter.

from uib_inf100_graphics.event_app import run_app
from tkinter import simpledialog, messagebox

def app_started(app):
    app.message = 'Klikk med musen for å starte!'
    app.count = 0

def timer_fired(app):
    app.count += 1

def mouse_pressed(app, event):
    name = simpledialog.askstring('Navn', 'Hva heter du?', parent=app._root)
    if name is None:
        app.message = 'Du avbrøt!'
    else:
        messagebox.showinfo('Info her', 'Du skrev: ' + name, parent=app._root)
        app.message = f'Hei, {name}!'

def redraw_all(app, canvas):
    canvas.create_text(200, 30, text=app.message)
    canvas.create_text(200, 70, text=f'{app.count}')

run_app(width=400, height=200)
Illustrasjon av programmet over
Bilder
# Vis et bilde hentet fra internett

from uib_inf100_graphics.event_app import run_app
from uib_inf100_graphics.helpers import load_image_http, scaled_image

def app_started(app):
    url = 'https://tinyurl.com/inf100logo-png'
    app.image1 = load_image_http(url)
    app.image2 = scaled_image(app.image1, 0.5)

def redraw_all(app, canvas):
    canvas.create_image(200, 150, pil_image=app.image1)
    canvas.create_image(500, 150, pil_image=app.image2)

run_app(width=700, height=300)
Illustrasjon av programmet over

For å vise et bilde lagret lokalt på datamaskinen, husk at filstien til bildet er relativ til mappen programmet kjøres fra, en mappe som ikke nødvendigvis er den samme mappen hvor programmet ligger. For å kjøre eksempelkoden under, last ned greensnake.png. Resultatet blir akkurat som over, men koden vil også fungere uten internett-tilgang.

# Vis et bilde lagret lokalt på datamaskinen

from uib_inf100_graphics.event_app import run_app
from uib_inf100_graphics.helpers import load_image, scaled_image

def app_started(app):
    app.image1 = load_image('greensnake.png')
    app.image2 = scaled_image(app.image1, 0.5)

def redraw_all(app, canvas):
    canvas.create_image(200, 150, pil_image=app.image1)
    canvas.create_image(500, 150, pil_image=app.image2)

run_app(width=700, height=300)
# Ankeret bestemmer hvor bildet tegnes i forhold til koordinatet (x, y)

from uib_inf100_graphics.event_app import run_app
from uib_inf100_graphics.helpers import load_image_http, scaled_image

def app_started(app):
    url = 'https://tinyurl.com/inf100logo-png'
    app.image1 = load_image_http(url)
    app.image2 = scaled_image(app.image1, 0.5)

def redraw_all(app, canvas):
    x, y = app.width/2, app.height/2
    # Prøv ulike verdier for anchor:
    # 'n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw', 'center'
    canvas.create_image(x, y, pil_image=app.image2, anchor='e')
    canvas.create_image(x, y, pil_image=app.image2, anchor='sw')
    canvas.create_oval(x-8, y-8, x+8, y+8, fill='yellow')

run_app(width=700, height=300)
Illustrasjon av koden over
# Finn bildet sin størrelse

from uib_inf100_graphics.event_app import run_app
from uib_inf100_graphics.helpers import load_image_http, scaled_image

def app_started(app):
    url = 'https://tinyurl.com/inf100logo-png'
    app.image1 = load_image_http(url)
    app.image2 = scaled_image(app.image1, 0.5)
    app.image3 = scaled_image(app.image1, 0.3)

def draw_image_with_size_below_it(app, canvas, image, cx, cy):
    canvas.create_image(cx, cy, pil_image=image)
    image_width, image_height = image.size
    msg = f'Bildestørrelse: {image_width} x {image_height}'
    canvas.create_text(cx, cy + image_height/2 + 20, text=msg, font='Arial 16')

def redraw_all(app, canvas):
    draw_image_with_size_below_it(app, canvas, app.image2, 200, 150)
    draw_image_with_size_below_it(app, canvas, app.image3, 500, 150)

run_app(width=700, height=300)
Illustrasjon av koden over
# Bruk transpose-metoden for å snu eller flippe et bilde

from uib_inf100_graphics.event_app import run_app
from uib_inf100_graphics.helpers import load_image_http, scaled_image
from PIL import Image

def app_started(app):
    url = 'https://tinyurl.com/inf100logo-png'
    org_image = load_image_http(url)
    org_image = scaled_image(org_image, 1/5)

    # Rotere et bilde 90 grader
    rotated_image = org_image.transpose(Image.ROTATE_90)
    app.images = [org_image, rotated_image]

    # Flere måter å rotere/flippe et bilde på
    app.images += [org_image.transpose(tp_method) for tp_method in (
        Image.ROTATE_180,
        Image.ROTATE_270,
        Image.FLIP_LEFT_RIGHT,
        Image.TRANSPOSE,
        Image.FLIP_TOP_BOTTOM,
        Image.TRANSVERSE,
    )]

def redraw_all(app, canvas):
    allocated_width_per_image = app.width / len(app.images)
    for i, img in enumerate(app.images):
        x = (i + 0.5) * allocated_width_per_image
        y = app.height / 2
        canvas.create_image(x, y, pil_image=img)

run_app(width=700, height=200)
Illustrasjon av koden over
# Hent farger med getpixel, og manipulér eller tegn nye bilder med putpixel

from uib_inf100_graphics.event_app import run_app
from uib_inf100_graphics.helpers import load_image_http
from PIL import Image

def app_started(app):
    url = 'https://tinyurl.com/inf100logo-png'
    app.image1 = load_image_http(url)

    # la oss lage en kopi som bytter om blå og grønn -verdier
    app.image1 = app.image1.convert('RGB')
    app.image2 = Image.new(mode='RGB', size=app.image1.size)
    for x in range(app.image2.width):
        for y in range(app.image2.height):
            r, g, b = app.image1.getpixel((x, y))
            app.image2.putpixel((x, y), (r, b, g)) # merk: b og g byttet plass

def redraw_all(app, canvas):
    canvas.create_image(200, 300, pil_image=app.image1)
    canvas.create_image(500, 300, pil_image=app.image2)

run_app(width=700, height=600)
Illustrasjon av koden over
# Bruk ImageDraw for å lage nye bilder og tegne på dem

from uib_inf100_graphics.event_app import run_app
from uib_inf100_graphics.helpers import scaled_image
from PIL import Image, ImageDraw

def app_started(app):
    image_width, image_height = app.width//3, app.height//2
    bg_color = (0, 255, 255) # cyan
    app.image1 = Image.new('RGB', (image_width, image_height), bg_color)

    # Nå som vi har laget et nytt bilde, bruk ImageDraw for å tegne i det
    # Se https://pillow.readthedocs.io/en/stable/reference/ImageDraw.html
    draw = ImageDraw.Draw(app.image1)
    draw.line((0, 0, image_width, image_height), width=10, fill=(255, 0, 0))
    draw.line((0, image_height, image_width, 0), width=10, fill=(0, 0, 255))

    # Så lager vi en skalert kopi for å vise at dette er et helt vanlig bilde
    app.image2 = scaled_image(app.image1, 2/3)

def redraw_all(app, canvas):
    canvas.create_image(app.width/4, app.height/2, pil_image=app.image1)
    canvas.create_image(app.width*3/4, app.height/2, pil_image=app.image2)

run_app(width=600, height=300)
Illustrasjon av koden over
# Bruk get_snapshot og save_snapshot for å ta bilde av programmet
# Merk: dette er litt ustabilt på noen operativsystemer, og kan
# kreve at du gir programmet spesiell tilgang for å ta screenshot

from uib_inf100_graphics.event_app import run_app
from uib_inf100_graphics.helpers import scaled_image

def app_started(app):
    app.image = None

def key_pressed(app, event):
    if (event.key == 'g'):
        snapshotImage = app.get_snapshot()
        app.image = scaled_image(snapshotImage, 0.45)
    elif (event.key == 's'):
        app.save_snapshot()

def redraw_all(app, canvas):
    canvas.create_text(350, 20, text='Press g to get_snapshot', fill='black')
    canvas.create_text(350, 40, text='Press s to save_snapshot', fill='black')
    canvas.create_rectangle(50, 100, 250, 250, fill='blue')
    if app.image is not None:
        canvas.create_image(525, 160, pil_image=app.image)

run_app(width=700, height=300)
Illustrasjon av koden over
Animerte bilder

En sprite er et bilde/en tegning som representerer et objekt som utgjør en (liten) del av skjermbildet. I sammenheng med dataspill vil en sprite typisk være knyttet til et logisk element i spillet, slik som en figur, et hus, en fiende, en energidrikk, et stykke med gress eller lignende. Det er vanlig at sprites er animert; for eksempel en animasjon av at figuren går. En slik animasjon består av en sekvens med bilder som spilles av i ring. Under er et eksempel på en bildefil som representerer en animert figur.

Filen sprite-strekmann referert til over

Bilde: CC0, Walkin man figure av JayNick, via freesvg.org.

For å animere denne spriten, klipper vi opp bildet i 8 like store biter, og viser dem frem etter hverandre i et sirkulært mønster.

# En demo for å animere sprites med bruk av Pillow/PIL
# crop-metoden klipper ut en del av bildet
# Se her for flere detaljer:
# https://pillow.readthedocs.io/en/stable/reference/Image.html

from uib_inf100_graphics.event_app import run_app
from uib_inf100_graphics.helpers import load_image_http

def app_started(app):
    # Bilde av JayNick, CC0, https://freesvg.org/walking-man-figure
    url = 'https://tinyurl.com/inf100-strekmann-png'
    spritestrip = load_image_http(url)

    # Vi lager en liste som inneholder hvert av de 8 bildene separat
    app.sprites = []
    image_width, image_height = spritestrip.size
    sprite_width = image_width / 8
    for i in range(8):
        left_x = i * sprite_width
        right_x = left_x + sprite_width
        sprite = spritestrip.crop((left_x, 0, right_x, image_height))
        app.sprites.append(sprite)
    app.sprite_counter = 0

def timer_fired(app):
    # Gå til neste bilde i bilde-sekvensen
    app.sprite_counter = (1 + app.sprite_counter) % len(app.sprites)

def redraw_all(app, canvas):
    # Bakgrunnsfarge
    bg = 'papaya whip'
    canvas.create_rectangle(0, 0, app.width, app.height, fill=bg, outline='')
    # Tegn strekmann
    sprite = app.sprites[app.sprite_counter]
    canvas.create_image(200, 200, pil_image=sprite)

run_app(width=400, height=400)
Illustrasjon av programmet over

Gif er en type bilder som er animert i utgangspunktet, slik som bildet under. Vi kan bruke animerte gif’er for å lage en animert sprite. Det er mulig å hente ut timing-informasjon for hvert stillbilde i gif’en, men det krever en del omtanke å få programmet ditt til å vise gif’en med riktig timing (det skjer ikke automatisk).

Animert gif

Bilde: CC0, «Traffic light» fra EditableGIFs.com

# En demo for å vise animert gif/apng sprites

from uib_inf100_graphics.event_app import run_app
from uib_inf100_graphics.helpers import load_image_http
from datetime import datetime, timedelta

def app_started(app):
    animation = load_image_http('https://files.stromme.me/inf100/traffic.gif')
    app.frames = extract_frames(animation)
    app.frame_index = 0
    app.last_frame_switch = datetime.now()

def extract_frames(multiframe_image):
    '''Given an image with multiple frames (such as a gif), return a list of
    individual frames. The frames will be tagged with their original duration
    in the 'duration' property of the 'info' dictionary.'''
    frames = []
    i = 0
    while True:
        try:
            multiframe_image.seek(i)
        except EOFError:
            break
        duration = multiframe_image.info['duration']
        frames.append(multiframe_image.copy())
        frames[-1].info['duration'] = duration
        i += 1
    return frames

def timer_fired(app):
    maintain_animation(app)

def maintain_animation(app):
    '''Ensure that the animation is displayed at the intended speed.
    This method interprets the duration as a minimum display time for
    the frame, and will hence drift over time.'''
    current_frame = app.frames[app.frame_index]
    current_time = datetime.now()
    min_display_time = timedelta(milliseconds=current_frame.info['duration'])
    time_since_last_frame_switch = current_time - app.last_frame_switch
    if min_display_time < time_since_last_frame_switch:
        # Go to next frame
        app.frame_index = (1 + app.frame_index) % len(app.frames)
        app.last_frame_switch = current_time

def redraw_all(app, canvas):
    current_frame = app.frames[app.frame_index]
    canvas.create_image(app.width/2, app.height/2, pil_image=current_frame)
    canvas.create_text(app.width/2, 20, text=(
        f'{app.frame_index}/{len(app.frames)} '
        f'{current_frame.info["duration"]=} '
    ))

run_app(width=500, height=300)
Illustrasjon av programmet over
Modus (ulike skjermer)

from uib_inf100_graphics.event_app import run_app
from uib_inf100_graphics.helpers import load_image_http, scaled_image
import random

##########################################
# Splash-skjerm -modus
##########################################

def splash_screen_mode_redraw_all(app, canvas):
    canvas.create_image(
        app.width / 2, 
        app.height / 2, 
        pil_image=app.splash_image,
    )
    canvas.create_text(
        app.width / 2, 
        app.height / 2,
        text=(
            'Demo av en modal applikasjon!\n'
            'Dette er en modal skjerm!\n'
            'Trykk en tast for å starte!'
        ), 
        font=app.default_font,
        justify='center',
    )

def splash_screen_mode_key_pressed(app, event):
    app.mode = 'game_mode'

##########################################
# Spill -modus
##########################################

def game_mode_redraw_all(app, canvas):
    canvas.create_text(
        app.width / 2,
        10,
        text=(
            f'Poeng: {app.score}\n'
            'Klikk på rundingen!\n'
            'Trykk h for hjelpe-skjerm\n'
            'Trykk v for å bryte MVC'
        ),
        font=app.default_font,
        justify='center',
        anchor='n',
    )
    draw_dot(canvas, app.x, app.y, app.r, app.color)
    if app.make_MVC_violation:
        app.ohNo = 'Gjør et brudd med MVC skjer her!'
    
def game_mode_timer_fired(app):
    move_dot(app)

def game_mode_mouse_pressed(app, event):
    d = ((app.x - event.x)**2 + (app.y - event.y)**2)**0.5
    if d <= app.r:
        app.score += 1
        randomize_dot(app)
    elif app.score > 0:
        app.score -= 1

def game_mode_key_pressed(app, event):
    if event.key == 'h':
        app.mode = 'help_mode'
    elif event.key == 'v':
        app.make_MVC_violation = True

##########################################
# Hjelpeskjerm -modus
##########################################

def help_mode_redraw_all(app, canvas):
    canvas.create_text(
        app.width / 2,
        app.height / 2,
        text=(
            'Her er hjelpeskjermen!\n'
            '(hjelpsom melding her)\n'
            'Trykk en tast for å snu'
        ),
        font=app.default_font,
        justify='center',
    )

def help_mode_key_pressed(app, event):
    app.mode = 'game_mode'

##########################################
# Felles hjelpefunksjoner
##########################################

def randomize_dot(app):
    app.x = random.randrange(20, app.width - 20)
    app.y = random.randrange(20, app.height - 20)
    app.r = random.randrange(10, 20)
    app.color = random.choice(['red', 'orange', 'yellow', 'green', 'blue'])
    app.dx = random.choice([+1, -1]) * random.randrange(3, 7)
    app.dy = random.choice([+1, -1]) * random.randrange(3, 7)

def move_dot(app):
    app.x += app.dx
    if (app.x < 0) or (app.x > app.width):
        app.dx = -app.dx
    app.y += app.dy
    if (app.y < 0) or (app.y > app.height):
        app.dy = -app.dy

def draw_dot(canvas, x, y, r, color):
    canvas.create_oval(x - r, y - r, x + r, y + r, fill=color)

##########################################
# Oppstart av applikasjonen
##########################################

def app_started(app):
    splash_img_url = 'https://files.stromme.me/inf100/splashbox-alpha.png'
    app.splash_image = load_image_http(splash_img_url)
    app.splash_image = scaled_image(app.splash_image, 0.85)
    app.default_font = 'Arial 20'
    app.mode = 'splash_screen_mode'
    app.score = 0
    app.timer_delay = 50
    app.make_MVC_violation = False
    randomize_dot(app)

run_app(width=500, height=250)
Illustrasjon av programmet over
Scrolling
# Sidelengs scrolling (hele verden beveger seg):

from uib_inf100_graphics.event_app import run_app
import random

def app_started(app):
    app.scroll_x = 0
    app.dots = []
    for _ in range(50):
        x = random.randrange(app.width)
        y = random.randrange(60, app.height)
        app.dots.append((x, y))

def key_pressed(app, event):
    if event.key == 'Left': 
        app.scroll_x -= 5
    elif event.key == 'Right':
        app.scroll_x += 5

def redraw_all(app, canvas):
    # Tegn spilleren (som alltid er midt på skjermen)
    draw_dot(canvas, app.width/2, app.height/2, 10, 'cyan')

    # Tegn prikkene, sideforskjøvet med et offset scroll_x
    for (cx, cy) in app.dots:
        cx -= app.scroll_x  # <-- Her sideforskyver vi prikkene på lerretet
        draw_dot(canvas, cx, cy, 10, 'lightGreen')

    # Tegn x- og y-aksen
    x = app.width/2 - app.scroll_x # <-- Her sideforskyver vi
    y = app.height/2
    canvas.create_line(x, 0, x, app.height)
    canvas.create_line(0, y, app.width, y)

    # Tegn instruksjoner og debug-informasjon
    x = app.width/2
    canvas.create_text(x, 20, text='Bruk piltaster for å flytte spilleren')
    canvas.create_text(x, 40, text=f'{app.scroll_x = }')

def draw_dot(canvas, x, y, r, color):
    canvas.create_oval(x - r, y - r, x + r, y + r, fill=color)

run_app(width=300, height=300)
Illustrasjon av programmet over
# Sidelengs scrolling når figuren er på vei ut av skjermen

from uib_inf100_graphics.event_app import run_app
import random

def app_started(app):
    app.scroll_x = -app.width/2 # Initiell scroll slik at x=0 er sentrert
    app.scroll_margin = 50
    app.player_x = 0 # spillerens initielle posisjon
    app.dots = [(random.choice(range(-app.width//2, app.width//2)),
                  random.choice(range(60, app.height))) for _ in range(50)]

def make_player_visible(app):
    # scroll skjermen så mye som nødvendig for at spilleren vises
    if (app.player_x < app.scroll_x + app.scroll_margin):
        app.scroll_x = app.player_x - app.scroll_margin
    if (app.player_x > app.scroll_x + app.width - app.scroll_margin):
        app.scroll_x = app.player_x - app.width + app.scroll_margin

def move_player(app, dx, dy):
    app.player_x += dx
    make_player_visible(app)

def key_pressed(app, event):
    if (event.key == "Left"):    move_player(app, -5, 0)
    elif (event.key == "Right"): move_player(app, +5, 0)

def redraw_all(app, canvas):
    # Tegn spilleren, sideforskjøvet med et offset scroll_x
    cx, cy, r = app.player_x, app.height/2, 10
    cx -= app.scroll_x # <-- Her sideforskyver vi spilleren
    canvas.create_oval(cx-r, cy-r, cx+r, cy+r, fill='cyan')

    # Tegn prikkene, sideforskjøvet med et offset scroll_x
    for (cx, cy) in app.dots:
        cx -= app.scroll_x  # <-- Her sideforskyver vi prikkene
        canvas.create_oval(cx-r, cy-r, cx+r, cy+r, fill='lightGreen')

    # Tegn x- og y-aksen
    x = -app.scroll_x # <-- Her sideforskyver vi y-aksen
    y = app.height/2
    canvas.create_line(x, 0, x, app.height)
    canvas.create_line(0, y, app.width, y)

    # Tegn instruksjoner og debug-informasjon
    x = app.width/2
    canvas.create_text(x, 20, text='Use arrows to move left or right',
                       fill='black')
    canvas.create_text(x, 40, text=f'app.scroll_x = {app.scroll_x}',
                       fill='black')

run_app(width=300, height=300)
# Sidelengs scrolling når figuren er på vei ut av skjermen
# Kollisjonsdeteksjon for spilleren

from uib_inf100_graphics.event_app import run_app

def app_started(app):
    # Scrolling
    app.scroll_margin = 50
    app.scroll_x = -app.scroll_margin
    app.scroll_y = -app.height / 2

    # Spiller - koordinate for hjørnet til venstre nederst
    app.player_x = 0
    app.player_y = 0
    app.player_width = 10
    app.player_height = 20

    # Murer
    app.walls = 5
    app.wall_points = [0]*app.walls
    app.wall_width = 20
    app.wall_height = 40
    app.wall_x_positions = [90 * (i + 1) for i in range(app.walls)]
    app.current_wall_hit = -1 # -1 når ingen mur er truffet

def get_player_bounds(app):
    # returnerer absolutt-posisjon til spiller på et uendelig lerret
    # tar ikke hensyn til scrolling
    (x0, y1) = (app.player_x, app.player_y)
    (x1, y0) = (x0 + app.player_width, y1 - app.player_height)
    return (x0, y0, x1, y1)

def get_wall_bounds(app, wall):
    # returnerer absolutt-posisjon til en mur på et uendelig lerret
    # tar ikke hensyn til scrolling
    (x0, y1) = (app.wall_x_positions[wall], 0)
    (x1, y0) = (x0 + app.wall_width, y1 - app.wall_height)
    return (x0, y0, x1, y1)

def get_wall_hit(app):
    # returnerer hvilken mur spilleren er over for øyeblikket
    # merk: i større spill bør denne funksjonen optimeres til å kun
    # sjekke de objektene som er synlige.
    player_bounds = get_player_bounds(app)
    for wall in range(app.walls):
        wall_bounds = get_wall_bounds(app, wall)
        if bounds_intersect(app, player_bounds, wall_bounds):
            return wall
    return -1

def bounds_intersect(app, bounds_a, bounds_b):
    # return l2<=r1 and t2<=b1 and l1<=r2 and t1<=b2
    (ax0, ay0, ax1, ay1) = bounds_a
    (bx0, by0, bx1, by1) = bounds_b
    return ((ax1 >= bx0) and (bx1 >= ax0) and
            (ay1 >= by0) and (by1 >= ay0))

def check_for_new_wall_hit(app):
    # sjekk om vi treffer en ny mur for første gang
    wall = get_wall_hit(app)
    if (wall != app.current_wall_hit):
        app.current_wall_hit = wall
        if (wall >= 0):
            app.wall_points[wall] += 1

def make_player_visible(app):
    # scroll for å gjøre spilleren synlig
    m = app.scroll_margin
    if (app.player_x < app.scroll_x + m):
        app.scroll_x = app.player_x - m
    if (app.player_x > app.scroll_x + app.width - m - app.player_width):
        app.scroll_x = app.player_x - app.width + m + app.player_width
    if (app.player_y < app.scroll_y + m + app.player_height):
        app.scroll_y = app.player_y - m - app.player_height
    if (app.player_y > app.scroll_y + app.height - m):
        app.scroll_y = app.player_y - app.height + m

def move_player(app, dx, dy):
    app.player_x += dx
    app.player_y += dy
    make_player_visible(app)
    check_for_new_wall_hit(app)

def size_changed(app):
    make_player_visible(app)

def mouse_pressed(app, event):
    app.player_x = event.x + app.scroll_x - app.player_width/2
    app.player_y = event.y + app.scroll_y + app.player_height/2
    make_player_visible(app)
    check_for_new_wall_hit(app)

def key_pressed(app, event):
    if (event.key == "Left"):    move_player(app, -5, 0)
    elif (event.key == "Right"): move_player(app, +5, 0)
    elif (event.key == "Up"):    move_player(app, 0, -5)
    elif (event.key == "Down"):  move_player(app, 0, +5)

def redraw_all(app, canvas):
    sx = app.scroll_x
    sy = app.scroll_y

    # Tegn x-aksen
    line_y = -sy
    line_height = 5
    canvas.create_rectangle(0, line_y, app.width, line_y+line_height,
                            fill="black")
    # Tegn murene
    # (Merk: kan optimiseres til å kun tegne synlige murer)
    for wall in range(app.walls):
        (x0, y0, x1, y1) = get_wall_bounds(app, wall)
        fill = "orange" if (wall == app.current_wall_hit) else "pink"
        canvas.create_rectangle(x0-sx, y0-sy, x1-sx, y1-sy, fill=fill)
        (cx, cy) = ((x0+x1)/2 - sx, (y0 + y1)/2 - sy)
        canvas.create_text(cx, cy, text=str(app.wall_points[wall]),
                            fill='black')
        cy = line_y + 5
        canvas.create_text(cx, cy, text=str(wall), anchor=N, fill='black')

    # Tegn spilleren
    (x0, y0, x1, y1) = get_player_bounds(app)
    canvas.create_oval(x0 - sx, y0 - sy, x1 - sx, y1 - sy, fill="cyan")

    # Tegn instruksjonene
    msg = "Bruk pilene eller musen for å flytte"
    canvas.create_text(app.width/2, 20, text=msg, fill='black')

run_app(width=300, height=300)
Spille av lyder

For å spille av lyder må du først installere pygame, og så laste ned uib_inf100_music.py og legge den i samme mappe som python-filen du kjører. For å installere pygame:

For at eksempelet under skal virke, må du også laste ned button.mp3 og music.mp3 i samme mappe programmet kjøres fra (som ikke nødvendigvis er samme mappe hvor programmet ligger).

from uib_inf100_graphics.event_app import run_app
from uib_inf100_music import load_sound_effect, load_looping_sound, stop_all_sounds

def app_started(app):
    app.sound_effect = load_sound_effect('button.mp3')
    app.background_music = load_looping_sound('music.mp3')

def app_stopped(app):
    stop_all_sounds()

def key_pressed(app, event):
    match event.key:
        case 'b':
            # toggle background music
            if app.background_music.is_playing():
                app.background_music.stop()
            else:
                app.background_music.play()
        case 'e':
            # play sound effect
            app.sound_effect.play()
        case 's':
            # stop all sounds
            stop_all_sounds()
        case _ if event.key.isdigit():
            # set volume
            new_volume = float(event.key)/9
            app.background_music.set_volume(new_volume)

def redraw_all(app, canvas):
    text = (
        f'{app.background_music.is_playing()=}\n'
        f'{app.background_music.get_volume()=}\n\n'
        'Trykk b for å starte/stoppe bakgrunnslyd\n'
        'Trykk e for å spille en lydeffekt\n'
        'Trykk s for å stoppe alle lyder\n'
        'Trykk 0-9 for å sette volum for bakgrunnslyd'
    )
    canvas.create_text(app.width/2, app.height/2, text=text, font='Arial 20')

run_app(width=600, height=300)