Maskinorienterad Programmering / Programmering av Inbyggda System

Hemuppgifter C-programmering

C - Föreläsning 3.

Denna vecka skall vi öva oss på bl a: static, #extern, enum, och separata .c-filer.

Strukturera i källfiler. Rensa upp globala variabler. En game-struct. En större objekttyp per fil (småobjekt kan ligga i samma fil som större objekt). Slapa massa mynt. State-machine - med enums, static, minnesallokering, Multitasking - har hanterat massa oberoende händelseförlopp (tasks). Nu ska införa beroenden.
Uppg. 1
Structs och även flera källfiler
Vi fortsätter med förra veckans projekt och där vi slutade. Redan i förra veckans uppgifter var det egentligen mycket lämpligt att kapsla in våra objekts variabler (position, angle, scale, speed...) med hjälp av structs och skapa en instans för skeppet respektive bakgrunden. Förmodligen grupperade du visserligen variablerna för skeppet för sig och variablerna för bakgrunden för sig, men har man många objekt blir koden lätt grötig utan ytterligare struktur.
      Structs möjliggör sådan struktur. Har man flera objekt blir det automatiskt tydligt vilken position och hastighet etc som tillhör vilket objekt. (Det blir senare också enkelt att skapa en array av dessa structs för att hantera flera objekt.)

  1. Börja med att skapa en struct för 2D-vektorer som vi kan använda för positioner men även skulle kunna använda för riktnings- och hastighetsvektorer. Det är lämpligt att de två structmedlemmarna x,y är floats. Dels så att positioner och hastigheter inte är begränsade till heltal och dels för att inte avrundningsfel ska ackumuleras, t ex om du senare vill flytta dina objekt efter någon mjuk kurva som bygger på någon flyttalsformel. Kalla därför din struct för vec2f , dvs vektor av två floats, vilket är ett vanligt vedertaget namn för en sådan här struct. Det är vanligt att även behöva ha motsvarande för ints och doubles (dvs vec2i resp. vec2d).
          Lägg structen i en separat källfil som du kallar för vecmath.h. Vet du inte hur du lägger till filer i CodeLite så kan du se det här nedan:


    Se därefter till att du skapar en ny fil av typen Header file (.h). Ange namnet vecmath.h.

    Include guards: Vi kommer till include guards först i nästa lektion, men du kan lika gärna lägga till det här och nu... En .h-fil är typiskt omgärdade av följande rader:

    #ifndef VECMATH_H // eller vad din fil heter. Versaler på #define VECMATH_H // filnamnet + "_H" är vanligt. ... resten av innehållet i .h-filen #endif // VECMATH_H -- här är VECMATH_H endast kommentar // för tydlighets skull

    Det gör att .h-filen inte kommer att inkluderas mer än en gång när en .c-fil kompileras. Om den skulle inkluderas fler än en gång så kommer innehållet att finnas med motsvarande antal gånger, vilket kan leda till felet "multiple definitions", t ex av structs. En .h-fil kan lätt råka inkluderas flera gånger om man inkluderar flera .h-filer som i sin tur råkar inkludera samma .h-fil. Detta är inte ovanligt, vilket du snart kommer att märka.
          Konstruktionen fungerar enligt följande: #ifndef, #define och #endif är så kallade preprocessor directives: #ifndef betyder "if not defined". Dvs, allt som står mellan #ifndef VECMATH_H och #endif behandlas vid kompileringen (av .c-filen) endast om VECMATH_H inte är definierat. Första gången som preprocessorn ser #include "vecmath.h" och försöker inkludera vecmath.h så är VECMATH_H (naturligtvis) inte definierat och alltså inkluderas innehållet. Första efterföljande rad är just en #define VECMATH_H som alltså ser till att VECMATH_H blir definierat. Om preprocessorn finner på en till #include "vecmath.h" lite senare så kommer VECMATH_H alltså att vara definierat och innehållet i .h-filen tas inte med. Detta är helt enkelt ett vanligt trick för att lösa ett vanligt problem. Vissa kompilatorer tillåter enklare sätt som t ex "#pragma once" för att lösa samma problem.

  2. Övning på structs och skapa ny .h-fil: Kapsla nu in skeppets och bakgrundens variabler med hjälp av en lämplig struct som du lägger i en ny separat .h-fil. Det finns givetvis många lösningar. Den avancerade eleven får gärna välja sin egen struktur men då vara beredd på att lösningsförslagen nedan inte stämmer 100% överens.

    Vi föreslår att du använder följande struct:

    Skapa denna struct inklusive .h-fil och anpassa din kod så att den använder den. En av många nödvändiga uppdateringar inkluderar att byta ut GfxObject ship, background; i main.c mot GameObject ship, background;. Bl a behöver även inparametrarna för shake() ändras från int* till float*. OBS, därmed behöver även följande rad ändras för att undvika en lurighet med att variabeln t är unsigned och -1 är signed:

    if( bShake && t < shakeStop) { *x += 2*(((int)t % 3) - (int)1); //lägg till (int) *y += ((rand() % 3) - (int)1); }

    Gör alltihop så långt som möjligt själv utan att titta i lösningen för det är en mycket nyttig övning om du är ovan vid C och structs. Räkna med att det tar tid. När du fått koden att kompilera, använd debuggern för att rätta eventuella återstående fel. Använd CodeLite's Help-menu och skriv t ex debug eller breakpoint för att få hjälp. Kompilera och testa att allt fungerar som förut.

    int *px = &x; ... *px = ... *px + speed ...;
    ... ship = createGfxObject( "../ship.png" ); ship.outputWidth = 200; ship.outputHeight = 200; int x = 400, y = 300, speed = 3; int *px = &x, *py = &y; ... while(true) // The real-time loop { ... if (state[SDL_SCANCODE_RIGHT]) *px = (*px+speed >= 799) ? 799 : *px+speed; if (state[SDL_SCANCODE_LEFT]) *px = (*px-speed <= 0) ? 0 : *px-speed; if (state[SDL_SCANCODE_DOWN]) *py = (*py+speed >= 599) ? 599 : *py+speed; if (state[SDL_SCANCODE_UP]) *py = (*py-speed <= 0) ? 0 : *py-speed; ...
  3. Structs med funktionspekare Lägg uppdateringen av skeppets position (från tangentbordsinmatningen) i en egen funktion update(). Lägg i fil player.c / player.h.
Globala variabler och
#extern
Global vs lokal variabel: En global variabel är definierad utanför en funktion och hamnar ofta i datasegmentet i programmets adressrymd. Därmed existerar en global variabel under hela programmets exekveringstid. En lokal variabel är definierad inuti en funktion och hamnar på stacken. Därmed är den flyktig, dvs upphör att existera när funktionen returnerat.
char str1[] = "hej"; // global variabel void func() { char str2[] = "då"; // lokal variabel }

Extern: Med include guards så inkluderas en .h-fil max en gång per .c-fil som kompileras. Detta löser t ex att structs som definieras i .h-filen inte blir definierade mer än en gång.
     Men .h-filen kan (och måste ofta) ändock bli inkluderad upp till en gång per .c-fil. Därför är det viktigt att inte lägga definitioner av globala variabler och funktioner i .h-filer. Istället lägger man endast deklarationerna i .h-filen - och definitionerna i motsvarande .c-fil. Annars skapas variabeln/funktionen en gång per .c-fil som inkluderar .h-filen. Endast deklaration (utan definition) för variabler gör man med #extern, vilket talar om att definitionen kommer senare (typiskt i en annan .c-fil).

exempel.h
#ifndef EXEMPEL_H #define EXEMPEL_H typedef struct { // typ- int x, y; // definition } Position; // extern char str1[]; // deklaration void func(); // deklaration #endif // EXEMPEL_H
exempel.c
#include exempel.h // för typer // etc. char str1[] = "hej"; // definition void func() // definition { char str2[] = "då"; }

Vid länkningen är det annars mycket sannolikt att länkaren kommer att klaga på "duplicate symbol" av en och samma variabel/funktion. Deklarationer av en och samma variabel eller funktion får förekomma hur många gånger som helst, så länge deklarationen inte ändras.

, dvs skapande av nya globala variabler (som t ex int apa = 0;), i .h-filerna, ty då skapas variabeln en gång per .c-fil som inkluderar .h-filen. Vid länkningen är det då mycket sannolikt att länkaren kommer att klaga på "duplicate symbol" av en och samma variabel. Istället lägger man skapandet av variabler i .c-filerna och har bara deklarationerna i .h-filen. Endast deklaration (utan definition) för variabler gör man med extern int apa; vilket talar om att definitionen kommer senare (typiskt i en annan .c-fil). Samma sak gäller definitioner av funktioner, dvs funktionskroppen skall ligga i .c-filen, men en extra deklaration (t ex void fkn(int);) ligger i .h-filen. Deklarationer av en och samma variabel eller funktion får förekomma hur många gånger som helst, så länge deklarationen inte ändras.

En sak till som vi kan ta upp direkt (men som också kommer först i nästa föreläsning): Även med State machines med enums för state. Multitasking - interrupts - kan avbryta programmet var som helst vilket kan vara bra eller inte bra. Känsligt vid kommunikation med hårdvara som kräver timing. E.g. drivrutiner. Ni får lära er inte bara C men också olika sätt att lösa tidsberoende tasks (händelseförlopp). globals - extremfallet en enda struct och en global variabel. Oberoende händelser - array av objekt. Beroende händelser - dels intern för ett objekt och dels mellan objekt. Extremfallet att objekt skapar och dödar varandra (bokstavligt). c) Structs: För förra veckans uppgifter är det mycket lämpligt att kapsla in objektets position (x,y) och hastighet i en struct tillsammans med objektet. Har man flera objekt blir det då automatiskt tydligt till vilket objekt som tillhör vilken position och vilken hastighet. (Det blir senare också enkelt att skapa en array av dessa structs för att hantera flera objekt.) Skapa denna struct och anpassa din kod så att den använder den. Tips: Skapa en struct som heter Object eller liknande. Det är lämpligt att x,y-positionen är floats. Dels så att hastigheten inte är begränsad till heltal och dels för att inte avrundningsfel ska ackumuleras om du senare vill flytta dina objekt efter någon mjuk kurva som bygger på någon flyttalsformel. d) Lägg uppdatering av skeppet i egen funktion updatePlayer(); updateFromKeyboard(&ship) - övning: pekare + structs + pilnotation. Lägg till ett till skepp som styrs med andra tangenter. d) Pekare, Array av skepp, component-based programming med enums och en variabel flags i globala funktioner update(), render(). Du börjar nu få kläm på att hantera flera oberoende händelser. Vi skall öka på svårighetsgraden med beroende händelser. Båda fallen är ofta viktigt i många olika sammanhang, som t ex robotprogrammering och styrsystem (många motorer skall kontrolleras samtidigt och olika sensorer skall läsas av och initiera förlopp samt kontinuerligt styra andra). Skott, kollisionsdetektering (spheres - ty de är rot-oberoende). --- Förel.3 --- Structs, arrayer av structs, typedef, funktionspekare, structs m. funktionspekare. Strukturera upp i flera filer. Globals osnyggt. Försök undvika. Lägg game->level-> Att allokera minne dynamiskt (som new i Java) kan ni redan eller kommer få göra i andra kurser. Vi går in på det i C-föreläsning 4 (med malloc). Men det har sina fördelar att inte allokera minne dynamiskt på heapen. Man får en mycket enkelt predikterbar minneskonsumption (fixed). Ett anrop till new (Java, C++) eller malloc (C, C++) kan då och då ta många klockcykler (storleksordnign 10K-1M cycles) vilket kan vara olämpligt för tidskritiska maskinorienterade uppgifter (drivrutiner, timing av skrivningar och läsningar till/från portar). Vi ska här därför visa att man ofta klarar sig mycket bra utan att använda dynamisk minnesallokering på heapen (new, malloc). Objektorienterad stil. Använd structs för att kapsla in datan till varje typ av objekt. Använd arrayer av structs för att ha flera instanser av samma typ. Loopa över arrayen. Ni har troligen ännu inte lärt er att skapa s k länkade listor (enkellänkade eller dubbellänkade). I C++ och Java är det enkelt o smidigt att använda dynamiska arrayer (arrayer av dynamisk storlek), t ex via std::vector resp. std::list. Både listor och dynamiska arrayer är egentligen snyggare att använda än statiska arrayer. Men statiska arrayer är triviala att skapa, så vi håller oss här till det. Component-based programming. Objektorientering vs component-based programming:

Component-based programming har ökat i popularitet senaste åren som ett svar på att objektorientering inte passar väl för alla problemtyper. I objektorientering representerar man olika typer av objekt med olika klasser. Klasser kan ärva andra klasser och därmed dess typ (dvs i någon mening dess egenskaper). Men i spel är det inte ovanligt att objekt emellanåt fundamentalt skall ändra egenskaper. Ett objekt dör och kan inte längre styra. Ett objekt får en power-up och kan inte längre bli skadat eller får nya kraftfulla egenskaper. Ett objekt är stillastående och ofarligt och förvandlas av någon anledning till ett rörligt monster. I ett styrsystem för robot/bil skall plötsligt ljudet från radion slås av, utritning på instrumentpanelen slås av för vissa element men slås på för andra... Med klasser är det inte enkelt o smidigt att låta ett objekt sluta ärva från en klass och börja ärva av en annan klass. Och antal kombinationer av klasser med olika arv kan snabbt bli ohanterligt. Det är inte omöjligt att lösa problemet med objektorientering, men grundtanken med klasser och arv passar mindre väl.

Istället bygger component-based programming i sin enklaste form på att varje egenskap som finns motsvaras av en boolesk flagga (el dyl) hos objektet som kan slås på eller av. Varje objekt innehåller en uppsättning av alla flaggor. Av praktiska skäl implementeras de ofta som bitar i en variabel som t ex heter properties (eller flera properties-variabler om bitarna inte räcker till). Då kan egenskaper enkelt ändras hos objektet genom att bara slå på eller av en bit.

