Arcade-pelien ohjelmointi
Pythonilla ja Pygamella

Chapter 10: Kontrollia ja grafiikkaa

Miten saamme objektit liikkumaan näppäimistön, hiiren tai peliohjaimen avulla?

10.1 Johdantoa

fig.controller

Tähän mennessä olemme saaneet objektit animoitua näyttöruudulla, mutta nyt pitäisi saada vuorovaikutettua objekteihin. Tähän on käytössä siis hiiri, näppäimistö ja/tai peliohjain. Onneksi tämä ei ole niin vaikeaa kuin aluksi voisi luulla.

Heti alussa tarvitsemme siis objektin, jota voidaan liikuttaa näytön jokaisen suuntaan. Tämä kannattaa toteuttaa käyttämällä funktiota, joka piirtää ojektin annettuun x-y-koordinaatiston pisteeseen. Eli takaisin kappaleen 9 asiaan, funktiot! Kerrataan, miten tehdään funktio, joka piirtää objektin.

Video: Draw with a function

Kaikki Pygamen piirtofunktiot vaativat screen parameterin, jolla asetetaan Pygamelle piirron kohdeikkuna. Tämä parametri pitää välittää jokaiselle funktiolle, jolla luodaan piirtoa näytölle.

Funtiolle pitää myös kertoa, mihin kohtaan näytöllä objekti piirretään. Tarvitaan siis x ja y parametrit. Seuraavassa on esimerkkikoodi funktiosta, joka piirtää lumiukon aina kun funktiota kutsutaan:

def draw_snowman(screen, x, y):
    # Piirretään pää ympyrällä
    pygame.draw.ellipse(screen, WHITE, [35+x, 0+y, 25, 25])
    # Piirretään keskivartalo ympyränä
    pygame.draw.ellipse(screen, WHITE, [23+x, 20+y, 50, 50])
    # Piirretään alakroppa ympyrällä
    pygame.draw.ellipse(screen, WHITE, [0+x, 65+y, 100, 100])

Sitten pääohjelmasilmukassa voidaan piirtää useita lumiukkoja, kuten nähdään kuvassa 10.1.

fig.snowmen
Figure 10.1: Snowmen drawn by a function
# Lumiukko ylös vasemmalle
draw_snowman(screen, 10, 10)

# Lumiukko ylös oikealle
draw_snowman(screen, 300, 10)

# Lumiukko alas vasemmalle
draw_snowman(screen, 10, 300)
Video: Convert existing code to a function

Toimiva ohjelmaesimerkki on saatavilla 'on-line' tästä:
ProgramArcadeGames.com/python_examples/f.php?file=functions_and_graphics.py

Tämä koodi on erilainen kuin vastaava koodi, jossa piirrettiin vastaavasti. Miten tuo piirtäminen saadaan toteutettua funktiolla. Katsotaan seuraavaksi koodi, joka piirtää tikku-ukkoja:

# Pää
pygame.draw.ellipse(screen, BLACK, [96,83,10,10], 0)

# Jalat
pygame.draw.line(screen, BLACK, [100,100], [105,110], 2)
pygame.draw.line(screen, BLACK, [100,100], [95,110], 2)

# Kroppa
pygame.draw.line(screen, RED, [100,100], [100,90], 2)

# Kädet
pygame.draw.line(screen, RED, [100,90], [104,100], 2)
pygame.draw.line(screen, RED, [100,90], [96,100], 2)
fig.stick_figure
Figure 10.2: Stick Figure

Tästä koodista on helppo määrittää funktio lisäämällä varattu sana def ja sisentämällä koodi sen alle. Funktiolle tarvitsee tuoda kaikki tikku-ukon piirtämiseen tarvittavat tiedot. screen muuttujalla määrätään, mihin ikkunaan piirretään, x ja y-koordinaateilla kerrotaan paikkatieto, johon tikku-ukko tulee piirtää.

Funktiota ei saa sijoittaa keskelle ohjelman pääsilmukkaa! Funktion koodi pitää siirtää pääohjelman koodista erilleen. Funktion määrittelyt tulee sijoittaa ohjelmakoodin alkuun. Siirretään funktio siis koodin alkuun. Katso kuva 10.3 , jossa asia on visualisoituna .

