Когда-то Digger был одной из самых продвинутых и и...
創建於:2025年12月19日
使用 GPT-5.2 Thinking 回答 Chat01
創建於:2025年12月19日
使用 GPT-5.2 Thinking 回答 Chat01
Когда-то Digger был одной из самых продвинутых и интересных компьютерных игр. В этом блоке задач мы воссоздадим некоторое её подмножество с помощью ООП.
Скачайте проект
Вам предстоит наполнить готовую заготовку игровыми элементами. Каждый элемент должен уметь:
Возвращать имя файла, в котором лежит соответствующая ему картинка (например, "Terrain.png")
Сообщать приоритет отрисовки. Чем выше приоритет, тем раньше рисуется соответствующий элемент, это важно для анимации.
Действовать — возвращать направление перемещения и, если объект во что-то превращается на следующем ходу, то результат превращения.
Разрешать столкновения двух элементов в одной клетке.
Terrain
Сделайте класс Terrain, реализовав ICreature. Сделайте так, чтобы он ничего не делал.
Player
Сделайте класс Player, реализовав ICreature.
Сделайте так, чтобы диггер шагал в разные стороны в зависимости от нажатой клавиши со стрелкой (Game.KeyPressed). Убедитесь, что диггер не покидает пределы игрового поля.
Сделайте так, чтобы земля исчезала в тех местах, где прошел диггер.
Запустите проект — игра должна заработать!
В методе Game.CreateMap вы можете менять карту, на которой будет запускаться игра. Используйте эту возможность для отладки.
Ниже — типовая реализация для заготовки из задачника про Digger (C# / Windows Forms), где есть интерфейс ICreature, структура/класс CreatureCommand и статический Game.KeyPressed.
csharpusing System; namespace Digger { public class Terrain : ICreature { public string GetImageFileName() => "Terrain.png"; // Чем выше — тем раньше рисуют (по условию), значит Terrain можно рисовать "сзади" public int GetDrawingPriority() => 10; public CreatureCommand Act(int x, int y) => new CreatureCommand { DeltaX = 0, DeltaY = 0, TransformTo = null }; // Земля должна исчезать там, где прошёл игрок: public bool DeadInConflict(ICreature conflictedObject) => conflictedObject is Player; } }
csharpusing System.Windows.Forms; namespace Digger { public class Player : ICreature { public string GetImageFileName() => "Digger.png"; // Игрок должен быть "поверх" земли → рисуем позже → приоритет ниже public int GetDrawingPriority() => 0; public CreatureCommand Act(int x, int y) { int dx = 0, dy = 0; switch (Game.KeyPressed) { case Keys.Left: dx = -1; break; case Keys.Right: dx = 1; break; case Keys.Up: dy = -1; break; case Keys.Down: dy = 1; break; } var newX = x + dx; var newY = y + dy; // Не выходим за границы карты if (newX < 0 || newX >= Game.MapWidth || newY < 0 || newY >= Game.MapHeight) return new CreatureCommand { DeltaX = 0, DeltaY = 0, TransformTo = null }; return new CreatureCommand { DeltaX = dx, DeltaY = dy, TransformTo = null }; } public bool DeadInConflict(ICreature conflictedObject) => false; } }
В разных версиях заготовки бывают отличия:
"Digger.png", а, например, "Player.png" — посмотрите имена файлов в папке с картинками;Game.MapWidth/Game.MapHeight иногда используют Game.Map.GetLength(0) и Game.Map.GetLength(1);CreatureCommand иногда конструируется не через object initializer, а через конструктор.Если пришлёте сюда 2–3 файла из заготовки (ICreature, CreatureCommand, Game), я подгоню код ровно под вашу конкретную структуру.
namespace Digger.Architecture;
public interface ICreature
{
string GetImageFileName();
int GetDrawingPriority();
CreatureCommand Act(int x, int y);
bool DeadInConflict(ICreature conflictedObject);
} namespace Digger.Architecture;
public class CreatureCommand
{
public int DeltaX;
public int DeltaY;
public ICreature TransformTo;
}
using Avalonia.Input;
using Digger.Architecture;
namespace Digger;
public static class Game
{
private const string mapWithPlayerTerrain = @"
TTT T
TTP T
T T T
TT TT";
private const string mapWithPlayerTerrainSackGold = @"
PTTGTT TS
TST TSTT
TTTTTTSTT
T TSTS TT
T TTTG ST
TSTSTT TT";
private const string mapWithPlayerTerrainSackGoldMonster = @"
PTTGTT TST
TST TSTTM
TTT TTSTTT
T TSTS TTT
T TTTGMSTS
T TMT M TS
TSTSTTMTTT
S TTST TG
TGST MTTT
T TMTTTT";
textpublic static ICreature[,] Map; public static int Scores; public static bool IsOver; public static Key KeyPressed; public static int MapWidth => Map.GetLength(0); public static int MapHeight => Map.GetLength(1); public static void CreateMap() { Map = CreatureMapCreator.CreateMap(mapWithPlayerTerrainSackGoldMonster); }
}
Отлично, у вас Avalonia, поэтому вместо Keys используются Avalonia.Input.Key. Ниже — готовые классы Terrain и Player под ваш интерфейс/Game.
csharpusing Digger.Architecture; namespace Digger; public class Terrain : ICreature { public string GetImageFileName() => "Terrain.png"; // Чем больше — тем раньше рисуется (значит "ниже" по слою). public int GetDrawingPriority() => 10; public CreatureCommand Act(int x, int y) => new CreatureCommand { DeltaX = 0, DeltaY = 0, TransformTo = null }; // Земля исчезает, когда в клетку приходит игрок public bool DeadInConflict(ICreature conflictedObject) => conflictedObject is Player; }
csharpusing Avalonia.Input; using Digger.Architecture; namespace Digger; public class Player : ICreature { public string GetImageFileName() => "Digger.png"; // если у вас файл называется иначе — поменяйте // Игрок должен быть "поверх" земли => рисуем позже => приоритет меньше public int GetDrawingPriority() => 0; public CreatureCommand Act(int x, int y) { var dx = 0; var dy = 0; switch (Game.KeyPressed) { case Key.Left: dx = -1; break; case Key.Right: dx = 1; break; case Key.Up: dy = -1; break; case Key.Down: dy = 1; break; } var nx = x + dx; var ny = y + dy; // Не выходим за границы карты if (nx < 0 || nx >= Game.MapWidth || ny < 0 || ny >= Game.MapHeight) { dx = 0; dy = 0; } return new CreatureCommand { DeltaX = dx, DeltaY = dy, TransformTo = null }; } public bool DeadInConflict(ICreature conflictedObject) => false; }
Если после этого “не видно” игрока — почти всегда причина в имени файла картинки: проверьте, как реально называется ресурс (часто это Player.png). В GetImageFileName() просто поставьте точное имя.
Пора добавить мешки с золотом и само золото!
Sack
Сделайте класс Sack, реализовав ICreature. Это будет мешок с золотом.
Мешок может лежать на любой другой сущности (диггер, земля, мешок, золото, край карты).
Если под мешком находится пустое место, он начинает падать.
Если мешок падает на диггера, диггер умирает, а мешок продолжает падать, пока не приземлится на землю, другой мешок, золото или край карты.
Диггер не может подобрать мешок, толкнуть его или пройти по нему.
Если мешок падает, а диггер находится непосредственно под ним и идет вверх, они могут "разминуться", и диггер окажется над мешком. Это поведение непросто исправить в существующей упрощенной архитектуре, поэтому считайте его нормальным.
Gold
Сделайте класс Gold, реализовав ICreature.
Мешок превращается в золото, если он падал дольше одной клетки игрового поля и приземлился на землю, на другой мешок, на золото или на край карты.
Мешок не превращается в золото, а остаётся мешком, если он падал ровно одну клетку.
Золото никогда не падает.
Когда диггер собирает золото, ему начисляется 10 очков (через Game.Scores).
Ниже код под вашу архитектуру (Avalonia + Game.Map).
csharpusing Digger.Architecture; namespace Digger; public class Sack : ICreature { private int fallDistance; // сколько клеток подряд падали public bool IsFalling => fallDistance > 0; public string GetImageFileName() => "Sack.png"; public int GetDrawingPriority() => 5; public CreatureCommand Act(int x, int y) { // Если снизу край карты — это "приземление" if (y + 1 >= Game.MapHeight) return Land(); var below = Game.Map[x, y + 1]; // Начинает падать только если снизу пусто if (!IsFalling && below is null) return Fall(); // Если уже падает — продолжает падать через пустоту и через игрока (раздавит) if (IsFalling && (below is null || below is Player)) return Fall(); // Иначе снизу опора: если падал > 1 клетки, превращаемся в золото if (IsFalling) return Land(); // Лежит спокойно return new CreatureCommand { DeltaX = 0, DeltaY = 0, TransformTo = null }; } private CreatureCommand Fall() { fallDistance++; return new CreatureCommand { DeltaX = 0, DeltaY = 1, TransformTo = null }; } private CreatureCommand Land() { var toGold = fallDistance > 1; fallDistance = 0; return new CreatureCommand { DeltaX = 0, DeltaY = 0, TransformTo = toGold ? new Gold() : null }; } public bool DeadInConflict(ICreature conflictedObject) => false; }
csharpusing Digger.Architecture; namespace Digger; public class Gold : ICreature { public string GetImageFileName() => "Gold.png"; public int GetDrawingPriority() => 5; public CreatureCommand Act(int x, int y) => new CreatureCommand { DeltaX = 0, DeltaY = 0, TransformTo = null }; public bool DeadInConflict(ICreature conflictedObject) { if (conflictedObject is Player) { Game.Scores += 10; return true; // золото исчезает } return false; } }
Act)DeadInConflict)csharppublic CreatureCommand Act(int x, int y) { var dx = 0; var dy = 0; switch (Game.KeyPressed) { case Key.Left: dx = -1; break; case Key.Right: dx = 1; break; case Key.Up: dy = -1; break; case Key.Down: dy = 1; break; } var nx = x + dx; var ny = y + dy; if (nx < 0 || nx >= Game.MapWidth || ny < 0 || ny >= Game.MapHeight) return new CreatureCommand { DeltaX = 0, DeltaY = 0, TransformTo = null }; // Нельзя пройти в мешок if (Game.Map[nx, ny] is Sack) return new CreatureCommand { DeltaX = 0, DeltaY = 0, TransformTo = null }; return new CreatureCommand { DeltaX = dx, DeltaY = dy, TransformTo = null }; } public bool DeadInConflict(ICreature conflictedObject) => conflictedObject is Sack sack && sack.IsFalling;
Если вдруг спрайты не находятся — проверьте реальные имена файлов в папке ресурсов (часто отличаются: Digger.png/Player.png, Sack.png/Bag.png, и т.п.).
app/Architecture/CreatureAnimation.cs(7,25): warning CS8618: Non-nullable field 'Command' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. [/app/Digger.csproj]
/app/Architecture/CreatureAnimation.cs(8,19): warning CS8618: Non-nullable field 'Creature' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. [/app/Digger.csproj]
/app/Game.cs(36,29): warning CS8618: Non-nullable field 'Map' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. [/app/Digger.csproj]
/app/DiggerTask.cs(62,69): warning CS8625: Cannot convert null literal to non-nullable reference type. [/app/Digger.csproj]
/app/DiggerTask.cs(66,33): error CS0246: The type or namespace name 'Player' could not be found (are you missing a using directive or an assembly reference?) [/app/Digger.csproj]
/app/Architecture/CreatureCommand.cs(7,19): warning CS8618: Non-nullable field 'TransformTo' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. [/app/Digger.csproj]
/app/DiggerTask.cs(25,53): error CS0246: The type or namespace name 'Player' could not be found (are you missing a using directive or an assembly reference?) [/app/Digger.csproj]
/app/DiggerTask.cs(31,76): warning CS8625: Cannot convert null literal to non-nullable reference type. [/app/Digger.csproj]
/app/DiggerTask.cs(37,76): warning CS8625: Cannot convert null literal to non-nullable reference type. [/app/Digger.csproj]
/app/DiggerTask.cs(48,27): warning CS8601: Possible null reference assignment. [/app/Digger.csproj]
/app/Architecture/CreatureMapCreator.cs(39,26): warning CS8600: Converting null literal or possible null value to non-nullable type. [/app/Digger.csproj]
/app/Architecture/CreatureMapCreator.cs(39,26): warning CS8603: Possible null reference return. [/app/Digger.csproj]
/app/Architecture/GameState.cs(60,10): warning CS8603: Possible null reference return. [/app/Digger.csproj]
/app/checking/Checker.cs(64,4): warning CS8604: Possible null reference argument for parameter 'actual' in 'void ComparisonExtensions.ShouldBe(object actual, object expected, string description, params object[] args)'. [/app/Digger.csproj]
/app/checking/Checker.cs(64,26): warning CS8604: Possible null reference argument for parameter 'expected' in 'void ComparisonExtensions.ShouldBe(object actual, object expected, string description, params object[] args)'. [/app/Digger.csproj]
/app/checking/DiggerTestCase.cs(108,10): warning CS8603: Possible null reference return. [/app/Digger.csproj]
Ошибка CS0246: Player could not be found означает только одно: компилятор не видит класса Player (обычно он либо в другом namespace, либо не public, либо файл не тот / не подключён).
В вашем проекте Game находится в namespace Digger;, и файл DiggerTask.cs ожидает, что Player тоже будет именно Digger.Player.
Player.cs (ВАЖНО: namespace ровно Digger)csharpusing Avalonia.Input; using Digger.Architecture; namespace Digger; public class Player : ICreature { public string GetImageFileName() => "Digger.png"; // или "Player.png" — как у вас в ресурсах public int GetDrawingPriority() => 0; public CreatureCommand Act(int x, int y) { var dx = 0; var dy = 0; switch (Game.KeyPressed) { case Key.Left: dx = -1; break; case Key.Right: dx = 1; break; case Key.Up: dy = -1; break; case Key.Down: dy = 1; break; } var nx = x + dx; var ny = y + dy; if (nx < 0 || nx >= Game.MapWidth || ny < 0 || ny >= Game.MapHeight) return new CreatureCommand { DeltaX = 0, DeltaY = 0, TransformTo = null }; // нельзя пройти в мешок if (Game.Map[nx, ny] is Sack) return new CreatureCommand { DeltaX = 0, DeltaY = 0, TransformTo = null }; return new CreatureCommand { DeltaX = dx, DeltaY = dy, TransformTo = null }; } public bool DeadInConflict(ICreature conflictedObject) => conflictedObject is Sack sack && sack.IsFalling; }
namespace Digger.Architecture; или namespace Digger.SomeFolder; вместо namespace Digger;internal class Player (нужно public class Player)*.cs или случайно лежит вне проекта (редко, но бывает)Сейчас TransformTo по смыслу может быть null, значит аннотация должна быть nullable.
В Architecture/CreatureCommand.cs:
csharppublic ICreature? TransformTo;
И (если хотите убрать warning про Map) в Game.cs:
csharppublic static ICreature[,] Map = null!;
Если после добавления Player.cs ошибка останется — почти наверняка у вас другой namespace у DiggerTask.cs. Скиньте первые 5–10 строк DiggerTask.cs (где namespace ...), и я скажу точное имя namespace, которое нужно совпасть.
using Digger.Architecture;
using Avalonia.Input;
namespace Digger;
public class Sack : ICreature
{
private int fallDistance;
textpublic bool IsFalling => fallDistance > 0; public string GetImageFileName() => "Sack.png"; public int GetDrawingPriority() => 5; public CreatureCommand Act(int x, int y) { if (y + 1 >= Game.MapHeight) return Land(); var below = Game.Map[x, y + 1]; if (!IsFalling && below is null) return Fall(); if (IsFalling && (below is null || below is Player)) return Fall(); if (IsFalling) return Land(); return new CreatureCommand { DeltaX = 0, DeltaY = 0, TransformTo = null }; } private CreatureCommand Fall() { fallDistance++; return new CreatureCommand { DeltaX = 0, DeltaY = 1, TransformTo = null }; } private CreatureCommand Land() { var toGold = fallDistance > 1; fallDistance = 0; return new CreatureCommand { DeltaX = 0, DeltaY = 0, TransformTo = toGold ? new Gold() : null }; } public bool DeadInConflict(ICreature conflictedObject) => false;
}
public class Gold : ICreature
{
public string GetImageFileName() => "Gold.png";
textpublic int GetDrawingPriority() => 5; public CreatureCommand Act(int x, int y) => new CreatureCommand { DeltaX = 0, DeltaY = 0, TransformTo = null }; public bool DeadInConflict(ICreature conflictedObject) { if (conflictedObject is Player) { Game.Scores += 10; return true; } return false; }
}
/app/Game.cs(36,29): warning CS8618: Non-nullable field 'Map' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. [/app/Digger.csproj]
/app/DiggerTask.cs(63,69): warning CS8625: Cannot convert null literal to non-nullable reference type. [/app/Digger.csproj]
/app/DiggerTask.cs(67,33): error CS0246: The type or namespace name 'Player' could not be found (are you missing a using directive or an assembly reference?) [/app/Digger.csproj]
/app/DiggerTask.cs(26,53): error CS0246: The type or namespace name 'Player' could not be found (are you missing a using directive or an assembly reference?) [/app/Digger.csproj]
/app/DiggerTask.cs(32,76): warning CS8625: Cannot convert null literal to non-nullable reference type. [/app/Digger.csproj]
/app/DiggerTask.cs(38,76): warning CS8625: Cannot convert null literal to non-nullable reference type. [/app/Digger.csproj]
/app/DiggerTask.cs(49,27): warning CS8601: Possible null reference assignment. [/app/Digger.csproj]
/app/Architecture/CreatureAnimation.cs(7,25): warning CS8618: Non-nullable field 'Command' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. [/app/Digger.csproj]
/app/Architecture/CreatureAnimation.cs(8,19): warning CS8618: Non-nullable field 'Creature' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. [/app/Digger.csproj]
/app/Architecture/GameState.cs(60,10): warning CS8603: Possible null reference return. [/app/Digger.csproj]
/app/checking/DiggerTestCase.cs(108,10): warning CS8603: Possible null reference return. [/app/Digger.csproj]
/app/checking/Checker.cs(64,4): warning CS8604: Possible null reference argument for parameter 'actual' in 'void ComparisonExtensions.ShouldBe(object actual, object expected, string description, params object[] args)'. [/app/Digger.csproj]
/app/checking/Checker.cs(64,26): warning CS8604: Possible null reference argument for parameter 'expected' in 'void ComparisonExtensions.ShouldBe(object actual, object expected, string description, params object[] args)'. [/app/Digger.csproj]
/app/Architecture/CreatureMapCreator.cs(39,26): warning CS8600: Converting null literal or possible null value to non-nullable type. [/app/Digger.csproj]
/app/Architecture/CreatureMapCreator.cs(39,26): warning CS8603: Possible null reference return. [/app/Digger.csproj]
/app/Architecture/CreatureCommand.cs(7,19): warning CS8618: Non-nullable field 'TransformTo' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. [/app/Digger.csproj]
Ошибки CS0246: Player could not be found означают, что в проекте вообще нет типа Digger.Player (либо он в другом namespace, либо не public, либо файла Player.cs нет/не компилируется). Из-за этого падают и проверки в DiggerTask.cs, и ваш Sack (строка below is Player).
Player.cs (обязательно namespace Digger; и public class Player)Создайте файл Player.cs:
csharpusing Avalonia.Input; using Digger.Architecture; namespace Digger; public class Player : ICreature { public string GetImageFileName() => "Digger.png"; // если у вас иначе — поставьте реальное имя файла public int GetDrawingPriority() => 0; public CreatureCommand Act(int x, int y) { var dx = 0; var dy = 0; switch (Game.KeyPressed) { case Key.Left: dx = -1; break; case Key.Right: dx = 1; break; case Key.Up: dy = -1; break; case Key.Down: dy = 1; break; } var nx = x + dx; var ny = y + dy; // границы поля if (nx < 0 || nx >= Game.MapWidth || ny < 0 || ny >= Game.MapHeight) return new CreatureCommand { DeltaX = 0, DeltaY = 0, TransformTo = null }; // нельзя зайти в мешок if (Game.Map[nx, ny] is Sack) return new CreatureCommand { DeltaX = 0, DeltaY = 0, TransformTo = null }; return new CreatureCommand { DeltaX = dx, DeltaY = dy, TransformTo = null }; } // игрок умирает только если в него врезался ПАДАЮЩИЙ мешок public bool DeadInConflict(ICreature conflictedObject) => conflictedObject is Sack s && s.IsFalling; }
После этого DiggerTask.cs перестанет ругаться на отсутствие Player, и ваш Sack тоже скомпилируется.
В Architecture/CreatureCommand.cs:
csharppublic ICreature? TransformTo;
Это уберёт CS8618/CS8625 вокруг TransformTo = null (и по смыслу это правильно: превращения часто нет).
Game.Map (не ошибка)Можно оставить как есть, но если хотите без warning — в Game.cs:
csharppublic static ICreature[,] Map = null!;
Если после добавления Player.cs ошибка всё равно останется — почти наверняка Player у вас есть, но в другом namespace (например Digger.Architecture). Тогда просто приведите его к namespace Digger; как выше.
using System;
namespace Rectangles
{
public static class RectanglesTask
{
public static bool AreIntersected(Rectangle r1, Rectangle r2)
{
float left = Math.Max(r1.Left, r2.Left);
float right = Math.Min(r1.Right, r2.Right);
float top = Math.Max(r1.Top, r2.Top);
float bottom = Math.Min(r1.Bottom, r2.Bottom);
textreturn left <= right && top <= bottom; } public static int IntersectionSquare(Rectangle r1, Rectangle r2) { if (!AreIntersected(r1, r2)) return 0; float left = Math.Max(r1.Left, r2.Left); float right = Math.Min(r1.Right, r2.Right); float top = Math.Max(r1.Top, r2.Top); float bottom = Math.Min(r1.Bottom, r2.Bottom); float width = right - left; float height = bottom - top; return (int)(width * height); } public static int IndexOfInnerRectangle(Rectangle r1, Rectangle r2) { bool r1InR2 = r1.Left >= r2.Left && r1.Right <= r2.Right && r1.Top >= r2.Top && r1.Bottom <= r2.Bottom; bool r2InR1 = r2.Left >= r1.Left && r2.Right <= r1.Right && r2.Top >= r1.Top && r2.Bottom <= r1.Bottom; if (r2InR1) return 1; if (r1InR2) return 0; return -1; } }
}
Сведите задачу пересечения прямоугольников к задаче пересечения отрезков
IndexOfInnerRectangle Данное условие дублируется 2 раза с точностью до переобозначения r1 на r2.
Создайте вспомогательный метод.
Вот как «свести пересечение прямоугольников к пересечению отрезков» и убрать дублирование в IndexOfInnerRectangle.
Идея:
csharpusing System; namespace Rectangles { public static class RectanglesTask { // Пересечение/соприкосновение отрезков [a1, a2] и [b1, b2] private static bool SegmentsIntersect(float a1, float a2, float b1, float b2) { var left = Math.Max(a1, b1); var right = Math.Min(a2, b2); return left <= right; } // Длина пересечения отрезков (0, если не пересекаются) private static float SegmentIntersectionLength(float a1, float a2, float b1, float b2) { var left = Math.Max(a1, b1); var right = Math.Min(a2, b2); return Math.Max(0, right - left); } public static bool AreIntersected(Rectangle r1, Rectangle r2) { return SegmentsIntersect(r1.Left, r1.Right, r2.Left, r2.Right) && SegmentsIntersect(r1.Top, r1.Bottom, r2.Top, r2.Bottom); } public static int IntersectionSquare(Rectangle r1, Rectangle r2) { var width = SegmentIntersectionLength(r1.Left, r1.Right, r2.Left, r2.Right); var height = SegmentIntersectionLength(r1.Top, r1.Bottom, r2.Top, r2.Bottom); return (int)(width * height); } // a внутри b (включая границы) private static bool IsInside(Rectangle a, Rectangle b) { return a.Left >= b.Left && a.Right <= b.Right && a.Top >= b.Top && a.Bottom <= b.Bottom; } public static int IndexOfInnerRectangle(Rectangle r1, Rectangle r2) { if (IsInside(r1, r2)) return 0; if (IsInside(r2, r1)) return 1; return -1; } } }
Если хотите ещё «строже» следовать идее отрезков, IsInside тоже можно сделать через включение отрезка в отрезок (аналогично), но так уже убрано дублирование и логика читается.
using System.Collections.Generic;
using System.Linq;
namespace Recognizer
{
internal static class MedianFilterTask
{
public static double[,] MedianFilter(double[,] original)
{
var width = original.GetLength(0);
var height = original.GetLength(1);
var filteredPixels = new double[width, height];
textfor (var x = 0; x < width; x++) for (var y = 0; y < height; y++) filteredPixels[x, y] = GetPixelValue(x, y, original, width, height); return filteredPixels; } private static double GetPixelValue(int x, int y, double[,] original, int width, int height) { var edgeValues = new List<double>(); // Проходим по соседним пикселям (включая сам пиксель) for (var offsetX = -1; offsetX <= 1; offsetX++) { for (var offsetY = -1; offsetY <= 1; offsetY++) { // Проверяем, находится ли соседний пиксель в пределах границ изображения if (PixelIsInsideBoundaries(x + offsetX, y + offsetY, width, height)) { edgeValues.Add(original[x + offsetX, y + offsetY]); } } } edgeValues.Sort(); return GetMedianValue(edgeValues); } private static bool PixelIsInsideBoundaries(int x, int y, int width, int height) { return (x >= 0 && y >= 0) && (x < width && y < height); } private static double GetMedianValue(List<double> edge) { int count = edge.Count; if (count % 2 == 0) { return (edge[count / 2 - 1] + edge[count / 2]) / 2.0; } else { return edge[count / 2]; } } }
}
Дублирование. Один из вызовов метода произойдет в любом случае. Подумайте, какой
У вас в GetMedianValue дублирование в виде if/else: в любом случае возвращается элемент с индексом count/2 (для нечётного — просто он, для чётного — он участвует в среднем). Поэтому можно убрать ветвление так, чтобы один “обязательный” вызов/доступ происходил всегда — это edge[count / 2].
Вот компактный вариант:
csharpprivate static double GetMedianValue(List<double> edge) { var mid = edge.Count / 2; var right = edge[mid]; // этот доступ нужен всегда return edge.Count % 2 == 1 ? right : (edge[mid - 1] + right) / 2.0; }
Можно ещё короче (но менее читабельно):
csharpprivate static double GetMedianValue(List<double> edge) { var mid = edge.Count / 2; return edge.Count % 2 == 1 ? edge[mid] : (edge[mid - 1] + edge[mid]) / 2.0; }