Eksamen

28. november 2022
4 timer
alle hjelpemidler tillatt
 

Spørsmålene (uten fasit) finner du her som pdf.


Løsningsforslag og utkast til sensorveiledning. Merk at sensorkomitéen står fritt til å endre sensorveiledningen etter eget forgodtbefinnende.


Oppgave 1: typer

0.5 poeng per riktig svar, totalt 5 mulige poeng.

d[a[-1]] == b ->    <class 'bool'>
e             ->    <class 'bool'>
b + 2         ->    (-error-)
f"{c}"        ->    <class 'str'>
a[0]          ->    <class 'int'>
9 * a[1]      ->    <class 'float'>
a in a        ->    <class 'bool'>
a[0:1]        ->    <class 'list'>
e and not e   ->    <class 'bool'>
c < 42        ->    <class 'bool'>
b + "a[0]"    ->    <class 'str'>
d["c"]        ->    (-error-)

Oppgave 2: presedens

2 poeng per riktig svar, totalt 4 mulige poeng.

  • 12 // 2 * 3 er det samme som (12 // 2) * 3

  • x and y or z in a er det samme som (x and y) or (z in a)

Oppgave 3: logiske operasjoner

0.5 poeng per riktig svar, totalt 5 mulige poeng. 0.2 poeng for ubesvart.

| a     | b     | c     | a or (b and c) | (not a) and b |
|-------+-------+-------+----------------+---------------|
| True  | True  | True  | True           | False         |
| False | False | True  | False          | False         |
| False | True  | True  | True           | True          |
| False | True  | False | False          | True          |
| 0     | "foo" | "bar" | "bar"          | "foo"         |

Oppgave 4: oppslagsverk

1 poeng per riktig svar, totalt 4 mulige poeng.

d = {
    42 : 0,
    '42' : 'bar',
    'foo' : 42,
    95 : 'foo',
    'bar' : 95,
}

print(d[95] == 'foo')
print('bar' == d['42'])
print(0 in d.values())
print('foo' == d[d['bar']])

Oppgave 5: lister

1 poeng per riktig svar, totalt 5 mulige poeng.

a = [[1, 2, 3], "foo", [42], 95]

print(a[0] == [1, 2, 3])
print("foo" == a[1])
print(95 == a[-1])
print(42 in a[2])
print(3 == a[::4][-1][-1])

Oppgave 6: if-elif-else

1 poeng per riktig svar, totalt 4 mulige poeng.

Merk at i tredje valg på denne oppgaven finnes flere muligheter for å ikke skrive ut C, men bare ett av dem er riktig på den måten at det kan kombineres med et valg i fjerde deloppgave som gir riktig utskrift der.

x = "foo"

if len(x) <= 3:
    print("A")
elif "foo" == x:
    print("B")
if not True:
    print("C")
else:
    print("D")

Oppgave 7: 2d-liste

2 poeng for riktig svar.

# Bruk "se steg" for å se at minnets tilstand stemmer
# Klikk "kjør" for å se svaret
a = [[95, 24], [42], 100]
print(a[-3])

Oppgave 8: 2d-liste med alias

Totalt 5 mulige poeng.

Det kan trekkes opp til 2 poeng dersom koden er svært omstendelig, merkelig eller mer kompleks enn nødvendig. Det kan også gis opp til 2 poeng dersom koden ikke oppfyller noen av punktene over, men likevel gir uttrykk for forståelse av oppgaven.

# Eksempelløsning (5/5 poeng)
a = [['foo', 'bar'], 'a']
b = [a[0], 'b']

# Denne varianten gir også full uttelling
temp = ['foo', 'bar']
a = [temp, 'a']
b = [temp, 'b']
# Denne varianten gir kun 3/5 poeng, siden a[0] og b[0] er ikke aliaser
a = [['foo', 'bar'], 'a']
b = [['foo', 'bar'], 'b']

Oppgave 9: skriv ut feil og advarsler

Totalt 10 mulige poeng.

