Egzamin próbny – powtarzalne błędy

Część 1. Aplikacja konsolowa

Błąd 1: Brak metody zwracającej największą liczbę w systemie dziesiętnym

To jeden z najdroższych błędów punktowo. Spotykałem szkielety metod, które wyglądały tak:

csharp

public long GetLargestNumber()
{
    return 0; // TODO: dokończyć
}

…i tyle. Metoda jest, technicznie istnieje, ale nie liczy nic. Egzaminator widzi return 0; i punktów nie ma.

Co trzeba zrobić: po zamianie symboli na cyfry trójkowe każdy wiersz tablicy to liczba w systemie trójkowym (np. "0121"). Trzeba ją przekonwertować na dziesiętną i znaleźć największą.

Liczba 0121 w systemie trójkowym to:

0 × 3³ + 1 × 3² + 2 × 3¹ + 1 × 3⁰ = 0 + 9 + 6 + 1 = 16

W C# konwersję załatwia jedna linia: Convert.ToInt64(napis, 3) — drugi argument to podstawa systemu liczbowego.

csharp

public long GetLargestNumber()
{
    long max = 0;
    foreach (string row in tablica)
    {
        long value = Convert.ToInt64(row, 3); // 3 = system trójkowy
        if (value > max)
        {
            max = value;
        }
    }
    return max;
}

Pamiętaj o kolejności wywołań w Main. Tę metodę wywołujesz po metodzie zamieniającej symbole na cyfry. Jeśli odpalisz ją wcześniej, Convert.ToInt64 rzuci wyjątkiem — bo +o* to nie cyfry.


Błąd 2: Brak klasy z prywatnym polem tablicowym i konstruktorem

Arkusz wprost wymaga osobnej klasy. Cały kod w Main() nie wystarczy — nawet jeśli liczy dobrze. Muszą być trzy rzeczy:

  • prywatne pole z tablicą stringów,
  • konstruktor przyjmujący tablicę i przypisujący ją do pola,
  • metody operujące na polu.

csharp

class SymbolConverter
{
    // Prywatne pole - dostępne TYLKO w tej klasie,
    // niedostępne nawet dla klas potomnych
    private string[] tablica;

    // Konstruktor - przyjmuje tablicę i przypisuje do pola
    public SymbolConverter(string[] dane)
    {
        tablica = dane;
    }

    // Metoda zamieniająca symbole na cyfry i wypisująca wynik
    public void ConvertAndPrint()
    {
        for (int i = 0; i < tablica.Length; i++)
        {
            tablica[i] = tablica[i]
                .Replace('+', '0')
                .Replace('o', '1')
                .Replace('*', '2');
            Console.WriteLine(tablica[i]);
        }
    }

    // Metoda zwracająca największą liczbę w systemie dziesiętnym
    public long GetLargestNumber()
    {
        long max = 0;
        foreach (string row in tablica)
        {
            long value = Convert.ToInt64(row, 3);
            if (value > max) max = value;
        }
        return max;
    }
}

Dwie rzeczy, na które zwróć szczególną uwagę:

  • Słowo private musi tam byćpublic albo brak modyfikatora to nie to samo — arkusz wymaga, żeby pole było niedostępne nawet dla klas potomnych, a to zapewnia tylko private.
  • Konstruktor musi mieć dokładnie tę samą nazwę co klasa (SymbolConverter). Bez tego to nie konstruktor, tylko zwykła metoda.

Błąd 3: Odczyt symbole.txt po ścieżce absolutnej

To jest błąd, który zabija program na stanowisku egzaminacyjnym. Wyobraź sobie:

csharp

// ŹLE
string[] lines = File.ReadAllLines(@"C:\Users\Jan\Desktop\symbole.txt");

Na Twoim komputerze działa. Egzaminator otwiera projekt na swoim komputerze — folder C:\Users\Jan\Desktop\ nie istnieje, program się wywala. Zero punktów za uruchomienie.

Opcja 1 (najprostsza): plik symbole.txt w folderze projektu, w kodzie sama nazwa pliku:

csharp

// DOBRZE
string[] lines = File.ReadAllLines("symbole.txt");

W Visual Studio: kliknij prawym na plik symbole.txt w Solution Explorer → Properties → ustaw Copy to Output Directory na Copy always. Plik trafi obok .exe przy każdej kompilacji.

Opcja 2 (jeszcze bezpieczniejsza): ścieżka względna do folderu z programem:

csharp

string sciezka = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "symbole.txt");
string[] lines = File.ReadAllLines(sciezka);

Ta wersja działa nawet jeśli ktoś uruchomi .exe z innego katalogu.


Kompletny program konsolowy z dokumentacją

Pokazuję od razu z komentarzami nagłówkowymi w stylu z Listingu 1 — to się przyda przy Błędzie 7.

csharp

using System;
using System.IO;

namespace KonsolaSymbole
{
    /*
    **********************************************
    nazwa klasy: SymbolConverter
    opis klasy: Przechowuje tablicę wierszy z symbolami,
                zamienia symbole na cyfry systemu trójkowego
                oraz znajduje największą liczbę w systemie dziesiętnym.
    autor: 12345678901
    **********************************************
    */
    class SymbolConverter
    {
        private string[] tablica;

        /*
        **********************************************
        nazwa metody: SymbolConverter (konstruktor)
        opis metody: Inicjalizuje obiekt wartością tablicy z pliku.
        parametry: dane - tablica napisów wczytanych z pliku symbole.txt
        zwracany typ i opis: brak (konstruktor)
        autor: 12345678901
        **********************************************
        */
        public SymbolConverter(string[] dane)
        {
            tablica = dane;
        }

        /*
        **********************************************
        nazwa metody: ConvertAndPrint
        opis metody: Zamienia symbole +, o, * na cyfry 0, 1, 2 w każdym
                     wierszu tablicy i wypisuje wynik na ekran.
        parametry: brak
        zwracany typ i opis: brak (void)
        autor: 12345678901
        **********************************************
        */
        public void ConvertAndPrint()
        {
            for (int i = 0; i < tablica.Length; i++)
            {
                tablica[i] = tablica[i]
                    .Replace('+', '0')
                    .Replace('o', '1')
                    .Replace('*', '2');
                Console.WriteLine(tablica[i]);
            }
        }

        /*
        **********************************************
        nazwa metody: GetLargestNumber
        opis metody: Konwertuje każdy wiersz tablicy z systemu trójkowego
                     na dziesiętny i zwraca największą znalezioną liczbę.
        parametry: brak
        zwracany typ i opis: long - największa liczba w systemie dziesiętnym
        autor: 12345678901
        **********************************************
        */
        public long GetLargestNumber()
        {
            long max = 0;
            foreach (string row in tablica)
            {
                long value = Convert.ToInt64(row, 3);
                if (value > max) max = value;
            }
            return max;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            string sciezka = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "symbole.txt");
            string[] lines = File.ReadAllLines(sciezka);

            SymbolConverter converter = new SymbolConverter(lines);

            Console.WriteLine("Po konwersji symboli na cyfry:");
            converter.ConvertAndPrint();

            Console.WriteLine("\nNajwieksza liczba (dziesietnie): " + converter.GetLargestNumber());

            Console.ReadKey();
        }
    }
}

Możesz używać komentarzy wieloliniowych /* ... */ albo kilku jednoliniowych // — oba są dozwolone. Najważniejsze, żeby każda metoda i konstruktor miała swój nagłówek.


Część 2. Aplikacja desktopowa (WPF)

Tworzenie projektu

  1. File → New → Project
  2. WPF Application (C#) — zwykłe, nie „.NET Framework”.
  3. Nazwa: KalkulatorBiegacza.

Zobaczysz dwa kluczowe pliki:

  • MainWindow.xaml — wygląd okna (XML opisujący interfejs),
  • MainWindow.xaml.cs — kod obsługujący zdarzenia (np. kliknięcia).

Błąd 4: Desktop ma tylko wygląd, bez obsługi przycisku

Spotykałem pracę, gdzie formatka wyglądała idealnie — kolory, czcionki, obraz biegacza, wszystko. Tylko że przycisk „Przelicz tempo” nic nie robił. To bardzo łatwo przeoczyć, bo aplikacja się uruchamia i wygląda dobrze.

Żeby przycisk działał, potrzebujesz dwóch rzeczy:

  1. W XAML: Click="PrzeliczButton_Click" na przycisku.
  2. W kodzie C#: metodę PrzeliczButton_Click, która faktycznie coś liczy.

Sama wizualizacja przycisku to za mało.


Plik MainWindow.xaml

xml

<Window x:Class="KalkulatorBiegacza.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Kalkulator biegacza" Height="450" Width="600"
        Background="Gray">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="200"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>

        <!-- Obraz biegacza w lewym górnym rogu -->
        <Image Source="biegacz.jpg" Grid.Row="0" Grid.RowSpan="3" Grid.Column="0"
               Margin="10" Stretch="Uniform"/>

        <!-- Nagłówek -->
        <TextBlock Text="Kalkulator biegacza" Foreground="White" FontSize="40"
                   Grid.Row="0" Grid.Column="1" Margin="10"/>

        <!-- Pole Kilometry -->
        <StackPanel Orientation="Horizontal" Grid.Row="1" Grid.Column="1" Margin="10">
            <TextBlock Text="Kilometry:" Foreground="White" FontSize="40" Width="200"/>
            <TextBox x:Name="KilometryBox" Width="200" Height="50"
                     FontSize="30" Foreground="Gray" Background="White"/>
        </StackPanel>

        <!-- Pole Czas -->
        <StackPanel Orientation="Horizontal" Grid.Row="2" Grid.Column="1" Margin="10">
            <TextBlock Text="Czas:" Foreground="White" FontSize="40" Width="200"/>
            <TextBox x:Name="CzasBox" Width="200" Height="50"
                     FontSize="30" Foreground="Gray" Background="White"
                     Text="GG:MM:SS"/>
        </StackPanel>

        <!-- Przycisk - UWAGA: Click="..." łączy go z metodą w kodzie -->
        <Button x:Name="PrzeliczButton" Content="Przelicz tempo"
                Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2"
                Margin="10" Height="40" FontSize="20"
                Background="Gray" Foreground="White"
                Click="PrzeliczButton_Click"/>

        <!-- Pole wyniku -->
        <TextBlock x:Name="WynikText" Text="Twoje tempo wynosi: ..."
                   Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2"
                   Foreground="White" FontSize="40"
                   HorizontalAlignment="Center" Margin="10"/>
    </Grid>
</Window>

Jak dodać obraz biegacza:

  1. Skopiuj plik biegacz.jpg do folderu projektu.
  2. Solution Explorer → prawy klik → Add → Existing Item → wybierz plik.
  3. Properties → Build Action: Resource albo Copy to Output Directory: Copy always.

Błąd 5: Walidacja czasu sprawdzająca tylko długość i dwukropki

Spotykana walidacja wygląda mniej więcej tak:

csharp

// ŹLE - zaakceptuje np. "AB:CD:EF"
if (czas.Length == 8 && czas[2] == ':' && czas[5] == ':') { ... }

Wpiszesz AB:CD:EF — przejdzie. A potem int.Parse("AB") rzuca wyjątkiem i aplikacja się wywala.

Lepiej: użyj TimeSpan.TryParseExact. To wbudowana metoda C#, która sama sprawdza format i parsuje liczby:

csharp

bool ok = TimeSpan.TryParseExact(
    czasNapis,
    @"hh\:mm\:ss",
    System.Globalization.CultureInfo.InvariantCulture,
    out TimeSpan czas);
  • Jeśli ok jest false → format zły, pokazujesz komunikat.
  • Jeśli true → masz gotowy obiekt TimeSpan czas, z którego łatwo wyciągniesz cokolwiek.

Jedna linia robi to, co inaczej zajęłoby Ci 15 linii ręcznych sprawdzeń. I robi to lepiej.


Błąd 6: Tempo liczone nieprawidłowo — nie z całkowitego czasu

Spotykałem podejście typu: „wyciągnę środkową liczbę z GG:MM:SS jako minuty i podzielę przez kilometry”. Dla 00:45:00na 10 km to przypadkiem wyjdzie dobrze — 45/10 = 4,5. Ale dla 01:30:00 na 10 km wyjdzie 30/10 = 3 min/km, a powinno być 9 min/km. Wpadka.

Poprawnie: zamień cały czas na sekundy, podziel przez kilometry, wynik przelicz z powrotem na minuty i sekundy.

Przykład z arkusza — 00:45:00 na 10 km:

  • 00:45:00 = 45 minut × 60 = 2700 sekund
  • 2700 / 10 = 270 sekund na kilometr
  • 270 sekund = 4 minuty 30 sekund → 4:30 min/km ✓

W kodzie:

csharp

double totalSeconds = czas.TotalSeconds;        // 2700
double secondsPerKm = totalSeconds / kilometry; // 270
int minuty = (int)(secondsPerKm / 60);          // 4
int sekundy = (int)(secondsPerKm % 60);         // 30
string wynik = $"{minuty}:{sekundy:D2} min/km"; // "4:30 min/km"

{sekundy:D2} pilnuje, żeby sekundy zawsze były dwucyfrowe (czyli 4:05, nie 4:5).


Plik MainWindow.xaml.cs — kompletna logika

csharp

using System;
using System.Globalization;
using System.Windows;

namespace KalkulatorBiegacza
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        /*
        **********************************************
        nazwa metody: PrzeliczButton_Click
        opis metody: Obsługuje kliknięcie przycisku "Przelicz tempo".
                     Waliduje dane wejściowe, oblicza tempo biegu
                     i wyświetla wynik lub komunikat o błędzie.
        parametry: sender - obiekt wywołujący zdarzenie
                   e - argumenty zdarzenia kliknięcia
        zwracany typ i opis: brak (void)
        autor: 12345678901
        **********************************************
        */
        private void PrzeliczButton_Click(object sender, RoutedEventArgs e)
        {
            // 1) Walidacja kilometrów - musi być liczbą naturalną (>0)
            if (!int.TryParse(KilometryBox.Text, out int kilometry) || kilometry <= 0)
            {
                WynikText.Text = "Niepoprawna liczba kilometrow";
                return;
            }

            // 2) Walidacja czasu w formacie GG:MM:SS - prawidłowe parsowanie!
            bool czasOk = TimeSpan.TryParseExact(
                CzasBox.Text,
                @"hh\:mm\:ss",
                CultureInfo.InvariantCulture,
                out TimeSpan czas);

            if (!czasOk)
            {
                WynikText.Text = "Niepoprawny format czasu";
                return;
            }

            // 3) Obliczenia - z CAŁKOWITEGO czasu w sekundach
            double secondsPerKm = czas.TotalSeconds / kilometry;
            int minuty = (int)(secondsPerKm / 60);
            int sekundy = (int)(secondsPerKm % 60);

            // 4) Wyświetlenie wyniku
            WynikText.Text = $"Twoje tempo wynosi: {minuty}:{sekundy:D2} min/km";
        }
    }
}

