Kontrolka Canvas – rysowanie i pozycjonowanie

Nauczysz się używać kontrolki Canvas w WPF – umieszczać elementy w dokładnych pozycjach, rysować kształty, obsługiwać mysz i tworzyć proste animacje.

Canvas kształty pozycjonowanie mysz & ruch
1

Czym jest Canvas?

Canvas to specjalny kontener w WPF, który pozwala umieszczać elementy w dokładnych pozycjach podanych w pikselach. Elementy nie układają się automatycznie – Ty decydujesz gdzie każdy stoi.

Analogia

Wyobraź sobie, że inne layouty (Grid, StackPanel) to gotowe szafy z półkami – elementy trafiają na kolejne miejsca. Canvas to pusta ściana – możesz powiesić coś dokładnie tam, gdzie chcesz, podając odległość od lewej i od góry.

Porównanie z innymi layoutami

Layout Jak pozycjonuje? Kiedy używać?
StackPanel Jeden pod drugim / obok siebie Proste listy, toolbary
Grid Wiersze i kolumny Formularze, okna aplikacji
WrapPanel Automatycznie zalega w rzędach Galerie, tagi
Canvas Dokładna pozycja XY w pikselach Gry, rysowanie, animacje
Ważne

Canvas nie skaluje się automatycznie jak Grid. Jeśli użytkownik zmieni rozmiar okna, elementy pozostają w tych samych pikselowych pozycjach. Canvas jest idealny do gier i rysowania, ale nie do tworzenia formularzy.

2

Canvas w XAML

Dodanie Canvas do okna wygląda tak samo jak każdy inny kontener. Różnica jest w tym, jak ustawiamy pozycję elementów wewnątrz niego.

MainWindow.xaml
<Window x:Class="MojaAplikacja.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Canvas – przykład"
        Width="500" Height="400">

  <Canvas x:Name="moiCanvas"
          Background="LightGray">

    <!-- Prostokąt w pozycji (50, 30) -->
    <Rectangle
        Canvas.Left="50"
        Canvas.Top="30"
        Width="100"
        Height="60"
        Fill="SteelBlue" />

    <!-- Koło w pozycji (200, 80) -->
    <Ellipse
        Canvas.Left="200"
        Canvas.Top="80"
        Width="80"
        Height="80"
        Fill="Tomato" />

  </Canvas>
</Window>

Attached Properties – właściwości dołączone

W Canvas używamy specjalnego zapisu Canvas.Left i Canvas.Top. To są tak zwane attached properties (właściwości dołączone) – Canvas „dokłeja” do każdego dziecka informację o jego pozycji.

Cztery właściwości pozycji
WłaściwośćZnaczeniePrzykład
Canvas.LeftOdległość od lewej krawędziCanvas.Left="50"
Canvas.TopOdległość od górnej krawędziCanvas.Top="30"
Canvas.RightOdległość od prawej krawędziCanvas.Right="20"
Canvas.BottomOdległość od dolnej krawędziCanvas.Bottom="10"

Najczęściej używasz Left i Top razem – to wyznacza pozycję lewego górnego rogu elementu.

ZIndex – kolejność warstw

Gdy elementy na sobie leżą, domyślnie ten zdefiniowany później w XAML jest widoczny na wierzchu. Możesz to zmienić właściwością Canvas.ZIndex.

ZIndex – przykład
<!-- Niebieski kwadrat – ZIndex 1, będzie pod czerwonym -->
<Rectangle Canvas.Left="40" Canvas.Top="40"
           Width="120" Height="120"
           Fill="SteelBlue"
           Canvas.ZIndex="1" />

<!-- Czerwony kwadrat – ZIndex 2, będzie na wierzchu -->
<Rectangle Canvas.Left="80" Canvas.Top="80"
           Width="120" Height="120"
           Fill="Tomato"
           Canvas.ZIndex="2" />

Im wyższa wartość ZIndex, tym element jest bliżej nas (na wierzchu).

3

Kształty wektorowe

Canvas współpracuje ze specjalnymi klasami kształtów z przestrzeni nazw System.Windows.Shapes. Są to kształty wektorowe – możesz je skalować bez utraty jakości.

Rectangle i Ellipse