# Eksempelløsning (10/10 poeng)
def print_errors_and_warnings(a):
    for msg in a:
        if msg.startswith('Error') or msg.startswith('Warning'):
            print(msg)

print_errors_and_warnings([
  "Info: had a good nights sleep before exam",
  "Warning: not enough snacks brought for exam",
  "Info: went for a walk before exam started",
  "Error: went to wrong exam location",
  "Debug: laptop_charge=92%, charing_cable_present=True",
  "Warning: time management is important",
])

# Denne er litt på grensen med tanke på lesbarhet og den skjønnsmessige
# vurderingen, men gir fremdeles 10/10 poeng.
def print_errors_and_warnings(a):
    for i in range(len(a)):
        if a[i][:5] == 'Error':
            print(a[i])
        if a[i][:7] == 'Warning':
            print(a[i])

print_errors_and_warnings([
  "Info: had a good nights sleep before exam",
  "Warning: not enough snacks brought for exam",
  "Info: went for a walk before exam started",
  "Error: went to wrong exam location",
  "Debug: laptop_charge=92%, charing_cable_present=True",
  "Warning: time management is important",
])
# Her blir det litt trekk under skjønnsmessige vurdering;
# koden er i overkant omstendelig, selv om den er korrekt (9/10).
def print_errors_and_warnings(a):
    for i in range(len(a)):
        for s in ['Error', 'Warning']:
            if len(a[i]) < len(s):
                continue
            for j in range(len(s)):
                if a[i][j] != s[j]:
                    break
            else:
                print(a[i])

print_errors_and_warnings([
  "Info: had a good nights sleep before exam",
  "Warning: not enough snacks brought for exam",
  "Info: went for a walk before exam started",
  "Error: went to wrong exam location",
  "Debug: laptop_charge=92%, charing_cable_present=True",
  "Warning: time management is important",
])
# Bruker 'in' i stedet for å sjekke fra begynnelsen av strengen (8/10).
# (da printes også strenger hvor f. eks. Warning kommer et annet sted
# enn i begynnelse av strengen).
def print_errors_and_warnings(a):
    for msg in a:
        if 'Error' in msg or 'Warning' in msg:
            print(msg)

print_errors_and_warnings([
  "Info: this is not a Warning, and should not be printed",
])
# Her går poengene for sammelingning mot Error og Warning tapt (8/10)
def print_errors_and_warnings(a):
    for i in range(len(a)):
        for s in ['Error', 'Warning']:
            if a[:len(s)] == s: # feil, skulle vært a[i][:len(s)] == s
                print(a[i])

print_errors_and_warnings([
  "Info: had a good nights sleep before exam",
  "Warning: not enough snacks brought for exam",
  "Info: went for a walk before exam started",
  "Error: went to wrong exam location",
  "Debug: laptop_charge=92%, charing_cable_present=True",
  "Warning: time management is important",
])
# Her er det gjort en feil med innrykket for den andre if-setningen.
# Selv om besvarelsen treffer mange kriterier bra, blir den
# skjønnsmessige vurderingen 0, siden feilen er såpass alvorlig.
# Poenget for fornuftig blokk i if-setningen går også tapt (6/10).
def print_errors_and_warnings(a):
    for i in range(len(a)):
        if a[i][:5] == 'Error':
            print(a[i])
            if a[i][:7] == 'Warning':
                print(a[i])

print_errors_and_warnings([
  "Info: had a good nights sleep before exam",
  "Warning: not enough snacks brought for exam",
  "Info: went for a walk before exam started",
  "Error: went to wrong exam location",
  "Debug: laptop_charge=92%, charing_cable_present=True",
  "Warning: time management is important",
])

Oppgave 10: løkker over indekser vs. løkker over elementer i lister

Totalt 10 mulige poeng.

I python virker for-løkker slik en kodesnutt/blokk blir kjørt flere ganger. En spesiell variabel vi kaller for iteranden har ulik verdi i hver iterasjon/gjennomgang av blokken. Hvilke verdier iteranden får, er avhengig av hva for-løkken itererer over. Dersom for-løkken itererer over en liste a = ['foo', 'bar', 'baz'], skrevet som for e in a: ..., betyr dette at kodeblokken inne i løkken vil kjøres tre ganger, og iteranden e vil ta verdiene 'foo', 'bar' og 'baz' i de ulike iterasjonene.

