Gå till innehållet

Projektuppgift 2 - Bordshanteraren

Introduktion till projektuppgift 2

I denna projektuppgift så ska vi skapa ett program som hanterar bordsinformationen åt en påhittad restaurang som vi kallar för Centralrestaurangen. Centralrestaurangen har 8 bord och dess personal vill ha ett program där de enkelt kan skriva in ett namn för varje bord samt hur många som sitter på bordet.

Programmet kommer att göras som ett menyprogram. Bordshanteraren ska spara all bordsinformation i en fil på datorn så att bordsinformationen finns kvar när man startar upp programmet nästa gång.

Filhantering och kursen Programmering 1

Att spara till och läsa in saker från en fil ingår egentligen inte i kursen Programmering 1 utan kommer först i Programmering 2. Det finns många olika sätt att göra det på och här visas endast det enklaste av dem, många program blir roligare om de sparar information i filer så det kan vara roligt att lära sig av denna anledning.

Exempelkörning av bordshanteraren

Detta är Centralrestaurangens bordshanterare
Fil med bordsinformation hittades ej, ny information skapades

Välj ett alternativ
1. Visa alla bord
2. Lägg till/ändra bordsinformation
3. Markera att ett bord är tomt
4. Avsluta programmet
2

Vilket bordsnummer vill du lägga till/ändra informationen för?
3
Skriv in bordets namn
Svensson
Hur många gäster finns vid bordet?
4

Välj ett alternativ
1. Visa alla bord
2. Lägg till/ändra bordsinformation
3. Markera att ett bord är tomt
4. Avsluta programmet
2

Vilket bordsnummer vill du lägga till/ändra informationen för?
7
Skriv in bordets namn
Andersson
Hur många gäster finns vid bordet?
3

Välj ett alternativ
1. Visa alla bord
2. Lägg till/ändra bordsinformation
3. Markera att ett bord är tomt
4. Avsluta programmet
1

Bord 1 - Inga gäster
Bord 2 - Inga gäster
Bord 3 - Namn: Svensson, antal gäster: 4
Bord 4 - Inga gäster
Bord 5 - Inga gäster
Bord 6 - Inga gäster
Bord 7 - Namn: Andersson, antal gäster: 3
Bord 8 - Inga gäster
Totalt antal gäster: 7

Välj ett alternativ
1. Visa alla bord
2. Lägg till/ändra bordsinformation
3. Markera att ett bord är tomt
4. Avsluta programmet
4

Denna körning av programmet startar med att skriva ut att en sparad fil med information inte kunde hittas. Efter att programmet har stängts av och startas igen så kommer följande att visas istället:

Detta är Centralrestaurangens bordshanterare
Bordsinformation lästes in från fil

Välj ett alternativ
1. Visa alla bord
2. Lägg till/ändra bordsinformation
3. Markera att ett bord är tomt
4. Avsluta programmet

Programmet kommer nu att innehålla samma bordsinformation som tidigare, Svensson på bord 3 och Andersson på bord 7. Innan vi ger oss på själva projektuppgiften ska vi nu se hur man gör för att spara till och läsa från filer.

Filhantering

Vi ska nu se på ett exempel där två filer skapas och fylls med innehåll och därefter läses in igen.

Console.WriteLine("Detta program skriver och läser information från filer");
Console.WriteLine();

string[] namn = { "Anna", "Albin", "Alexander", "Anita" };

// Skapa eller skriv över fil1.txt
File.WriteAllText("fil1.txt", "Detta skrivs i fil 1");

// Skapa eller skriv över fil2.txt
File.WriteAllLines("fil2.txt", namn);

// Läs in och skriv ut innehållet i fil1.txt
Console.WriteLine("Här är innehållet i fil1.txt");
string fil1Innehåll = File.ReadAllText("fil1.txt");
Console.WriteLine(fil1Innehåll);
Console.WriteLine();

// Läs in innehållet i fil2.txt.
string[] fil2Innehåll = File.ReadAllLines("fil2.txt");

// Skriv ut varje rad från fil2.txt på var sin rad
Console.WriteLine("Här är innehållet i fil2.txt");
foreach (string rad in fil2Innehåll)
{
    Console.WriteLine(rad);
}
Console.WriteLine();

// Kolla om det finns en fil som heter fil1.txt
if (File.Exists("fil1.txt"))
{
    Console.WriteLine("Det finns en fil som heter fil1.txt");
}
else
{
    Console.WriteLine("Det finns INTE en fil som heter fil1.txt");
}

// Avsluta inte direkt
Console.ReadKey();

Notera att både metoderna som skriver information från början av en fil (WriteAllText och WriteAllLines) skapar filen om den inte finns sedan tidigare.

Metoderna med Text eller Lines

I exemplet visas två olika grupper av metoder för filhantering: en grupp med metodnamn som slutar på Text och en som slutar på Lines. Metoderna som slutar på Text använder sig av strängar och är bra om du bara vill spara en enskild sak i en fil. Om du istället vill spara flera saker så är metoderna som slutar på Lines bättre och det är dessa som kommer användas i bordshanteraren.

Läsning av filer som inte finns

Om du försöker läsa in innehållet från en fil som inte finns kommer ditt program att krascha, därför är det bra att alltid kolla om en fil finns först innan du försöker läsa in information från den.

Uppgift P2.1

Skapa ett program där användaren får välja om hen vill skapa/skriva över en fil eller läsa in innehållet från en fil. Användaren ska därefter få skriva in namnet på filen. Om användaren valde att skapa/skriva över en fil ska hen sedan få skriva in en sträng som ska sparas i filen, om hen valde att läsa in innehållet från en fil ska filens innehåll visas. Innan du läser in filen ska du undersöka om filen finns.

Lösningstips P2.1

Skriv en meny med användarens alternativ och låt hen välja 1 eller 2, undersök sedan variabeln du läste in med en switch-sats.

Eftersom du bara ska skriva en sträng till filen så kan du använda File.ReadAllText och File.WriteAllText.

Lösningsförslag P2.1

Planering och implementering av Bordshanteraren

Precis som i den förra projektuppgiften så börjar vi med att planera programmet med pseudokod, vi börjar med huvudmenyn och vad som ska hända innan användaren kommer till huvudmenyn. Huvudmenyn ska se ut så här:

Välj ett alternativ
1. Visa alla bord
2. Lägg till/ändra bordsinformation
3. Markera att ett bord är tomt
4. Avsluta programmet

En första planering av programmet kan se ut så här:

// Skriv välkomstmeddelande

// if (en sparad fil finns)
    // Läs in den sparade informationen
// else
    // Skapa information med bara tomma bord
    // Spara den skapade informationen till en fil

// Sätt menyVal till "0"

// while (menyval != ”4”)

    // Skriv ut meny

    // Läs in menyVal

    // switch menyVal
        // case 1: Visa alla bord
        // case 2: Lägg till/ändra bordsinformation
        // case 3: Markera att ett bord är tomt
        // case 4: Gör ingenting (programmet avslutas)
        // default: Skriv att alternativet var ogiltigt

Därefter så implementerar vi vår planering, vi skriver kod som utför det som vi vill ska hända. Innan vi kan börja implementera koden i denna planering så måste vi bestämma hur vi ska spara informationen om varje bord på ett sätt som går att spara i en fil.

I en fil sparas all information som text och vi kan använda raderna i en fil som en uppdelning av texten den innehåller. Vi behöver sparas information om 8 olika bord, så då väljer vi att spara information om varje bord på en egen rad i filen. Vi kan med File.WriteAllLines spara värdet av en array av strängar till en fil, så vi kommer att använda en array med storleken 8 för att spara information om de 8 bord som restaurangen har. Information för varje bord blir då en sträng. Ett problem för oss är att vi vill spara både ett namn och antalet gäster för varje bord. Vi kommer att kombinera dessa värden i en sträng och bearbeta all information med metoderna Join och Split som man kan använda med arrayer.

0;Inga gäster
0;Inga gäster
4;Svensson
0;Inga gäster
0;Inga gäster
0;Inga gäster
3;Andersson
0;Inga gäster

När vi ska använda informationen som finns sparad i textfilen i vårt program måste vi dela upp varje rad i en array med hjälp av Split.

CSV

Att spara information uppdelat med ett tecken som avskiljare, i vårt fall semikolon ;, är ganska vanligt. Om man använder ett kommatecken som avskiljare så kallas detta sätt att spara information på för Comma Separated Values eller CSV.