Prostokąty i elipsy w XAML
<!-- Zwykły prostokąt -->
<Rectangle Canvas.Left="20" Canvas.Top="20"
           Width="150" Height="80"
           Fill="SteelBlue"
           Stroke="DarkBlue"
           StrokeThickness="3" />

<!-- Prostokąt z zaokrąglonymi rogami -->
<Rectangle Canvas.Left="20" Canvas.Top="120"
           Width="150" Height="80"
           Fill="MediumSeaGreen"
           RadiusX="15"
           RadiusY="15" />

<!-- Koło (Width == Height) -->
<Ellipse Canvas.Left="200" Canvas.Top="20"
         Width="100" Height="100"
         Fill="Tomato"
         Stroke="DarkRed"
         StrokeThickness="2" />

<!-- Elipsa (różne Width i Height) -->
<Ellipse Canvas.Left="200" Canvas.Top="140"
         Width="160" Height="60"
         Fill="Gold" />
Zaokrąglone rogi – RadiusX i RadiusY

Właściwości RadiusX i RadiusY zaokrąglają rogi prostokąta. Im większa wartość, tym bardziej zaokrąglone rogi. Ustawienie obu na 5–20 to popularny sposób na nowoczesny wygląd przycisków i kart.

Line, Polyline, Polygon

Linie i wielokąty w XAML
<!-- Linia od punktu (10,10) do (200,100) -->
<Line X1="10" Y1="10"
      X2="200" Y2="100"
      Stroke="Black"
      StrokeThickness="3" />

<!-- Linia przerywana -->
<Line X1="10" Y1="150"
      X2="200" Y2="150"
      Stroke="Gray"
      StrokeThickness="2"
      StrokeDashArray="4 2" />

<!-- Łamana (otwarta – bez wypełnienia) -->
<Polyline
    Points="20,180 80,120 140,160 200,100 260,140"
    Stroke="OrangeRed"
    StrokeThickness="3"
    Fill="Transparent" />

<!-- Wielokąt (zamknięty – z wypełnieniem) -->
<Polygon
    Points="150,20 180,80 120,80"
    Fill="MediumPurple"
    Stroke="DarkViolet"
    StrokeThickness="2" />
KształtOpisWymagane właściwości
LineOdcinek między dwoma punktamiX1, Y1, X2, Y2
PolylineŁamana – otwarta linia wielopunktowaPoints
PolygonWielokąt – zamknięty, można wypełnićPoints
RectangleProstokątWidth, Height
EllipseElipsa lub kołoWidth, Height
4

Kolory i wypełnienia

Fill i Stroke

Każdy kształt ma dwie kluczowe właściwości wizualne: Fill (wypełnienie) i Stroke (obramowanie).

WłaściwośćCo kontrolujePrzykłady wartości
FillWypełnienie wnętrza kształtu"Red", "#FF5733", "Transparent"
StrokeKolor linii obramowania"Black", "DarkBlue"
StrokeThicknessGrubość obramowania w pikselach"1", "3", "5"
StrokeDashArrayWzór przerywania linii"4 2" = 4px kreska, 2px przerwa

Gradienty

Zamiast jednolitego koloru możesz użyć gradientu. WPF obsługuje gradient liniowy (LinearGradientBrush) i radialny (RadialGradientBrush).

LinearGradientBrush – gradient liniowy
<Rectangle Canvas.Left="20" Canvas.Top="20"
           Width="200" Height="100">
  <Rectangle.Fill>
    <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
      <!-- Od lewej -->
      <GradientStop Color="SteelBlue" Offset="0" />
      <!-- Do prawej -->
      <GradientStop Color="MediumPurple" Offset="1" />
    </LinearGradientBrush>
  </Rectangle.Fill>
</Rectangle>
StartPoint i EndPoint

Punkty podaje się jako wartości od 0 do 1. (0,0) to lewy górny róg, (1,0) to prawy górny róg, (0,1) to lewy dolny. StartPoint="0,0" EndPoint="1,0" to gradient poziomy (lewa→prawa), a "0,0"→"0,1" to pionowy (góra→dół).