Indeksene/posisjonene i listen a er 0, 1 og 2. Dersom vi skriver for i in range(len(a)): ..., betyr dette at iteranden i vil ta verdiene 0, 1 og 2 i de ulike iterasjonene; vi sier at for-løkken er over indeksene i a. I en slik løkke kan vi bruke indeksen i til å hente ut elementet i listen a som ligger på posisjon i ved å skrive a[i]. For eksempel, a[0], a[1] og a[2] evaluerer til henholdsvis 'foo', 'bar' og 'baz', som er elementene i listen.

En løkke over indekser er mer anvendelig enn en løkker over kun elementene, siden vi da har tilgang til både posisjonen og verdien til elementet. Det er for eksempel nødvendig å bruke løkker med indekser hvis vi skal mutere listen, for eksempel slik det blir gjort i den innerste løkken til subtract_one_from_all_positives i snake.py.

Ofte klarer vi oss likevel med en løkke over kun elementene i en liste, uten at vi trenger å bruke indeksene. Dette er for eksempel tilfellet i get_max_value_in_2dlist i snake.py, der vi bare trenger å iterere over elementene i listen for å finne det største elementet. Kode blir som regel mer leselig ved å bruke løkker over elementer.

Oppgave 11: feil i funksjon som teller tegn i en streng

Totalt 10 mulige poeng.

# Originalversjon
def count_a(s):
    for c in range(len(s)):
        count = 0
        if c == "a":
            count += 1
        return count

print(count_a("banan"))
# Reparert versjon
def count_a(s):
    count = 0
    for c in s:
        if c == "a":
            count += 1
    return count

print(count_a("banan"))

Koden over har flere feil.

  • Løkken blander indekser med tegn i strengen. Løkken er over indekser (den bruker range(len(...))), men iteranden c blir brukt som om den er et symbol i strengen (f. eks. c == "a"). For å reparerer dette, må vi endre for c in range(len(s)): til for c in s:.

  • count blir satt til 0 på nytt hver gang blokken i løkken kjører. Dette er fordi count blir definert inni løkken. For å reparerer dette, må vi flytte count = 0 utenfor løkken (før løkken starter).

  • return count blir kjørt allerede etter første gjennomgang av løkken, og kodeblokken i løkken utføres derfor bare én gang. Når return -setningen utføres inne i en løkke, avbrytes løkken og funksjonskallet med én gang. For å reparerer dette, må vi endre innrykket og flytte return count utenfor løkken (etter at løkken er ferdig).

Oppgave 12: tegn en slange

Totalt 10 mulige poeng.

Denne koden har noe flere magiske tall i koden enn vi burde, men får full score. I denne løsningen brytes mønsteret opp i S’er, og hjelpefunksjonen draw_single_s tegner én enkelt slik S.

from uib_inf100_graphics import *

def draw_single_s(canvas, x_lo, x_hi, y_top, y_bot, t, c):
    x_mid = (x_lo + x_hi) / 2
    y_mid = (y_top + y_bot) / 2
    canvas.create_line(x_lo,  y_mid, x_lo,  y_bot, width=t, fill=c)
    canvas.create_line(x_lo,  y_bot, x_mid, y_bot, width=t, fill=c)
    canvas.create_line(x_mid, y_bot, x_mid, y_top, width=t, fill=c)
    canvas.create_line(x_mid, y_top, x_hi,  y_top, width=t, fill=c)
    canvas.create_line(x_hi,  y_top, x_hi,  y_mid, width=t, fill=c)

def redraw_all(app, canvas):
    n = 3 # antall S'er
    w = 250/n # avstand (horisontalt) mellom hver S-figur
    for i in range(n):
        x_lo = 50 + i*w
        x_hi = x_lo + w
        draw_single_s(canvas, x_lo, x_hi, 50, 150, 5, "green")

    canvas.create_line(300, 100, 350, 100, width=5, fill="green")
    canvas.create_oval(330, 80, 370, 120, fill="green")