Ofta så är det svårt att planera sin kod i minsta detalj innan man väl har skrivit den. I detta fall så visar vi att man inte alltid behöver hålla sig strikt till sin planering genom att lägga till några variabler högst upp i programmet. Dessa variabler innehåller värden som vi antagligen kommer att behöva använda på flera ställen i programmet, och genom att använda en variabel för dessa värden så minskar risken att man skriver fel någonstans och dessutom så blir programmet lättare att ändra i framtiden.

string[] bordsInformation;
string filnamn = "centralbord.txt";
string tomtBordBeskrivning = "0;Inga gäster";
int antalbord = 8;

Console.WriteLine("Detta är Centralrestaurangens bordshanterare");

// Läs in från fil ifall den finns
if (File.Exists(filnamn))
{
    bordsInformation = File.ReadAllLines(filnamn);
    Console.WriteLine("Bordsinformation lästes in från fil");
}
else
{
    // Skapa bordslistan och fyll den med information
    bordsInformation = new string[antalbord];
    for (int i = 0; i < bordsInformation.Length; i++)
    {
        bordsInformation[i] = tomtBordBeskrivning;
    }
    File.WriteAllLines(filnamn, bordsInformation);
    Console.WriteLine("Fil med bordsinformation hittades ej, ny information skapades");
}            
Console.WriteLine();

// Programmets huvudloop
string menyVal = "0";            
while (menyVal != "4")
{
    Console.WriteLine("Välj ett alternativ");
    Console.WriteLine("1. Visa alla bord");
    Console.WriteLine("2. Lägg till/ändra bordsinformation");
    Console.WriteLine("3. Markera att ett bord är tomt");
    Console.WriteLine("4. Avsluta programmet");
    menyVal = Console.ReadLine();

    // Tom rad innan användarens val körs
    Console.WriteLine();

    switch (menyVal)
    {
        // Visa alla bord
        case "1":
            break;

        // Lägg till/ändra bordsinformation
        case "2":
            break;

        // Markera att ett bord är tomt
        case "3":
            break;

        // Gör ingenting (programmet avslutas)
        case "4":
            break;

        default:
            Console.WriteLine("Du valde inte ett giltigt alternativ");
            break;
    }

    // Tom rad innan nästa körning
    Console.WriteLine();
}

Huvudmenyn är avklarad och det är dags att implementera koden för varje menyalternativ som användaren kan välja. Vi börjar med alternativ 1 där användaren får se informationen om varje bord i restaurangen. Dessutom så ska det totala antalet gäster på restaurangen visas efter att informationen om varje bord har skrivits ut.

// Visa alla bord
case "1":
    // totaltAntalGäster = 0
    // Loopa igenom hela arrayen bordsInformation
        // if (bordsInformation[i] == tomtBordBeskrivning)
            // Skriv ut att bordet är tomt
            // continue

        // Dela upp bordsinformationen med Split
        // totaltAntalGäster += bordetsAntalGäster
        // Skriv ut bordets nummer, namn och antal gäster
    // Skriv ut det totala antalet gäster
    break;

Vi ska med en loop gå igenom hela arrayen bordsInformation och skriva ut information om varje bord. Om det är så att bordsInformationen för det nuvarande bordet vi undersöker i for-loopen är likadan som beskrivningen av ett tomt bord så kan vi skriva att bordet är tomt och därefter avsluta denna körning av loopen med continue. Vi hade kunnnat lägga resten av koden i for-loopen inuti ett else-block men den här lösningen gör att koden blir mindre indragen, för många indragningsnivåer gör snabbt att koden kan bli svårare att läsa vilket du kanske har märkt tidigare.

Indragningsnivåer

Tänk på att för många indragningsnivåer i koden kan göra koden svårläst när du jobbar med dina kommande program.

Nu är det dags att implementera vår planering.

// Visa alla bord
case "1":
    int totaltAntalGäster = 0;
    for (int i = 0; i < bordsInformation.Length; i++)
    {
        if (bordsInformation[i] == tomtBordBeskrivning)
        {
            Console.WriteLine($"Bord {i + 1} - Inga gäster");
            continue;
        }

        // Detta sker bara om bordet inte är tomt
        string[] enskiltBordsinformation = bordsInformation[i].Split(';');
        int antalGäster = int.Parse(enskiltBordsinformation[0]);
        string bordsnamn = enskiltBordsinformation[1];
        totaltAntalGäster += antalGäster;
        Console.WriteLine($"Bord {i + 1} - Namn: {bordsnamn}, antal gäster: {antalGäster}");

    }
    Console.WriteLine($"Totalt antal gäster: {totaltAntalGäster}");
    break;