RadialGradientBrush – gradient radialny
<Ellipse Canvas.Left="20" Canvas.Top="20"
         Width="120" Height="120">
  <Ellipse.Fill>
    <RadialGradientBrush>
      <!-- Centrum – jasny kolor -->
      <GradientStop Color="Yellow" Offset="0" />
      <!-- Krawędź – ciemny kolor -->
      <GradientStop Color="OrangeRed" Offset="1" />
    </RadialGradientBrush>
  </Ellipse.Fill>
</Ellipse>
5

Praca w C#

Możesz tworzyć i przesuwać elementy Canvas bezpośrednio z kodu C#. Jest to niezbędne w grach i animacjach, gdzie elementy muszą się poruszać.

Dodawanie elementów do Canvas

MainWindow.xaml.cs – dodawanie prostokątów
private void DodajKsztalty()
{
    // 1. Tworzymy obiekt kształtu
    Rectangle kwadrat = new Rectangle()
    {
        Width  = 100,
        Height = 100,
        Fill   = Brushes.SteelBlue,
        Stroke = Brushes.DarkBlue,
        StrokeThickness = 2
    };

    // 2. Ustawiamy pozycję przez static methods Canvas
    Canvas.SetLeft(kwadrat, 50);   // 50 px od lewej
    Canvas.SetTop(kwadrat, 30);    // 30 px od góry

    // 3. Dodajemy do Canvas
    moiCanvas.Children.Add(kwadrat);
}

private void DodajKolo(double x, double y, Brush kolor)
{
    Ellipse kolo = new Ellipse()
    {
        Width  = 50,
        Height = 50,
        Fill   = kolor
    };

    Canvas.SetLeft(kolo, x);
    Canvas.SetTop(kolo, y);
    moiCanvas.Children.Add(kolo);
}
Ważne!

Samo stworzenie obiektu new Rectangle() nie wyświetla go na ekranie. Musisz jeszcze dodać go do canvas.Children.Add(...). Bez tego kształt istnieje w pamięci, ale jest niewidoczny.

Przesuwanie elementów

Żeby przesunąć element, wywołujesz Canvas.SetLeft i Canvas.SetTop z nową pozycją. WPF automatycznie przerysuje element w nowym miejscu.

Przesuwanie kształtu
Rectangle _kwadrat;   // pole klasy – przechowujemy referencję
double _x = 50;
double _y = 50;

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    _kwadrat = new Rectangle() { Width = 60, Height = 60, Fill = Brushes.Tomato };
    Canvas.SetLeft(_kwadrat, _x);
    Canvas.SetTop(_kwadrat, _y);
    moiCanvas.Children.Add(_kwadrat);
}

private void PrzesunWPrawo()
{
    _x = _x + 10;   // zwiększamy X o 10 pikseli
    Canvas.SetLeft(_kwadrat, _x);   // aktualizujemy pozycję
}

Odczytywanie pozycji elementu

Canvas.GetLeft / GetTop
// Odczyt aktualnej pozycji
double aktualneX = Canvas.GetLeft(_kwadrat);
double aktualneY = Canvas.GetTop(_kwadrat);

// Sprawdzamy czy wyszedł poza prawy brzeg (Canvas ma szerokość 400)
if (aktualneX + _kwadrat.Width > moiCanvas.ActualWidth)
{
    // Odbij – przesuń z powrotem
    Canvas.SetLeft(_kwadrat, 0);
}

Usuwanie elementów

Usuwanie z Canvas
// Usuń konkretny element
moiCanvas.Children.Remove(_kwadrat);

// Usuń wszystko z Canvas
moiCanvas.Children.Clear();
6

Obsługa myszy

Pobieranie pozycji kursora

Metoda e.GetPosition(canvas) zwraca pozycję kursora względem podanego elementu. Użyj jej w zdarzeniach myszy, aby wiedzieć gdzie użytkownik kliknął lub gdzie jest kursor.

XAML – podpięcie zdarzeń
<Canvas x:Name="moiCanvas"
        Background="LightGray"
        MouseLeftButtonDown="Canvas_MouseLeftButtonDown"
        MouseMove="Canvas_MouseMove">
