/
d4nil
/
Konenkov_2413_Course_Work
Обзор
Документация
Войти
/
d4nil
/
Konenkov_2413_Course_Work
Код
Запросы
1
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
Game
TapTapDash.Core/Models/GameModel.cs
1 050 строк
41 KB
Konenkov Daniel
feat: исправлены тесты на проверку поражения в игре
11 янв 2026, 12:49
11 янв 2026, 12:49
272e437
Код
Авторство
О чём код?
using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; namespace TapTapDash.Core.Models; /// <summary> /// Модель игры /// Содержит всю игровую логику: игровое поле, трассу, персонажа, счет и генерацию трассы /// </summary> public class GameModel { private const int FieldWidth = 20; // Ширина игрового поля private const int FieldHeight = 15; // Высота игрового поля private const double StraightProbability = 0.6; // 60% вероятность прямого участка private const double TurnProbability = 0.2; // 20% вероятность поворота private const double GapProbability = 0.2; // 20% вероятность пропасти private const int MinDistanceFromEdge = 1; // Минимальное расстояние от края private readonly Random _random; /// <summary> /// Игровое поле (сетка клеток) /// </summary> public Cell[,] Field { get; private set; } /// <summary> /// Ширина игрового поля /// </summary> public int Width => FieldWidth; /// <summary> /// Высота игрового поля /// </summary> public int Height => FieldHeight; /// <summary> /// Трасса (последовательность клеток) /// </summary> public List<Cell> Track { get; private set; } /// <summary> /// Персонаж /// </summary> public Character Character { get; private set; } = null!; // Инициализируется в InitializeGame() /// <summary> /// Счет игры /// </summary> public int Score { get; private set; } /// <summary> /// Статус игры /// </summary> public GameStatus Status { get; private set; } /// <summary> /// Накопленное время для движения (для плавного движения) /// </summary> private double _accumulatedTime; /// <summary> /// Направление генерации трассы (используется при генерации) /// </summary> private Direction _generationDirection; /// <summary> /// Предыдущая Y-координата персонажа /// </summary> private int _previousCharacterY; /// <summary> /// Предыдущая клетка персонажа /// </summary> private Cell? _previousCharacterCell; /// <summary> /// Последняя клетка, на которой был нажат пробел (для защиты от многократного нажатия) /// </summary> private (int X, int Y)? _lastSpacePressCell; /// <summary> /// Объект блокировки для синхронизации доступа к полю между потоками /// </summary> private readonly object _fieldLock = new object(); /// <summary> /// Объект блокировки для синхронизации доступа к персонажу между потоками /// </summary> private readonly object _characterLock = new object(); /// <summary> /// Конструктор модели игры /// </summary> public GameModel() { _random = new Random(); Field = new Cell[FieldWidth, FieldHeight]; Track = new List<Cell>(); Status = GameStatus.Playing; Score = 0; _accumulatedTime = 0; _generationDirection = Direction.Down; InitializeGame(); } /// <summary> /// Инициализация игры /// </summary> private void InitializeGame() { // Инициализируем пустое поле (все клетки - пропасти) for (int x = 0; x < FieldWidth; x++) { for (int y = 0; y < FieldHeight; y++) { Field[x, y] = new Cell(x, y, CellType.Gap); } } // Создаем начальную трассу (прямой участок вниз) // Персонаж должен начинать сверху и двигаться вниз int startX = FieldWidth / 2; // Начинаем в центре int startY = 2; // Начинаем сверху (не в самой верхней строке, чтобы было место для генерации) // Создаем начальный прямой участок трассы (3-4 клетки вниз от начальной позиции) for (int y = startY; y < startY + 4 && y < FieldHeight; y++) { var cell = new Cell(startX, y, CellType.Straight); Track.Add(cell); Field[startX, y] = cell; } // Создаем персонажа в начальной позиции (сверху) Character = new Character(startX, startY, Direction.Down); _previousCharacterY = startY; // Генерируем начальные строки трассы GenerateInitialTrack(); } /// <summary> /// Генерация начальной трассы (заполнение поля) /// </summary> private void GenerateInitialTrack() { // Генерируем трассу построчно, используя тот же метод, что и во время игры // Генерируем по одной клетке в каждой строке int startY = Track[Track.Count - 1].Y; // Генерируем трассу построчно до нижней части поля // Используем GenerateSingleCellAtY для каждой строки for (int y = startY + 1; y < FieldHeight; y++) { // Используем тот же метод генерации, что и во время игры GenerateRaw(); } } /// <summary> /// Генерация строки поля с трассой в указанной строке Y /// Используется для начальной генерации построчно и во время игры /// </summary> private void GenerateRaw() { do { Cell lastCell = Track[Track.Count - 1]; Cell newCell = DetermineNextCell(lastCell); // Блокируем доступ к полю во время генерации lock (_fieldLock) { Field[newCell.X, newCell.Y] = newCell; } Track.Add(newCell); } while (_generationDirection != Direction.Down); } /// <summary> /// Обновление игры (вызывается в игровом цикле) /// </summary> /// <param name="deltaTime">Время, прошедшее с последнего обновления (в секундах)</param> public void Update(double deltaTime) { if (Status != GameStatus.Playing) { return; // Игра не активна } _accumulatedTime += deltaTime; // Персонаж движется с постоянной скоростью // Перемещаем персонажа на одну клетку каждые 1/Speed секунд double timePerCell = 1.0 / Character.Speed; while (_accumulatedTime >= timePerCell) { _accumulatedTime -= timePerCell; PerformGameStep(); } } /// <summary> /// Выполнение одного шага игры /// </summary> private void PerformGameStep() { // 1. Определить маневр (уже определен при нажатии пробела через PerformManeuver) // 2. Определить новое направление движения персонажа (уже определено в Character.Direction) // 3. Изменить направление движения персонажа и обновить его координаты int nextX = Character.X; int nextY = Character.Y; switch (Character.Direction) { case Direction.Up: nextY--; break; case Direction.Down: nextY++; break; case Direction.Left: nextX--; break; case Direction.Right: nextX++; break; } lock (_fieldLock) { _previousCharacterCell = Field[Character.X, Character.Y]; } // Обновляем позицию персонажа (с блокировкой) lock (_characterLock) { Character.X = nextX; Character.Y = nextY; } // Сбрасываем защиту от многократного нажатия, если персонаж перешел на новую клетку if (_lastSpacePressCell.HasValue && (_lastSpacePressCell.Value.X != nextX || _lastSpacePressCell.Value.Y != nextY)) { _lastSpacePressCell = null; } // 4. Если направление движения вниз и персонаж спустился на одну клетку вниз -> Генерируем новую строчку и сдвигаем поле bool shouldShift; lock (_characterLock) { shouldShift = Character.Direction == Direction.Down && Character.Y > _previousCharacterY; } if (shouldShift) { // Сразу сдвигаем все строчки вверх (включая персонажа) // Это создает эффект прокрутки трассы, а персонаж визуально остается на месте ShiftFieldUp(); // Персонаж спустился вниз на одну клетку - генерируем одну новую строчку трассы GenerateRaw(); // Уменьшаем Y-координату персонажа, чтобы он визуально оставался на месте lock (_characterLock) { Character.Y--; // Обновляем предыдущую Y-координату _previousCharacterY = Character.Y; } } // 5. Проверить поражение if (CheckDefeat()) { Status = GameStatus.GameOver; return; } // 6. Если препятствие пройдено -> Счет += 1 if (CheckObstaclePassed()) { Score++; } Cell currentCell; lock (_fieldLock) { lock (_characterLock) { currentCell = Field[Character.X, Character.Y]; if (currentCell.Type == CellType.Gap && Character.IsJumping) { Character.IsJumping = false; } } } } /// <summary> /// Проверка поражения /// </summary> private bool CheckDefeat() { // Проверяем границы поля (выход за пределы массива) if (Character.X < 0 || Character.X >= FieldWidth || Character.Y < 0 || Character.Y >= FieldHeight) { return true; // Персонаж вышел за границы поля } var currentCell = Field[Character.X, Character.Y]; // Проверяем, находится ли персонаж на трассе // Если персонаж в процессе прыжка, он может проходить через пропасти без поражения if (currentCell.Type == CellType.Gap && !Character.IsJumping) { // Персонаж попал в пропасть и не в процессе прыжка - поражение return true; } return false; } /// <summary> /// Проверка, пройдено ли препятствие /// </summary> private bool CheckObstaclePassed() { var currentCell = Field[Character.X, Character.Y]; if (currentCell.Type == CellType.Gap && Character.IsJumping) { // Персонаж сделал прыжок над пропастью return true; } if (_previousCharacterCell.Type == CellType.Turn && currentCell.Type != CellType.Gap) { return true; } return false; } /// <summary> /// Сдвиг поля вверх (удаление первой строки, сдвиг остальных) /// </summary> private void ShiftFieldUp() { // Блокируем доступ к полю во время сдвига lock (_fieldLock) { // Удаляем первую строку из поля int topY = 0; for (int x = 0; x < FieldWidth; x++) { Field[x, topY] = new Cell(x, topY, CellType.Gap); } // Сдвигаем все клетки вверх // ВАЖНО: сначала очищаем все позиции, чтобы не было дубликатов for (int y = 0; y < FieldHeight - 1; y++) { for (int x = 0; x < FieldWidth; x++) { // Копируем клетку из строки ниже var cell = Field[x, y + 1]; // Обновляем Y-координату клетки cell.Y = y; // Копируем клетку в новую позицию (сохраняем все свойства, включая маркеры) Field[x, y] = cell; } } // Инициализируем последнюю строку пропастями только там, где нет клеток трассы int bottomY = FieldHeight - 1; for (int x = 0; x < FieldWidth; x++) { Field[x, bottomY] = new Cell(x, bottomY, CellType.Gap); } } // НЕ уменьшаем ординату персонажа - он уже переместился вниз в PerformGameStep // Если уменьшить его Y здесь, он визуально останется на месте } /// <summary> /// Определение следующей клекти при генерации пути /// </summary> private Cell DetermineNextCell(Cell lastCell) { if (lastCell.X == MinDistanceFromEdge) { if (_generationDirection == Direction.Down) { if (lastCell.Type == CellType.Straight) { CellType newCellType = generateStraightOrGapOrTurn(); if (newCellType == CellType.Turn) { Marker marker = new Marker(Direction.Right, MarkerType.Turn); _generationDirection = Direction.Right; return new Cell(lastCell.X, lastCell.Y + 1, CellType.Turn, marker); } else if (newCellType == CellType.Gap) { Marker marker = new Marker(Direction.Down, MarkerType.Jump); lastCell.Marker = marker; return new Cell(lastCell.X, lastCell.Y + 1, CellType.Gap); } else { return new Cell(lastCell.X, lastCell.Y + 1, CellType.Straight); } } else if (lastCell.Type == CellType.Gap) { CellType newCellType = generateStraightOrTurn(); if (newCellType == CellType.Turn) { Marker marker = new Marker(Direction.Right, MarkerType.Turn); _generationDirection = Direction.Right; return new Cell(lastCell.X, lastCell.Y + 1, CellType.Turn, marker); } else { return new Cell(lastCell.X, lastCell.Y + 1, CellType.Straight); } } } else if (_generationDirection == Direction.Right) { CellType newCellType = generateStraightOrTurn(); if (newCellType == CellType.Turn) { Marker marker = new Marker(Direction.Down, MarkerType.Turn); _generationDirection = Direction.Down; return new Cell(lastCell.X + 1, lastCell.Y, CellType.Turn, marker); } else { return new Cell(lastCell.X + 1, lastCell.Y, CellType.Straight); } } } else if (lastCell.X == FieldWidth - MinDistanceFromEdge - 1) { if (_generationDirection == Direction.Down) { if (lastCell.Type == CellType.Straight) { CellType newCellType = generateStraightOrGapOrTurn(); if (newCellType == CellType.Turn) { Marker marker = new Marker(Direction.Left, MarkerType.Turn); _generationDirection = Direction.Left; return new Cell(lastCell.X, lastCell.Y + 1, CellType.Turn, marker); } else if (newCellType == CellType.Gap) { Marker marker = new Marker(_generationDirection, MarkerType.Jump); lastCell.Marker = marker; return new Cell(lastCell.X, lastCell.Y + 1, CellType.Gap); } else { return new Cell(lastCell.X, lastCell.Y + 1, CellType.Straight); } } else if (lastCell.Type == CellType.Gap) { CellType newCellType = generateStraightOrTurn(); if (newCellType == CellType.Turn) { Marker marker = new Marker(Direction.Left, MarkerType.Turn); _generationDirection = Direction.Left; return new Cell(lastCell.X, lastCell.Y + 1, CellType.Turn, marker); } else { return new Cell(lastCell.X, lastCell.Y + 1, CellType.Straight); } } } else if (_generationDirection == Direction.Left) { CellType newCellType = generateStraightOrTurn(); if (newCellType == CellType.Turn) { Marker marker = new Marker(Direction.Down, MarkerType.Turn); _generationDirection = Direction.Down; return new Cell(lastCell.X - 1, lastCell.Y, CellType.Turn, marker); } else { return new Cell(lastCell.X - 1, lastCell.Y, CellType.Straight); } } } else if (lastCell.X == MinDistanceFromEdge + 1) { if (_generationDirection == Direction.Down) { if (lastCell.Type == CellType.Straight) { CellType newCellType = generateStraightOrGapOrTurn(); if (newCellType == CellType.Turn) { Direction newDirection = generateLeftOrRightDirection(); Marker marker = new Marker(newDirection, MarkerType.Turn); _generationDirection = newDirection; return new Cell(lastCell.X, lastCell.Y + 1, CellType.Turn, marker); } else if (newCellType == CellType.Gap) { Marker marker = new Marker(Direction.Down, MarkerType.Jump); lastCell.Marker = marker; return new Cell(lastCell.X, lastCell.Y + 1, CellType.Gap); } else { return new Cell(lastCell.X, lastCell.Y + 1, CellType.Straight); } } else if (lastCell.Type == CellType.Gap) { CellType newCellType = generateStraightOrTurn(); if (newCellType == CellType.Turn) { Direction newDirection = generateLeftOrRightDirection(); Marker marker = new Marker(newDirection, MarkerType.Turn); _generationDirection = newDirection; return new Cell(lastCell.X, lastCell.Y + 1, CellType.Turn, marker); } else { return new Cell(lastCell.X, lastCell.Y + 1, CellType.Straight); } } } else if (_generationDirection == Direction.Left) { Marker marker = new Marker(Direction.Down, MarkerType.Turn); _generationDirection = Direction.Down; return new Cell(lastCell.X - 1, lastCell.Y, CellType.Turn, marker); } else if (_generationDirection == Direction.Right) { CellType newCellType = generateStraightOrTurn(); if (newCellType == CellType.Turn) { Marker marker = new Marker(Direction.Down, MarkerType.Turn); _generationDirection = Direction.Down; return new Cell(lastCell.X + 1, lastCell.Y, CellType.Turn, marker); } else { return new Cell(lastCell.X + 1, lastCell.Y, CellType.Straight); } } } else if (lastCell.X == FieldWidth - MinDistanceFromEdge - 2) { if (_generationDirection == Direction.Down) { if (lastCell.Type == CellType.Straight) { CellType newCellType = generateStraightOrGapOrTurn(); if (newCellType == CellType.Turn) { Direction newDirection = generateLeftOrRightDirection(); Marker marker = new Marker(newDirection, MarkerType.Turn); _generationDirection = newDirection; return new Cell(lastCell.X, lastCell.Y + 1, CellType.Turn, marker); } else if (newCellType == CellType.Gap) { Marker marker = new Marker(_generationDirection, MarkerType.Jump); lastCell.Marker = marker; return new Cell(lastCell.X, lastCell.Y + 1, CellType.Gap); } else { return new Cell(lastCell.X, lastCell.Y + 1, CellType.Straight); } } else if (lastCell.Type == CellType.Gap) { CellType newCellType = generateStraightOrTurn(); if (newCellType == CellType.Turn) { Direction newDirection = generateLeftOrRightDirection(); Marker marker = new Marker(newDirection, MarkerType.Turn); _generationDirection = newDirection; return new Cell(lastCell.X, lastCell.Y + 1, CellType.Turn, marker); } else { return new Cell(lastCell.X, lastCell.Y + 1, CellType.Straight); } } } else if (_generationDirection == Direction.Right) { Marker marker = new Marker(Direction.Down, MarkerType.Turn); _generationDirection = Direction.Down; return new Cell(lastCell.X + 1, lastCell.Y, CellType.Turn, marker); } else if (_generationDirection == Direction.Left) { CellType newCellType = generateStraightOrTurn(); if (newCellType == CellType.Turn) { Marker marker = new Marker(Direction.Down, MarkerType.Turn); _generationDirection = Direction.Down; return new Cell(lastCell.X - 1, lastCell.Y, CellType.Turn, marker); } else { return new Cell(lastCell.X - 1, lastCell.Y, CellType.Straight); } } } else { if (_generationDirection == Direction.Down) { if (lastCell.Type == CellType.Straight) { CellType newCellType = generateStraightOrGapOrTurn(); if (newCellType == CellType.Turn) { Direction newDirection = generateLeftOrRightDirection(); Marker marker = new Marker(newDirection, MarkerType.Turn); _generationDirection = newDirection; return new Cell(lastCell.X, lastCell.Y + 1, CellType.Turn, marker); } else if (newCellType == CellType.Gap) { Marker marker = new Marker(_generationDirection, MarkerType.Jump); lastCell.Marker = marker; return new Cell(lastCell.X, lastCell.Y + 1, CellType.Gap); } else { return new Cell(lastCell.X, lastCell.Y + 1, CellType.Straight); } } else if (lastCell.Type == CellType.Gap) { CellType newCellType = generateStraightOrTurn(); if (newCellType == CellType.Turn) { Direction newDirection = generateLeftOrRightDirection(); Marker marker = new Marker(newDirection, MarkerType.Turn); _generationDirection = newDirection; return new Cell(lastCell.X, lastCell.Y + 1, CellType.Turn, marker); } else { return new Cell(lastCell.X, lastCell.Y + 1, CellType.Straight); } } } else if (_generationDirection == Direction.Left) { if (lastCell.Type == CellType.Straight) { CellType newCellType = generateStraightOrGapOrTurn(); if (newCellType == CellType.Turn) { Marker marker = new Marker(Direction.Down, MarkerType.Turn); _generationDirection = Direction.Down; return new Cell(lastCell.X - 1, lastCell.Y, CellType.Turn, marker); } else if (newCellType == CellType.Gap) { Marker marker = new Marker(_generationDirection, MarkerType.Jump); lastCell.Marker = marker; return new Cell(lastCell.X - 1, lastCell.Y, CellType.Gap); } else { return new Cell(lastCell.X - 1, lastCell.Y, CellType.Straight); } } else { CellType newCellType = generateStraightOrTurn(); if (newCellType == CellType.Turn) { Marker marker = new Marker(Direction.Down, MarkerType.Turn); _generationDirection = Direction.Down; return new Cell(lastCell.X - 1, lastCell.Y, CellType.Turn, marker); } else { return new Cell(lastCell.X - 1, lastCell.Y, CellType.Straight); } } } else if (_generationDirection == Direction.Right) { if (lastCell.Type == CellType.Straight) { CellType newCellType = generateStraightOrGapOrTurn(); if (newCellType == CellType.Turn) { Marker marker = new Marker(Direction.Down, MarkerType.Turn); _generationDirection = Direction.Down; return new Cell(lastCell.X + 1, lastCell.Y, CellType.Turn, marker); } else if (newCellType == CellType.Gap) { Marker marker = new Marker(_generationDirection, MarkerType.Jump); lastCell.Marker = marker; return new Cell(lastCell.X + 1, lastCell.Y, CellType.Gap); } else { return new Cell(lastCell.X + 1, lastCell.Y, CellType.Straight); } } else { CellType newCellType = generateStraightOrTurn(); if (newCellType == CellType.Turn) { Marker marker = new Marker(Direction.Down, MarkerType.Turn); _generationDirection = Direction.Down; return new Cell(lastCell.X + 1, lastCell.Y, CellType.Turn, marker); } else { return new Cell(lastCell.X + 1, lastCell.Y, CellType.Straight); } } } } return new Cell(lastCell.X, lastCell.Y + 1, CellType.Straight); } private CellType generateStraightOrGapOrTurn() { // Генерация прямого участка, либо поворота, либо пропасти double rand = _random.NextDouble(); if (rand < StraightProbability) { return CellType.Straight; } else if (rand < StraightProbability + TurnProbability) { return CellType.Turn; } else { return CellType.Gap; } } private CellType generateStraightOrTurn() { return _random.NextDouble() < 0.5 ? CellType.Straight : CellType.Turn; } private Direction generateLeftOrRightDirection() { return _random.NextDouble() < 0.5 ? Direction.Left : Direction.Right; } /// <summary> /// Выполнение маневра (поворот или прыжок) /// </summary> public void PerformManeuver() { if (Status != GameStatus.Playing) { return; // Игра не активна } // Получаем текущую позицию персонажа с блокировкой int currentX, currentY; lock (_characterLock) { currentX = Character.X; currentY = Character.Y; } // Защита от многократного нажатия пробела на одной клетке if (_lastSpacePressCell.HasValue && _lastSpacePressCell.Value.X == currentX && _lastSpacePressCell.Value.Y == currentY) { // Пробел уже был нажат на этой клетке - игнорируем return; } // Получаем текущую клетку персонажа Cell currentCell; lock (_fieldLock) { currentCell = Field[currentX, currentY]; } // Проверяем, является ли клетка исключением (не нужно сходить с пути) bool isExceptionCell = IsExceptionCell(currentCell, currentX, currentY); // Если клетка с маркером - обрабатываем маркер if (currentCell.Marker != null && !currentCell.Marker.IsUsed) { // Маркер на текущей клетке if (currentCell.Marker.Type == MarkerType.Turn) { // Выполняем поворот lock (_characterLock) { Character.Direction = currentCell.Marker.Direction; } // Отмечаем маркер как использованный currentCell.Marker.IsUsed = true; // Запоминаем клетку, на которой был нажат пробел _lastSpacePressCell = (currentX, currentY); } else if (currentCell.Marker.Type == MarkerType.Jump) { // Выполняем прыжок (перемещаем персонажа через пропасть) PerformJump(); // Отмечаем маркер как использованный currentCell.Marker.IsUsed = true; // Запоминаем клетку, на которой был нажат пробел _lastSpacePressCell = (currentX, currentY); } } // Если маркера нет и клетка не является исключением - сходим с пути else if (!isExceptionCell) { // Сходим с пути на обычной клетке PerformPathExit(); // Запоминаем клетку, на которой был нажат пробел _lastSpacePressCell = (currentX, currentY); } } /// <summary> /// Проверка, является ли клетка исключением (не нужно сходить с пути) /// </summary> private bool IsExceptionCell(Cell cell, int x, int y) { // Клетка поворота - исключение if (cell.Type == CellType.Turn) { return true; } // Клетка пропасти - исключение if (cell.Type == CellType.Gap) { return true; } // Прямая клетка перед пропастью - исключение if (IsStraightCellBeforeGap(x, y)) { return true; } return false; } /// <summary> /// Проверка, является ли клетка прямой клеткой перед пропастью /// </summary> private bool IsStraightCellBeforeGap(int x, int y) { Direction direction; lock (_characterLock) { direction = Character.Direction; } // Определяем координаты следующей клетки в направлении движения int nextX = x; int nextY = y; switch (direction) { case Direction.Up: nextY--; break; case Direction.Down: nextY++; break; case Direction.Left: nextX--; break; case Direction.Right: nextX++; break; } // Проверяем границы if (nextX < 0 || nextX >= FieldWidth || nextY < 0 || nextY >= FieldHeight) { return false; } // Проверяем, является ли следующая клетка пропастью Cell nextCell; lock (_fieldLock) { nextCell = Field[nextX, nextY]; } return nextCell.Type == CellType.Gap; } /// <summary> /// Выполнение схода с пути на обычной клетке /// </summary> private void PerformPathExit() { Direction currentDirection; lock (_characterLock) { currentDirection = Character.Direction; } Direction newDirection; // Если движется вниз → поворачивает направо if (currentDirection == Direction.Down) { newDirection = Direction.Right; } // Если движется вправо или влево → поворачивает вниз else if (currentDirection == Direction.Left || currentDirection == Direction.Right) { newDirection = Direction.Down; } else { // Для движения вверх (не должно происходить в нормальной игре, но на всякий случай) newDirection = Direction.Down; } // Устанавливаем новое направление lock (_characterLock) { Character.Direction = newDirection; } } /// <summary> /// Выполнение прыжка через пропасть /// </summary> private void PerformJump() { // Прыжок начинается - персонаж будет проходить через пропасть пошагово // Устанавливаем флаг прыжка, чтобы персонаж мог проходить через пропасти без поражения lock (_characterLock) { Character.IsJumping = true; } } /// <summary> /// Постановка игры на паузу /// </summary> public void Pause() { if (Status == GameStatus.Playing) { Status = GameStatus.Paused; } } /// <summary> /// Снятие игры с паузы /// </summary> public void Resume() { if (Status == GameStatus.Paused) { Status = GameStatus.Playing; } } /// <summary> /// Получение копии поля для безопасного чтения из другого потока /// </summary> public Cell[,] GetFieldSnapshot() { lock (_fieldLock) { Cell[,] snapshot = new Cell[FieldWidth, FieldHeight]; for (int y = 0; y < FieldHeight; y++) { for (int x = 0; x < FieldWidth; x++) { // Создаем глубокую копию Cell, чтобы изменения в оригинале не влияли на снимок var originalCell = Field[x, y]; Cell copiedCell; if (originalCell.Marker != null) { // Копируем маркер тоже var copiedMarker = new Marker(originalCell.Marker.Direction, originalCell.Marker.Type) { IsUsed = originalCell.Marker.IsUsed }; copiedCell = new Cell(originalCell.X, originalCell.Y, originalCell.Type, copiedMarker); } else { copiedCell = new Cell(originalCell.X, originalCell.Y, originalCell.Type); } snapshot[x, y] = copiedCell; } } return snapshot; } } /// <summary> /// Получение копии персонажа для безопасного чтения из другого потока /// </summary> public Character GetCharacterSnapshot() { lock (_characterLock) { // Создаем копию персонажа return new Character(Character.X, Character.Y, Character.Direction) { IsJumping = Character.IsJumping }; } } }