def draw_stick_figure(screen,x,y):
    # Pää
    pygame.draw.ellipse(screen, BLACK, [96,83,10,10], 0)

    # Jalat
    pygame.draw.line(screen, BLACK, [100,100], [105,110], 2)
    pygame.draw.line(screen, BLACK, [100,100], [95,110], 2)

    # Kroppa
    pygame.draw.line(screen, RED, [100,100], [100,90], 2)

    # Kädet
    pygame.draw.line(screen, RED, [100,90], [104,100], 2)
    pygame.draw.line(screen, RED, [100,90], [96,100], 2)
fig.making_a_function
Figure 10.3: Making a Function and Putting it in the Right Place

Funktiossa on määriteltynä koordinaatit x ja y parametreina, mutta ne eivät vaikuta tässä mitään. Voit asettaa mitä tahansa x ja y-arvoja ja kuitenkin tikku-ukko piirretään aina samaan paikkaan. Ei siis kovin yleiskäyttöinen funktio. Seuraavassa koodissa lisäämme funktion koodiin x ja y parametrien käytön.

def draw_stick_figure(screen, x, y):
    # Pää
    pygame.draw.ellipse(screen, BLACK,[96+x,83+y,10,10],0)

    # Jalat
    pygame.draw.line(screen, BLACK, [100+x,100+y], [105+x,110+y], 2)
    pygame.draw.line(screen, BLACK, [100+x,100+y], [95+x,110+y], 2)

    # Kroppa
    pygame.draw.line(screen, RED, [100+x,100+y], [100+x,90+y], 2)

    # Kädet
    pygame.draw.line(screen, RED, [100+x,90+y], [104+x,100+y], 2)
    pygame.draw.line(screen, RED, [100+x,90+y], [96+x,100+y], 2)

Ongelmaksi muodostuu nyt se, että ukko piirretään aina tietylle etäisyydelle origosta. Oletuksena on, että origo on (0, 0):ssa ja piirto tehdään aina vähintään 100 pikselin etäisyydelle origosta. Katso kuvasta 10.4 , miten kuva piirretään origosta (0, 0) katsottuna vähintään tietylle etäisyydelle.

fig.stick_figure_2
Figure 10.4: Stick Figure

Lisäämällä x:n ja y:n arvot funktioon, saadaan tikku-ukon paikkaa siirrettyä näiden koordinaattiarvojen verran asetetusta minimikohdasta alkaen, jos siis kutsutaan:

draw_stick_figure(screen, 50, 50)

Tikku-ukkoa ei siis piirretä paikkaan (50, 50), vaan nämä parametriarvot lisätään funktiossa oleviin 'minimi'arvoihin. Ukko siis piirretään paikkaan (150, 150). Miten saisimme funktion piirtämään juuri siihen paikkaan kuin kutsussa paikaksi annetaan.

fig.code_example
Figure 10.5: Finding the Smallest X and Y Values

Etsitään pienin x arvo ja pienin y arvo. Katso kuvaa 10.5. Sitten vähennämme nämä arvot jokaisesta x ja y arvosta funktiossa. älä sekoita korkeus (height) ja leveys (width) arvoja. Tässä on esimerkki, kun pienin x ja pienin y arvot vähennetään:

def draw_stick_figure(screen, x, y):
    # Pää 
    pygame.draw.ellipse(screen, BLACK,[96-95+x,83-83+y,10,10],0)

    # Jalat
    pygame.draw.line(screen, BLACK, [100-95+x,100-83+y], [105-95+x,110-83+y], 2)
    pygame.draw.line(screen, BLACK, [100-95+x,100-83+y], [95-95+x,110-83+y], 2)

    # Kroppa
    pygame.draw.line(screen, RED, [100-95+x,100-83+y], [100-95+x,90-83+y], 2)

    # Kädet
    pygame.draw.line(screen, RED, [100-95+x,90-83+y], [104-95+x,100-83+y], 2)
    pygame.draw.line(screen, RED, [100-95+x,90-83+y], [96-95+x,100-83+y], 2)

Tai, jos tehtäisiin tämä vieläkin yksinkertaisemmin ja tehdään tuo vähennys itse funktion parametrissa:

def draw_stick_figure(screen, x, y):
    # Head
    pygame.draw.ellipse(screen, BLACK, [1+x,y,10,10], 0)

    # Legs
    pygame.draw.line(screen, BLACK ,[5+x,17+y], [10+x,27+y], 2)
    pygame.draw.line(screen, BLACK, [5+x,17+y], [x,27+y], 2)

    # Body
    pygame.draw.line(screen, RED, [5+x,17+y], [5+x,7+y], 2)

    # Arms
    pygame.draw.line(screen, RED, [5+x,7+y], [9+x,17+y], 2)
    pygame.draw.line(screen, RED, [5+x,7+y], [1+x,17+y], 2)

10.2 Hiiri

Video: Move with the mouse

Hienoa, nyt osaamme kirjoittaa funktion, joka piirtää objektin määrättyyn kohtaan näytölle. Miten saamme nuo koordinaattipisteet? Helpoiten tämä selviää hiirellä. Tarvitaan ainoastaan yksi rivi koodia, jolla selviää koordinaattipiste:

pos = pygame.mouse.get_pos()

Jujuna tässä on se, että koordinaattipisteiden arvot palautetaan listaan, tarkemmin sanottuna ei-mutatoituvaan monikkoon. Molemmat, sekä x että y-koordinaattiarvo, palautetaan yhtenä lukuna. Jos siis kirjoitetaan print(pos) saamme aikaiseksi tulostuksen kuvassa 10.6.

fig.coordinates
Figure 10.6: Coordinates

Muuttuja pos on monikko, jossa on kaksi lukua. x koordinaatti on paikassa 0 ja y koordinaatti paikassa 1. Nämä arvot on helposti saatavissa ja välitettävissä funktiolle kuvan piirtämistä varten:

# Peli logiikka
pos = pygame.mouse.get_pos()
x = pos[0]
y = pos[1]

# Piirtämisen komennot
draw_stick_figure(screen, x, y)

Hiiren paikan 'metsästys, pitäisi sijoittaa “pelin logiikka” osaan ohjelman pääsilmukkaan. Funktion kutsu pitää sijoittaa “piirto” osaan pääohjelmasilmukkaan.

Ainoa ongelma tässä tulee, kun hiiri piirtää ukon yläosaan, niin osoitin jää piirrettävän objektin päälle. Tämä vaikeuttaa piirroksen havaitsemista, kuten näkyy kuvassa 10.7.

fig.mouse_and_figure
Figure 10.7: Stick Figure With Mouse Cursor On Top

Hiiren osoitin saadaan piilotettua juuri ennen piirtämistä seuraavalla koodilla:

# Piilota hiiren osoitin
pygame.mouse.set_visible(False)

Täydellinen toimiva ohjelmakoodi löytyy tästä:
ProgramArcadeGames.com/python_examples/f.php?file=move_mouse.py

10.3 Näppäimistö

Video: Move with the keyboard

Näppäimistöllä kontrolloiminen on hieman monimutkaisempaa. Näppäimistöllä ei voida 'napata' x- ja y-koordinaatteja kuten hiirellä. Niinpä meidän onkin tehtävä seuraavaa:

Tämä näyttää monimutkaiselta, mutta kyseessä on samantapainen toimintamalli kuin teimme liikkuvalla suorakulmiolla aiemmin sillä poikkeuksella, että nopeutta kontrolloidaan näppäimistöllä.

Heti aluksi asetamme sijainnin ja nopeuden ennen pääohjelmasilmukan aloitusta:

# Nopeus pikseleinä per freimi
x_speed = 0
y_speed = 0

# Nykyinen sijainti
x_coord = 10
y_coord = 10

Pääohjelmasilmukan while loopissa meidän pitää lisätä muutamia kohtia tapahtumasilmukkaan. Sen lisäksi, että ohjelma tutkii lopetustapahtumaa pygame.QUIT, pitää ohjelman tunnistaa näppäintapahtumat. Tapahtuma generoidaan aina, kun käyttäjä painaa näppäintä.

pygame.KEYDOWN tapahtuma generoidaan aina kun näppäin painetaan alas. pygame.KEYUP tapahtuma taasen aina kun näppäin palautetaan ylös. Kun käyttäjä painaa näppäintä, nopeusvektori asetetaan arvoon 3 tai -3 per freimi. JA kun käyttäjä päästää näppäimen ylös palautetaan nopeusvektorin arvo nollaksi. Lopulta objektin koordinaattipisteen arvoja säädetään tämän vektorin avulla ja objekti piirrtetään uudelleen uuteen paikkaan. Katso alla olevaa koodia:

for event in pygame.event.get():
    if event.type == pygame.QUIT:
        done = True

    # Käyttäjä painoi näppäintä
    if event.type == pygame.KEYDOWN:
        # selvitetään oliko kyseessä nuolinäppäin
        # säädetään nopeus
        if event.key == pygame.K_LEFT:
            x_speed = -3
        if event.key == pygame.K_RIGHT:
            x_speed = 3
        if event.key == pygame.K_UP:
            y_speed = -3
        if event.key == pygame.K_DOWN:
            y_speed = 3

    # käyttäjä vapautti näppäimen
    if event.type == pygame.KEYUP:
        # Jos näppäin oli nuolinäppäin, aseta nopeusvektori nollaan
        if event.key == pygame.K_LEFT:
            x_speed = 0
        if event.key == pygame.K_RIGHT:
            x_speed = 0
        if event.key == pygame.K_UP:
            y_speed = 0
        if event.key == pygame.K_DOWN:
            y_speed = 0

# Siirrä objektia nopeusvektorin mukaan.
x_coord += x_speed
y_coord += y_speed

# Piirretään tikku-ukko
draw_stick_figure(screen, x_coord, y_coord)

Tästä löytyy koko esimerkkikoodi:
ProgramArcadeGames.com/python_examples/f.php?file=move_keyboard.py

Huomaa, että tässä esimerkissä ei ole estetty pelihahmon liikkumista näytön ulkopuolelle. Tämä tehdään lisäämällä pelin logiikkaosaan joukko if lauseita, joilla tarkistetaan objektin x_coord ja y_coord arvot, eli paikkakoordinaatit, ovatko ne ulkopuolella ja jos ovat niin palautetaan objekti näytön reunaan. Tähän tarvittava koodi jääköön harjoitukseksi.

Alla on taulukoituna kaikki Pygamessa käytettäävät näppäinkoodit:

Pygame koodiASCIIyleinen nimitys
K_BACKSPACE\bbackspace
K_RETURN\rreturn
K_TAB\ttab
K_ESCAPE^[escape
K_SPACE välilyönti
K_COMMA,pilkku
K_MINUS-miinus
K_PERIOD.piste
K_SLASH/kauttaviiva
K_000
K_111
K_222
K_333
K_444
K_555
K_666
K_777
K_888
K_999
K_SEMICOLON;puolipiste
K_EQUALS=yhtäsuuri
K_LEFTBRACKET[hakasulku auki
K_RIGHTBRACKET]hakasulku kiinni
K_BACKSLASH\takakenoviiva
K_BACKQUOTE`painotusmerkki
K_aaa
K_bbb
K_ccc
K_ddd
K_eee
K_fff
K_ggg
K_hhh
K_iii
K_jjj
K_kkk
K_lll
K_mmm
K_nnn
K_ooo
K_ppp
K_qqq
K_rrr
K_sss
K_ttt
K_uuu
K_vvv
K_www
K_xxx
K_yyy
K_zzz
K_DELETEdelete
K_KP0keypad0
K_KP1keypad1
K_KP2keypad2
K_KP3keypad3
K_KP4keypad4
K_KP5keypad5
K_KP6keypad6
K_KP7keypad7
K_KP8keypad8
K_KP9keypad9 period
K_KP_PERIOD.keypad divide
K_KP_DIVIDE/keypad multiply
K_KP_MULTIPLY*keypad minus
K_KP_MINUS-keypad plus
K_KP_PLUS+keypad enter
K_KP_ENTER\rkeypad equals
K_KP_EQUALS=keypad
K_UPupnuoli ylös
K_DOWNdownnuoli alas
K_RIGHTrightnuoli oikealle
K_LEFTleftnuoli vasemmalle
K_INSERTinsert
K_HOMEhome
K_ENDend
K_PAGEUPpageup
K_PAGEDOWNpagedown
K_F1F1
K_F2F2
K_F3F3
K_F4F4
K_F5F5
K_F6F6
K_F7F7
K_F8F8
K_F9F9
K_F10F10
K_F11F11
K_F12F12
K_NUMLOCKnumlock
K_CAPSLOCKcapslock
K_RSHIFTrightshift
K_LSHIFTleftshift
K_RCTRLrightctrl
K_LCTRLleftctrl
K_RALTrightalt
K_LALTleftalt

10.4 Peliohjain

Peliohjain vaatii erilaisia ohjauskoodeja, mutta perusajatus on samanlainen.

Heti aluksi tarkistetaan, onko peliohjain kytkettynä ja jos on niin asennetaan se käyttöön. Tämä toimenpide tehdään vain kerran ennen ohjelman pääsilmukkaa:

# Current position
x_coord = 10
y_coord = 10

# Tutkitaan tietokoneeseen liitetyt joystickit
joystick_count = pygame.joystick.get_count()
if joystick_count == 0:
    # Ei joystickia!
    print("Error, I didn't find any joysticks.")
else:
    # Käytä joystick #0 ja asenna se
    my_joystick = pygame.joystick.Joystick(0)
    my_joystick.init()

Joystick palauttaa kaksi desimaalilukuarvoa. Jos tikku on keskiasennossa, palauttaa se arvon (0, 0). Jos tikku on ylös etuvasempaan palauttaa se arvon (-1, -1). Jos tikku on alaoikealla palauttaa se arvon (1, 1). Jos tikku on joidenkin suuntien välissä, skaalataan arvoa näiden suuntien välissä jatkuvasti. Katso peliohjaimen kuvia aloittaen kuvasta 10.8. Näistä saat hyvän kuvan tikun toiminnasta.

fig.c
Figure 10.8: Center (0,0)
fig.ul
Figure 10.9: Up Left (-1,-1)
fig.u
Figure 10.10: Up (0,-1)
fig.ur
Figure 10.11: Up Right (1,-1)
fig.r
Figure 10.12: Right (1,0)
fig.dr
Figure 10.13: Down Right (1,1)
fig.d
Figure 10.14: Down (0,1)
fig.dl
Figure 10.15: Down Left (-1,1)
fig.controller_left
Figure 10.16: Left (-1,0)

Ohjelman pääsilmukan sisällä peliohjaimen palauttamia arvoja kerrotaan toistuvasti, jotta saadaan laskettua objektia siirrettävä matka. Alla olevassa koodissa peliohjaimen kääntäminen täysillä johonkin suuntaan liikuttaa objektia 10 pikseliä jokaisessa freimissä. Ohjaimen arvot siis kerrotaan luvulla 10.

# Tämä sijoitetaan ohjelman pääsilmukkaan

# Onko peliohjain kytketty
if joystick_count != 0:

    # Tässä tutkitaan peliohjaimen asento ja sijoitetaan arvo muuttujaan
    # Palautetaan arvo väliltä -1.0 ja +1.0
    horiz_axis_pos = my_joystick.get_axis(0)
    vert_axis_pos = my_joystick.get_axis(1)

    # Siirrä x arvoa jatkuvasti suuntaansa. Kerrotaan luvulla 10 jotta nopeutta tulee lisää
    # Muutetaan luku kokonaisluvuksi (integer), koska emme voi piirtää pisteeseen 3.5, vain 3 tai 4.
    x_coord = x_coord + int(horiz_axis_pos * 10)
    y_coord = y_coord + int(vert_axis_pos * 10)

# Tyhjennä näyttö
screen.fill(WHITE)

# Piittä otus tiettyyn paikkaan 
draw_stick_figure(screen, x_coord, y_coord)

Katso tästä täysin toimiva koodi:
ProgramArcadeGames.com/python_examples/f.php?file=move_game_controller.py.

Peliohjaimissa on paljon erilaisia kontrolleja; tikku, painikkeet ja tikussa "hattupainike". Alla on esimerkkiohjelma, joka tulostaa kaikki ohjaimen tapahtumat ikkunaan. Huomaathan, että peliohjain pitää olla kytkettynä koneeseen ennen kuin käynnistät ohjelman. Muuten ohjelma ei löydä ohjainta.

fig.joystick_calls
Figure 10.17: Joystick Calls Program
"""
Sample Python/Pygame Programs
Simpson College Computer Science
http://programarcadegames.com/
http://simpson.edu/computer-science/

Show everything we can pull off the joystick
"""
import pygame

# Define some colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)


class TextPrint(object):
    """
    This is a simple class that will help us print to the screen
    It has nothing to do with the joysticks, just outputting the
    information.
    """
    def __init__(self):
        """ Constructor """
        self.reset()
        self.x_pos = 10
        self.y_pos = 10
        self.font = pygame.font.Font(None, 20)

    def print(self, my_screen, text_string):
        """ Draw text onto the screen. """
        text_bitmap = self.font.render(text_string, True, BLACK)
        my_screen.blit(text_bitmap, [self.x_pos, self.y_pos])
        self.y_pos += self.line_height

    def reset(self):
        """ Reset text to the top of the screen. """
        self.x_pos = 10
        self.y_pos = 10
        self.line_height = 15

    def indent(self):
        """ Indent the next line of text """
        self.x_pos += 10

    def unindent(self):
        """ Unindent the next line of text """
        self.x_pos -= 10


pygame.init()

# Set the width and height of the screen [width,height]
size = [500, 700]
screen = pygame.display.set_mode(size)

pygame.display.set_caption("My Game")

# Loop until the user clicks the close button.
done = False

# Used to manage how fast the screen updates
clock = pygame.time.Clock()

# Initialize the joysticks
pygame.joystick.init()

# Get ready to print
textPrint = TextPrint()

# -------- Main Program Loop -----------
while not done:
    # EVENT PROCESSING STEP
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            done = True

        # Possible joystick actions: JOYAXISMOTION JOYBALLMOTION JOYBUTTONDOWN
        # JOYBUTTONUP JOYHATMOTION
        if event.type == pygame.JOYBUTTONDOWN:
            print("Joystick button pressed.")
        if event.type == pygame.JOYBUTTONUP:
            print("Joystick button released.")

    # DRAWING STEP
    # First, clear the screen to white. Don't put other drawing commands
    # above this, or they will be erased with this command.
    screen.fill(WHITE)
    textPrint.reset()

    # Get count of joysticks
    joystick_count = pygame.joystick.get_count()

    textPrint.print(screen, "Number of joysticks: {}".format(joystick_count))
    textPrint.indent()

    # For each joystick:
    for i in range(joystick_count):
        joystick = pygame.joystick.Joystick(i)
        joystick.init()

        textPrint.print(screen, "Joystick {}".format(i))
        textPrint.indent()

        # Get the name from the OS for the controller/joystick
        name = joystick.get_name()
        textPrint.print(screen, "Joystick name: {}".format(name))

        # Usually axis run in pairs, up/down for one, and left/right for
        # the other.
        axes = joystick.get_numaxes()
        textPrint.print(screen, "Number of axes: {}".format(axes))
        textPrint.indent()

        for i in range(axes):
            axis = joystick.get_axis(i)
            textPrint.print(screen, "Axis {} value: {:>6.3f}".format(i, axis))
        textPrint.unindent()

        buttons = joystick.get_numbuttons()
        textPrint.print(screen, "Number of buttons: {}".format(buttons))
        textPrint.indent()

        for i in range(buttons):
            button = joystick.get_button(i)
            textPrint.print(screen, "Button {:>2} value: {}".format(i, button))
        textPrint.unindent()

        # Hat switch. All or nothing for direction, not like joysticks.
        # Value comes back in an array.
        hats = joystick.get_numhats()
        textPrint.print(screen, "Number of hats: {}".format(hats))
        textPrint.indent()

        for i in range(hats):
            hat = joystick.get_hat(i)
            textPrint.print(screen, "Hat {} value: {}".format(i, str(hat)))
        textPrint.unindent()

        textPrint.unindent()

    # ALL CODE TO DRAW SHOULD GO ABOVE THIS COMMENT

    # Go ahead and update the screen with what we've drawn.
    pygame.display.flip()

    # Limit to 60 frames per second
    clock.tick(60)

# Close the window and quit.
# If you forget this line, the program will 'hang'
# on exit if running from IDLE.
pygame.quit()

10.5 Review

10.5.1 Multiple Choice Quiz

Klikkaa tästä monivalintatehtävään.

10.5.2 Short Answer Worksheet

Klikkaa tästä kappaleen kertaustehtäviin.

10.5.3 Lab

Klikkaa tästä ohjelmointitehtäviin.


You are not logged in. Log in here and track your progress.