</Canvas>
C# – obsługa zdarzeń myszy
private void Canvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    // Pobieramy pozycję kliknięcia względem Canvas
    Point pozycja = e.GetPosition(moiCanvas);

    // Wyświetlamy współrzędne (np. w pasku tytułu)
    Title = $"Kliknięto: X={pozycja.X:F0}, Y={pozycja.Y:F0}";

    // Rysujemy małe kółko w miejscu kliknięcia
    Ellipse punkt = new Ellipse()
    {
        Width  = 12,
        Height = 12,
        Fill   = Brushes.Red
    };
    // Centrujemy kółko na klikniętym punkcie (odejmujemy połowę rozmiaru)
    Canvas.SetLeft(punkt, pozycja.X - 6);
    Canvas.SetTop(punkt, pozycja.Y - 6);
    moiCanvas.Children.Add(punkt);
}

private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
    Point pozycja = e.GetPosition(moiCanvas);
    Title = $"Kursor: X={pozycja.X:F0}, Y={pozycja.Y:F0}";
}

Przeciąganie elementów (Drag & Drop)

Przeciąganie to trzy etapy: złap (MouseDown), przesuń (MouseMove), puść (MouseUp).

XAML
<Canvas x:Name="moiCanvas" Background="LightGray">
  <Rectangle x:Name="ruchomyKwadrat"
             Canvas.Left="100" Canvas.Top="100"
             Width="80" Height="80"
             Fill="SteelBlue"
             Cursor="Hand"
             MouseLeftButtonDown="Kwadrat_MouseDown"
             MouseMove="Kwadrat_MouseMove"
             MouseLeftButtonUp="Kwadrat_MouseUp" />
</Canvas>
C# – logika przeciągania
bool _przeciagam = false;
Point _przesunięcie;   // różnica między kursorem a lewym-górnym rogiem elementu

private void Kwadrat_MouseDown(object sender, MouseButtonEventArgs e)
{
    _przeciagam = true;

    // Zapobiegamy "zgubieniu" myszy przy szybkim ruchu
    ruchomyKwadrat.CaptureMouse();

    // Zapamiętujemy gdzie w obrębie elementu kliknęliśmy
    Point kursorNaCanvas = e.GetPosition(moiCanvas);
    double elemX = Canvas.GetLeft(ruchomyKwadrat);
    double elemY = Canvas.GetTop(ruchomyKwadrat);
    _przesunięcie = new Point(kursorNaCanvas.X - elemX, kursorNaCanvas.Y - elemY);
}

private void Kwadrat_MouseMove(object sender, MouseEventArgs e)
{
    if (!_przeciagam) return;

    Point kursor = e.GetPosition(moiCanvas);

    // Nowa pozycja = pozycja kursora minus zapamiętane przesunięcie
    double noweX = kursor.X - _przesunięcie.X;
    double noweY = kursor.Y - _przesunięcie.Y;

    Canvas.SetLeft(ruchomyKwadrat, noweX);
    Canvas.SetTop(ruchomyKwadrat, noweY);
}

private void Kwadrat_MouseUp(object sender, MouseButtonEventArgs e)
{
    _przeciagam = false;
    ruchomyKwadrat.ReleaseMouseCapture();   // oddajemy przechwycenie myszy
}
Dlaczego CaptureMouse?

Bez CaptureMouse(), gdy ruszysz myszą bardzo szybko, kursor „ucieknie” z elementu i MouseMove przestanie działać – element zatrzyma się w pół drogi. CaptureMouse() sprawia, że element dostaje zdarzenia myszy nawet gdy kursor jest poza nim. Zawsze pamiętaj o ReleaseMouseCapture() w MouseUp!

7

Animacja – DispatcherTimer

DispatcherTimer to zegar WPF, który co określony czas wywołuje metodę. Używamy go do animacji – co kilkanaście milisekund przesuwamy elementy, tworząc efekt płynnego ruchu.

Analogia

Wyobraź sobie, że rysujesz animację klatkową w zeszycie. DispatcherTimer to ktoś, kto co chwilę mówi „rysuj następną klatkę!”. Ty wtedy przesuwasz element o kilka pikseli – i tak 60 razy na sekundę powstaje płynna animacja.

Przykład: odbijająca się piłka

XAML
<Canvas x:Name="moiCanvas" Background="#1a1a2e">
  <Ellipse x:Name="pilka"
           Width="40" Height="40"
           Fill="OrangeRed"
           Canvas.Left="100"
           Canvas.Top="100" />
