$$ \newcommand{\floor}[1]{\left\lfloor{#1}\right\rfloor} \newcommand{\ceil}[1]{\left\lceil{#1}\right\rceil} \renewcommand{\mod}{\,\mathrm{mod}\,} \renewcommand{\div}{\,\mathrm{div}\,} \newcommand{\metar}{\,\mathrm{m}} \newcommand{\cm}{\,\mathrm{cm}} \newcommand{\dm}{\,\mathrm{dm}} \newcommand{\litar}{\,\mathrm{l}} \newcommand{\km}{\,\mathrm{km}} \newcommand{\s}{\,\mathrm{s}} \newcommand{\h}{\,\mathrm{h}} \newcommand{\minut}{\,\mathrm{min}} \newcommand{\kmh}{\,\mathrm{\frac{km}{h}}} \newcommand{\ms}{\,\mathrm{\frac{m}{s}}} \newcommand{\mss}{\,\mathrm{\frac{m}{s^2}}} \newcommand{\mmin}{\,\mathrm{\frac{m}{min}}} \newcommand{\smin}{\,\mathrm{\frac{s}{min}}} $$

Prijavi problem


Obeleži sve kategorije koje odgovaraju problemu

Još detalja - opišite nam problem


Uspešno ste prijavili problem!
Status problema i sve dodatne informacije možete pratiti klikom na link.
Nažalost nismo trenutno u mogućnosti da obradimo vaš zahtev.
Molimo vas da pokušate kasnije.

Догађаји тастатуре

На почетку претходне лекције смо објаснили однос очитавања стања према реаговању на догађаје и поменули да догађаје можемо у том контексту да схватимо као промене стања. Када је реч о тастатури, за контроле и формулар можемо да дефинишемо функције које реагују на спуштање и подизање неког од тастера на тастатури (догађаји Form1_KeyDown и Form1_KeyUp). Осим ових елементарних догађаја, на располагању нам је и догађај Form1_KeyPress, који одговара притиску на неки од тастера (убрзо ћемо појаснити сврху овог догађаја).

../_images/interact_KeyboardEvents.png

Свака од ових функција за обраду догађаја тастатуре прихвата и један праметар који садржи додатне информације о догађају. Тај прараметар у аутоматски генерисаном „костуру” функције добија име e.

У случају функција Form1_KeyDown и Form1_KeyUp, овај праметар је типа KeyEventArgs и садржи својство KeyCode, које говори који тастер је генерисао догађај (прешао у доњи, односно горњи положај). У лекцији Очитавање тастатуре смо поменули да за сваки тастер постоји именована константа која га представља и при томе смо набројали неке од тих константи. Да бисмо у функцији Form1_KeyDown или Form1_KeyUp проверили да ли је то тастер који нас интересује, треба да упоредимо својство KeyCode са одговарајућом константом. Приметите да ове константе одговарају физичким тастерима, без обзира на функцију која је тренутно додељена тим тастерима (на пример избором другог језика).

Од осталих својстава параметра e вреди поменути и логичка својства e.Shift, e.Alt и e.Control, која говоре да ли је заједно са тастером чији код је e.KeyCode притиснут и неки од тастера Shift, Alt, односно Control.

Функција Form1_KeyPress има параметар e типа KeyPressEventArgs. Овај параметар има својство e.KeyChar које је карактер. За разлику од функција Form1_KeyDown и Form1_KeyUp које се баве физичким тстерима, у функцији Form1_KeyPress можемо да сазнамо да ли је откуцано на пример неко ћирилично слово.

Следе примери.

Падајуће речи

Написати програм за вежбање куцања. Програм приказује реч која се полако спушта, док корисник покушава да је откуца без грешке. Ако корисник успе да откуца реч, добија известан број поена и појављује се нова реч, а ако не успе да је откуца пре него што реч стигне субише близу доње ивице прозора, губи један живот (и појављује се нова реч).

На анимацији доле, задата реч је приказана белом бојом, а део који је корисник откуцао црном.

../_images/interact_FallingWords.gif

Поред тајмера, биће нам потебно дугме за почетак игре. Осим ових компоненти, користићемо и неколико лабела:

  • Лабела lblPoeniTekst само садржи текст „Poeni”

  • Лабела lblPoeni садржи тренутни број поена

  • Лабела lblZivotiTekst само садржи текст „Životi”

  • Лабела lblZivoti садржи тренутни број живота

  • Лабела lblZadataRec садржи задату реч која пада

  • Лабела lblPogodjeniDeo садржи префикс задате речи који је корисник тачно откуцао

Тиме смо (уз уобичајена подешавања боја, насловне линије и сл.) завршили дизајнирање формулара. Прелазимо на програмирање.

У програму користимо низ стрингова SpisakReci, из којег бирамо речи за задавање. Речи ћемо задавати случајним избором, па нам треба и генератор случајних бројева. У пратећем низу PoeniZaTacnuRec за сваку реч имамо њену вредност у поенима, ако се тачно откуца.

Функције NovaIgra и NovaRec су врло једноставне па их нећемо посебно објашњавати. У функцији timer1_Tick поступак је у принципу стандардна анимација - израчунавамо нови, нижи положај речи, а ако је реч превише ниско одузимамо један живот и покрећемо нову реч или завршавамо игру.

Функција Form1_KeyPress се користи први пут, а и нешто је сложенија, па заслужује више пажње.

Да бисмо илустровали могућности обраде догађаја KeyPress, омогућили смо да се нова игра започне и притискм на тастер ’n’ (водите рачуна да тастатура буде латинична). Ако је игра већ у току, проверавамо да ли је откуцано слово баш оно које правилно наставља префикс задате речи, који је корисник претходно откуцао. Ако је слово тачно откуцано, додајемо га у лабелу lblPogodjeniDeo и у случају да је то последње слово задате речи, додајемо поене и покрећемо нобу реч. Ако је слово погрешно, ресетујемо тачно откуцани део речи (лабелу lblPogodjeniDeo) на празан стринг, што значи да корисник мора поново да почне од првог слова.

using System;
using System.Drawing;
using System.Windows.Forms;

namespace PadajuceReci
{
    public partial class Form1 : Form
    {
        const int REC_X = 300;
        const int MARGINA = 50;

        string[] SpisakReci = {
            "kada", "fasada", "red", "rad", "okolo", "koliko", "dato", "ukrasti",
            "parada", "odrediti", "prioritet", "topot", "trud", "kratko", "dugo",
            "krasta", "sertifikat", "graduirati", "hlad", "plata", "palata",
            "klada", "pošteda", "opšte", "korist", "ruža", "rupa", "tuš",  "gas",
             "rasad",  "prase",  "ujak",  "straža",  "ruglo",  "ograda",  "garaža"
        };
        int[] PoeniZaTacnuRec = { 0, 1, 1, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55 };
        int Poeni, Zivoti;
        bool IgraJeUToku;
        Random Rnd = new Random();

        public Form1()
        {
            InitializeComponent();
            Text = "Padajuće reči";
            BackColor = Color.FromArgb(23, 187, 156);
            ClientSize = new Size(640, 480);
        }

        private void NovaIgra()
        {
            Poeni = 0; lblPoeni.Text = Poeni.ToString();
            Zivoti = 5; lblZivoti.Text = Zivoti.ToString();
            IgraJeUToku = true;
            NovaRec();
        }

        private void NovaRec()
        {
            lblZadataRec.Text = SpisakReci[Rnd.Next(SpisakReci.Length)];
            lblZadataRec.Location = new Point(REC_X, MARGINA);
            lblPogodjeniDeo.Text = "";
            lblPogodjeniDeo.Location = new Point(REC_X, MARGINA + lblZadataRec.Height);
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            if (!IgraJeUToku)
                return;

            int y = lblZadataRec.Location.Y;
            lblZadataRec.Location = new Point(REC_X, y + 1);
            lblPogodjeniDeo.Location = new Point(REC_X, y + lblZadataRec.Height + 1);
            if (y > ClientSize.Height - MARGINA)
            {
                Zivoti--;
                lblZivoti.Text = Zivoti.ToString();
                if (Zivoti > 0) NovaRec();
                else IgraJeUToku = false;
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            NovaIgra();
        }

        private void Form1_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (!IgraJeUToku)
            {
                if (e.KeyChar == 'n')
                    NovaIgra();

                return;
            }

            // ako je igra u toku, proveri da li je otkucano slovo dobro
            int k = lblPogodjeniDeo.Text.Length;
            if (lblZadataRec.Text[k] == e.KeyChar)
            {
                lblPogodjeniDeo.Text += e.KeyChar;
                k++;
                if (k == lblZadataRec.Text.Length)
                {
                    Poeni += PoeniZaTacnuRec[k];
                    lblPoeni.Text = Poeni.ToString();
                    NovaRec();
                }
            }
            else
                lblPogodjeniDeo.Text = "";
        }
    }
}

Понг

Ово је једна од најстаријих компјутерских игара и врло је позната. Играју два играча померајући рекете горе-доле и одбијајући лоптицу. Играч који не успе да врати лоптицу губи поен (то јест његов противник добија поен). У овом примеру игра нема крај.

../_images/interact_Pong.png

У овом програму формулар је празан, па се дизајнирање своди на додавање тајмера (поене смо могли да сместимо у лабеле, али овде их исцртавамо).

За опис сцене потребно је да пратимо положаје центра левог и десног рекета и положај и брзину лоптице (променљиве LeviReketX, LeviReketY, DesniReketX, DesniReketY, LopticaX, LopticaY, LopticaDX, LopticaDY).

Једноставније функције ни овај пут нећемо посебно објашњавати. У функцији OdbijanjeOdReketa има пар ствари на које вреди обратити пажњу:

  • начин на који се поставља нова X координата лоптице када дође до одбијања: пошто се лоптица креће за по четири пиксела лево или десно, она може да утоне неколико пиксела у рекет. Овде треба пазити да у следећем фрејму лоптица буде довољно далеко од рекета да не би дошло до погрешног закључка да поново треба да се одбије (то би довело бага у коме лоптица не може да се одвоји од рекета јер се стално одбија лево-десно). Ми смо проблем решили тако што експлицитно премештамо центар лоптице ван рекета (наредба LopticaX = reketX + smer * DEBLJINA_REKETA / 2;).

  • начин на који се одређује вертикална компонента брзине LopticaDY. Желимо да смер у коме лоптица креће након одбијања зависи и од тога којим делом рекета је лоптица одбијена. Ако је лоптица погођена средином рекета, она се одбија правилно (под истим углом). Што је тачка рекета која је у контакту са лоптицом виша, то је и смер лоптице по одбијању више померен на горе и обрнуто. Овај ефекат постижемо тако што вертикалној брзини лоптице LopticaDY додајемо величину, сразмерну вертикалном растојању лоптице од центра рекета (у програму смо ову величину назвали spin). При томе пазимо да вертикална брзина не изађе из опсега [-3, 3].

У функцији timer1_Tick померамо лоптицу и пазимо на одбијања. Одбијања од горње и доње ивице се обрађују на већ уобичајени начин, док одбијања од рекета обрађује поменута функција OdbijanjeOdReketa, која се овде само позива.

У функцији Form1_KeyDown проверавамо да ли неки од рекета треба померити. Леви играч помера рекет притисцима на тастере ’Q’ и ’A’, а десни на тастере ’P’ и ’L’. Ако је неки од тих тастера притиснут, ажурирамо положај одговарајућег рекета и тражимо ново исцртавање.

Функција Form1_Paint је нешто дужа, али у њој нема ништа ново, само има више ствари које треба исцртати.

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

namespace Pong
{
    public partial class Form1 : Form
    {
        const int DUZINA_REKETA = 50;
        const int DEBLJINA_REKETA = 8;
        const int R_LOPTICE = 5;
        const int MARGINA = 40;
        int CX, CY; // centar prozora

        int PoeniLevo = 0, PoeniDesno = 0;
        int LeviReketX, LeviReketY; // centar levog reketa
        int DesniReketX, DesniReketY; // centar desnog reketa
        int LopticaX, LopticaY; // polozaj centra loptice
        int LopticaDX, LopticaDY; // brzina loptice
        Random Rnd = new Random();

        private int SlucajanSmer() { return 2 * Rnd.Next(2) - 1; } // -1 ili +1

        private void OdbijanjeOdReketa(int reketX, int reketY, int smer)
        {
            // Ako je loptica na reketu ...
            if (2 * Math.Abs(LopticaX - reketX) <= DEBLJINA_REKETA &&
                2 * Math.Abs(LopticaY - reketY) <= DUZINA_REKETA)
            {
                LopticaX = reketX + smer * DEBLJINA_REKETA / 2; // izbacujemo lopticu van reketa
                LopticaDX = -LopticaDX; // loptica horizontalno prosto menja smer,
                // a vertikalno dobija 'spin' prema tome kojim delom reketa je pogodjena
                int spin = 6 * (LopticaY - reketY) / DUZINA_REKETA;
                LopticaDY = Math.Min(3, Math.Max(-3, LopticaDY + spin));
            }
        }

        private void Servis()
        {
            LopticaX = CX;
            LopticaY = CY;
            LopticaDX = 4 * SlucajanSmer(); // -4 ili +4
            LopticaDY = Rnd.Next(-3, 4); // -3 .. +3
            LeviReketY = CY;
            DesniReketY = CY;
        }

        public Form1()
        {
            InitializeComponent();
            Text = "Pong";
            BackColor = Color.FromArgb(23, 187, 156);
            ClientSize = new Size(700, 400);

            // Postavi 'konstante' koje zavise od velicine prozora:
            CX = ClientSize.Width / 2; // centar prozora
            CY = ClientSize.Height / 2;
            LeviReketX = MARGINA;
            DesniReketX = 2 * CX - MARGINA;

            // Pocni igru
            Servis();
            Invalidate();
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            // pomeramo lopticu
            LopticaX += LopticaDX;
            LopticaY += LopticaDY;

            // odbijanje od gornje i donje ivice prozora
            if (LopticaY >= ClientSize.Height || LopticaY <= 0)
                LopticaDY = -LopticaDY;

            // Proveri da li loptica treba da se odbije od levog ili desnog reketa
            OdbijanjeOdReketa(LeviReketX, LeviReketY, 1);
            OdbijanjeOdReketa(DesniReketX, DesniReketY, -1);

            // provera da li je loptica iza nekog od reketa (poen za protivnika)
            if (LopticaX < LeviReketX - MARGINA / 2) { PoeniDesno++; Servis(); }
            if (LopticaX > DesniReketX + MARGINA / 2) { PoeniLevo++; Servis(); }

            Invalidate();
        }

        private void Form1_KeyDown(object sender, KeyEventArgs e)
        {
            int minY = DUZINA_REKETA / 2;
            int maxY = ClientSize.Height - DUZINA_REKETA / 2;
            int dy = 5;
            bool pomereni = true;
            switch (e.KeyCode)
            {
                case Keys.P: DesniReketY = Math.Max(minY, DesniReketY - dy); break;
                case Keys.L: DesniReketY = Math.Min(maxY, DesniReketY + dy); break;
                case Keys.Q: LeviReketY = Math.Max(minY, LeviReketY - dy); break;
                case Keys.A: LeviReketY = Math.Min(maxY, LeviReketY + dy); break;
                default: pomereni = false; break;
            }
            if (pomereni)
                Invalidate();
        }

        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            Brush cetka = new SolidBrush(Color.White);
            int cx = ClientSize.Width / 2;

            // mreza
            Pen olovkaZaMrezu = new Pen(Color.White, 2);
            olovkaZaMrezu.DashStyle = DashStyle.Dash;
            g.DrawLine(olovkaZaMrezu, cx, 0, cx, ClientSize.Height);

            // reketi
            int rw = DEBLJINA_REKETA, rh = DUZINA_REKETA;
            g.FillRectangle(cetka, LeviReketX - rw / 2, LeviReketY - rh / 2, rw, rh);
            g.FillRectangle(cetka, DesniReketX - rw / 2, DesniReketY - rh / 2, rw, rh);

            // loptica
            g.FillEllipse(cetka, LopticaX - R_LOPTICE, LopticaY - R_LOPTICE, 2 * R_LOPTICE, 2 * R_LOPTICE);

            // poeni
            StringFormat sf = new StringFormat
            {
                LineAlignment = StringAlignment.Center,
                Alignment = StringAlignment.Center
            };
            int POENI_SIRINA = 100;
            Rectangle leviPoeniRect = new Rectangle(cx - POENI_SIRINA, 0, POENI_SIRINA, MARGINA);
            Rectangle desniPoeniRect = new Rectangle(cx, 0, POENI_SIRINA, MARGINA);
            g.DrawString(PoeniLevo.ToString(), this.Font, cetka, leviPoeniRect, sf);
            g.DrawString(PoeniDesno.ToString(), this.Font, cetka, desniPoeniRect, sf);
        }
    }
}

Замке

У овој игри корисник помоћу стрелица управља белом тачком. Свака од црних тачака се креће ка најближој црвеној или белој тачки. Црвене тачке су непокретне. Када црна тачка стигне до црвене, она нестаје. Циљ играча је да „преживи” до нестајања свих црних тачака, јер ако нека од црних тачака стигне до беле тачке, играч је изгубио.

Црне тачке се померају са сваким новим фрејмом, а додатно и при сваком померању играча, односно беле тачке.

Играч може да укључи и искључи помоћ притиском на тастер F1. Помоћ се састоји у означавању оног дела прозора у коме је бела тачка ближе од било које црвене. То значи да црне тачке које се нађу у означеном делу прозора иду ка играчу (белој такчи), па на тај начин играч зна од којих црних тачака му прети опасност. Овај означени део прозора ћемо звати опасна зона.

../_images/interact_Traps.png

Ни у овом програму нема додатних компоненти осим тајмера, па прелазимо одмах на програм.

Пошто се померање црних тачака (у програму их зовемо гониоци) дешава и на тик тајмера и на померање играча, издвојили смо га у посебну функцију PomeriGonioce. У овој функцији за сваку црну тачку налазимо најближу црвену или белу, померамо је за по један пиксел хоризонтално и вертикално ка циљу и проверавамо да ли је та тачка стигла до циља. Ако није стигла до циља копирамо је у нову листу гониоца (она ће наставити да постоји и да јури свој циљ), а ако је стигла црвену тачку не копирамо је (тиме она престаје да постоји). Наравно, треба проверити и да ли је та црна тачка стигла играча и ако јесте завршити игру уз поруку о неуспеху. Такође, ако након обраде свих црних тачака нема више ни једне црне тачке у листи, треба завршити игру уз поруку о успеху.

Пошто у овом програму користимо стрелице, треба да испрограмирамо догађај KeyDown формулара (а не KeyPress). У функцији Form1_KeyDown провераваћемо да ли је код притиснутог тастера један од Keys.F1, Keys.N, Keys.Up, Keys.Down, Keys.Left, Keys.Right.

  • У случају пристика на F1 укључујемо помоћ ако није било укључена и обрнуто (наредба PrikaziOpasno = !PrikaziOpasno;).

  • У случају пристика на тастер N покрећемо нову игру (чак и ако је претходна у току)

  • у случају пристика на неку од стрелица покушавамо да померимо играча, и ако је то могуће (играч померањем не излази из прозора), померамо играча и гониоце.

Пошто изглед опасне зоне зависи од положаја играча (и непокретних, црвених тачака) не исплати се чувати податке о тачкама које се налазе у овој зони. Наиме, при сваком померању играча ти подаци се скоковито мењају, па би одржавање таквих инфомација било прескупо. Уместо тога, опасну зону израчунавамо онда када нам је потребна, а то је када је треба приказати. Проверу припадности опасној зони и цртање опасне зоне радимо тачку по тачку (то су у ствари мала поља), јер је то најједноставнији начин, мада није и најефикаснији.

Приметимо само да је опасна зона један многоугао, чије странице се налазе на симетралама дужи којима је један крај бела тачка а други крај нека од црвених тачака. Имајући ово у виду, могу се израчунати темена опасне зоне (то су пресеци неких од поменутих симетрала), а затим зона нацртати као многоугао. Овај ефикаснији поступак није једноставан, па га овде нисмо имплементирали.

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;

namespace Zamke
{
    public partial class Form1 : Form
    {
        const int VelicinaPolja = 8;
        const int BrojRedova = 50;
        const int BrojKolona = 50;
        const int BrojGonilaca = 100;
        const int BrojZamki = 5;

        Random Rnd = new Random();
        List<Point> Gonioci = new List<Point>(); // Gonioci su crni i pokretni
        List<Point> Zamke = new List<Point>();   // Zamke su crvene i nepokretne
        Point GlavniLik = new Point(-1, -1); // Glavni lik je beo i upravljan strelicama
        bool IgraJeUToku = false;
        bool PrikaziOpasno = false;
        string Poruka;

        public Form1()
        {
            InitializeComponent();
            Text = "Zamke";
            ClientSize = new Size(VelicinaPolja * BrojRedova, VelicinaPolja * BrojKolona);
            BackColor = Color.FromArgb(23, 187, 156);
        }

        private int Kvadrat(int n) { return n * n; }

        private void PomeriGonioce()
        {
            List<Point> noviGonioci = new List<Point>();
            for (int g = 0; g < Gonioci.Count; g++)
            {
                // Nadji tacku najblizu g-tom goniocu
                int dMin = Kvadrat(GlavniLik.X - Gonioci[g].X) + Kvadrat(GlavniLik.Y - Gonioci[g].Y);
                Point najbliza = GlavniLik;
                for (int z = 0; z < Zamke.Count; z++)
                {
                    int d = Kvadrat(Zamke[z].X - Gonioci[g].X) + Kvadrat(Zamke[z].Y - Gonioci[g].Y);
                    if (dMin > d)
                    {
                        dMin = d;
                        najbliza = Zamke[z];
                    }
                }

                // Pomeri g-tog gonioca ka najblizoj tacki
                int dx = Math.Sign(najbliza.X - Gonioci[g].X);
                int dy = Math.Sign(najbliza.Y - Gonioci[g].Y);
                Gonioci[g] = new Point(Gonioci[g].X + dx, Gonioci[g].Y + dy);

                // Da li je stigao i sta je stigao
                if (Gonioci[g].X != najbliza.X || Gonioci[g].Y != najbliza.Y)
                    noviGonioci.Add(Gonioci[g]); // nije stigao najblizu tacku, nastavlja da goni
                else if (Gonioci[g].X == GlavniLik.X && Gonioci[g].Y == GlavniLik.Y)
                {
                    IgraJeUToku = false; // stigao je mene
                    noviGonioci.Add(Gonioci[g]); // nije upao u zamku, ostaje na tabli
                    Poruka = "Uh, koliko ih je...";
                }
            }
            Gonioci = noviGonioci;
            if (Gonioci.Count == 0)
            {
                IgraJeUToku = false; // svi gonioci upali u zamke
                Poruka = "Bravo!";
            }

            Invalidate();
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            if (!IgraJeUToku)
                return;

            PomeriGonioce();
        }

        private void NovaIgra()
        {
            GlavniLik = new Point(Rnd.Next(BrojKolona), Rnd.Next(BrojRedova));

            Zamke.Clear();
            for (int i = 0; i < BrojZamki; i++)
                Zamke.Add(new Point(Rnd.Next(BrojKolona), Rnd.Next(BrojRedova)));

            Gonioci.Clear();
            while (Gonioci.Count < BrojGonilaca)
            {
                int x = Rnd.Next(BrojKolona);
                int y = Rnd.Next(BrojRedova);
                if (Kvadrat(x - GlavniLik.X) + Kvadrat(y - GlavniLik.Y) > Kvadrat(15))
                    Gonioci.Add(new Point(x, y));
            }
            IgraJeUToku = true;
            Poruka = "";
            Invalidate();
        }

        private void Form1_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.F1)
            {
                PrikaziOpasno = !PrikaziOpasno;
                return;
            }
            if (e.KeyCode == Keys.N)
            {
                NovaIgra();
                return;
            }

            if (!IgraJeUToku)
                return;

            int x = GlavniLik.X, y = GlavniLik.Y;
            bool pomeren = false;
            switch (e.KeyCode)
            {
                case Keys.Up: y--; pomeren = true;  break;
                case Keys.Down: y++; pomeren = true; break;
                case Keys.Left: x--; pomeren = true; break;
                case Keys.Right: x++; pomeren = true; break;
            }
            if (pomeren && 0 <= x && x < BrojKolona && 0 <= y && y < BrojRedova)
            {
                GlavniLik = new Point(x, y);
                PomeriGonioce();
            }
        }

        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            Brush cetkaOpasno = new SolidBrush(Color.FromArgb(170, 255, 255));
            Brush cetkaZamka = new SolidBrush(Color.Red);
            Brush cetkaGlavniLik = new SolidBrush(Color.White);
            Brush cetkaGonilac = new SolidBrush(Color.Black);

            if (PrikaziOpasno)
            {
                for (int y = 0; y < BrojRedova; y++)
                    for (int x = 0; x < BrojKolona; x++)
                    {
                        int dMin = Kvadrat(GlavniLik.X - x) + Kvadrat(GlavniLik.Y - y);
                        Point najbliza = GlavniLik;
                        for (int z = 0; z < Zamke.Count; z++)
                        {
                            int d = Kvadrat(Zamke[z].X - x) + Kvadrat(Zamke[z].Y - y);
                            if (dMin > d)
                            {
                                dMin = d;
                                najbliza = Zamke[z];
                            }
                        }
                        if (najbliza == GlavniLik)
                            g.FillRectangle(cetkaOpasno,
                                x * VelicinaPolja, y * VelicinaPolja,
                                VelicinaPolja, VelicinaPolja);
                    }
            }

            foreach (var zam in Zamke)
                g.FillRectangle(cetkaZamka,
                    zam.X * VelicinaPolja, zam.Y * VelicinaPolja,
                    VelicinaPolja, VelicinaPolja);

            g.FillRectangle(cetkaGlavniLik,
                GlavniLik.X * VelicinaPolja, GlavniLik.Y * VelicinaPolja,
                VelicinaPolja, VelicinaPolja);

            foreach (var gon in Gonioci)
                g.FillRectangle(cetkaGonilac,
                    gon.X * VelicinaPolja, gon.Y * VelicinaPolja,
                    VelicinaPolja, VelicinaPolja);

            if (Poruka != "")
            {
                Font f = new Font("Arial", 25);
                StringFormat sf = new StringFormat
                {
                    LineAlignment = StringAlignment.Center,
                    Alignment = StringAlignment.Center
                };
                g.DrawString(Poruka, f, cetkaGlavniLik, ClientRectangle, sf);
            }
        }
    }
}