run_app(width=400, height=200)

import matplotlib.pyplot as plt

def draw_snake(n):
    """ n = antall svinger på slangen """
    # Planen er å lage to lister med x og y -kooridnater, slik at å
    # plotte disse koordinatene med plt.plot() vil tegne slangen for
    # oss. Når vi er ferdig, skal listene se f. eks. slik ut:
    # xs = [0,  0,  1,  1,  2,  2,  3,  3,  4]
    # ys = [0, -1, -1,  1,  1, -1, -1,  0,  0]

    # Første koordinat i listen er (0, 0)
    xs = [0]
    ys = [0]

    # Legger til svingene til slangen
    for i in range(n):
        xs.append(xs[-1])
        ys.append(1 if i%2 else -1)
        xs.append(xs[-1] + 1)
        ys.append(1 if i%2 else -1)

    # Legger til siste punkter på slangen (frem til hodet)
    xs.append(xs[-1])
    ys.append(0)
    xs.append(xs[-1] + 1)
    ys.append(0)

    # Tegn slangekroppen
    plt.plot(xs, ys, c="green", linewidth=3)
    # Tegn hodet som en prikk på siste posisjon i slangekroppen
    plt.scatter(xs[-1], ys[-1], s=500, c="green")
    # Fjern aksene
    plt.axis("off")
    # Vis tegningen
    plt.show()

draw_snake(6)

Denne varianten er basert på at mønsteret ikke er en full S, men en en U som tegnes annenhver gang oppover og nedover.

Denne løsningen er dessuten interaktiv (den bruker en variabel app.n som key_pressed kan endre på), men dette er ikke nødvendig for å få full score, det er bare en kjekk måte å sjekke om koden fungerer på.

from uib_inf100_graphics import *

def app_started(app):
    app.n = 6 # antall svinger

def key_pressed(app, event):
    if event.key == "Left":
        app.n = max(1, app.n - 1)
    elif event.key == "Right":
        app.n += 1

def draw_uturn(canvas, x, y, w, h, col):
    """ Tegner en "firkantet" U-figur.
    
        Argumenter:
        x, y: koordinater til øvre venstre hjørne av U-figur
        w, h: bredde og høyde til U-figur
        col: farge på U-figur

        Hvis høyden er negativ, tegnes U-figuren med åpning
        på undersiden, og (x, y) er da koordinat til nedre
        venstre hjørne.
    """
    canvas.create_line(x, y, x, y+h, fill=col, width=5)
    canvas.create_line(x, y+h, x+w, y+h, fill=col, width=5)
    canvas.create_line(x+w, y+h, x+w, y, fill=col, width=5)

def redraw_all(app, canvas):
    n = app.n
    margin = 50 # avstand til kanten
    x_body_left = margin
    x_body_right = app.width - 2 * margin   # Hvor svingene slutter
    x_head = app.width - margin
    r_head = 20 # radius til hodet
    y_center = app.height/2
    y_amplitude = app.height/2 - margin
    color = "green"

    # Avstand mellom hver "sving" på slangen
    turn_width = (x_body_right - x_body_left)/n 

    # Tegne kroppen
    sign = 1
    for i in range(n):
        draw_uturn(
            canvas=(canvas),
            x=(x_body_left + i*turn_width),
            y=(y_center),
            w=(turn_width),
            h=(y_amplitude * sign),
            col=(color),
        )
        sign *= -1  # Bytter om man tegner U'en nedover eller oppover
    
    # Tegne halsen og hodet
    canvas.create_line(
        x_body_right, y_center, # x1, y1
        x_head, y_center, # x2, y2
        fill=color, width=5,
    )
    canvas.create_oval(
        x_head - r_head, y_center - r_head, # x1, y1
        x_head + r_head, y_center + r_head, # x2, y2
        fill=color,
    )

run_app(width=400, height=200)