</Canvas>
C# – animacja
double _x = 100, _y = 100;   // pozycja piłki
double _vx = 4, _vy = 3;     // prędkość: ile pikseli na klatkę
DispatcherTimer _timer;

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    _timer = new DispatcherTimer();
    _timer.Interval = TimeSpan.FromMilliseconds(16);  // ~60 klatek/sek
    _timer.Tick += Timer_Tick;
    _timer.Start();
}

private void Timer_Tick(object sender, EventArgs e)
{
    // Przesuwamy piłkę
    _x += _vx;
    _y += _vy;

    // Sprawdzamy odbicie od ścian
    if (_x <= 0 || _x + pilka.Width >= moiCanvas.ActualWidth)
        _vx = -_vx;   // odbij poziomo

    if (_y <= 0 || _y + pilka.Height >= moiCanvas.ActualHeight)
        _vy = -_vy;   // odbij pionowo

    // Aktualizujemy pozycję na Canvas
    Canvas.SetLeft(pilka, _x);
    Canvas.SetTop(pilka, _y);
}
InterwałKlatek/sek (FPS)Zastosowanie
16 ms~60 FPSPłynne gry i animacje
33 ms~30 FPSWystarczające dla większości gier
100 ms10 FPSProste animacje, UI updates
500 ms2 FPSOdliczanie, tik-tok
8

Projekt – gra „łap kółka”

Połączymy wszystko w małą grę: co sekundę pojawia się losowe kółko, gracz musi je kliknąć zanim zniknie. Za każde kliknięcie dostaje punkt.

MainWindow.xaml
<Grid>
  <Grid.RowDefinitions>
    <RowDefinition Height="40" />
    <RowDefinition Height="*" />
  </Grid.RowDefinitions>

  <!-- Pasek z wynikiem -->
  <StackPanel Grid.Row="0" Orientation="Horizontal"
              Background="#222" VerticalAlignment="Stretch">
    <TextBlock Text="Wynik: " Foreground="White"
               FontSize="18" Margin="10,0"
               VerticalAlignment="Center" />
    <TextBlock x:Name="lblWynik"
               Text="0" Foreground="OrangeRed"
               FontSize="18" FontWeight="Bold"
               VerticalAlignment="Center" />
  </StackPanel>

  <!-- Plansza gry -->
  <Canvas x:Name="moiCanvas"
          Grid.Row="1"
          Background="#1a1a2e" />
</Grid>
MainWindow.xaml.cs
int _wynik = 0;
Ellipse _aktualneKolo;
DispatcherTimer _timer;
Random _rnd = new Random();

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    _timer = new DispatcherTimer();
    _timer.Interval = TimeSpan.FromSeconds(1);  // nowe kółko co sekundę
    _timer.Tick += Timer_Tick;
    _timer.Start();
}

private void Timer_Tick(object sender, EventArgs e)
{
    // Usuń poprzednie kółko jeśli istnieje
    if (_aktualneKolo != null)
        moiCanvas.Children.Remove(_aktualneKolo);

    // Losujemy rozmiar i pozycję nowego kółka
    int rozmiar = _rnd.Next(30, 80);
    double maxX = moiCanvas.ActualWidth  - rozmiar;
    double maxY = moiCanvas.ActualHeight - rozmiar;
    double x = _rnd.Next(0, (int)maxX);
    double y = _rnd.Next(0, (int)maxY);

    // Losujemy kolor
    Brush[] kolory = { Brushes.OrangeRed, Brushes.LimeGreen,
                        Brushes.Cyan,      Brushes.Yellow,
                        Brushes.HotPink };
    Brush kolor = kolory[_rnd.Next(kolory.Length)];

    // Tworzymy nowe kółko
    _aktualneKolo = new Ellipse()
    {
        Width   = rozmiar,
        Height  = rozmiar,
        Fill    = kolor,
        Cursor  = Cursors.Hand
    };

    // Podpinamy obsługę kliknięcia
    _aktualneKolo.MouseLeftButtonDown += Kolo_Klikniete;

    Canvas.SetLeft(_aktualneKolo, x);
    Canvas.SetTop(_aktualneKolo, y);
    moiCanvas.Children.Add(_aktualneKolo);
}