En sak vi hela tiden behöver hålla koll på när vi skriver ut eller läser in ett bordsnummer är att vi ökar värdet av arrayens index med 1. Om vi inte gör detta så kommer borden att vara numrerade från 0 till 7 vilket skulle låta märkligt, det är ju vanligare att numrera de flesta saker från 1 till 8.

Metoden Split används för att dela upp information om ett bord till en array. Arrayens första element kommer att vara antalet gäster och det andra kommer att vara bordsnamnet.

Vi ska nu planera alternativ 2 i programmet, där användaren får skriva in bordsinformation för ett av restaurangens bord.

// Lägg till/ändra bordsinformation
case "2":

    // Fråga om och läs in bordsNummerÄndra
    // if (bordsnummerÄndra <= 0 || bordsnummerÄndra > bordsInformation.Length)
        // Skriv att bordsnumret är felaktigt
        // break

    // string nyttBordInfo = new string[2]
    // Läs in antalGäster och namn
    // Spara nyttBordInfo i arrayen bordsInformation
    // Uppdatera sparfilen
    break;

Även här utnyttjar vi ett tidigt avbrott för att slippa kod indragen i ett else-block. Denna gång är det dock genom att avbryta hela caset med break istället för att avbryta en körning av en loop med continue. Vi gör det tidiga avbrottet för att se till att användaren inte skriver in ett ogiltigt bordsnummer av misstag eftersom det skulle krascha programmet. Genom att uppdatera sparfilen direkt när vi har ändrat bordsInformation så ser vi till att ingen information försvinner ifall användaren skulle stänga av programmet.

Så här ser implementeringen av alternativ 2 ut.

// Lägg till/ändra bordsinformation
case "2":
    Console.WriteLine("Vilket bordsnummer vill du lägga till/ändra informationen för?");
    int bordsnummerÄndra = int.Parse(Console.ReadLine());
    if (bordsnummerÄndra <= 0 || bordsnummerÄndra > bordsInformation.Length)
    {
        Console.WriteLine($"{bordsnummerÄndra} är inte ett giltigt bordsnummer");
        break;
    }

    string[] nyttBordInfo = new string[2];
    Console.WriteLine("Skriv in bordets namn");
    nyttBordInfo[1] = Console.ReadLine();
    Console.WriteLine("Hur många gäster finns vid bordet?");
    nyttBordInfo[0] = Console.ReadLine();
    bordsInformation[bordsnummerÄndra - 1] = string.Join(";", nyttBordInfo);

    // Uppdatera sparfilen
    File.WriteAllLines(filnamn, bordsInformation);
    break; 

Bordets namn sparas i det andra elementet i arrayen nyttBordInfo eftersom antalet gäster alltid ska sparas först.

Nu återstår alternativ 3 kvar att planera.

// Markera att ett bord är tomt
case "3":
    // Fråga om och läs in bordsNummerRadera
    // if (bordsnummerRadera <= 0 || bordsnummerRadera > bordsInformation.Length)
        // Skriv att bordsnumret är felaktigt
        // break

    // Ändra rätt plats i bordsInformation till tomtBordBeskrivning
    // Uppdatera sparfilen
    break;

Planering av alternativ 3 blir ganska lik planering av alternativ 2. Först så ska ett bordsnummer läsas in som inte får vara mindre än 1 och inte heller mer än längden av bordsInformation. Om användaren skrev in ett korrekt nummer så ändrar vi beskrivning av ett bord till det värde som finns sparat i en variabel längst upp i hela programmet. Implementeringen följer här.

// Markera att ett bord är tomt
case "3":
    Console.WriteLine("Vilket bordsnummer vill du markera som tomt?");
    int bordsnummerRadera = int.Parse(Console.ReadLine());
    if (bordsnummerRadera <= 0 || bordsnummerRadera > bordsInformation.Length)
    {
        Console.WriteLine($"{bordsnummerRadera} är inte ett giltigt bordsnummer");
        break;
    }

    bordsInformation[bordsnummerRadera - 1] = tomtBordBeskrivning;
    Console.WriteLine($"Bord {bordsnummerRadera} är markerat som tomt");

    // Uppdatera sparfilen
    File.WriteAllLines(filnamn, bordsInformation);
    break;