Logiken för respektive egenskap implementeras i varsin egen funktion. Varje funktion loopar helt enkelt igenom samtliga objekt och utför logiken för de objekt som har motsvarande flagga satt.

Både objektorienterad programmering och component-based programming har sina för- och nackdelar och det är fördelaktigt att känna till båda. Vi har här visat hur man kan blanda. Multitasking. pilnotation. Gå igenom varje "föregående lektion" och gör motsvarande hemuppg. Credits to: http://wrathgames.com/blog/2011/12/26/explosion-sound-effect/ Music: http://wrathgames.com/blog/free-development-resources/music/ Sound bible: http://soundbible.com/tags-explosion.html https://www.freesoundeffects.com/free-sounds/explosion-10070/ https://www.freesound.org/browse/tags/game/?page=2#sound Pekarövning - skriv ut texten baklänges mha pekare. Gör en funktion som returnerar strängen (inline) baklänges. Faktiskt något svårare än det verkar då man måste kopiera strängen. Dessutom - vem deallokerar den? Uppgift för nybörjaren så väl som för den mest förhärdade C-programmerare. Pekarövning 2 - gör funktion som tar in två float-variabler och incrementerar deras värden. Pekarövning 3 - malloc. Då måste man använda pekare. SDL_GetTicks() - för att avläsa tid. Angående mer avancerade förlopp: Hur gör du för att lösa om man vill initiera ett förlopp ifall spelaren trycker på space och därefter space igen efter c:a 1s (med felmarginal +-0.2s ? Allt medan spelet rullar. Lägg till en funktion checkDoublePress() som läser av tangentbordet och har en räknare som räknar upp och även invalideras om inte space är tryckt. Om du dör - backa tiden 30s. Eller kör tiden snabbt baklänges... Undo. Fyrverkeri. Efter 5s skall fiende 2 starta. Skjuta efter 3s. Fiende 3 skall... Slutord: Du har nu fått grundläggande kläm på hur man kan programmera kontrollerade förlopp av många oberoende och beroende händelser. Detta faller ofta inom ramen för s k realtidssystem. (Algoritmer är mer av typen - hur beräknar jag en lösning till det här enskilda problemet.) Kontrollfråga: Antag du har en robot där du vill styra bl a två st motorer. T ex två motorer för en robotarm. Den ena motorn skall rotera 90 grader i steg om 1 grader per tidssteg. Den andra motorn skall starta 10 tidssteg efter den första och rotera 30 grader i steg om 0.1. Om du känner att du har kläm på hur en lösning kan ser ut så är det bra. Annars, gå igenom hemuppgifterna igen noggrannare. Allmänna ord - skall man använda ungersk notation - Microsoft nej. medlemmar: m_name, m_pName, gName, nNames, Skall man ha "{" på ny rad eller ej.. Gör det ni tycker är lämpligast. Det enda som är säkert är att rekommendationerna ständigt ändras och utvecklas.