Mønsteret deles opp i S’er, og én S tegnes i hver iterasjon av løkken. Avstanden mellom hver sving er fast, og hodet flytter seg avhengig av hvor mange S’er som er tegnet. Koden inneholder flere magiske tall enn det som er ideelt, men får likevel full score.

from uib_inf100_graphics import *

def redraw_all(app, canvas):
    n = 3
    x = 50

    # Kroppen
    for _ in range(n):
        # Inne i løkken: én "S"
        canvas.create_line(x, 100, x, 150, width=5, fill="green")
        canvas.create_line(x, 150, x + 40, 150, width=5, fill="green")
        x += 40
        canvas.create_line(x, 150, x, 50, width=5, fill="green")
        canvas.create_line(x, 50, x + 40, 50, width=5, fill="green")
        x += 40
        canvas.create_line(x, 50, x, 100, width=5, fill="green")
    canvas.create_line(x, 100, x + 40, 100, width=5, fill="green")
    x += 40

    # Hodet
    canvas.create_oval(x-20, 80, x+20, 120, fill="green")

run_app(width=400, height=200)

En variant som benytter seg av create_line -metoden sin mulighet til å tegne mange streker på én gang. Idéen her er den samme som ble benyttet i løsningen for matplotlib, men verdiene er ganget opp og forskjøvet slik at de plasseres midt i bildet.

Denne løsningen er også interaktiv (den bruker en variabel app.n som key_pressed kan endre på), men det er ikke nødvendig for å få full score.

from uib_inf100_graphics import *

def app_started(app):
    app.n = 6 # antall svinger

def key_pressed(app, event):
    if event.key == "Left":
        app.n = max(0, app.n - 1)
    elif event.key == "Right":
        app.n += 1

def redraw_all(app, canvas):
    n = app.n

    margin = 50
    turn_distance = (app.width - 2*margin)/(n+1)
    amplitude = app.height/2 - margin
    x_offset = margin
    y_offset = app.height/2
    
    # Første koordinat i listen er (x_offset, y_offset). Listen er
    # 1-dimensjonal, men punktene kommer parvis, først x og så y.
    # (dette er formatet create_line forventer)
    pts = [x_offset, y_offset]

    # Legger til svingene til slangen
    for i in range(n):
        prev_x = pts[-2]
        pts.append(prev_x)
        pts.append(y_offset + (amplitude if i%2 else -amplitude))
        pts.append(prev_x + turn_distance)
        pts.append(y_offset + (amplitude if i%2 else -amplitude))

    # Legger til siste punkter på slangen (frem til hodet)
    prev_x = pts[-2]
    pts.append(prev_x)
    pts.append(y_offset)
    pts.append(prev_x + turn_distance)
    pts.append(y_offset)

    # Tegne kroppen
    canvas.create_line(*pts, width=5, fill="green")

    # Hodet
    x, y = pts[-2:]
    r = 20
    canvas.create_oval(x-r, y-r, x+r, y+r, fill="green")

run_app(width=400, height=200)

I denne koden er det små avrundingsfeil som gjør at det ikke blir helt nøyaktig tegnet, ellers er alle punkter oppfylt. Det blir likevel full score.

from uib_inf100_graphics import *

def redraw_all(app, canvas):
    n = 6
    d = 250//n

    # Vertikale streker
    canvas.create_line(50, 100, 50, 150, width=5, fill="green")
    for x in range(50 + d, 300-d, d):
        canvas.create_line(x, 50, x, 150, width=5, fill="green")
    canvas.create_line(300, 100, 300, 50, width=5, fill="green")

    # Horisontale streker
    for x in range(50, 300-d, 2*d):
        canvas.create_line(x, 150, x+d, 150, width=5, fill="green")
    for x in range(50 + d, 300-d, 2*d):
        canvas.create_line(x, 50, x+d, 50, width=5, fill="green")
    canvas.create_line(350, 100, 300, 100, width=5, fill="green")

    # Hodet
    canvas.create_oval(330, 80, 370, 120, fill="green")

run_app(width=400, height=200)