Med detta så är programmet färdigskrivet och du har fått ett program där du kan skapa, ändra och radera information som sparas till en fil.

Hela programmet

Följ länken nedan för att se koden till hela programmet.

Hela programmet

Du har nu fått skriva två längre program med hjälp av denna bok och säkert märkt att det är krångligt att hitta saker i koden i ett så här stort program. I nästa kapitel kommer du att lära dig hur man kan strukturera upp sitt program så att det blir lättare att hitta i och lättare att förstå.

Förslag på förändringar och utökningar av Bordshanteraren

Här följer förslag på vad du själv kan göra för att bygga ut Bordshanteraren. Du väljer själv hur mycket du vill göra och vad du har tid med, men du rekommenderas att åtminstone genomföra delen ”Ingen ogiltig inmatning”.

Ingen ogiltig inmatning

Det är inte rimligt att ett bord skulle kunna ha ett negativt antal gäster, men i vårt program går det att skriva in ett negativt tal när man ska ange hur många gäster ett bord har. Det är inte heller bra att man kan ange en tom sträng som bordsnamn eller en sträng som innehåller ett semikolon som vi använder som avgränsare mellan antalet gäster och bordsnamnen i vår sparfil. Ändra programmet så att ogiltig värden, t.ex. negativa antal gäster eller namn med semikolon, inte kan anges av användaren. Finns det mer ogiltiga värden som du behöver korrigera?

Programmets utseende och känsla

Ändra programmets titel, bakgrundsfärg och textfärg till någon kombination som du tycker om.

Skriv ut text i olika delar av programmet i olika färg, t.ex. skulle varannat bord kunna skrivas ut i en annan färg när man visar informationen om varje bord.

Nota för varje bord

Låt varje bord ha en nota som börjar på 0 kronor. Användaren ska kunna öka värdet av ett bords nota med hjälp av ett nytt huvudmenyalternativ. Notan för ett bord ska också sparas i sparfilen. Tänk på att det inte ska vara möjligt att skriva in några ogiltiga/orimliga värden när man ökar ett bords nota.

Max antal gäster per bord

Skapa ett nytt huvudmenyalternativ där användaren ska kunna ställa in det maximala antalet gäster som ett bord kan ha. Eftersom Centralrestaurangen har olika stora bord så ska användaren kunna ange olika storlekar för varje bord. Denna information ska också sparas i sparfilen.

Olika sparfiler

Låt användaren välja mellan flera olika sparfiler när hen använder programmet. Programmet ska komma ihåg vilken den senast använda sparfilen är när det startas och automatiskt öppna denna sparfil. Olika sparfiler ska kunna ha olika antal bord, användaren ska själv få välja antalet bord som restaurangen har när en sparfil skapas.

Verifiera sparfilen

Kontrollera att sparfilen innehåller information som passar för vårt program när sparfilen läses in vid programmets uppstart. Om informationen i sparfilen inte passar vårt program, t.ex. genom att någon har ändrat på sparfilen med hjälp av något annat program, så måste sparfilen skapas om på samma sätt som om den inte fanns.  

Uppgradera dina gamla program

Uppgradera ditt spel ”21:an” så att den som vann spelet senast sparas i en fil. Har du kanske några andra gamla program som kan uppgraderas genom att information kan sparas i en fil?

Förslag på egen projektuppgift – Musiktopplistan

Skapa ett program där användaren får skriva in sina 20 favoritlåtar i en lista. Programmet ska vara ett menyprogram. Användaren ska kunna ändra vilka låtar som finns i listan med ett menyalternativ. Det ska också finnas ett menyalternativ för att visa hela topplistan och ett menyalternativ som låter användaren söka efter ett låtnamn. Om användaren t.ex. söker på ordet ”love” så ska alla låtar som innehåller ordet love, oavsett om det är skrivet med små eller stora bokstäver, visas. Låtarnas plats i topplistan ska också skrivas ut när man har gjort en sökning som fick träffar i topplistan.