feat: initial project structure with Snake.Core, CLI and Avalonia skeleton
This commit is contained in:
66
.gitignore
vendored
Normal file
66
.gitignore
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
*.userprefs
|
||||
|
||||
## Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
## Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
## Visual Studio cache/options directory
|
||||
.vs/
|
||||
|
||||
## MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
## NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
## .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
## NuGet Packages
|
||||
*.nupkg
|
||||
*.snupkg
|
||||
**/[Pp]ackages/*
|
||||
!**/[Pp]ackages/build/
|
||||
|
||||
## Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
## VS Code
|
||||
.vscode/
|
||||
|
||||
## OS generated files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
## Avalonia
|
||||
*.DotSettings.user
|
||||
8
Directory.Build.props
Normal file
8
Directory.Build.props
Normal file
@@ -0,0 +1,8 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
66
README.md
Normal file
66
README.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# Snake (C#)
|
||||
|
||||
Классическая игра «Змейка» на .NET 8 с разделением логики и интерфейсов.
|
||||
|
||||
## Структура решения
|
||||
|
||||
| Проект | Назначение |
|
||||
|--------|------------|
|
||||
| `Snake.Core` | Игровая логика: поле, змейка, еда, движение, коллизии |
|
||||
| `Snake.CLI` | Консольный интерфейс |
|
||||
| `Snake.Avalonia` | Графический интерфейс (Avalonia UI) |
|
||||
|
||||
## Требования
|
||||
|
||||
- [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0)
|
||||
|
||||
## Запуск
|
||||
|
||||
```bash
|
||||
# Консольная версия
|
||||
dotnet run --project Snake.CLI
|
||||
|
||||
# Avalonia UI (требуется графическая среда)
|
||||
dotnet run --project Snake.Avalonia
|
||||
```
|
||||
|
||||
## Git Flow
|
||||
|
||||
Проект использует [Git Flow](https://nvie.com/posts/a-successful-git-branching-model/):
|
||||
|
||||
| Ветка | Назначение |
|
||||
|-------|------------|
|
||||
| `main` | Стабильные релизы |
|
||||
| `develop` | Интеграционная ветка для текущей разработки |
|
||||
| `feature/*` | Новые функции (ветвление от `develop`) |
|
||||
| `release/*` | Подготовка релиза (ветвление от `develop`) |
|
||||
| `hotfix/*` | Срочные исправления в production (ветвление от `main`) |
|
||||
|
||||
### Типичный workflow
|
||||
|
||||
```bash
|
||||
# Начать новую функцию
|
||||
git checkout develop
|
||||
git checkout -b feature/my-feature
|
||||
|
||||
# Завершить функцию
|
||||
git checkout develop
|
||||
git merge --no-ff feature/my-feature
|
||||
git branch -d feature/my-feature
|
||||
|
||||
# Релиз
|
||||
git checkout -b release/1.0.0 develop
|
||||
# ... финальные правки, тег версии ...
|
||||
git checkout main
|
||||
git merge --no-ff release/1.0.0
|
||||
git tag -a v1.0.0
|
||||
git checkout develop
|
||||
git merge --no-ff release/1.0.0
|
||||
```
|
||||
|
||||
## Игровая логика (Snake.Core)
|
||||
|
||||
- `Board` — размеры поля и проверка границ
|
||||
- `Snake` — сегменты тела, направление, движение
|
||||
- `Food` — случайное размещение еды на свободных клетках
|
||||
- `SnakeGame` — игровой цикл, счёт, коллизии со стенами и собой
|
||||
10
Snake.Avalonia/App.axaml
Normal file
10
Snake.Avalonia/App.axaml
Normal file
@@ -0,0 +1,10 @@
|
||||
<Application xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="Snake.Avalonia.App"
|
||||
RequestedThemeVariant="Default">
|
||||
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
|
||||
|
||||
<Application.Styles>
|
||||
<FluentTheme />
|
||||
</Application.Styles>
|
||||
</Application>
|
||||
23
Snake.Avalonia/App.axaml.cs
Normal file
23
Snake.Avalonia/App.axaml.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Snake.Avalonia;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
desktop.MainWindow = new MainWindow();
|
||||
}
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
}
|
||||
10
Snake.Avalonia/MainWindow.axaml
Normal file
10
Snake.Avalonia/MainWindow.axaml
Normal file
@@ -0,0 +1,10 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:views="using:Snake.Avalonia.Views"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Snake.Avalonia.MainWindow"
|
||||
Title="Snake">
|
||||
<views:GameView />
|
||||
</Window>
|
||||
11
Snake.Avalonia/MainWindow.axaml.cs
Normal file
11
Snake.Avalonia/MainWindow.axaml.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Snake.Avalonia;
|
||||
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
24
Snake.Avalonia/Program.cs
Normal file
24
Snake.Avalonia/Program.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Avalonia;
|
||||
using System;
|
||||
|
||||
namespace Snake.Avalonia;
|
||||
|
||||
class Program
|
||||
{
|
||||
// Initialization code. Don't use any Avalonia, third-party APIs or any
|
||||
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
|
||||
// yet and stuff might break.
|
||||
[STAThread]
|
||||
public static void Main(string[] args) => BuildAvaloniaApp()
|
||||
.StartWithClassicDesktopLifetime(args);
|
||||
|
||||
// Avalonia configuration, don't remove; also used by visual designer.
|
||||
public static AppBuilder BuildAvaloniaApp()
|
||||
=> AppBuilder.Configure<App>()
|
||||
.UsePlatformDetect()
|
||||
#if DEBUG
|
||||
.WithDeveloperTools()
|
||||
#endif
|
||||
.WithInterFont()
|
||||
.LogToTrace();
|
||||
}
|
||||
24
Snake.Avalonia/Snake.Avalonia.csproj
Normal file
24
Snake.Avalonia/Snake.Avalonia.csproj
Normal file
@@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="12.0.4" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="12.0.4" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="12.0.4" />
|
||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="12.0.4" />
|
||||
<PackageReference Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.1">
|
||||
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
|
||||
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Snake.Core\Snake.Core.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
7
Snake.Avalonia/Views/GameView.axaml
Normal file
7
Snake.Avalonia/Views/GameView.axaml
Normal file
@@ -0,0 +1,7 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="Snake.Avalonia.Views.GameView">
|
||||
<TextBlock Text="Snake game UI — connect Snake.Core here"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</UserControl>
|
||||
15
Snake.Avalonia/Views/GameView.axaml.cs
Normal file
15
Snake.Avalonia/Views/GameView.axaml.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Snake.Avalonia.Views;
|
||||
|
||||
/// <summary>
|
||||
/// Placeholder for the Avalonia game view.
|
||||
/// Wire up <see cref="Snake.Core.SnakeGame"/> here for rendering and input.
|
||||
/// </summary>
|
||||
public partial class GameView : UserControl
|
||||
{
|
||||
public GameView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
18
Snake.Avalonia/app.manifest
Normal file
18
Snake.Avalonia/app.manifest
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<!-- This manifest is used on Windows only.
|
||||
Don't remove it as it might cause problems with window transparency and embedded controls.
|
||||
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
|
||||
<assemblyIdentity version="1.0.0.0" name="Snake.Avalonia.Desktop"/>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- A list of the Windows versions that this application has been tested on
|
||||
and is designed to work with. Uncomment the appropriate elements
|
||||
and Windows will automatically select the most compatible environment. -->
|
||||
|
||||
<!-- Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>
|
||||
50
Snake.CLI/ConsoleRenderer.cs
Normal file
50
Snake.CLI/ConsoleRenderer.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using Snake.Core;
|
||||
|
||||
namespace Snake.CLI;
|
||||
|
||||
internal static class ConsoleRenderer
|
||||
{
|
||||
public static void Render(SnakeGame game)
|
||||
{
|
||||
Console.Clear();
|
||||
Console.WriteLine($"Score: {game.Score}");
|
||||
|
||||
if (game.IsGameOver)
|
||||
Console.WriteLine("Game Over! Press R to restart or Q to quit.");
|
||||
|
||||
var board = game.Board;
|
||||
var snakeCells = game.Snake.Segments.ToHashSet();
|
||||
var food = game.Food.Position;
|
||||
|
||||
Console.Write('+');
|
||||
Console.Write(new string('-', board.Width * 2));
|
||||
Console.WriteLine('+');
|
||||
|
||||
for (var y = 0; y < board.Height; y++)
|
||||
{
|
||||
Console.Write('|');
|
||||
|
||||
for (var x = 0; x < board.Width; x++)
|
||||
{
|
||||
var position = new Position(x, y);
|
||||
var symbol = position == food
|
||||
? '@'
|
||||
: position == game.Snake.Head
|
||||
? 'O'
|
||||
: snakeCells.Contains(position)
|
||||
? 'o'
|
||||
: ' ';
|
||||
|
||||
Console.Write(symbol);
|
||||
Console.Write(' ');
|
||||
}
|
||||
|
||||
Console.WriteLine('|');
|
||||
}
|
||||
|
||||
Console.Write('+');
|
||||
Console.Write(new string('-', board.Width * 2));
|
||||
Console.WriteLine('+');
|
||||
Console.WriteLine("Controls: Arrow keys to move, R to restart, Q to quit.");
|
||||
}
|
||||
}
|
||||
44
Snake.CLI/Program.cs
Normal file
44
Snake.CLI/Program.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Snake.CLI;
|
||||
using Snake.Core;
|
||||
|
||||
const int boardWidth = 20;
|
||||
const int boardHeight = 15;
|
||||
const int tickMs = 120;
|
||||
|
||||
SnakeGame game = new(boardWidth, boardHeight);
|
||||
ConsoleRenderer.Render(game);
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (Console.KeyAvailable)
|
||||
{
|
||||
var key = Console.ReadKey(intercept: true).Key;
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case ConsoleKey.UpArrow:
|
||||
game.SetDirection(Direction.Up);
|
||||
break;
|
||||
case ConsoleKey.DownArrow:
|
||||
game.SetDirection(Direction.Down);
|
||||
break;
|
||||
case ConsoleKey.LeftArrow:
|
||||
game.SetDirection(Direction.Left);
|
||||
break;
|
||||
case ConsoleKey.RightArrow:
|
||||
game.SetDirection(Direction.Right);
|
||||
break;
|
||||
case ConsoleKey.Q:
|
||||
return;
|
||||
case ConsoleKey.R when game.IsGameOver:
|
||||
game = new SnakeGame(boardWidth, boardHeight);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!game.IsGameOver)
|
||||
game.Tick();
|
||||
|
||||
ConsoleRenderer.Render(game);
|
||||
Thread.Sleep(tickMs);
|
||||
}
|
||||
14
Snake.CLI/Snake.CLI.csproj
Normal file
14
Snake.CLI/Snake.CLI.csproj
Normal file
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Snake.Core\Snake.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
24
Snake.Core/Board.cs
Normal file
24
Snake.Core/Board.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
namespace Snake.Core;
|
||||
|
||||
public sealed class Board
|
||||
{
|
||||
public Board(int width, int height)
|
||||
{
|
||||
if (width < 2)
|
||||
throw new ArgumentOutOfRangeException(nameof(width), "Width must be at least 2.");
|
||||
|
||||
if (height < 2)
|
||||
throw new ArgumentOutOfRangeException(nameof(height), "Height must be at least 2.");
|
||||
|
||||
Width = width;
|
||||
Height = height;
|
||||
}
|
||||
|
||||
public int Width { get; }
|
||||
|
||||
public int Height { get; }
|
||||
|
||||
public bool IsWithinBounds(Position position) =>
|
||||
position.X >= 0 && position.X < Width &&
|
||||
position.Y >= 0 && position.Y < Height;
|
||||
}
|
||||
9
Snake.Core/Direction.cs
Normal file
9
Snake.Core/Direction.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Snake.Core;
|
||||
|
||||
public enum Direction
|
||||
{
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right
|
||||
}
|
||||
38
Snake.Core/Food.cs
Normal file
38
Snake.Core/Food.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
namespace Snake.Core;
|
||||
|
||||
public sealed class Food
|
||||
{
|
||||
private readonly Random _random;
|
||||
|
||||
public Food(Board board, IEnumerable<Position> occupiedPositions, Random? random = null)
|
||||
{
|
||||
_random = random ?? Random.Shared;
|
||||
Board = board;
|
||||
Respawn(occupiedPositions);
|
||||
}
|
||||
|
||||
public Board Board { get; }
|
||||
|
||||
public Position Position { get; private set; }
|
||||
|
||||
public void Respawn(IEnumerable<Position> occupiedPositions)
|
||||
{
|
||||
var occupied = occupiedPositions.ToHashSet();
|
||||
var freeCells = new List<Position>();
|
||||
|
||||
for (var y = 0; y < Board.Height; y++)
|
||||
{
|
||||
for (var x = 0; x < Board.Width; x++)
|
||||
{
|
||||
var position = new Position(x, y);
|
||||
if (!occupied.Contains(position))
|
||||
freeCells.Add(position);
|
||||
}
|
||||
}
|
||||
|
||||
if (freeCells.Count == 0)
|
||||
throw new InvalidOperationException("No free cells available for food.");
|
||||
|
||||
Position = freeCells[_random.Next(freeCells.Count)];
|
||||
}
|
||||
}
|
||||
8
Snake.Core/GameTickResult.cs
Normal file
8
Snake.Core/GameTickResult.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Snake.Core;
|
||||
|
||||
public enum GameTickResult
|
||||
{
|
||||
Moved,
|
||||
AteFood,
|
||||
GameOver
|
||||
}
|
||||
3
Snake.Core/Position.cs
Normal file
3
Snake.Core/Position.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace Snake.Core;
|
||||
|
||||
public readonly record struct Position(int X, int Y);
|
||||
9
Snake.Core/Snake.Core.csproj
Normal file
9
Snake.Core/Snake.Core.csproj
Normal file
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
80
Snake.Core/Snake.cs
Normal file
80
Snake.Core/Snake.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
namespace Snake.Core;
|
||||
|
||||
public sealed class Snake
|
||||
{
|
||||
private readonly LinkedList<Position> _segments = new();
|
||||
|
||||
public Snake(Position start, int length, Direction direction)
|
||||
{
|
||||
if (length < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(length), "Length must be at least 1.");
|
||||
|
||||
Direction = direction;
|
||||
|
||||
var offset = DirectionToOffset(direction);
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
_segments.AddLast(new Position(
|
||||
start.X - offset.X * i,
|
||||
start.Y - offset.Y * i));
|
||||
}
|
||||
}
|
||||
|
||||
public Direction Direction { get; private set; }
|
||||
|
||||
public Position Head => _segments.First!.Value;
|
||||
|
||||
public IReadOnlyCollection<Position> Segments => _segments;
|
||||
|
||||
public void SetDirection(Direction direction)
|
||||
{
|
||||
if (direction == Direction)
|
||||
return;
|
||||
|
||||
if (AreOpposite(Direction, direction))
|
||||
return;
|
||||
|
||||
Direction = direction;
|
||||
}
|
||||
|
||||
public Position PeekNextHead()
|
||||
{
|
||||
var offset = DirectionToOffset(Direction);
|
||||
return new Position(Head.X + offset.X, Head.Y + offset.Y);
|
||||
}
|
||||
|
||||
public Position Move(bool grow = false)
|
||||
{
|
||||
var newHead = PeekNextHead();
|
||||
|
||||
_segments.AddFirst(newHead);
|
||||
|
||||
if (!grow)
|
||||
_segments.RemoveLast();
|
||||
|
||||
return newHead;
|
||||
}
|
||||
|
||||
public bool Occupies(Position position) =>
|
||||
_segments.Contains(position);
|
||||
|
||||
private static Position DirectionToOffset(Direction direction) =>
|
||||
direction switch
|
||||
{
|
||||
Direction.Up => new Position(0, -1),
|
||||
Direction.Down => new Position(0, 1),
|
||||
Direction.Left => new Position(-1, 0),
|
||||
Direction.Right => new Position(1, 0),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(direction))
|
||||
};
|
||||
|
||||
private static bool AreOpposite(Direction current, Direction next) =>
|
||||
(current, next) switch
|
||||
{
|
||||
(Direction.Up, Direction.Down) => true,
|
||||
(Direction.Down, Direction.Up) => true,
|
||||
(Direction.Left, Direction.Right) => true,
|
||||
(Direction.Right, Direction.Left) => true,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
60
Snake.Core/SnakeGame.cs
Normal file
60
Snake.Core/SnakeGame.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
namespace Snake.Core;
|
||||
|
||||
public sealed class SnakeGame
|
||||
{
|
||||
public SnakeGame(int width, int height, Random? random = null)
|
||||
{
|
||||
_random = random ?? Random.Shared;
|
||||
Board = new Board(width, height);
|
||||
|
||||
var start = new Position(width / 2, height / 2);
|
||||
Snake = new Snake(start, length: 3, Direction.Right);
|
||||
Food = new Food(Board, Snake.Segments, _random);
|
||||
}
|
||||
|
||||
private readonly Random _random;
|
||||
|
||||
public Board Board { get; }
|
||||
|
||||
public Snake Snake { get; }
|
||||
|
||||
public Food Food { get; }
|
||||
|
||||
public int Score { get; private set; }
|
||||
|
||||
public bool IsGameOver { get; private set; }
|
||||
|
||||
public void SetDirection(Direction direction) => Snake.SetDirection(direction);
|
||||
|
||||
public GameTickResult Tick()
|
||||
{
|
||||
if (IsGameOver)
|
||||
return GameTickResult.GameOver;
|
||||
|
||||
var newHead = Snake.PeekNextHead();
|
||||
|
||||
if (!Board.IsWithinBounds(newHead) || Snake.Occupies(newHead))
|
||||
{
|
||||
IsGameOver = true;
|
||||
return GameTickResult.GameOver;
|
||||
}
|
||||
|
||||
var ateFood = newHead == Food.Position;
|
||||
Snake.Move(grow: ateFood);
|
||||
|
||||
if (ateFood)
|
||||
{
|
||||
Score++;
|
||||
if (Snake.Segments.Count >= Board.Width * Board.Height)
|
||||
{
|
||||
IsGameOver = true;
|
||||
return GameTickResult.AteFood;
|
||||
}
|
||||
|
||||
Food.Respawn(Snake.Segments);
|
||||
return GameTickResult.AteFood;
|
||||
}
|
||||
|
||||
return GameTickResult.Moved;
|
||||
}
|
||||
}
|
||||
34
Snake.sln
Normal file
34
Snake.sln
Normal file
@@ -0,0 +1,34 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Snake.Core", "Snake.Core\Snake.Core.csproj", "{C74F838E-FFB4-499F-99DF-57142C110454}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Snake.CLI", "Snake.CLI\Snake.CLI.csproj", "{8DB20DDA-CE97-41B6-B51E-C195DD387C70}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Snake.Avalonia", "Snake.Avalonia\Snake.Avalonia.csproj", "{4C16155D-6998-4635-8478-5F27FDC4189D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{C74F838E-FFB4-499F-99DF-57142C110454}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C74F838E-FFB4-499F-99DF-57142C110454}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C74F838E-FFB4-499F-99DF-57142C110454}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C74F838E-FFB4-499F-99DF-57142C110454}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8DB20DDA-CE97-41B6-B51E-C195DD387C70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8DB20DDA-CE97-41B6-B51E-C195DD387C70}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8DB20DDA-CE97-41B6-B51E-C195DD387C70}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8DB20DDA-CE97-41B6-B51E-C195DD387C70}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4C16155D-6998-4635-8478-5F27FDC4189D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4C16155D-6998-4635-8478-5F27FDC4189D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4C16155D-6998-4635-8478-5F27FDC4189D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4C16155D-6998-4635-8478-5F27FDC4189D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
6
global.json
Normal file
6
global.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "8.0.0",
|
||||
"rollForward": "latestMinor"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user