Her trekkes det 2 poeng fordi koden ikke har en enkelt variabel som kan endres for å endre antall svinger. Vertikale og horisontale streker tegnes hver for seg.

from uib_inf100_graphics import *

def redraw_all(app, canvas):
    # Vertikale streker
    canvas.create_line(50, 100, 50, 150, width=5, fill="green")
    for x in range(90, 290, 40):
        canvas.create_line(x, 50, x, 150, width=5, fill="green")
    canvas.create_line(290, 100, 290, 50, width=5, fill="green")

    # Horisontale streker
    for x in range(50, 290, 80):
        canvas.create_line(x, 150, x+40, 150, width=5, fill="green")
    for x in range(90, 290, 80):
        canvas.create_line(x, 50, x+40, 50, width=5, fill="green")
    canvas.create_line(350, 100, 290, 100, width=5, fill="green")

    # Hodet
    canvas.create_oval(330, 80, 370, 120, fill="green")

run_app(width=400, height=200)

Denne løkken er på en måte mindre interessant enn løkker hvor man enten regner ut koordinatene eller oppdager hvor mange iterasjoner det skal være, så denne koden bærer et større preg av å være hardkodet enn andre løsninger. Vi gir den under tvil full uttelling for kravet om å bruke løkke. Det trekkes 2 poeng fordi koden ikke har en variabel som kan endres for å endre antall svinger.

from uib_inf100_graphics import *

def redraw_all(app, canvas):
    # Punkter for kroppen
    points = [(50, 100), (50, 150), (90, 150), (90, 50), (130, 50),
        (130, 150), (170, 150), (170, 50), (210, 50), (210, 150),
        (250, 150), (250, 50), (290, 50), (290, 100), (350, 100)]

    # Tegne kroppen
    for i in range(len(points) - 1):
        x1, y1 = points[i]
        x2, y2 = points[i+1]
        canvas.create_line(x1, y1, x2, y2, width=5, fill="green")

    # Hodet
    canvas.create_oval(330, 80, 370, 120, fill="green")

run_app(width=400, height=200)

Her trekkes det 4 poeng fordi koden hverken har løkker eller en variabel som kan endres for å endre antall svinger.

from uib_inf100_graphics import *

def redraw_all(app, canvas):
    # Vertikale streker
    canvas.create_line(50, 100, 50, 150, width=5, fill="green")
    canvas.create_line(90, 50, 90, 150, width=5, fill="green")
    canvas.create_line(130, 50, 130, 150, width=5, fill="green")
    canvas.create_line(170, 50, 170, 150, width=5, fill="green")
    canvas.create_line(210, 50, 210, 150, width=5, fill="green")
    canvas.create_line(250, 50, 250, 150, width=5, fill="green")
    canvas.create_line(290, 100, 290, 50, width=5, fill="green")

    # Horisontale streker
    canvas.create_line(50, 150, 90, 150, width=5, fill="green")
    canvas.create_line(130, 150, 170, 150, width=5, fill="green")
    canvas.create_line(210, 150, 250, 150, width=5, fill="green")
    canvas.create_line(90, 50, 130, 50, width=5, fill="green")
    canvas.create_line(170, 50, 210, 50, width=5, fill="green")
    canvas.create_line(250, 50, 290, 50, width=5, fill="green")
    canvas.create_line(290, 100, 350, 100, width=5, fill="green")

    # Hodet
    canvas.create_oval(330, 80, 370, 120, fill="green")

run_app(width=400, height=200)

Oppgave 13: Frodes regneark (omgjøring til én dato-kolonne)

Totalt 10 mulige poeng.

# Program for å omgjøre regneark fra Frode's format med to dato-kolonner,
# til ønsket format med kun én dato-kolonne

########### Instruksjoner #############

# For å bruke programmet, endre på filnavnene på linjen under. Merk at
# programmet må kjøres i samme mappe hvor filen input_filename ligger.
# Filen output_filename lagres i den samme mappen, og vil overskrive 
# gamle filer med samme navn uten advarsel.

input_filename = "sample_input.csv"
output_filename = "sample_output.csv"

########### Ikke gjør endringer under her #############

def read_file(path):
    """ Given the file path (file name) of a plain text file, returns
    the content of the file as a string. """
    with open(path, "rt", encoding='utf-8') as f:
        return f.read()

def write_file(path, contents):
    """ Writes the contents to the file with the given file path. If
    the file does not exist, it will be created. If the file does
    exist, its old content will be overwritten. """
    with open(path, "wt", encoding='utf-8') as f:
        f.write(contents)

file_content = read_file(input_filename)
result_table = ["personnummer,medisin,dato,endring"]

for i, line in enumerate(file_content.splitlines()):
    # Hopper over første linje
    if i == 0:
        continue
    id_no, medicine, startdate, enddate = line.split(",")

    result_table.append(",".join([id_no, medicine, startdate, "1"]))
    result_table.append(",".join([id_no, medicine, enddate, "0"]))
result_table.append("") # Avslutter fil med blank linje

write_file(output_filename, "\n".join(result_table))

Oppgave 14: bordtennis for én

Totalt 15 mulige poeng.

Viser her kun endringer i forhold til koden for sprettende figur i kursnotatene.

Del A: opprett en racket

  • Oppretter variabler for racketen sin posisjon og størrelse i app_started.
  • Endrer på racketen sin y-posisjon med piltastene i key_pressed.
  • Tegner racketen i redraw_all.
def app_started(app):
    ...
    app.racket_x = app.width - 20
    app.racket_y = app.height/2
    app.racket_height = app.height/4

def key_pressed(app, event):
    ...
    elif event.key == "Up":
        app.racket_y -= 10
        if app.racket_y < app.racket_height/2:
            app.racket_y = app.racket_height/2
    elif event.key == "Down":
        app.racket_y += 10
        if app.racket_y > app.height - app.racket_height/2:
            app.racket_y = app.height - app.racket_height/2

def redraw_all(app, canvas):
    ...
    canvas.create_line(
        app.racket_x, app.racket_y - app.racket_height/2, # x1, y1
        app.racket_x, app.racket_y + app.racket_height/2, # x2, y2
        width=5,
    )

I del A kan man i tillegg kan man gjøre noen mindre justeringer, slik som å bytte ut create_rectangle med create_oval for å gjøre ballen rund, og å endre størrelsen på vinduet.


Del B: dersom ballen går utenfor skjermen bak racketen skal den ikke sprette, men spillet går over i “game over” -modus.

  • Oppretter variabelen app.game_over i app_started.
  • Endrer på elif-setningen som utføres når ballen treffer veggen på høyre side; i stedet for at ballen skal sprette tilbake, skal app.game_over settes til True.
  • Endrer på redraw_all slik at den tegner “Game over” dersom app.game_over er True.

```python {runbuttons=false}

```python {runbuttons=false}
def app_started(app):
    ...
    app.game_over = False

def do_step(app):
    ...
    elif app.square_left > app.width - app.square_size:
        # Fjerner det tidligere innholdet fra denne
        # if-setningen og bytter det ut med:
        app.game_over = True

def redraw_all(app, canvas):
    if app.game_over:
        canvas.create_text(
            app.width/2, app.height/2,
            text="Game over!", font="Arial 24 bold"
        )
        return
    ...

Del C: dersom ballen treffer racketen skal den sprette tilbake.

  • Oppretter en hjelpefunksjon ball_hits_racket som sjekker om ballen treffer racketen. Denne funksjonen tar inn app-objektet (som har informasjon om både ballen og racketen sin posisjon) og returnerer True eller False.
  • I do_step opprettes en ny elif-setning i tilknytning til sjekkene for om ballen treffer veggen til venstre eller høyre.
def do_step(app):
    ...
    if app.square_left < 0:
        ...
    elif ball_hits_racket(app):
        app.square_left = app.racket_x - app.square_size
        app.dx = -app.dx
    elif app.square_left > app.width - app.square_size:
       ...

