🔥 Zapisy zamknięte, ale możesz pobrać Roadmapę .NET i dołączyć do listy oczekujących — Pobierz i dołącz do Listy VIP →

Odgadywanie hasła – Zadanie rekrutacyjne #17

Czego szuka rekruter: game loop, walidacja wejścia, komunikaty zwrotne, licznik prób
Poziom: Junior


Zadanie

Napisz grę słowną: program wybiera losowe hasło ze zdefiniowanej listy, gracz odgaduje litera po literze. Program pokazuje odkryte litery (_ _ _ _ _) i informuje o błędach. Gra kończy się gdy gracz odgadnie hasło lub skończy mu się liczba prób.


Czego szuka rekruter

To zadanie o game loop i zarządzaniu stanem gry. Rekruter sprawdza:

  • Pętlę gry z jasno zdefiniowanym warunkiem zakończenia
  • Stan gry jako zestaw zmiennych (odkryte litery, zużyte próby, błędne litery)
  • Walidację wejścia (pojedyncza litera, już użyta litera)
  • Czytelne komunikaty dla gracza

Najprostszą wersją jest “zgadywanie litery po literze” (Hangman). Rekruter może rozszerzyć zadanie: “a co gdyby hasło trzeba było podać całe?”.


Modelowanie stanu gry

record GameState(
    string Word,
    HashSet<char> GuessedLetters,  // wszystkie podane litery (trafione + błędne)
    int MaxAttempts,
    int WrongCount
)
{
    public bool IsWon => Word.All(c => GuessedLetters.Contains(char.ToLowerInvariant(c)));
    public bool IsLost => WrongCount >= MaxAttempts;
    public bool IsOver => IsWon || IsLost;
    
    public string DisplayWord =>
        string.Join(" ", Word.Select(c => 
            GuessedLetters.Contains(char.ToLowerInvariant(c)) ? c : '_'));
}

✅ Rozwiązanie

static string[] _words = { "programowanie", "komputer", "algorytm", "interfejs", "rekurencja" };

static void PlayGame()
{
    var random = new Random();
    string word = _words[random.Next(_words.Length)].ToLowerInvariant();
    
    var state = new GameState(
        Word: word,
        GuessedLetters: new HashSet<char>(),
        MaxAttempts: 6,
        WrongCount: 0
    );
    
    Console.WriteLine($"Hasło ma {word.Length} liter. Masz {state.MaxAttempts} prób.");
    
    while (!state.IsOver)
    {
        Console.WriteLine($"\n{state.DisplayWord}");
        Console.WriteLine($"Błędne litery: {string.Join(", ", state.GuessedLetters.Where(c => !word.Contains(c)))}");
        Console.WriteLine($"Pozostałe próby: {state.MaxAttempts - state.WrongCount}");
        
        char guess = ReadSingleLetter(state.GuessedLetters);
        
        state.GuessedLetters.Add(guess);
        
        if (word.Contains(guess))
        {
            Console.WriteLine($"✓ Litera '{guess}' jest w słowie!");
            // State jest rekordem — musimy go odtworzyć z nowym GuessedLetters
            // (GuessedLetters to mutable HashSet, więc modyfikujemy bezpośrednio)
        }
        else
        {
            state = state with { WrongCount = state.WrongCount + 1 };
            Console.WriteLine($"✗ Brak litery '{guess}' w słowie.");
        }
    }
    
    Console.WriteLine($"\n{state.DisplayWord}");
    Console.WriteLine(state.IsWon
        ? $"Brawo! Odgadłeś '{word}'!"
        : $"Przegrałeś. Hasło to: '{word}'.");
}

static char ReadSingleLetter(HashSet<char> usedLetters)
{
    while (true)
    {
        Console.Write("Podaj literę: ");
        string? input = Console.ReadLine()?.Trim().ToLowerInvariant();
        
        if (string.IsNullOrEmpty(input) || input.Length != 1)
        {
            Console.WriteLine("Podaj dokładnie jedną literę.");
            continue;
        }
        
        char c = input[0];
        
        if (!char.IsLetter(c))
        {
            Console.WriteLine("Podaj literę (nie cyfrę ani znak specjalny).");
            continue;
        }
        
        if (usedLetters.Contains(c))
        {
            Console.WriteLine($"Litera '{c}' była już użyta.");
            continue;
        }
        
        return c;
    }
}

Rozszerzenia do omówienia na rozmowie

Tryb zgadywania całego słowa

static void TryGuessWord(GameState state, string word)
{
    Console.Write("Znasz hasło? Wpisz je (lub Enter żeby pominąć): ");
    string? attempt = Console.ReadLine()?.Trim().ToLowerInvariant();
    
    if (!string.IsNullOrEmpty(attempt))
    {
        if (attempt == word)
            // wygrał
        else
            // kara (stracona próba)
    }
}

Poziomy trudności

record Difficulty(int MaxAttempts, string[] WordList);

static readonly Difficulty Easy   = new(8, new[] { "kot", "pies", "dom" });
static readonly Difficulty Medium = new(6, _words);
static readonly Difficulty Hard   = new(4, new[] { "konstantynopolitan", "pszczelarz" });

Testy jednostkowe (logiki gry)

[Fact]
public void GameState_AllLettersGuessed_IsWon()
{
    var state = new GameState("abc", new HashSet<char> { 'a', 'b', 'c' }, 6, 0);
    Assert.True(state.IsWon);
}

[Fact]
public void GameState_MaxWrong_IsLost()
{
    var state = new GameState("abc", new HashSet<char>(), MaxAttempts: 3, WrongCount: 3);
    Assert.True(state.IsLost);
}

[Fact]
public void GameState_DisplayWord_ShowsGuessedLetters()
{
    var state = new GameState("abc", new HashSet<char> { 'a', 'c' }, 6, 0);
    Assert.Equal("a _ c", state.DisplayWord);
}

Co powiedzieć na rozmowie

“Game loop jest prosta: dopóki gra nie jest zakończona — pokaż stan, pobierz wejście, zaktualizuj stan. Kluczowe rozdzielenie: GameState przechowuje dane, ReadSingleLetter obsługuje wejście z walidacją, PlayGame koordynuje całość. Dzięki temu logika gry jest testowalna niezależnie od Console. HashSet<char> dla użytych liter daje O(1) sprawdzanie czy litera była już użyta.”


Podsumowanie

Odgadywanie hasła testuje umiejętność modelowania stanu i pisania czytelnego game loop. Kluczowe wzorce: while (!state.IsOver), separacja ReadInput od logiki gry, HashSet dla O(1) sprawdzania użytych liter.

👨‍💻
Mariusz Jurczenko
Senior .NET Developer · 10+ lat doświadczenia komercyjnego

Programista .NET z doświadczeniem komercyjnym w firmach takich jak COIG, Kamsoft czy Etisoft Smart Solutions. Twórca kursów, z których skorzystało już ponad 11 000 osób w Strefie Kursów i ponad 1 000 kursantów na dev-hobby.pl.

Specjalizacja: Clean Code, Clean Architecture i uczenie programowania tak, żeby dało się je naprawdę zrozumieć — nie wykuć.

Dodaj komentarz

czytanie to początek

Zamień wiedzę w umiejętności

Pobierz darmową Roadmapę .NET i ułóż takie tematy jak ten w spójną ścieżkę do pierwszej pracy.

Pobieram roadmapę →