Лабиринт. Клавиатура

Склонируем наш проект который мы делали ранее. Добавим  в него героя (шарик), которым мы сможем управлять с клавиатуры. Герой может перемещаться только по пустым клеткам. 

Объявим две новые переменные позицию нашего героя 

// коориднаты позиции нашего героя
int heroX;
int heroY;

public void Init()
{
    InitBorder(); // закрашиваем стенами периметр лабиринта
    RandomWallsInit(); // слкучайным образом создаем стены внутри 
    // случайным образом ищем пустую клетку внутри лабиринта, чтобы расположит туда нашего героя
    do
    {
        heroX = Random.Next(1, map.GetLength(0) - 1);
        heroY = Random.Next(1, map.GetLength(1) - 1);
    } while (map[heroX, heroY] != 0); // делаем цикл до тех пор пока не нашли пустую клетку
}
Обратите внимание, что поиск новой позиции для героя удобно делать с помощью цикла do .. while. Поскольку он обязательно выполнит одну итерацию. 

Объявим четыре флага движения нашего героя

bool moveLeft;
bool moveRight;
bool moveUp;
bool moveDown;

Подпишемся на события нажатия и отпуская клавиш в нашем конструкторе и добавим обработчики для этих событий:

    KeyUp += Form1_KeyUp;
    KeyDown += Form1_KeyDown;
} // конец конструктора Form1

// при нажатии кнопки включаем нужный флаг
private void Form1_KeyDown(object? sender, KeyEventArgs e)
{
    switch (e.KeyCode)
    {
        case Keys.Left:
            moveLeft = true;
            break;
        case Keys.Right:
            moveRight = true;
            break;
        case Keys.Up:
            moveUp = true;
            break;
        case Keys.Down:
            moveDown = true;
            break;
    }
}

// при поднятии кнопки отключаем нужный флаг
private void Form1_KeyUp(object? sender, KeyEventArgs e)
{
    switch (e.KeyCode)
    {
        case Keys.Left:
            moveLeft = false;
            break;
        case Keys.Right:
            moveRight = false;
            break;
        case Keys.Up:
            moveUp = false;
            break;
        case Keys.Down:
            moveDown = false;
            break;
    }
}

Введем новую функцию отрисовки героя и вызовем ее в цикле отрисовки, после отрисовки карты

public void DrawHero(Graphics gr)
{
    int heroSize = 24;
    var gap = (CellSize - heroSize) / 2;
    gr.FillEllipse(Brushes.LimeGreen, heroX * CellSize + gap, heroY * CellSize + gap, heroSize, heroSize);
    gr.DrawEllipse(Pens.Green, heroX * CellSize + gap, heroY * CellSize + gap, heroSize, heroSize);
}


private void Form1_Paint(object? sender, PaintEventArgs e)
{
    var gr = e.Graphics;
    gr.Clear(Color.Black);
    gr.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;            

    UpdateScene();
    //draw scene
    DrawMap(gr);
    DrawHero(gr);            
}

Теперь нам нужно менять положение героя в зависимости от нажатых кнопок. Будем проверять изолированно каждое направление, чтобы можно было комибинровать одновремиенно нескольно кнопок и двигаться подиагонали в лабиринте. Для каждого напраления будем проверять свободна ли клетка куда мы хотим переместить или нет. Если этого не делать, то герой сможет проходить сквозь стены по диагонали.

Если обновлять позицию героя на каждой итерации отрисовки сцены, то он будет двигаться слишком быстро (скажем 30 клеток в секунду). Чтобы ограничить скорость героя и контроллировать его введем новый параметр (переменную) moveDelay, которая будет ограничивать минимальное время между смещениями героя по карте (не чаще чем раз в N миллисекунд)

Напишем функцию обновления сцены:

DateTime lastMove = DateTime.Now; // время последнего перемещения героя

public void UpdateScene()
{
    int moveDelay = 100; // задержка движения в миллисекундах
// проверяем последнее время движения героя, если прошло меньше moveDelay миллисекунд, то выходим без каких-либо изменений на сцене
    if (DateTime.Now.Subtract(lastMove).TotalMilliseconds <= moveDelay) 
        return;

    lastMove = DateTime.Now;
    int newHeroX = heroX;
    int newHeroY = heroY;

    if (moveLeft && map[newHeroX - 1, newHeroY] == 0)
        newHeroX--;

    if (moveRight && map[newHeroX + 1, newHeroY] == 0)
        newHeroX++;

    if (moveUp && map[newHeroX, newHeroY - 1] == 0)
        newHeroY--;

    if (moveDown && map[newHeroX, newHeroY + 1] == 0)
        newHeroY++;

// проверяем свободно ли место на карте в новой вычисленной координате для перемещения героя туда
    if (map[newHeroX, newHeroY] == 0) 
    { // если свободно, то перемещаем героя туда
        heroX = newHeroX;
        heroY = newHeroY;
    }
}

Запустите и убедитесь что нажатие кнопок на клавиатуре (стрелок) приводит к движению зеленого шарика: