Pierwszy projekt w C#

Pierwszy projekt w C#: Gra zgadywanka krok po kroku

Pierwszy projekt w C#: Gra zgadywanka krok po kroku

Masz dość tutoriali typu Hello World i nie wiesz, jak zacząć prawdziwy projekt w C#?
W tym artykule pokażę Ci, jak zbudować działającą grę zgadywankę w C#, krok po kroku z poprawną architekturą, walidacją danych i czytelną strukturą kodu.

To nie jest kolejny tutorial do przepisania. To fundament pod realne projekty.


🎯 Dlaczego “Hello World” to za mało?

Większość kursów kończy się na:

Console.WriteLine("Hello World");

Problem? To nie jest aplikacja. To jedna instrukcja.


❌ Czego NIE uczysz się z Hello World

  • zarządzania stanem aplikacji
  • walidacji danych wejściowych
  • logiki warunkowej
  • struktury projektu

✅ Czego nauczysz się w tym projekcie

Budując grę, poznasz:

  • pętle i kontrolę przepływu
  • walidację inputu (TryParse)
  • zarządzanie stanem
  • separację odpowiedzialności
  • podstawy architektury

🧱 Architektura – zanim napiszesz kod

Zamiast zaczynać od kodu – zaczynamy od modelu mentalnego.

Podziel aplikację na:


📦 GameState – magazyn danych

  • przechowuje stan gry
  • liczba do zgadnięcia
  • liczba prób
  • status gry

🤖 InputValidator – walidacja

  • sprawdza czy input jest poprawny
  • chroni aplikację przed błędami

🤖 GameLogic – logika

  • sprawdza wynik
  • decyduje o stanie gry

🎨 UIManager – prezentacja

  • wyświetla dane
  • zbiera input

👉 Zasada: każda klasa = jedna odpowiedzialność


🧩 Implementacja krok po kroku


🧱 Krok 1 – GameState (stan gry)

public class GameState
{
    public int SecretNumber { get; private set; }
    public int MaxAttempts { get; private set; }
    public int AttemptsUsed { get; private set; }
    public int AttemptsLeft => MaxAttempts - AttemptsUsed;
    public bool IsGameWon { get; private set; }

    public GameState(int maxAttempts = 7)
    {
        var random = new Random();
        SecretNumber = random.Next(1, 101); // 1-100
        MaxAttempts = maxAttempts;
        AttemptsUsed = 0;
        IsGameWon = false;
    }

    public void IncrementAttempt()
    {
        AttemptsUsed++;
    }

    public void MarkAsWon()
    {
        IsGameWon = true;
    }

    public bool IsGameOver()
    {
        return IsGameWon || AttemptsLeft == 0;
    }
}

💡 Dlaczego to jest dobre?

  • enkapsulacja (private set)
  • brak “rozlanego stanu” po aplikacji
  • kontrolowane zmiany przez metody

🤖 Krok 2 – InputValidator

public class InputValidator
{
    public bool TryParseGuess(string input, out int guess)
    {
        guess = 0;

        // Sprawdź czy to w ogóle liczba
        if (!int.TryParse(input, out guess))
        {
            Console.WriteLine("❌ To nie jest liczba! Spróbuj ponownie.\n");
            return false;
        }

        // Sprawdź zakres
        if (guess < 1 || guess > 100)
        {
            Console.WriteLine("❌ Liczba musi być między 1 a 100!\n");
            return false;
        }

        return true;
    }
}

💡 Best practice

  • używaj TryParse zamiast Parse
  • nie dopuszczaj do wyjątków w normalnym flow

🤖 Krok 3 – GameLogic

public class GameLogic
{
    private readonly GameState _state;

    public GameLogic(GameState state)
    {
        _state = state;
    }

    public GuessResult CheckGuess(int guess)
    {
        _state.IncrementAttempt();

        if (guess == _state.SecretNumber)
        {
            _state.MarkAsWon();
            return GuessResult.Correct;
        }
        else if (guess < _state.SecretNumber)
        {
            return GuessResult.TooLow;
        }
        else
        {
            return GuessResult.TooHigh;
        }
    }
}

// Enum dla wyników
public enum GuessResult
{
    TooLow,
    TooHigh,
    Correct
}

💡 Dlaczego to jest ważne?

  • Dependency Injection → testowalność
  • brak tight coupling
  • czysta logika biznesowa

🎨 Krok 4 – UIManager

public class UIManager
{
    public void DisplayWelcome()
    {
        Console.Clear();
        Console.WriteLine("🎮 === GRA ZGADYWANKA === 🎮\n");
        Console.WriteLine("Wylosowałem liczbę od 1 do 100.");
        Console.WriteLine("Spróbuj ją odgadnąć!\n");
    }

    public void DisplayAttemptPrompt(int attemptNumber, int attemptsLeft)
    {
        Console.WriteLine($"Próba {attemptNumber} (Pozostało: {attemptsLeft})");
        Console.Write("Twoja liczba: ");
    }

    public void DisplayResult(GuessResult result, int attemptsUsed)
    {
        switch (result)
        {
            case GuessResult.TooLow:
                Console.WriteLine("📊 Za mało! Spróbuj wyżej.\n");
                break;
            case GuessResult.TooHigh:
                Console.WriteLine("📊 Za dużo! Spróbuj niżej.\n");
                break;
            case GuessResult.Correct:
                Console.WriteLine($"\n🎉 GRATULACJE! Odgadłeś za {attemptsUsed} prób(y)!\n");
                break;
        }
    }

    public void DisplayGameOver(int secretNumber)
    {
        Console.WriteLine($"\n💀 PRZEGRAŁEŚ! Poprawna liczba to: {secretNumber}\n");
    }

    public bool AskPlayAgain()
    {
        Console.Write("Chcesz zagrać ponownie? (t/n): ");
        string input = Console.ReadLine()?.ToLower();
        return input == "t" || input == "tak";
    }
}

💡 Separation of Concerns

UI nie podejmuje decyzji – tylko wyświetla.


🔄 Krok 5 – Main (składanie wszystkiego)

class Program
{
    static void Main()
    {
        var ui = new UIManager();
        var validator = new InputValidator();
        bool playAgain = true;

        while (playAgain)
        {
            // 1. Inicjalizacja magazynu
            var gameState = new GameState(maxAttempts: 7);
            var gameLogic = new GameLogic(gameState);

            // 2. Powitanie
            ui.DisplayWelcome();

            // 3. Główna pętla gry
            while (!gameState.IsGameOver())
            {
                // 3a. Prompt
                ui.DisplayAttemptPrompt(
                    gameState.AttemptsUsed + 1, 
                    gameState.AttemptsLeft
                );

                // 3b. Pobierz i waliduj input
                string input = Console.ReadLine();
                if (!validator.TryParseGuess(input, out int guess))
                    continue; // Powtórz próbę bez liczenia

                // 3c. Sprawdź guess
                var result = gameLogic.CheckGuess(guess);

                // 3d. Wyświetl feedback
                ui.DisplayResult(result, gameState.AttemptsUsed);
            }

            // 4. Koniec gry
            if (!gameState.IsGameWon)
            {
                ui.DisplayGameOver(gameState.SecretNumber);
            }

            // 5. Restart?
            playAgain = ui.AskPlayAgain();
        }
    }
}

💡 Co tu się dzieje?

To jest linia montażowa aplikacji:

  1. Pobierz dane
  2. Zwaliduj
  3. Przetwórz
  4. Wyświetl

🧪 Edge cases – o których większość zapomina

✔️ błędny input (abc)
✔️ liczba poza zakresem
✔️ brak prób
✔️ restart gry


👉 To jest różnica między:

  • kodem demo
  • a kodem produkcyjnym

🚀 Rozszerzenia (Level Up)

🟢 Easy

  • poziomy trudności
  • historia prób

🟡 Medium

  • zapis wyniku do pliku
  • system punktów

🔴 Hard

  • AI (binary search)
  • wersja GUI (WPF / Avalonia)

🔗 Zobacz też


📌 Podsumowanie

Zbudowałeś:

  • działającą aplikację
  • strukturę projektu
  • separację odpowiedzialności
  • obsługę edge case’ów

👉 To jest pierwszy realny projekt do portfolio.


📣 Call To Action

💬 Zostaw komentarz – co dodałeś do gry?
📥 Pobierz roadmapę (link przy zapisie na listę VIP)


Zobacz także — powiązane artykuły

👉 MCP w .NET (C#) – jak zbudować serwer AI krok po kroku

👉 Tworzenie klas i obiektów w C# — kompletny przewodnik

👉 Pattern Matching w C# – switch expressions i type patterns

Dołącz do Listy VIP

I otrzymaj roadmapę Junior .NET Developer oraz najlepszą ofertę, gdy tylko ruszą zapisy!!!

Kontakt: mariuszjurczenko@dev-hobby.pl
Zero spamu. Możesz wypisać się w każdej chwili.

Dodaj komentarz