def ball_hits_racket(app):
    # Hvis ballen er langt til venstre for racketen kan vi
    # returnere False med en gang. Etter denne sjekken vet
    # vi at kanten på ballen er til høyre for racketen.
    if app.square_left + app.square_size < app.racket_x:
        return False

    # Forenklet sjekk for om ballen treffer racketen:
    # vi antar a ballen kun er en prikk (har høyde 0), og
    # sjekker om prikken er innenfor racketen i y-retning.
    ball_cy = app.square_top + app.square_size/2
    if ball_cy < app.racket_y - app.racket_height/2:
        return False
    if ball_cy > app.racket_y + app.racket_height/2:
        return False
    return True

Det finnes mange ulike varianter for å sjekke om ballen treffer racketen. Alle varianter som er relativt funksjonelle vil godtas.

Man kan gjerne bytte ut begrepet square med ball i koden for å gjøre den mer lesbar. Det er ikke gjort her.


Under vises programmet i sin helhet:

from uib_inf100_graphics import *

def app_started(app):
    app.square_left = app.width//2
    app.square_top = app.height//2
    app.square_size = 25
    app.dx = -4
    app.dy = 5
    app.is_paused = False
    app.timer_delay = 25 # millisekunder

    app.racket_x = app.width - 20
    app.racket_y = app.height/2
    app.racket_height = app.height/4
    app.game_over = False

def key_pressed(app, event):
    if event.key == "p":
        app.is_paused = not app.is_paused
    elif event.key == "s":
        do_step(app)
    elif event.key == "Up":
        app.racket_y -= 10
        if app.racket_y < app.racket_height/2:
            app.racket_y = app.racket_height/2
    elif event.key == "Down":
        app.racket_y += 10
        if app.racket_y > app.height - app.racket_height/2:
            app.racket_y = app.height - app.racket_height/2

def timer_fired(app):
    if not app.is_paused:
        do_step(app)

def ball_hits_racket(app):
    # Hvis ballen er langt til venstre for racketen kan vi
    # returnere False med en gang. Etter denne sjekken vet
    # vi at kanten på ballen er til høyre for racketen.
    if app.square_left + app.square_size < app.racket_x:
        return False

    # Forenklet sjekk for om ballen treffer racketen:
    # vi antar a ballen kun er en prikk (har høyde 0), og
    # sjekker om prikken er innenfor racketen i y-retning.
    ball_cy = app.square_top + app.square_size/2
    if ball_cy < app.racket_y - app.racket_height/2:
        return False
    if ball_cy > app.racket_y + app.racket_height/2:
        return False
    return True

def do_step(app):
    # Flytt horisontalt
    app.square_left += app.dx

    # Sjekk om firkanten har gått utenfor lerretet, og hvis ja,
    # snu retning; men flytt også firkanten til kanten (i stedet
    # for å gå forbi). Merk: det finnes andre, mer sofistikerte
    # måter å håndtere at rektangelet går forbi kanten...
    if app.square_left < 0:
        # snu retningen!
        app.square_left = 0
        app.dx = -app.dx
    elif ball_hits_racket(app):
        app.square_left = app.racket_x - app.square_size
        app.dx = -app.dx
    elif app.square_left > app.width - app.square_size:
        app.game_over = True
    
    # Flytt vertikalt på samme måte
    app.square_top += app.dy
    if app.square_top < 0:
        # snu retningen!
        app.square_top = 0
        app.dy = -app.dy
    elif app.square_top > app.height - app.square_size:
        app.square_top = app.height - app.square_size
        app.dy = -app.dy

def redraw_all(app, canvas):
    # Game over
    if app.game_over:
        canvas.create_text(
            app.width/2, app.height/2,
            text="Game over!", font="Arial 24 bold"
        )
        return

    # tegner ballen
    canvas.create_oval(
        app.square_left,
        app.square_top,
        app.square_left + app.square_size,
        app.square_top + app.square_size,
        fill="yellow",
    )

    # tegner racket
    canvas.create_line(
        app.racket_x, app.racket_y - app.racket_height/2, # x1, y1
        app.racket_x, app.racket_y + app.racket_height/2, # x2, y2
        width=5,
    )

run_app(width=400, height=300)