private void Kolo_Klikniete(object sender, MouseButtonEventArgs e)
{
    _wynik++;
    lblWynik.Text = _wynik.ToString();

    // Usuń kliknięte kółko natychmiast
    moiCanvas.Children.Remove(_aktualneKolo);
    _aktualneKolo = null;

    // Zatrzymujemy propagację zdarzenia (nie przeniknie do Canvas)
    e.Handled = true;
}
Co tu się dzieje?

Timer co sekundę usuwa stare kółko i tworzy nowe w losowym miejscu. Kliknięcie kółka (Kolo_Klikniete) zwiększa wynik i usuwa je od razu. Właściwość e.Handled = true zatrzymuje zdarzenie – bez tego kliknięcie „przeleciałoby” dalej do Canvas, co mogłoby powodować nieoczekiwane efekty.

9

Częste błędy i pułapki

❌ Błąd 1: Zapomniane Children.Add

❌ Źle

Rectangle r = new Rectangle();
Canvas.SetLeft(r, 50);
// Zapomniałem dodać do Canvas!
// Element nie pojawi się na ekranie

✅ Dobrze

Rectangle r = new Rectangle();
Canvas.SetLeft(r, 50);
Canvas.SetTop(r, 30);
moiCanvas.Children.Add(r); // ✅

❌ Błąd 2: ActualWidth = 0 przed załadowaniem okna

❌ Źle – w konstruktorze

public MainWindow()
{
    InitializeComponent();
    // ActualWidth = 0! Okno jeszcze niewidoczne
    double x = moiCanvas.ActualWidth / 2;
}

✅ Dobrze – w Loaded

private void Window_Loaded(object s, RoutedEventArgs e)
{
    // Tutaj ActualWidth ma prawidłową wartość
    double x = moiCanvas.ActualWidth / 2;
}

❌ Błąd 3: Brak ReleaseMouseCapture

❌ Źle

private void MouseDown(...)
{
    element.CaptureMouse();
    // Brak ReleaseMouseCapture w MouseUp!
    // Mysz "utknięta" przy elemencie
}

✅ Dobrze

private void MouseUp(...)
{
    _przeciagam = false;
    element.ReleaseMouseCapture(); // ✅
}

❌ Błąd 4: Modyfikacja kolekcji podczas iteracji

❌ Źle – wyjątek!

foreach (var elem in moiCanvas.Children)
{
    // Błąd: nie można usuwać
    // podczas iteracji foreach!
    moiCanvas.Children.Remove(elem);
}

✅ Dobrze

// Usuń wszystko jednocześnie
moiCanvas.Children.Clear();

// Lub usuń konkretny element
moiCanvas.Children.Remove(konkretny);
10

Zadania do wykonania

Zadanie 1 łatwe

Scena XAML – narysuj w XAML prostą scenę zawierającą:

  • Niebieskie niebo (tło Canvas)
  • Żółte słońce (Ellipse w prawym górnym rogu)
  • Zieloną trawę (Rectangle na dole, szerokość = cały Canvas)
  • Brązowy dom (Rectangle + trójkąt dach z Polygon)
Zadanie 2 łatwe

Klikanie rysuje – po kliknięciu w Canvas dodawaj małe kółko (średnica 20px) w miejscu kliknięcia. Dodaj przycisk „Wyczyść” który usuwa wszystkie kółka.

Zadanie 3 średnie

Ruchomy pojazd – narysuj prostego samochodu (Rectangle + 2× Ellipse jako koła) i animuj go za pomocą DispatcherTimer tak, by jechał od lewej do prawej. Gdy wyjedzie poza krawędź, pojawia się z lewej strony od nowa.

Zadanie 4 średnie

Przeciągnij kwadraty – wyświetl 5 kolorowych kwadratów i umożliw ich przeciąganie myszą (Drag & Drop). Użytkownik powinien móc dowolnie układać je na planszy.

Zadanie 5 trudne

Gra Snake – zaimplementuj prostego węża:

  • Wąż porusza się po siatce (np. 20×20 komórek, każda 20px)
  • Strzałki klawiatury zmieniają kierunek
  • Co sekundę pojawia się „jabłko” (czerwone kółko) w losowym miejscu
  • Zjedzenie jabłka wydłuża węża o 1 i dodaje punkt
  • Uderzenie w ścianę lub samego siebie kończy grę