Przejdź do głównej treści
INF.04-01-26.06-SGCzerwiec 2026 · 35 kroków · ok. 87 min · 32 kryteria z klucza

Rozwiązanie krok po kroku: Menedżer haseł i generator (C# + Angular)

Rozwiązanie dotyczy arkusza z menedżerem haseł w wersji C# + Angular: w konsoli metoda, która losuje 12-znakowe hasło z pięciu zestawów znaków (w tym polskich liter) i wstawia każdy znak w losowe miejsce, a w przeglądarce jednokomponentowa aplikacja Angular z formularzem Bootstrapa, polem wyboru blokującym pole hasła i podsumowaniem zapisanych danych - to ta sama technologia, w której CKE pokazało ilustracje 1-3 arkusza. Do tego dokumentacja metody według wzoru z arkusza, zrzuty i plik egzamin.

Kroki idą zdanie po zdaniu za treścią arkusza - każdy zaczyna się poleceniem z arkusza, a kod pokazuje dokładnie te linie, które je spełniają. Kod od początku jest pisany tak, jak w prawdziwych projektach; na końcu dwa kroki porównują ten wariant z rozwiązaniem w Pythonie i React (druga zakładka).

Co widać w podglądzie. Wyniki programu konsolowego pochodzą z prawdziwego uruchomienia kodu z danego kroku (.NET 10) - hasła są naprawdę losowe, a każde sprawdziłem: 12 znaków, 3 + 3 + 2 + 2 + 2 z właściwych zestawów. Podgląd aplikacji webowej wykonuje kod komponentu z kroku: klasę z app.ts i szablon app.html (z [(ngModel)], [disabled], (ngSubmit) i @if) - możesz wpisywać dane, odznaczać pole wyboru i klikać „Zapisz”. Scenariusz z ilustracji 1-3 przeszedłem w podglądzie i w prawdziwej aplikacji zbudowanej poleceniem ng build (Angular 22) - z tym samym wynikiem.

Jak liczą się punkty. Klucz ma 32 kryteria (R.1.1 - R.4.9), każde za 1 punkt. Próg zdania części praktycznej to 75%, czyli 24 punkty - można stracić 8 kryteriów i nadal zdać. Przy każdym kroku jest napisane, które kryteria spełnia; egzaminator ocenia spełnienie kryteriów z klucza, nie zgodność z tym kodem.

  1. Etap 1: Przygotowanie

    Krok 1 · po tym etapie masz 0 z 32 kryteriów (pkt)

    Krok 1: Folder zdającego i trzy podfoldery

    ok. 3 min
    Polecenie z arkusza

    Utwórz folder i nazwij go numerem zdającego. W folderze utwórz trzy podfoldery o nazwach: konsolowa, webowa, dokumentacja. Po wykonaniu zadania umieścisz w nich rezultaty swojej pracy.

    Na pulpicie zakładasz folder o nazwie z numerem PESEL, a w nim konsolowa, webowa i dokumentacja. Rozpakowujesz też archiwum Web1.7z z pulpitu (hasło podane w arkuszu: *LoGiny&H@sla8) - jest w nim plik szablonu z przykładowym formularzem Bootstrapa i znaki.txt z polskimi literami do skopiowania. Hasło archiwum zawiera *, & i @ - wpisujesz je ręcznie, uważnie, bo w oknie hasła nie widać znaków.

  2. Etap 2: Aplikacja konsolowa

    Kroki 2-12 · po tym etapie masz 13 z 32 kryteriów (pkt)

    Krok 2: Projekt konsola w C#

    ok. 3 min
    Polecenie z arkusza

    Zastosowany obiektowy język programowania zgodny z zainstalowanym na stanowisku egzaminacyjnym: C++ lub C#, lub Java, lub Python. Dowolne podejście: strukturalne lub obiektowe.

    Wybierasz C# i podejście strukturalne: metody statyczne w klasie Program. W Visual Studio: Utwórz nowy projekt → Aplikacja konsoli, nazwa konsola (stąd archiwum konsola.zip), z zaznaczoną opcją „Nie używaj instrukcji najwyższego poziomu” - szablon ma wtedy klasyczną klasę Program z metodą Main, obok której dopiszesz własne metody. W terminalu to samo daje dotnet new console -n konsola --use-program-main. Szablon wypisuje „Hello, World!” - uruchamiasz go (Ctrl+F5), żeby sprawdzić, że środowisko działa.

    konsola/Program.cs C# nowy plik
    namespace konsola;
    
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
        }
    }
    Wynik programu po tym kroku
    > konsola.exe
    Hello, World!
  3. Krok 3: Zestawy liter alfabetu łacińskiego

    Kryteria:

    R.1.4 Polskie lub angielskie nazewnictwo zmiennych/pól. Nazewnictwo jest znaczące. Wyjątkami od reguły są zmienne pomocnicze, np. bufor, tmp, iteratory pętli. Kryterium nie jest spełnione tylko wtedy, gdy nazwy zmiennych nic nie znaczą, np. x

    ok. 2 min
    Polecenie z arkusza

    Losowane są: 3 małe litery z alfabetu łacińskiego (26 znaków: a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z), 3 wielkie litery z alfabetu łacińskiego (26 znaków: A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z).

    Każdy zestaw to zwykły napis string - z napisu wylosujesz jeden znak. Słowo const robi z niego stałą klasy: wartości nie da się zmienić, a kompilator pilnuje tego za ciebie. W C# stałe nazywa się zwykle wielką literą (Lowercase, Uppercase) - jak klasy i metody (R.1.4).

    konsola/Program.cs C# zmienione: 3 linie
    namespace konsola;
    
    class Program
    {
        const string Lowercase = "abcdefghijklmnopqrstuvwxyz";
        const string Uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
        }
    }

    Wynik programu bez zmian - taki jak po kroku 2.

  4. Krok 4: Zestaw liter ze znakami diakrytycznymi

    Kryteria:

    R.2.2 Zdefiniowane zestawy do losowania, przynajmniej dla znaków diakrytycznych ąęłńóśżźĄĘŁŃÓŚŻŹ oraz specjalnych !@#$%^&*()_+ wylosowane 2 znaki specjalne oraz 2 litery ze znakami diakrytycznymi z tych zestawów

    ok. 2 min
    Polecenie z arkusza

    2 litery ze znakami diakrytycznymi ze zbioru: ą, ę, ł, ń, ó, ś, ż, ź, Ą, Ę, Ł, Ń, Ó, Ś, Ż, Ź (patrz plik znaki.txt).

    Szesnaście polskich liter - małe i wielkie. Kopiujesz je z pliku znaki.txt z archiwum zamiast wpisywać z klawiatury (łatwo pomylić ż z ź). Typ char w C# przechowuje znaki Unicode, więc polskie litery są zwykłymi znakami napisu; Visual Studio zapisuje pliki .cs w UTF-8.

    konsola/Program.cs C# zmienione: 1 linia
    namespace konsola;
    
    class Program
    {
        const string Lowercase = "abcdefghijklmnopqrstuvwxyz";
        const string Uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        const string Diacritics = "ąęłńóśżźĄĘŁŃÓŚŻŹ";
    
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
        }
    }

    Wynik programu bez zmian - taki jak po kroku 2.

  5. Krok 5: Zestawy cyfr i znaków specjalnych

    Kryteria: ,

    R.2.2 Zdefiniowane zestawy do losowania, przynajmniej dla znaków diakrytycznych ąęłńóśżźĄĘŁŃÓŚŻŹ oraz specjalnych !@#$%^&*()_+ wylosowane 2 znaki specjalne oraz 2 litery ze znakami diakrytycznymi z tych zestawów

    R.1.5 Typy pasują do przypisanych im danych, np. hasło jest typu napisowego (w przypadku języka Python typ wynika z przypisanych danych)

    ok. 1 min
    Polecenie z arkusza

    2 cyfry z zakresu 0 – 9, 2 znaki specjalne z zestawu: !@#$%^&*()_+

    Cyfry też trzymasz jako napis "0123456789", nie liczby - wylosowana cyfra ma się dokleić do hasła jak każdy inny znak. Znaki specjalne przepisujesz dokładnie w kolejności z arkusza; w cudzysłowie żaden z nich nie ma w C# specjalnego znaczenia (wyjątkiem byłyby \ i "). Masz komplet pięciu zestawów (R.2.2, R.1.5).

    konsola/Program.cs C# zmienione: 2 linie
    namespace konsola;
    
    class Program
    {
        const string Lowercase = "abcdefghijklmnopqrstuvwxyz";
        const string Uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        const string Diacritics = "ąęłńóśżźĄĘŁŃÓŚŻŹ";
        const string Digits = "0123456789";
        const string Special = "!@#$%^&*()_+";
    
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
        }
    }

    Wynik programu bez zmian - taki jak po kroku 2.

  6. Krok 6: Metoda wstawiająca znak w losowe miejsce

    Kryteria: ,

    R.2.4 Każdy z 12 znaków jest umieszczony w losowym miejscu w haśle

    R.1.3 Znaczące polskie lub angielskie nazewnictwo metody/funkcji

    ok. 3 min
    Polecenie z arkusza

    Każdy z wylosowanych 12 znaków jest umieszczany w losowym miejscu w haśle.

    Generator liczb pseudolosowych to obiekt klasy Random - jeden na cały program, w polu static, z którego korzystają metody statyczne. Pomocnicza metoda robi dwa losowania:

    • random.Next(characters.Length) - indeks od 0 do długości zestawu minus 1 (górna granica w Next nie wchodzi), a characters[...] bierze spod niego znak,
    • random.Next(password.Length + 1) - miejsce w haśle: od 0 (przed pierwszym znakiem) do długości hasła (za ostatnim) - stąd + 1,
    • password.Insert(position, ...) wstawia napis w podane miejsce i zwraca nowy napis (napisów w C# nie zmienia się w miejscu); znak zamieniasz na napis przez ToString().

    Gdy każdy znak trafia w losowe miejsce, kolejność grup znika - o to chodzi w R.2.4. Arkusz zabrania gotowej funkcji generującej całe hasło; losowanie pojedynczego znaku czy liczby to dozwolone cegiełki.

    Typowe błędy

    random.Next(password.Length) nigdy nie wstawi znaku na KOŃCU hasła, a dla pustego hasła zwraca zawsze 0. Ostatnie miejsce byłoby nieosiągalne i rozkład przestałby być równy.

    konsola/Program.cs C# zmienione: 9 linii
    namespace konsola;
    
    class Program
    {
        const string Lowercase = "abcdefghijklmnopqrstuvwxyz";
        const string Uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        const string Diacritics = "ąęłńóśżźĄĘŁŃÓŚŻŹ";
        const string Digits = "0123456789";
        const string Special = "!@#$%^&*()_+";
    
        static Random random = new Random();
    
        static string InsertRandomChar(string password, string characters)
        {
            char character = characters[random.Next(characters.Length)];
            int position = random.Next(password.Length + 1);
            return password.Insert(position, character.ToString());
        }
    
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
        }
    }

    Wynik programu bez zmian - taki jak po kroku 2.

  7. Krok 7: Metoda GeneratePassword i zmienna hasła

    Kryteria: , ,

    R.2.1 Zdefiniowana zmienna typu napisowego dla hasła oraz funkcja zwraca napis (wnętrze funkcji może być z błędami)

    R.1.3 Znaczące polskie lub angielskie nazewnictwo metody/funkcji

    R.1.5 Typy pasują do przypisanych im danych, np. hasło jest typu napisowego (w przypadku języka Python typ wynika z przypisanych danych)

    ok. 1 min
    Polecenie z arkusza

    Hasło przechowywane w zmiennej / polu typu napisowego. Funkcja / metoda zwracająca wygenerowane 12-znakowe hasło według zasad.

    Szkielet głównej metody: zmienna string password zaczyna jako pusty napis i jest zwracana przez return - typ string w nagłówku metody mówi, że metoda zwraca napis (R.2.1, R.1.5). Kolejne kroki dołożą do hasła znaki. Nazwa mówi, co metoda robi (R.1.3).

    konsola/Program.cs C# zmienione: 6 linii
    namespace konsola;
    
    class Program
    {
        const string Lowercase = "abcdefghijklmnopqrstuvwxyz";
        const string Uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        const string Diacritics = "ąęłńóśżźĄĘŁŃÓŚŻŹ";
        const string Digits = "0123456789";
        const string Special = "!@#$%^&*()_+";
    
        static Random random = new Random();
    
        static string InsertRandomChar(string password, string characters)
        {
            char character = characters[random.Next(characters.Length)];
            int position = random.Next(password.Length + 1);
            return password.Insert(position, character.ToString());
        }
    
        static string GeneratePassword()
        {
            string password = "";
            return password;
        }
    
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
        }
    }

    Wynik programu bez zmian - taki jak po kroku 2.

  8. Krok 8: Trzy małe i trzy wielkie litery

    Kryteria: ,

    R.2.3 Wylosowane 3 małe litery, 3 wielkie litery, 2 cyfry

    R.1.2 Kod zapisany z wcięciami dla zagnieżdżeń bloków

    ok. 2 min
    Polecenie z arkusza

    Losowane są: 3 małe litery z alfabetu łacińskiego, 3 wielkie litery z alfabetu łacińskiego.

    Pętla for wykonuje się trzy razy dla każdego zestawu. W każdym obrocie wynik metody pomocniczej zapisujesz z powrotem do password - bez przypisania nowy napis z Insert by przepadł. Klamry bloków w osobnych liniach i wcięcia o 4 spacje to konwencja C# (R.1.1, R.1.2).

    konsola/Program.cs C# zmienione: 8 linii
    namespace konsola;
    
    class Program
    {
        const string Lowercase = "abcdefghijklmnopqrstuvwxyz";
        const string Uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        const string Diacritics = "ąęłńóśżźĄĘŁŃÓŚŻŹ";
        const string Digits = "0123456789";
        const string Special = "!@#$%^&*()_+";
    
        static Random random = new Random();
    
        static string InsertRandomChar(string password, string characters)
        {
            char character = characters[random.Next(characters.Length)];
            int position = random.Next(password.Length + 1);
            return password.Insert(position, character.ToString());
        }
    
        static string GeneratePassword()
        {
            string password = "";
            for (int i = 0; i < 3; i++)
            {
                password = InsertRandomChar(password, Lowercase);
            }
            for (int i = 0; i < 3; i++)
            {
                password = InsertRandomChar(password, Uppercase);
            }
            return password;
        }
    
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
        }
    }

    Wynik programu bez zmian - taki jak po kroku 2.

  9. Krok 9: Dwie litery ze znakami diakrytycznymi

    Kryteria:

    R.2.2 Zdefiniowane zestawy do losowania, przynajmniej dla znaków diakrytycznych ąęłńóśżźĄĘŁŃÓŚŻŹ oraz specjalnych !@#$%^&*()_+ wylosowane 2 znaki specjalne oraz 2 litery ze znakami diakrytycznymi z tych zestawów

    ok. 1 min
    Polecenie z arkusza

    2 litery ze znakami diakrytycznymi ze zbioru: ą, ę, ł, ń, ó, ś, ż, ź, Ą, Ę, Ł, Ń, Ó, Ś, Ż, Ź (patrz plik znaki.txt).

    Ta sama pętla, inny zestaw i dwa obroty. Dzięki metodzie pomocniczej każda grupa to cztery krótkie linie - nie powtarzasz kodu losowania pięć razy.

    konsola/Program.cs C# zmienione: 4 linie
    namespace konsola;
    
    class Program
    {
        const string Lowercase = "abcdefghijklmnopqrstuvwxyz";
        const string Uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        const string Diacritics = "ąęłńóśżźĄĘŁŃÓŚŻŹ";
        const string Digits = "0123456789";
        const string Special = "!@#$%^&*()_+";
    
        static Random random = new Random();
    
        static string InsertRandomChar(string password, string characters)
        {
            char character = characters[random.Next(characters.Length)];
            int position = random.Next(password.Length + 1);
            return password.Insert(position, character.ToString());
        }
    
        static string GeneratePassword()
        {
            string password = "";
            for (int i = 0; i < 3; i++)
            {
                password = InsertRandomChar(password, Lowercase);
            }
            for (int i = 0; i < 3; i++)
            {
                password = InsertRandomChar(password, Uppercase);
            }
            for (int i = 0; i < 2; i++)
            {
                password = InsertRandomChar(password, Diacritics);
            }
            return password;
        }
    
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
        }
    }

    Wynik programu bez zmian - taki jak po kroku 2.

  10. Krok 10: Dwie cyfry i dwa znaki specjalne

    Kryteria: , ,

    R.2.2 Zdefiniowane zestawy do losowania, przynajmniej dla znaków diakrytycznych ąęłńóśżźĄĘŁŃÓŚŻŹ oraz specjalnych !@#$%^&*()_+ wylosowane 2 znaki specjalne oraz 2 litery ze znakami diakrytycznymi z tych zestawów

    R.2.3 Wylosowane 3 małe litery, 3 wielkie litery, 2 cyfry

    R.2.5 Funkcja zwraca wygenerowane hasło

    ok. 2 min
    Polecenie z arkusza

    2 cyfry z zakresu 0 – 9, 2 znaki specjalne z zestawu: !@#$%^&*()_+. Hasło jest zapisane w zmiennej napisowej i zwracane z funkcji.

    Ostatnie dwie grupy. Razem: 3 + 3 + 2 + 2 + 2 = 12 znaków, każdy wstawiony w losowe miejsce. Metoda jest kompletna i zwraca gotowe hasło (R.2.5).

    konsola/Program.cs C# zmienione: 8 linii
    namespace konsola;
    
    class Program
    {
        const string Lowercase = "abcdefghijklmnopqrstuvwxyz";
        const string Uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        const string Diacritics = "ąęłńóśżźĄĘŁŃÓŚŻŹ";
        const string Digits = "0123456789";
        const string Special = "!@#$%^&*()_+";
    
        static Random random = new Random();
    
        static string InsertRandomChar(string password, string characters)
        {
            char character = characters[random.Next(characters.Length)];
            int position = random.Next(password.Length + 1);
            return password.Insert(position, character.ToString());
        }
    
        static string GeneratePassword()
        {
            string password = "";
            for (int i = 0; i < 3; i++)
            {
                password = InsertRandomChar(password, Lowercase);
            }
            for (int i = 0; i < 3; i++)
            {
                password = InsertRandomChar(password, Uppercase);
            }
            for (int i = 0; i < 2; i++)
            {
                password = InsertRandomChar(password, Diacritics);
            }
            for (int i = 0; i < 2; i++)
            {
                password = InsertRandomChar(password, Digits);
            }
            for (int i = 0; i < 2; i++)
            {
                password = InsertRandomChar(password, Special);
            }
            return password;
        }
    
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
        }
    }

    Wynik programu bez zmian - taki jak po kroku 2.

  11. Krok 11: Program główny: pięć haseł

    Kryteria: , ,

    R.2.7 Program wyświetla pięć wygenerowanych haseł (w uruchomionej aplikacji lub na zrzucie i obowiązkowo w kodzie)

    R.1.7 Komunikacja z użytkownikiem prowadzona jest w sposób zrozumiały. Jeżeli kod nie uruchamia się z powodu błędów kompilacji - sprawdzić w kodzie aplikacji

    R.1.1 Kod źródłowy zapisany w sposób czytelny: instrukcje w osobnych liniach, stosowane spacje pomiędzy operatorami, konsekwentnie stosowana wybrana konwencja dla nawiasów klamrowych instrukcji blokowej

    ok. 2 min
    Polecenie z arkusza

    Aplikacja w programie głównym generuje 5 haseł i wyświetla je ze znaczącym komunikatem. Komunikacja z użytkownikiem jest prowadzona w sposób zrozumiały.

    W Main zastępujesz „Hello, World!”: komunikat, co program robi, i pętla od 1 do 5 (<= 5). Napis interpolowany $"Hasło {number}: {GeneratePassword()}" wstawia w klamry numer i wynik metody.

    Pierwsza linia jest tu ważna: Console.OutputEncoding = System.Text.Encoding.UTF8;. Konsola Windows wypisuje znaki w stronie kodowej systemu - w polskim Windows to strona 852, która polskie litery ma, ale w systemie z inną stroną (np. 437 w angielskim Windows) ą i ł zamieniłyby się na znaki zapytania. UTF-8 obejmuje wszystkie znaki, więc hasło wygląda tak samo na każdym stanowisku.

    Uruchamiasz (Ctrl+F5) - podgląd to prawdziwy wynik: każde hasło ma 12 znaków i inny układ.

    Typowe błędy

    Pętla for (int number = 0; number < 5; number++) wypisze „Hasło 0” - program działa, ale numeracja od zera myli użytkownika.

    konsola/Program.cs C# zmienione: 6 linii
    namespace konsola;
    
    class Program
    {
        const string Lowercase = "abcdefghijklmnopqrstuvwxyz";
        const string Uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        const string Diacritics = "ąęłńóśżźĄĘŁŃÓŚŻŹ";
        const string Digits = "0123456789";
        const string Special = "!@#$%^&*()_+";
    
        static Random random = new Random();
    
        static string InsertRandomChar(string password, string characters)
        {
            char character = characters[random.Next(characters.Length)];
            int position = random.Next(password.Length + 1);
            return password.Insert(position, character.ToString());
        }
    
        static string GeneratePassword()
        {
            string password = "";
            for (int i = 0; i < 3; i++)
            {
                password = InsertRandomChar(password, Lowercase);
            }
            for (int i = 0; i < 3; i++)
            {
                password = InsertRandomChar(password, Uppercase);
            }
            for (int i = 0; i < 2; i++)
            {
                password = InsertRandomChar(password, Diacritics);
            }
            for (int i = 0; i < 2; i++)
            {
                password = InsertRandomChar(password, Digits);
            }
            for (int i = 0; i < 2; i++)
            {
                password = InsertRandomChar(password, Special);
            }
            return password;
        }
    
        static void Main(string[] args)
        {
            Console.OutputEncoding = System.Text.Encoding.UTF8;
            Console.WriteLine("Generator bezpiecznych haseł - pięć haseł po 12 znaków:");
            for (int number = 1; number <= 5; number++)
            {
                Console.WriteLine($"Hasło {number}: {GeneratePassword()}");
            }
        }
    }
    Wynik programu po tym kroku
    > konsola.exe
    Generator bezpiecznych haseł - pięć haseł po 12 znaków:
    Hasło 1: S2an%ŻX$cA5Ż
    Hasło 2: ąPggEź)b_93S
    Hasło 3: ŹdśF5G$v+f0O
    Hasło 4: f@7*S9egBąFÓ
    Hasło 5: Zr3ĘqOM5ąr_@
  12. Krok 12: Folder konsolowa: archiwum, źródło, plik exe

    Kryteria:

    R.4.9 Przygotowana jest dokumentacja w postaci płyty: oba projekty są spakowane oraz zmodyfikowane pliki są umieszczone na zewnątrz archiwów

    ok. 3 min
    Polecenie z arkusza

    Kod aplikacji przygotuj do nagrania na płytę. W folderze konsolowa należy umieścić archiwum całego projektu o nazwie konsola.zip, i skopiowany z projektu plik z kodem źródłowym programu (modyfikowane pliki) oraz plik wykonywalny, jeżeli istnieje.

    Najpierw dopisujesz komentarz dokumentujący z części III - archiwum ma zawierać kod z komentarzem. Potem pakujesz folder projektu do konsola.zip (foldery bin i obj można pominąć - Visual Studio odtworzy je przy budowaniu), a obok kopiujesz Program.cs i plik wykonywalny konsola.exe z bin\Debug\net… razem z sąsiednimi konsola.dll i konsola.runtimeconfig.json, bez których się nie uruchomi.

    Wynik programu bez zmian - taki jak po kroku 11.

  13. Etap 3: Aplikacja webowa

    Kroki 13-28 · po tym etapie masz 21 z 32 kryteriów (pkt)

    Krok 13: Nowy projekt Angular

    ok. 5 min
    Polecenie z arkusza

    Z zastosowaniem dostępnego na stanowisku egzaminacyjnym frameworka Angular lub biblioteki React.js wykonaj aplikację internetową typu front-end obsługującą logowanie.

    Wybierasz Angular. Projekt zakładasz w terminalu narzędziem Angular CLI:

    npx @angular/cli@latest new web
    cd web
    npx ng serve
    

    (jeśli CLI jest zainstalowane globalnie, wystarczy ng new web). Kreator zadaje kilka pytań: format arkuszy stylów - CSS, renderowanie po stronie serwera (SSR) - nie; pozostałe odpowiedzi domyślne. Nazwa web od razu pasuje do archiwum web.zip. ng serve uruchamia serwer pod http://localhost:4200.

    Komponent główny App składa się z trzech plików w src/app: app.ts - klasa z danymi i metodami, app.html - szablon (widok), app.css - style komponentu. Dekorator @Component łączy klasę z szablonem i mówi, pod jakim znacznikiem komponent trafia na stronę (app-root). (W Angularze 19 i starszych pliki nazywają się app.component.ts i app.component.html - zawartość jest ta sama.)

    web/src/app/app.ts TypeScript nowy plik
    import { Component, signal } from '@angular/core';
    import { RouterOutlet } from '@angular/router';
    
    @Component({
      imports: [RouterOutlet],
      selector: 'app-root',
      styleUrl: './app.css',
      templateUrl: './app.html',
    })
    export class App {
      protected readonly title = signal('web');
    }

    Wynik programu bez zmian - taki jak po kroku 11.

  14. Krok 14: Biblioteka Bootstrap w projekcie

    Kryteria:

    R.3.2 Do projektu dołączona jest biblioteka Bootstrap oraz sformatowane: przycisk klasą co najmniej: btn btn-primary pierwszy paragraf klasą co najmniej: lead text-primary

    ok. 2 min
    Polecenie z arkusza

    Do utworzenia formularza ostylowanego biblioteką Bootstrap należy posłużyć się przykładem z pliku szablon.html wypakowanego z archiwum. Fragmenty kodu HTML należy zmodyfikować zgodnie ze składnią Angular lub React.js.

    Bootstrap instalujesz jako pakiet: npm install bootstrap w folderze web. Arkusz (tabela 1) każe dopisać do styles.css linię @import "~bootstrap/dist/css/bootstrap.css"; - z tyldą. Tak działał starszy Angular (budowanie przez webpack). Obecny Angular buduje projekt narzędziem esbuild, które tyldy nie rozumie - ng build kończy się błędem „Could not resolve "~bootstrap/..."” (sprawdziłem w Angular 22). Piszesz więc ścieżkę bez tyldy; gdyby na stanowisku był stary Angular, zadziała i jedna, i druga postać.

    styles.css to globalny arkusz stylów całej aplikacji, więc Bootstrap obejmie każdy komponent (R.3.2).

    Typowe błędy

    Import z tyldą z tabeli 1 w nowym Angularze: aplikacja się nie zbuduje, a bez zrzutu działającej aplikacji przepadają kryteria R.3.6 - R.3.9. Gdy ng serve zgłosi błąd importu, usuń ~.

    web/src/styles.css CSS nowy plik
    @import "bootstrap/dist/css/bootstrap.css";

    Wynik programu bez zmian - taki jak po kroku 11.

  15. Krok 15: Komponent App z dwoma blokami

    Kryteria:

    R.3.1 Zdefiniowany jest jeden komponent, który zawiera nagłówek h1, formularz, trzy pola edycyjne, checkbox, przycisk "Zapisz" i trzy paragrafy, przynajmniej jedno pole edycyjne ma zdefiniowany atrybut placeholder, pole do wpisywania hasła jest typu password

    ok. 3 min
    Polecenie z arkusza

    Aplikacja składa się z jednego komponentu, w którym znajdują się formularz i podsumowanie danych z formularza. Komponent składa się z dwóch bloków.

    Zastępujesz całą zawartość app.html - szablon z kreatora to strona powitalna Angulara. Nowy szablon: div z klasą Bootstrapa container (wyśrodkowana kolumna o rozsądnej szerokości), a w nim dwa bloki: pierwszy na nagłówek i formularz, drugi na podsumowanie (R.3.1). W szablonie Angulara piszesz zwykły HTML - atrybut to class, nie className jak w React.

    W app.ts usuwasz to, czego nie użyjesz: RouterOutlet (nawigacji między stronami nie ma) i przykładowe pole title. Lista imports w dekoratorze zostaje pusta - za chwilę trafi do niej moduł formularzy. Plik app.routes.ts może zostać bez zmian. Podgląd jest jeszcze pusty.

    web/src/app/app.html HTML nowy plik
    <div class="container">
      <div>
      </div>
      <div>
      </div>
    </div>
    web/src/app/app.ts TypeScript zmienione: 3 linie
    import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-root',
      imports: [],
      templateUrl: './app.html',
      styleUrl: './app.css'
    })
    export class App {
    }
    Podgląd strony po tym kroku (web/src/app/app.html)
  16. Krok 16: Nagłówek pierwszego stopnia

    Kryteria:

    R.3.1 Zdefiniowany jest jeden komponent, który zawiera nagłówek h1, formularz, trzy pola edycyjne, checkbox, przycisk "Zapisz" i trzy paragrafy, przynajmniej jedno pole edycyjne ma zdefiniowany atrybut placeholder, pole do wpisywania hasła jest typu password

    ok. 1 min
    Polecenie z arkusza

    Elementy bloku pierwszego: Nagłówek pierwszego stopnia o treści „Menedżer haseł - wykonał”, dalej wstawiony numer zdającego.

    <h1> z treścią z arkusza. Na egzaminie po słowie „wykonał” wpisujesz numer PESEL - w tym rozwiązaniu stoi tam autor, Bartosz Bryniarski. Rozmiar i krój nagłówka nadaje Bootstrap.

    web/src/app/app.html HTML zmienione: 1 linia
    <div class="container">
      <div>
        <h1>Menedżer haseł - wykonał Bartosz Bryniarski</h1>
      </div>
      <div>
      </div>
    </div>
    Podgląd strony po tym kroku (web/src/app/app.html)
  17. Krok 17: Formularz i pole Adres WWW

    Kryteria: ,

    R.3.3 Pola edycyjne są powiązane z etykietami za pomocą atrybutu for oraz zastosowane formatowanie Bootstrap do kontrolek np.: <div class="form-group m-4"> <label for="adres">Adres WWW:</label> <input id="adres" class="form-control" .../> </div> W React.js zastosowane className zamiast class oraz domknięte wszystkie znaczniki, poprawne użycie htmlFor.

    R.3.1 Zdefiniowany jest jeden komponent, który zawiera nagłówek h1, formularz, trzy pola edycyjne, checkbox, przycisk "Zapisz" i trzy paragrafy, przynajmniej jedno pole edycyjne ma zdefiniowany atrybut placeholder, pole do wpisywania hasła jest typu password

    ok. 3 min
    Polecenie z arkusza

    Pola edycyjne są powiązane z etykietami: „Adres WWW:”, „Login:”, „Hasło:”. Pola do wprowadzenia adresu WWW i loginu zawierają tekst podpowiedzi: "Adres strony WWW", "Twój login".

    Każde pole przenosisz z szablonu z archiwum: div z klasą form-group (grupa etykieta + pole) i m-4 (margines 1,5 rem), w nim label i input z klasą form-control (pełna szerokość, ramka, zaokrąglenie). Etykietę wiążesz z polem atrybutem for="url" wskazującym id="url" pola - kliknięcie etykiety ustawia kursor w polu (R.3.3). Podpowiedź to placeholder.

    W Angularze szablon to HTML, więc przykład z archiwum przenosisz niemal bez zmian - inaczej niż w React, gdzie trzeba zamienić class na className, for na htmlFor i domykać <input />.

    web/src/app/app.html HTML zmienione: 6 linii
    <div class="container">
      <div>
        <h1>Menedżer haseł - wykonał Bartosz Bryniarski</h1>
        <form>
          <div class="form-group m-4">
            <label for="url">Adres WWW:</label>
            <input id="url" type="text" class="form-control" placeholder="Adres strony WWW">
          </div>
        </form>
      </div>
      <div>
      </div>
    </div>
    Podgląd strony po tym kroku (web/src/app/app.html)
  18. Krok 18: Pole Login

    Kryteria:

    R.3.3 Pola edycyjne są powiązane z etykietami za pomocą atrybutu for oraz zastosowane formatowanie Bootstrap do kontrolek np.: <div class="form-group m-4"> <label for="adres">Adres WWW:</label> <input id="adres" class="form-control" .../> </div> W React.js zastosowane className zamiast class oraz domknięte wszystkie znaczniki, poprawne użycie htmlFor.

    ok. 1 min
    Polecenie z arkusza

    Pola edycyjne są powiązane z etykietami: „Adres WWW:”, „Login:”, „Hasło:”. Pola do wprowadzenia adresu WWW i loginu zawierają tekst podpowiedzi: "Adres strony WWW", "Twój login".

    Drugie pole według tego samego wzoru, z identyfikatorem login i podpowiedzią „Twój login”. Identyfikatory muszą być różne - inaczej obie etykiety wskazywałyby pierwsze pole.

    web/src/app/app.html HTML zmienione: 4 linie
    <div class="container">
      <div>
        <h1>Menedżer haseł - wykonał Bartosz Bryniarski</h1>
        <form>
          <div class="form-group m-4">
            <label for="url">Adres WWW:</label>
            <input id="url" type="text" class="form-control" placeholder="Adres strony WWW">
          </div>
          <div class="form-group m-4">
            <label for="login">Login:</label>
            <input id="login" type="text" class="form-control" placeholder="Twój login">
          </div>
        </form>
      </div>
      <div>
      </div>
    </div>
    Podgląd strony po tym kroku (web/src/app/app.html)
  19. Krok 19: Pole Hasło typu password

    Kryteria: ,

    R.3.1 Zdefiniowany jest jeden komponent, który zawiera nagłówek h1, formularz, trzy pola edycyjne, checkbox, przycisk "Zapisz" i trzy paragrafy, przynajmniej jedno pole edycyjne ma zdefiniowany atrybut placeholder, pole do wpisywania hasła jest typu password

    R.3.3 Pola edycyjne są powiązane z etykietami za pomocą atrybutu for oraz zastosowane formatowanie Bootstrap do kontrolek np.: <div class="form-group m-4"> <label for="adres">Adres WWW:</label> <input id="adres" class="form-control" .../> </div> W React.js zastosowane className zamiast class oraz domknięte wszystkie znaczniki, poprawne użycie htmlFor.

    ok. 1 min
    Polecenie z arkusza

    Pole do wpisania hasła umożliwia ukrywanie wpisywanych znaków.

    Trzecie pole ma type="password" - przeglądarka zamienia wpisywane znaki na kropki (R.3.1). Podpowiedzi arkusz tu nie przewiduje.

    web/src/app/app.html HTML zmienione: 4 linie
    <div class="container">
      <div>
        <h1>Menedżer haseł - wykonał Bartosz Bryniarski</h1>
        <form>
          <div class="form-group m-4">
            <label for="url">Adres WWW:</label>
            <input id="url" type="text" class="form-control" placeholder="Adres strony WWW">
          </div>
          <div class="form-group m-4">
            <label for="login">Login:</label>
            <input id="login" type="text" class="form-control" placeholder="Twój login">
          </div>
          <div class="form-group m-4">
            <label for="password">Hasło:</label>
            <input id="password" type="password" class="form-control">
          </div>
        </form>
      </div>
      <div>
      </div>
    </div>
    Podgląd strony po tym kroku (web/src/app/app.html)
  20. Krok 20: Pole wyboru Wygeneruj hasło, domyślnie zaznaczone

    Kryteria: ,

    R.3.1 Zdefiniowany jest jeden komponent, który zawiera nagłówek h1, formularz, trzy pola edycyjne, checkbox, przycisk "Zapisz" i trzy paragrafy, przynajmniej jedno pole edycyjne ma zdefiniowany atrybut placeholder, pole do wpisywania hasła jest typu password

    R.3.7 W stanie początkowym checkbox jest zaznaczony, pole do wpisywania hasła jest nieaktywne, gdy checkbox zostanie odznaczony - pole hasła jest aktywne, przy ponownym zaznaczeniu - staje się nieaktywne (w uruchomionej aplikacji lub na zrzucie i obowiązkowo w kodzie)

    ok. 4 min
    Polecenie z arkusza

    Pole wyboru jest domyślnie zaznaczone i pole do wpisania hasła jest nieaktywne.

    Stan pola wyboru trzymasz w polu klasy komponentu: generate = true (na start zaznaczone). Z szablonem łączy je [(ngModel)]="generate" - powiązanie dwukierunkowe („banan w pudełku”): pole wyboru pokazuje wartość generate, a kliknięcie zapisuje do niej nową. Zapis z tabeli 1 arkusza.

    ngModel należy do modułu FormsModule. Tabela 1 każe dodać go w pliku app.module.ts - tak wyglądały projekty w starszym Angularze. Obecny Angular tworzy komponenty samodzielne (bez app.module.ts), więc FormsModule importujesz w app.ts i dopisujesz do listy imports dekoratora @Component - efekt jest ten sam.

    Ważne: pole z ngModel wewnątrz <form> musi mieć atrybut name - po nim formularz rejestruje swoje pola. Bez niego aplikacja zgłasza błąd NG01350 i przestaje działać. Klasy form-check, form-check-input, form-check-label to wzór Bootstrapa dla pól wyboru; grupa hasła dostała marginesy z boków i u dołu (mx-4 mb-4), żeby przylegała do pola wyboru (R.3.1, R.3.7).

    Typowe błędy

    [ngModel]="generate" (tylko w nawiasach kwadratowych) działa w jedną stronę: pole pokazuje stan, ale kliknięcie go nie zmienia. Dwukierunkowe powiązanie to [(ngModel)] - nawiasy okrągłe w kwadratowych.

    web/src/app/app.html HTML zmienione: 5 linii
    <div class="container">
      <div>
        <h1>Menedżer haseł - wykonał Bartosz Bryniarski</h1>
        <form>
          <div class="form-group m-4">
            <label for="url">Adres WWW:</label>
            <input id="url" type="text" class="form-control" placeholder="Adres strony WWW">
          </div>
          <div class="form-group m-4">
            <label for="login">Login:</label>
            <input id="login" type="text" class="form-control" placeholder="Twój login">
          </div>
          <div class="form-check mx-4">
            <input id="generate" name="generate" type="checkbox" class="form-check-input" [(ngModel)]="generate">
            <label for="generate" class="form-check-label">Wygeneruj hasło</label>
          </div>
          <div class="form-group mx-4 mb-4">
            <label for="password">Hasło:</label>
            <input id="password" type="password" class="form-control">
          </div>
        </form>
      </div>
      <div>
      </div>
    </div>
    web/src/app/app.ts TypeScript zmienione: 3 linie
    import { Component } from '@angular/core';
    import { FormsModule } from '@angular/forms';
    
    @Component({
      selector: 'app-root',
      imports: [FormsModule],
      templateUrl: './app.html',
      styleUrl: './app.css'
    })
    export class App {
      generate = true;
    }
    Podgląd strony po tym kroku (web/src/app/app.html)
  21. Krok 21: Pole hasła nieaktywne, gdy pole wyboru jest zaznaczone

    Kryteria: ,

    R.3.4 Zapisana przynajmniej w jednym miejscu realizacja powiązania stanu kontrolki checkbox z polem hasła

    R.3.7 W stanie początkowym checkbox jest zaznaczony, pole do wpisywania hasła jest nieaktywne, gdy checkbox zostanie odznaczony - pole hasła jest aktywne, przy ponownym zaznaczeniu - staje się nieaktywne (w uruchomionej aplikacji lub na zrzucie i obowiązkowo w kodzie)

    ok. 2 min
    Polecenie z arkusza

    Gdy pole wyboru jest zaznaczone to pole do wpisania hasła jest nieaktywne (ilustracja 2), w przeciwnym wypadku jest aktywne (ilustracja 3).

    Jedno powiązanie właściwości: [disabled]="generate". Nawiasy kwadratowe oznaczają, że wartością atrybutu jest wyrażenie z klasy komponentu, a nie napis. Gdy generate ma wartość true, pole jest nieaktywne; po odznaczeniu Angular odświeża widok i pole się odblokowuje. Żadnego if ani getElementById - widok wynika ze stanu (R.3.4, R.3.7). Odznacz i zaznacz pole wyboru w podglądzie.

    Typowe błędy

    document.getElementById("password").disabled = ... - klucz wprost odbiera za metody DOM CAŁY rezultat 3. W Angularze wiążesz właściwości w szablonie.

    web/src/app/app.html HTML zmienione: 1 linia
    <div class="container">
      <div>
        <h1>Menedżer haseł - wykonał Bartosz Bryniarski</h1>
        <form>
          <div class="form-group m-4">
            <label for="url">Adres WWW:</label>
            <input id="url" type="text" class="form-control" placeholder="Adres strony WWW">
          </div>
          <div class="form-group m-4">
            <label for="login">Login:</label>
            <input id="login" type="text" class="form-control" placeholder="Twój login">
          </div>
          <div class="form-check mx-4">
            <input id="generate" name="generate" type="checkbox" class="form-check-input" [(ngModel)]="generate">
            <label for="generate" class="form-check-label">Wygeneruj hasło</label>
          </div>
          <div class="form-group mx-4 mb-4">
            <label for="password">Hasło:</label>
            <input id="password" type="password" class="form-control" [disabled]="generate">
          </div>
        </form>
      </div>
      <div>
      </div>
    </div>
    Podgląd strony po tym kroku (web/src/app/app.html)
  22. Krok 22: Niebieski przycisk Zapisz

    Kryteria: ,

    R.3.2 Do projektu dołączona jest biblioteka Bootstrap oraz sformatowane: przycisk klasą co najmniej: btn btn-primary pierwszy paragraf klasą co najmniej: lead text-primary

    R.3.1 Zdefiniowany jest jeden komponent, który zawiera nagłówek h1, formularz, trzy pola edycyjne, checkbox, przycisk "Zapisz" i trzy paragrafy, przynajmniej jedno pole edycyjne ma zdefiniowany atrybut placeholder, pole do wpisywania hasła jest typu password

    ok. 1 min
    Polecenie z arkusza

    Przycisk „Zapisz” jest koloru niebieskiego za sprawą odpowiedniej klasy Bootstrap.

    Klasy btn btn-primary: btn to wygląd przycisku Bootstrapa, btn-primary - kolor podstawowy, niebieski (tabela 1). W szablonie z archiwum w miejscu koloru są trzy kropki (btn ... m-2) - to luka do uzupełnienia; margines m-2 zostawiasz. type="submit" sprawia, że kliknięcie zatwierdza formularz - obsłużysz to za chwilę (R.3.2). Blok pierwszy odpowiada ilustracji 1.

    web/src/app/app.html HTML zmienione: 1 linia
    <div class="container">
      <div>
        <h1>Menedżer haseł - wykonał Bartosz Bryniarski</h1>
        <form>
          <div class="form-group m-4">
            <label for="url">Adres WWW:</label>
            <input id="url" type="text" class="form-control" placeholder="Adres strony WWW">
          </div>
          <div class="form-group m-4">
            <label for="login">Login:</label>
            <input id="login" type="text" class="form-control" placeholder="Twój login">
          </div>
          <div class="form-check mx-4">
            <input id="generate" name="generate" type="checkbox" class="form-check-input" [(ngModel)]="generate">
            <label for="generate" class="form-check-label">Wygeneruj hasło</label>
          </div>
          <div class="form-group mx-4 mb-4">
            <label for="password">Hasło:</label>
            <input id="password" type="password" class="form-control" [disabled]="generate">
          </div>
          <button type="submit" class="btn btn-primary m-2">Zapisz</button>
        </form>
      </div>
      <div>
      </div>
    </div>
    Podgląd strony po tym kroku (web/src/app/app.html)
  23. Krok 23: Pola tekstowe powiązane z polami klasy

    Kryteria:

    R.1.4 Polskie lub angielskie nazewnictwo zmiennych/pól. Nazewnictwo jest znaczące. Wyjątkami od reguły są zmienne pomocnicze, np. bufor, tmp, iteratory pętli. Kryterium nie jest spełnione tylko wtedy, gdy nazwy zmiennych nic nie znaczą, np. x

    ok. 3 min
    Polecenie z arkusza

    Elementy bloku drugiego: Elementy są wypełniane po zatwierdzeniu formularza przyciskiem.

    Żeby po zatwierdzeniu wypisać dane, komponent musi je znać. Każde pole tekstowe dostaje własne pole klasy (url, login, password, na start puste napisy) i powiązanie [(ngModel)] - każdy wpisany znak trafia od razu do klasy. I znów: każde pole z ngModel w formularzu ma name. Na ekranie nic się nie zmienia - pola działają jak wcześniej, tylko komponent pamięta ich treść.

    web/src/app/app.html HTML zmienione: 3 linie
    <div class="container">
      <div>
        <h1>Menedżer haseł - wykonał Bartosz Bryniarski</h1>
        <form>
          <div class="form-group m-4">
            <label for="url">Adres WWW:</label>
            <input id="url" type="text" class="form-control" name="url" placeholder="Adres strony WWW" [(ngModel)]="url">
          </div>
          <div class="form-group m-4">
            <label for="login">Login:</label>
            <input id="login" type="text" class="form-control" name="login" placeholder="Twój login" [(ngModel)]="login">
          </div>
          <div class="form-check mx-4">
            <input id="generate" name="generate" type="checkbox" class="form-check-input" [(ngModel)]="generate">
            <label for="generate" class="form-check-label">Wygeneruj hasło</label>
          </div>
          <div class="form-group mx-4 mb-4">
            <label for="password">Hasło:</label>
            <input id="password" type="password" class="form-control" name="password" [disabled]="generate" [(ngModel)]="password">
          </div>
          <button type="submit" class="btn btn-primary m-2">Zapisz</button>
        </form>
      </div>
      <div>
      </div>
    </div>
    web/src/app/app.ts TypeScript zmienione: 3 linie
    import { Component } from '@angular/core';
    import { FormsModule } from '@angular/forms';
    
    @Component({
      selector: 'app-root',
      imports: [FormsModule],
      templateUrl: './app.html',
      styleUrl: './app.css'
    })
    export class App {
      url = '';
      login = '';
      password = '';
      generate = true;
    }
    Podgląd strony po tym kroku (web/src/app/app.html)
  24. Krok 24: Zatwierdzenie formularza: metoda save

    Kryteria: ,

    R.3.5 Zdefiniowana funkcja realizująca zatwierdzenie formularza, jest ona powiązana z formularzem zdarzeniem onSubmit lub onClick w React, a w Angular ngSubmit lub click

    R.1.3 Znaczące polskie lub angielskie nazewnictwo metody/funkcji

    ok. 3 min
    Polecenie z arkusza

    Elementy są wypełniane po zatwierdzeniu formularza przyciskiem.

    Podsumowanie ma pokazywać dane z chwili zatwierdzenia, a nie zmieniać się przy każdym wpisanym znaku. Dlatego potrzebny jest osobny obiekt saved - kopia czterech wartości. Na start ma puste pola i generate: true, bo ilustracja 1 pokazuje już „Hasło automatycznie generowane”.

    Metodę save() wiążesz z formularzem zdarzeniem (ngSubmit)="save()" - nawiasy okrągłe to powiązanie zdarzenia (R.3.5). ngSubmit zgłasza formularz Angulara (NgForm z FormsModule), który sam blokuje domyślne zachowanie przeglądarki, czyli przeładowanie strony - nie trzeba pisać preventDefault. Metoda przepisuje bieżące wartości przez this. - w klasie do pól sięgasz zawsze przez this. : void to typ zwracany TypeScriptu: metoda nic nie zwraca.

    Typowe błędy

    onclick="save()" w stylu HTML - klucz wprost wymienia przypisywanie metod do zdarzeń w ten sposób jako powód niezaliczenia CAŁEGO rezultatu 3. W Angularze: (ngSubmit)="save()" albo (click)="save()".

    web/src/app/app.html HTML zmienione: 1 linia
    <div class="container">
      <div>
        <h1>Menedżer haseł - wykonał Bartosz Bryniarski</h1>
        <form (ngSubmit)="save()">
          <div class="form-group m-4">
            <label for="url">Adres WWW:</label>
            <input id="url" type="text" class="form-control" name="url" placeholder="Adres strony WWW" [(ngModel)]="url">
          </div>
          <div class="form-group m-4">
            <label for="login">Login:</label>
            <input id="login" type="text" class="form-control" name="login" placeholder="Twój login" [(ngModel)]="login">
          </div>
          <div class="form-check mx-4">
            <input id="generate" name="generate" type="checkbox" class="form-check-input" [(ngModel)]="generate">
            <label for="generate" class="form-check-label">Wygeneruj hasło</label>
          </div>
          <div class="form-group mx-4 mb-4">
            <label for="password">Hasło:</label>
            <input id="password" type="password" class="form-control" name="password" [disabled]="generate" [(ngModel)]="password">
          </div>
          <button type="submit" class="btn btn-primary m-2">Zapisz</button>
        </form>
      </div>
      <div>
      </div>
    </div>
    web/src/app/app.ts TypeScript zmienione: 5 linii
    import { Component } from '@angular/core';
    import { FormsModule } from '@angular/forms';
    
    @Component({
      selector: 'app-root',
      imports: [FormsModule],
      templateUrl: './app.html',
      styleUrl: './app.css'
    })
    export class App {
      url = '';
      login = '';
      password = '';
      generate = true;
      saved = { url: '', login: '', password: '', generate: true };
    
      save(): void {
        this.saved = { url: this.url, login: this.login, password: this.password, generate: this.generate };
      }
    }
    Podgląd strony po tym kroku (web/src/app/app.html)
  25. Krok 25: Pierwszy paragraf: niebieski, wiodący, adres

    Kryteria: ,

    R.3.2 Do projektu dołączona jest biblioteka Bootstrap oraz sformatowane: przycisk klasą co najmniej: btn btn-primary pierwszy paragraf klasą co najmniej: lead text-primary

    R.3.8 Przy zaznaczonym checkbox, po wpisaniu adresu WWW i loginu oraz wciśnięciu przycisku pod przyciskiem są wypisane adres i login wprowadzone w formularzu oraz tekst: "Hasło automatycznie generowane" (w uruchomionej aplikacji lub na zrzucie i obowiązkowo w kodzie)

    ok. 2 min
    Polecenie z arkusza

    Trzy paragrafy ze stylem nadanym odpowiednimi klasami Bootstrap: margines wewnętrzny 0,5 rem. Dodatkowo tekst pierwszego paragrafu koloru niebieskiego, tekst jest wiodący (tabela 1). Paragraf pierwszy zawiera napis: „Adres: <adres>”.

    Blok drugi. Klasy z tabeli 1: p-2 - margines wewnętrzny 0,5 rem; text-primary - niebieski tekst; lead - tekst wiodący (R.3.2). Wartość z klasy wstawiasz interpolacją {{ saved.url }} - podwójne klamry w szablonie Angulara. Wpisz adres i kliknij „Zapisz”.

    web/src/app/app.html HTML zmienione: 1 linia
    <div class="container">
      <div>
        <h1>Menedżer haseł - wykonał Bartosz Bryniarski</h1>
        <form (ngSubmit)="save()">
          <div class="form-group m-4">
            <label for="url">Adres WWW:</label>
            <input id="url" type="text" class="form-control" name="url" placeholder="Adres strony WWW" [(ngModel)]="url">
          </div>
          <div class="form-group m-4">
            <label for="login">Login:</label>
            <input id="login" type="text" class="form-control" name="login" placeholder="Twój login" [(ngModel)]="login">
          </div>
          <div class="form-check mx-4">
            <input id="generate" name="generate" type="checkbox" class="form-check-input" [(ngModel)]="generate">
            <label for="generate" class="form-check-label">Wygeneruj hasło</label>
          </div>
          <div class="form-group mx-4 mb-4">
            <label for="password">Hasło:</label>
            <input id="password" type="password" class="form-control" name="password" [disabled]="generate" [(ngModel)]="password">
          </div>
          <button type="submit" class="btn btn-primary m-2">Zapisz</button>
        </form>
      </div>
      <div>
        <p class="lead text-primary p-2">Adres: {{ saved.url }}</p>
      </div>
    </div>
    Podgląd strony po tym kroku (web/src/app/app.html)
  26. Krok 26: Drugi paragraf: login

    Kryteria:

    R.3.8 Przy zaznaczonym checkbox, po wpisaniu adresu WWW i loginu oraz wciśnięciu przycisku pod przyciskiem są wypisane adres i login wprowadzone w formularzu oraz tekst: "Hasło automatycznie generowane" (w uruchomionej aplikacji lub na zrzucie i obowiązkowo w kodzie)

    ok. 1 min
    Polecenie z arkusza

    Paragraf drugi zawiera napis: „Login: <login>”.

    Ten sam wzór, z samą klasą p-2 - kolor i wyróżnienie dotyczą tylko pierwszego paragrafu.

    web/src/app/app.html HTML zmienione: 1 linia
    <div class="container">
      <div>
        <h1>Menedżer haseł - wykonał Bartosz Bryniarski</h1>
        <form (ngSubmit)="save()">
          <div class="form-group m-4">
            <label for="url">Adres WWW:</label>
            <input id="url" type="text" class="form-control" name="url" placeholder="Adres strony WWW" [(ngModel)]="url">
          </div>
          <div class="form-group m-4">
            <label for="login">Login:</label>
            <input id="login" type="text" class="form-control" name="login" placeholder="Twój login" [(ngModel)]="login">
          </div>
          <div class="form-check mx-4">
            <input id="generate" name="generate" type="checkbox" class="form-check-input" [(ngModel)]="generate">
            <label for="generate" class="form-check-label">Wygeneruj hasło</label>
          </div>
          <div class="form-group mx-4 mb-4">
            <label for="password">Hasło:</label>
            <input id="password" type="password" class="form-control" name="password" [disabled]="generate" [(ngModel)]="password">
          </div>
          <button type="submit" class="btn btn-primary m-2">Zapisz</button>
        </form>
      </div>
      <div>
        <p class="lead text-primary p-2">Adres: {{ saved.url }}</p>
        <p class="p-2">Login: {{ saved.login }}</p>
      </div>
    </div>
    Podgląd strony po tym kroku (web/src/app/app.html)
  27. Krok 27: Trzeci paragraf: hasło albo informacja o generowaniu

    Kryteria: , ,

    R.3.8 Przy zaznaczonym checkbox, po wpisaniu adresu WWW i loginu oraz wciśnięciu przycisku pod przyciskiem są wypisane adres i login wprowadzone w formularzu oraz tekst: "Hasło automatycznie generowane" (w uruchomionej aplikacji lub na zrzucie i obowiązkowo w kodzie)

    R.3.9 Przy odznaczonym checkbox, po wpisaniu adresu WWW, loginu i hasła oraz wciśnięciu przycisku pod przyciskiem są wypisane adres, login i hasło wprowadzone w formularzu (w uruchomionej aplikacji lub na zrzucie i obowiązkowo w kodzie)

    R.3.1 Zdefiniowany jest jeden komponent, który zawiera nagłówek h1, formularz, trzy pola edycyjne, checkbox, przycisk "Zapisz" i trzy paragrafy, przynajmniej jedno pole edycyjne ma zdefiniowany atrybut placeholder, pole do wpisywania hasła jest typu password

    ok. 3 min
    Polecenie z arkusza

    Paragraf trzeci, gdy pole wyboru jest zaznaczone, zawiera napis „Hasło automatycznie generowane” (ilustracja 2), w przeciwnym wypadku napis „Hasło: <hasło>” (ilustracja 3).

    Wybór jednego z dwóch napisów zapisujesz w szablonie blokiem @if (warunek) { ... } @else { ... } - wbudowaną składnią sterującą Angulara (od wersji 17). W starszych projektach to samo robi dyrektywa *ngIf z else; @if nie wymaga żadnego importu. Warunkiem jest saved.generate - stan pola wyboru z chwili zapisu, więc samo kliknięcie pola wyboru nie zmienia podsumowania, dopiero „Zapisz”.

    Aplikacja jest kompletna. Sprawdź w podglądzie oba scenariusze z ilustracji: zaznaczone pole wyboru (adres, login i „Hasło automatycznie generowane” - R.3.8) i odznaczone z wpisanym hasłem (R.3.9).

    web/src/app/app.html HTML zmienione: 7 linii
    <div class="container">
      <div>
        <h1>Menedżer haseł - wykonał Bartosz Bryniarski</h1>
        <form (ngSubmit)="save()">
          <div class="form-group m-4">
            <label for="url">Adres WWW:</label>
            <input id="url" type="text" class="form-control" name="url" placeholder="Adres strony WWW" [(ngModel)]="url">
          </div>
          <div class="form-group m-4">
            <label for="login">Login:</label>
            <input id="login" type="text" class="form-control" name="login" placeholder="Twój login" [(ngModel)]="login">
          </div>
          <div class="form-check mx-4">
            <input id="generate" name="generate" type="checkbox" class="form-check-input" [(ngModel)]="generate">
            <label for="generate" class="form-check-label">Wygeneruj hasło</label>
          </div>
          <div class="form-group mx-4 mb-4">
            <label for="password">Hasło:</label>
            <input id="password" type="password" class="form-control" name="password" [disabled]="generate" [(ngModel)]="password">
          </div>
          <button type="submit" class="btn btn-primary m-2">Zapisz</button>
        </form>
      </div>
      <div>
        <p class="lead text-primary p-2">Adres: {{ saved.url }}</p>
        <p class="p-2">Login: {{ saved.login }}</p>
        <p class="p-2">
          @if (saved.generate) {
            Hasło automatycznie generowane
          } @else {
            Hasło: {{ saved.password }}
          }
        </p>
      </div>
    </div>
    Podgląd strony po tym kroku (web/src/app/app.html)
  28. Krok 28: Folder webowa: web.zip i zmienione pliki

    Kryteria:

    R.4.9 Przygotowana jest dokumentacja w postaci płyty: oba projekty są spakowane oraz zmodyfikowane pliki są umieszczone na zewnątrz archiwów

    ok. 3 min
    Polecenie z arkusza

    Kod aplikacji przygotuj do nagrania na płytę. W folderze webowa powinno znaleźć się archiwum całego folderu projektu o nazwie web.zip oraz skopiowane pliki z kodem źródłowym, które były modyfikowane.

    Pakujesz folder web do web.zip, ale bez node_modules (odtwarza go npm install z package.json) i bez ukrytego folderu .angular (pamięć podręczna budowania) - z nimi archiwum miałoby setki megabajtów. Obok kopiujesz zmienione pliki: app.ts, app.html i styles.css.

    Podgląd strony bez zmian - taki jak po kroku 27.

  29. Etap 4: Dokumentacja aplikacji

    Kroki 29-32 · po tym etapie masz 32 z 32 kryteriów (pkt)

    Krok 29: Komentarz dokumentujący metodę GeneratePassword

    Kryteria: , , , ,

    R.4.1 Komentarz jest zgodny z Listingiem 1 arkusza egzaminacyjnego. Komentarz może być wieloliniowy lub kilka jednoliniowych, lub Docstrings (potrójny cudzysłów)

    R.4.2 W komentarzu umieszczona nazwa funkcji / metody oraz opisane jej działanie

    R.4.3 W komentarzu umieszczone nazwa i krótki opis wszystkich parametrów lub "brak", gdy nie ma parametrów

    R.4.4 W komentarzu umieszczona nazwa typu zwracanego i opis co jest zwracane lub "brak", gdy nic nie jest zwracane

    R.4.5 W komentarzu umieszczony numer zdającego

    ok. 4 min
    Polecenie z arkusza

    W kodzie źródłowym aplikacji konsolowej za pomocą komentarza utwórz jeden nagłówek do dowolnej funkcji / metody według wzoru z listingu 1. Komentarz powinien znaleźć się nad lub pod nazwą funkcji. W miejscu nawiasów <> należy podać odpowiednie opisy. W miejscu autor należy podać numer zdającego.

    Komentarz wieloliniowy /* ... */ nad metodą, z polami z listingu 1 (R.4.1). Przy podejściu obiektowym arkusz pozwala napisać „metody” zamiast „funkcji”. Wypełniasz pola zgodnie z kodem: nazwa i działanie (R.4.2), parametry - metoda nie ma żadnych, więc „brak” (R.4.3), zwracany typ string i co zwraca (R.4.4), autor (R.4.5) - na egzaminie numer PESEL, tu Bartosz Bryniarski.

    Typowe błędy

    Komentarz przy InsertRandomChar też jest poprawny, ale wtedy trzeba opisać jej DWA parametry - „brak” przy metodzie z parametrami nie spełnia R.4.3.

    konsola/Program.cs C# zmienione: 9 linii
    namespace konsola;
    
    class Program
    {
        const string Lowercase = "abcdefghijklmnopqrstuvwxyz";
        const string Uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        const string Diacritics = "ąęłńóśżźĄĘŁŃÓŚŻŹ";
        const string Digits = "0123456789";
        const string Special = "!@#$%^&*()_+";
    
        static Random random = new Random();
    
        static string InsertRandomChar(string password, string characters)
        {
            char character = characters[random.Next(characters.Length)];
            int position = random.Next(password.Length + 1);
            return password.Insert(position, character.ToString());
        }
    
        /**********************************************
         nazwa metody:        GeneratePassword
         opis metody:         tworzy 12-znakowe hasło z 3 małych liter, 3 wielkich liter,
                              2 liter ze znakami diakrytycznymi, 2 cyfr i 2 znaków
                              specjalnych, każdy znak wstawiając w losowe miejsce hasła
         parametry:           brak
         zwracany typ i opis: string - wygenerowane hasło
         autor:               Bartosz Bryniarski
        ***********************************************/
        static string GeneratePassword()
        {
            string password = "";
            for (int i = 0; i < 3; i++)
            {
                password = InsertRandomChar(password, Lowercase);
            }
            for (int i = 0; i < 3; i++)
            {
                password = InsertRandomChar(password, Uppercase);
            }
            for (int i = 0; i < 2; i++)
            {
                password = InsertRandomChar(password, Diacritics);
            }
            for (int i = 0; i < 2; i++)
            {
                password = InsertRandomChar(password, Digits);
            }
            for (int i = 0; i < 2; i++)
            {
                password = InsertRandomChar(password, Special);
            }
            return password;
        }
    
        static void Main(string[] args)
        {
            Console.OutputEncoding = System.Text.Encoding.UTF8;
            Console.WriteLine("Generator bezpiecznych haseł - pięć haseł po 12 znaków:");
            for (int number = 1; number <= 5; number++)
            {
                Console.WriteLine($"Hasło {number}: {GeneratePassword()}");
            }
        }
    }
    Wynik programu po tym kroku
    > konsola.exe
    Generator bezpiecznych haseł - pięć haseł po 12 znaków:
    Hasło 1: %HHo(7q7gęŚG
    Hasło 2: jŻoeMCŚ_5!A8
    Hasło 3: 3)el4ŹVŃTB*j
    Hasło 4: !kż_bót2B4MI
    Hasło 5: V@2wpŃCjO%2ś
  30. Krok 30: Zrzuty konsola1 i konsola2

    Kryteria: , ,

    R.2.6 Program uruchamia się w konsoli, co jest udokumentowane zrzutem ekranu

    R.1.6 Podjęta próba uruchomienia kodu, na zrzucie widoczny jest uruchomiony program lub błędy jego kompilacji / interpretacji

    R.4.6 Zapisany przynajmniej jeden zrzut ekranu z uruchomienia lub kompilacji aplikacji konsolowej

    ok. 3 min
    Polecenie z arkusza

    Wykonaj zrzuty ekranu dokumentujące uruchomienie aplikacji utworzonych podczas egzaminu. Zrzuty powinny obejmować cały obszar ekranu monitora z widocznym paskiem zadań. Jeżeli aplikacja uruchamia się, na zrzucie należy umieścić okno z wynikiem działania programu oraz otwarte środowisko programistyczne z projektem lub okno terminala z kompilacją projektu. Aplikacja konsolowa – dowolna liczba zrzutów nazwanych konsola1, konsola2, ...

    Uruchamiasz program w Visual Studio (Ctrl+F5) i robisz zrzut całego ekranu z oknem konsoli, Visual Studio i paskiem zadań - konsola1.png. Program nie pyta o nic użytkownika, więc jeden zrzut dokumentuje całą interakcję; drugi (konsola2.png) po ponownym uruchomieniu pokazuje, że hasła są za każdym razem inne. Zrzuty zapisujesz w folderze dokumentacja.

    Wynik programu po tym kroku
    > konsola.exe
    Generator bezpiecznych haseł - pięć haseł po 12 znaków:
    Hasło 1: %HHo(7q7gęŚG
    Hasło 2: jŻoeMCŚ_5!A8
    Hasło 3: 3)el4ŹVŃTB*j
    Hasło 4: !kż_bót2B4MI
    Hasło 5: V@2wpŃCjO%2ś
    
    > konsola.exe
    Generator bezpiecznych haseł - pięć haseł po 12 znaków:
    Hasło 1: d4ópG^łyO2W#
    Hasło 2: Mż%Pqc8Ńt7#W
    Hasło 3: !L1v!4ŁkJEęr
    Hasło 4: ÓQ$n9fDŃqV5*
    Hasło 5: 3Ó+źL5V&bHlx
  31. Krok 31: Zrzuty web1, web2, web3

    Kryteria: ,

    R.3.6 Aplikacja jest interpretowana bez błędów. Układ elementów w stanie początkowym jest zgodny z obrazem 1 z arkusza egzaminacyjnego, co udokumentowane zrzutem ekranu

    R.4.7 Zapisany przynajmniej jeden zrzut ekranu z uruchomienia aplikacji webowej lub z błędami interpretacji, na zrzucie widoczne jest środowisko, w którym powstała aplikacja

    ok. 4 min
    Polecenie z arkusza

    Aplikacja webowa – dowolna liczba zrzutów nazwanych web1, web2, ... (stan początkowy, interakcje z zaznaczonym i odznaczonym polem wyboru)

    npx ng serve w terminalu i adres http://localhost:4200 w przeglądarce. Trzy zrzuty całego ekranu, na każdym przeglądarka z aplikacją i środowisko z projektem albo terminal z działającym serwerem:

    • web1.png - stan początkowy (ilustracja 1, R.3.6),
    • web2.png - wpisany adres i login, pole wyboru zaznaczone, po „Zapisz” (ilustracja 2),
    • web3.png - pole wyboru odznaczone, wpisane hasło, po „Zapisz” (ilustracja 3).

    Podgląd pokazuje kompletną aplikację - możesz w niej przejść te trzy stany.

    Typowe błędy

    Zrzut samego okna przeglądarki, bez środowiska albo terminala, nie spełnia R.4.7 - na zrzucie ma być widać, gdzie powstała aplikacja.

    Podgląd strony po tym kroku (web/src/app/app.html)
  32. Krok 32: Plik egzamin

    Kryteria:

    R.4.8 Dokumentacja zawiera: nazwę systemu operacyjnego, nazwy środowisk programistycznych, nazwy języków programowania

    ok. 2 min
    Polecenie z arkusza

    Utwórz plik tekstowy i nazwij go egzamin. Dokument powinien zawierać informacje o narzędziach wykorzystanych na egzaminie: Nazwę systemu operacyjnego, Nazwy środowisk programistycznych, Nazwy języków programowania. Zrzuty ekranu i dokument umieść w folderze dokumentacja.

    Plik tekstowy egzamin w folderze dokumentacja (w tym arkuszu - nie w folderze głównym). Trzy informacje: system, środowiska i języki. Angular to framework, a językiem komponentu jest TypeScript (JavaScript z typami) - warto napisać jedno i drugie (R.4.8).

    egzamin.txt Tekst nowy plik
    System operacyjny: Windows 11
    Środowiska programistyczne: Visual Studio 2022 (aplikacja konsolowa), Visual Studio Code z Node.js i Angular CLI (aplikacja webowa)
    Języki programowania: C# (aplikacja konsolowa), TypeScript i HTML - framework Angular (aplikacja webowa)

    Podgląd strony bez zmian - taki jak po kroku 31.

  33. Etap 5: Porównanie z wariantem Python + React

    Kroki 33-34 · po tym etapie masz 32 z 32 kryteriów (pkt)

    Krok 33: Konsola: C# a Python

    ok. 3 min
    Polecenie z arkusza

    Nie można zastosować metody wbudowanej lub z funkcji bibliotecznych do generowania gotowego hasła.

    Algorytm jest ten sam - pięć pętli i metoda wstawiająca znak w losowe miejsce - zmieniają się narzędzia:

    • losowanie znaku: characters[random.Next(characters.Length)] w C#, random.choice(characters) w Pythonie,
    • wstawienie w miejsce: password.Insert(position, ...) w C#, wycinki password[:position] + char + password[position:] w Pythonie; w obu językach napis jest niezmienny, więc wynik trzeba przypisać,
    • zakres miejsca: random.Next(password.Length + 1) (górna granica nie wchodzi) i random.randint(0, len(password)) (obie granice wchodzą) - ten sam przedział, różnie zapisany,
    • polskie litery w konsoli: Python 3 w PyCharm wypisuje je bez ustawień, w C# warto ustawić Console.OutputEncoding na UTF-8.

    Do haseł w prawdziwych programach używa się generatora kryptograficznego: w C# RandomNumberGenerator.GetInt32(...) z System.Security.Cryptography, w Pythonie moduł secrets. Na egzaminie wystarczy Random - klucz tego nie sprawdza.

    Podgląd strony bez zmian - taki jak po kroku 31.

  34. Krok 34: Aplikacja web: Angular a React - co wybrać

    ok. 3 min
    Polecenie z arkusza

    Z zastosowaniem dostępnego na stanowisku egzaminacyjnym frameworka Angular lub biblioteki React.js wykonaj aplikację internetową typu front-end obsługującą logowanie.

    Ta sama aplikacja w obu technologiach:

    • szablon: Angular - osobny plik HTML (class, for jak w HTML), React - JSX w funkcji (className, htmlFor, domknięte <input />),
    • stan pól: Angular - pola klasy i [(ngModel)] (dwukierunkowo, jedna linia), React - useState plus value i onChange przy każdym polu,
    • zatwierdzenie: (ngSubmit)="save()" (Angular sam blokuje przeładowanie strony), onSubmit={handleSubmit} z event.preventDefault() w React,
    • warunek w widoku: blok @if / @else w Angularze, operator ? : w klamrach JSX w React,
    • pułapki: w Angularze brak name przy ngModel w formularzu (błąd NG01350) i import Bootstrapa z tyldą z tabeli 1; w React - class zamiast className i wywołanie funkcji zamiast jej przekazania w onClick.

    Angular ma więcej „rusztowania” (dekorator, moduły, kreator z pytaniami), ale formularze pisze się w nim krócej. Wybierz to, co ćwiczyłeś - klucz punktuje obie technologie tak samo (R.3.3 i R.3.5 wymieniają odpowiedniki z obu).

    Podgląd strony bez zmian - taki jak po kroku 31.

  35. Etap 6: Oddanie pracy

    Krok 35 · po tym etapie masz 32 z 32 kryteriów (pkt)

    Krok 35: Nagranie płyty

    Kryteria:

    R.4.9 Przygotowana jest dokumentacja w postaci płyty: oba projekty są spakowane oraz zmodyfikowane pliki są umieszczone na zewnątrz archiwów

    ok. 5 min
    Polecenie z arkusza

    UWAGA: Nagraj płytę z rezultatami pracy. W folderze z numerem zdającego powinny się znajdować trzy foldery: dokumentacja, konsolowa i webowa. W folderze dokumentacja: pliki ze zrzutami oraz plik egzamin. W folderze konsolowa: spakowany cały projekt aplikacji konsolowej, modyfikowane pliki źródłowe, plik wykonywalny, jeżeli istnieje. W folderze webowa: spakowany cały projekt aplikacji webowej, modyfikowane pliki źródłowe.

    Sprawdzasz zawartość przed nagraniem:

    • dokumentacja: konsola1.png, konsola2.png, web1.png - web3.png, egzamin,
    • konsolowa: konsola.zip (spakowany po dopisaniu komentarza), Program.cs, konsola.exe z plikami towarzyszącymi,
    • webowa: web.zip (bez node_modules i .angular), app.ts, app.html, styles.css.

    Oba projekty spakowane, zmienione pliki obok archiwów (R.4.9). Nagrywasz, sprawdzasz odczyt płyty, opisujesz ją numerem zdającego.

    Podgląd strony bez zmian - taki jak po kroku 31.

Podgląd

To jedno z możliwych rozwiązań. Egzaminator ocenia spełnienie kryteriów z klucza odpowiedzi, a nie zgodność z tym kodem - inna, poprawna droga też daje punkty.

Autor rozwiązania: . Licencja CC BY-NC 4.0 - kopiowanie i przeróbki do celów niekomercyjnych, z podaniem autora i źródła (link do tej strony) (zasady wykorzystania). Treść arkusza i klucza oceniania pochodzi z CKE.