Mer interaktiv grafikk

Innholdet på denne siden er ikke en del av pensum i INF100 dette semesteret, og vil ikke komme på eksamen. Vi publiserer det her slik at dem som ønsker å jobbe med åpen oppgave kan få inspirasjon og lære mer om mulighetene som finnes i uib_inf100_graphics -rammeverket.


Innebygde tastaturbindinger
from uib_inf100_graphics import *

def app_started(app):
    app.counter = 0

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

def redraw_all(app, canvas):
    canvas.create_text(200,  50,
                       text='Tastaturbindinger demo',
                       font='Arial 20',
                       fill='black')
    canvas.create_text(200, 100,
                       text='Trykk ctrl-p for pause', fill='black')
    canvas.create_text(200, 150,
                       text='Trykk ctrl-s for å ta skjermbilde', fill='black')
    canvas.create_text(200, 200,
                       text='Trykk ctrl-q for å lukke', fill='black')
    canvas.create_text(200, 250,
                       text='Trykk ctrl-x for å avslutte alt', fill='black')
    canvas.create_text(200, 300, text=f'{app.counter}', fill='black')

run_app(width=400, height=400) # ctrl-q fortsetter videre, ctrl-x gjør ikke
run_app(width=600, height=600)
Brukerstyrte hendelser
from uib_inf100_graphics import *

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='Brukerstyrte hendelser',
                       font=font, fill='black')
    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, fill='black')

run_app(width=600, height=600)
Dialogbokser og popup
from uib_inf100_graphics import *

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 = app.get_user_input('Hva heter du?')
    if (name == None):
        app.message = 'Du avbrøt!'
    else:
        app.show_message('Du skrev: ' + name)
        app.message = f'Hei, {name}!'

def redraw_all(app, canvas):
    font = 'Arial 24 bold'
    canvas.create_text(app.width/2,  app.height/2,
                       text=app.message, font=font, fill='black')
    canvas.create_text(app.width/2,  app.height*5/6,
                       text=f"{app.count}", font=font, fill='black')

run_app(width=500, height=300)
Bilder
# Vis et bilde hentet fra internett

from uib_inf100_graphics import *

def app_started(app):
    url = 'https://tinyurl.com/inf100logo-png'
    app.image1 = app.load_image(url)
    app.image2 = app.scale_image(app.image1, 2/3) # Skalérer bildet

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)

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 testbilde.gif.

# Vis et bilde lagret lokalt på datamaskinen

from uib_inf100_graphics import *

def app_started(app):
    app.image1 = app.load_image('testbilde.gif')
    app.image2 = app.scale_image(app.image1, 2/3)

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)
# Ankeret bestemmer hvor bildet tegnes i forhold til koordinatet (x, y)

from uib_inf100_graphics import *

def app_started(app):
    url = 'https://tinyurl.com/inf100logo-png'
    app.image1 = app.load_image(url)
    app.image2 = app.scale_image(app.image1, 1/2) # Skalérer bildet

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="n")
    canvas.create_image(x, y, pil_image=app.image2, anchor="sw")
    canvas.create_oval(x-10, y-10, x+10, y+10, fill="yellow")

run_app(width=700, height=600)
# Finn bildet sin størrelse

from uib_inf100_graphics import *

def app_started(app):
    url = 'https://tinyurl.com/inf100logo-png'
    app.image1 = app.load_image(url)
    app.image2 = app.scale_image(app.image1, 2/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 20 bold', fill='black')

def redraw_all(app, canvas):
    draw_image_with_size_below_it(app, canvas, app.image1, 200, 300)
    draw_image_with_size_below_it(app, canvas, app.image2, 500, 300)

run_app(width=700, height=600)
# Bruk transpose-metoden for å snu eller flippe et bilde

from uib_inf100_graphics import *

def app_started(app):
    url = 'https://tinyurl.com/inf100logo-png'
    org_image = app.load_image(url)
    org_image = app.scale_image(org_image, 1/4)

    # 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)
# Hent farger med getpixel, og manipulér eller tegn nye bilder med putpixel

from uib_inf100_graphics import *

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

    # la oss lage en kopi som kun bruker rødfarge
    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,0,0))

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

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

from uib_inf100_graphics import *

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 = app.scale_image(app.image1, 2/3)

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)
# 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 import *

def app_started(app):
    app.image = None

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

def redraw_all(app, canvas):
    canvas.create_text(350, 20, text='Press g to getSnapshot', fill='black')
    canvas.create_text(350, 40, text='Press s to saveSnapshot', fill='black')
    canvas.create_rectangle(50, 100, 250, 500, fill='cyan')
    if (app.image != None):
        canvas.create_image(525, 300, image=ImageTk.PhotoImage(app.image))

run_app(width=700, height=600)
Animerte bilder

En sprite er et bilde/en tegning som representerer 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 import *

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

    # Vi lager en liste som inneholder hvert av 8 bildene separat
    app.sprites = []
    image_width, image_height = spritestrip.size
    sprite_width = image_width / 8 # det er 8 bilder i denne animasjonen
    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
    canvas.create_rectangle(0, 0, app.width, app.height, 
                            fill="papaya whip", width=0)
    # Tegn strekmann
    sprite = app.sprites[app.sprite_counter]
    canvas.create_image(200, 200, pil_image=sprite)

run_app(width=400, height=400)

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. Merk at timing-informasjon som egentli er lagret i gif’en ikke blir inkludert når vi bruker funksjonen for å importere den vist under.

Animert gif

Bilde: CC0, Kurtkaiser via Wikimedia Commons.

For å kjøre koden under, last ned ild-sprite.gif og ha den i samme mappe som programmet kjøres fra.

# En demo for å vise animert gif sprites med bruk av Pillow/PIL
# Timing-informasjon fra gif går tapt med denne metoden

from uib_inf100_graphics import *

def app_started(app):
    app.timer_delay = 30
    app.sprite_images = load_animated_gif('ild-sprite.gif')
    app.sprite_counter = 0

def load_animated_gif(path):
    # vi laster første bilde utenfor try/except slik at vi krasjer så fort
    # som mulig hvis vi ikke finner noe bilde i det hele tatt.
    sprite_images = [ PhotoImage(file=path, format='gif -index 0') ]
    i = 1
    while True:
        try:
            options = f'gif -index {i}'
            sprite_images.append(PhotoImage(file=path, format=options))
            i += 1
        except Exception as e:
            return sprite_images

def timer_fired(app):
    app.sprite_counter = (1 + app.sprite_counter) % len(app.sprite_images)

def redraw_all(app, canvas):
    photo_image = app.sprite_images[app.sprite_counter]
    canvas.create_image(app.width/2, app.height/2, image=photo_image)

run_app(width=500, height=500)
Modus (ulike skjermer)
# Demonstrasjon av mode (ulike skjermer).

from uib_inf100_graphics import *
import random

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

def splash_screen_mode_redraw_all(app, canvas):
    font = 'Arial 26 bold'
    canvas.create_text(app.width/2, 150, text='Demo av en modal applikasjon!',
                       font=font, fill='black')
    canvas.create_text(app.width/2, 200, text='Dette er en modal skjem!',
                       font=font, fill='black')
    canvas.create_text(app.width/2, 250, text='Trykk en tast for å starte!',
                       font=font, fill='black')

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

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

def game_mode_redraw_all(app, canvas):
    font = 'Arial 26 bold'
    canvas.create_text(app.width/2, 20, text=f'Poeng: {app.score}',
                       font=font, fill='black')
    canvas.create_text(app.width/2, 60, text='Klikk på rundingen!',
                       font=font, fill='black')
    canvas.create_text(app.width/2, 100, text='Trykk h for hjelpe-skjerm',
                       font=font, fill='black')
    canvas.create_text(app.width/2, 140, text='Trykk v for å bryte MVC',
                       font=font, fill='black')
    canvas.create_oval(app.x-app.r, app.y-app.r, app.x+app.r, app.y+app.r,
                       fill=app.color)
    if app.make_MVC_violation:
        app.ohNo = 'Et brudd med MVC!'

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):
    font = 'Arial 26 bold'
    canvas.create_text(app.width/2, 150, text='Her er hjelpeskjermen!', 
                       font=font, fill='black')
    canvas.create_text(app.width/2, 250, text='(Hjelpsom melding her)',
                       font=font, fill='black')
    canvas.create_text(app.width/2, 350, text='Trykk en tast for å snu',
                       font=font, fill='black')

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

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

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

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

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

def app_started(app):
    app.mode = 'splash_screen_mode'
    app.score = 0
    app.timer_delay = 50
    app.make_MVC_violation = False
    randomize_dot(app)

run_app(width=600, height=500)
Scrolling
# Sidelengs scrolling (hele verden beveger seg):

from uib_inf100_graphics import *
import random

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

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)
    cx, cy, r = app.width/2, app.height/2, 10
    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 på lerretet
        canvas.create_oval(cx-r, cy-r, cx+r, cy+r, fill='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',
                       fill='black')
    canvas.create_text(x, 40, text=f'{app.scroll_x = }',
                       fill='black')

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

from uib_inf100_graphics import *
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 import *

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 filen du kjører (ved siden av uib_inf100_graphics.py). For å installere pygame:

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

from uib_inf100_graphics import *
from uib_inf100_music import load_sound

def app_started(app):
    app.sound = load_sound("button.mp3")

def app_stopped(app):
    app.sound.stop()

def key_pressed(app, event):
    if (event.key == 's'):
        if app.sound.is_playing(): app.sound.stop()
        else: app.sound.start()
    elif (event.key == 'l'):
        app.sound.start(loops=-1)
    elif event.key.isdigit():
        app.sound.start(loops=int(event.key))

def timer_fired(app):
    pass

def redraw_all(app, canvas):
    font = "Arial 20"
    for i, text in enumerate((
        f'{app.sound.path} ({app.sound.loops = })',
        f'{app.sound.is_playing() = }',
        'Trykk s for å starte/stoppe lyd',
        'Trykk et tall for å spille lyden så mange ganger',
        'Trykk l for å spille av i evig løkke',
    )): 
        canvas.create_text(app.width/2, (i+1) * 30, text=text,
                            font=font, fill='black')

run_app(width=600, height=200)