Krótka wzmianka o .NET MAUI

Jeśli chcesz to samo zrobić w MAUI (np. żeby pobawić się aplikacją mobilną w domu), logika obliczeń jest identyczna. Różnice są tylko w XAML:

  • Zamiast <Window> używasz <ContentPage>.
  • Zamiast <TextBlock> używasz <Label>.
  • Zamiast Background="Gray" piszesz BackgroundColor="Gray".
  • Reszta (przyciski, TextBox-y, walidacja czasu, obliczenia) działa tak samo.

Na egzaminie INF.04 wystarczy WPF. MAUI traktuj jako dodatek do nauki na własne potrzeby.


Część 3. Dokumentacja — Błąd 7

Dokumentacja w komentarzach kodu, nie tylko w pliku egzamin.docx

Plik egzamin.docx z opisem narzędzi to nie wszystko. Arkusz wymaga trzech rzeczy:

1. Komentarze nagłówkowe nad każdą metodą aplikacji konsolowej — według szablonu z Listingu 1. Masz to pokazane w kompletnym kodzie konsolowym wyżej. Pamiętaj o konstruktorze — on też potrzebuje komentarza.

2. Plik egzamin.docx w folderze dokumentacja z informacjami o narzędziach:

System operacyjny: Windows 11
Środowisko programistyczne: Visual Studio 2026
Język programowania: C#
Framework aplikacji desktopowej: WPF (.NET 8)

3. Zrzuty ekranu też w folderze dokumentacja:

  • konsola1.pngkonsola2.png — działająca aplikacja konsolowa + widoczne IDE,
  • des1.png — stan początkowy (puste pola),
  • des2.png — poprawne obliczenie tempa,
  • des3.png — komunikat o błędnym formacie czasu.

Klawisz PrtScn robi zrzut całego ekranu. Wklej go w Paint i zapisz jako .pngPasek zadań musi być widoczny — to wymóg arkusza.


Checklist przed oddaniem płyty

Przejdź przez tę listę zanim zapakujesz płytę:

Aplikacja konsolowa:

  •  Jest osobna klasa, nie wszystko w Main.
  •  Pole tablicowe ma modyfikator private.
  •  Jest konstruktor przyjmujący tablicę.
  •  Metoda zamieniająca symbole i wypisująca tablicę działa.
  •  Metoda zwracająca największą liczbę faktycznie liczy, a nie zwraca 0.
  •  symbole.txt wczytywany ze ścieżki względnej, nie C:\....
  •  Każda metoda i konstruktor ma komentarz nagłówkowy.

Aplikacja desktopowa:

  •  Przycisk ma Click="..." w XAML.
  •  Metoda PrzeliczButton_Click istnieje w .cs i coś robi.
  •  Walidacja czasu używa TimeSpan.TryParseExact, nie tylko .Length.
  •  Tempo liczone z całkowitych sekund: czas.TotalSeconds / km.
  •  Komunikat błędu pojawia się przy niepoprawnym czasie.
  •  Obraz biegacza jest widoczny.
  •  Kolory się zgadzają: szare tło, biała czcionka 40px, inputy białe z szarą czcionką 30px.

Struktura plików na płycie:

[numer_zdającego]/
├── konsolowa/
│   ├── konsola.zip
│   ├── Program.cs
│   └── KonsolaSymbole.exe (jeśli istnieje)
├── desktopowa/
│   ├── des.zip
│   ├── MainWindow.xaml
│   └── MainWindow.xaml.cs
└── dokumentacja/
    ├── egzamin.docx
    ├── konsola1.png
    ├── konsola2.png
    ├── des1.png
    ├── des2.png
    └── des3.png