ALOT again
This commit is contained in:
+2
-1
@@ -2,7 +2,7 @@ using System.Numerics;
|
||||
|
||||
namespace Snake.Model;
|
||||
|
||||
// 0,0 is top left
|
||||
// Struct to store a point. A bit functional looking
|
||||
public readonly record struct Point{
|
||||
public readonly int X;
|
||||
public readonly int Y;
|
||||
@@ -12,6 +12,7 @@ public readonly record struct Point{
|
||||
Y = y;
|
||||
}
|
||||
|
||||
// This is where the magic happens
|
||||
public static implicit operator Point((int x, int y) tuple){
|
||||
return new Point(tuple.x, tuple.y);
|
||||
}
|
||||
|
||||
+165
-108
@@ -1,14 +1,15 @@
|
||||
using model;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Snake.Model;
|
||||
|
||||
public class SnakeLevel : IDisposable{
|
||||
public static readonly TimeSpan TickTimeSpan = TimeSpan.FromMilliseconds(500);
|
||||
public int Size {get; }
|
||||
|
||||
public enum LevelBlock{
|
||||
#region Enums
|
||||
/// <summary>
|
||||
/// States that a 'block' can be in
|
||||
/// </summary>
|
||||
public enum LevelBlock
|
||||
{
|
||||
Empty,
|
||||
Obstacle,
|
||||
Egg,
|
||||
@@ -17,72 +18,121 @@ public class SnakeLevel : IDisposable{
|
||||
OutOfBounds
|
||||
}
|
||||
|
||||
public enum SnakeDirection{
|
||||
/// <summary>
|
||||
/// The directions that the snake can be headed
|
||||
/// </summary>
|
||||
public enum SnakeDirection
|
||||
{
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right
|
||||
}
|
||||
|
||||
public enum GameState{
|
||||
/// <summary>
|
||||
/// States that the game can be in
|
||||
/// </summary>
|
||||
public enum GameState
|
||||
{
|
||||
Running,
|
||||
Paused,
|
||||
Dead
|
||||
}
|
||||
#endregion
|
||||
|
||||
private readonly List<Point> _obstacles;
|
||||
#region Fields
|
||||
public static readonly TimeSpan TickTimeSpan = TimeSpan.FromMilliseconds(500); // Time between two ingame ticks
|
||||
private readonly List<Point> _obstacles; // List of obstacles on the map
|
||||
private readonly List<Point> _snake; // Coordinates of the snake's body but not the head
|
||||
private readonly List<Point> _eggs; // Coordinates of the current eggs
|
||||
private int _round; // Countdown to till the new egg
|
||||
private ITimer timer; // The timer object
|
||||
private GameState _state; // The current game state
|
||||
private SnakeDirection _last_direction = SnakeDirection.Down; // The direction the snake moved last round
|
||||
private SnakeDirection _snake_head_direction; // Current direction of the snake
|
||||
internal bool _disable_snake_movement = false; // Snake movement can be disabled for testing
|
||||
internal bool _disable_egg_generation = false; // Egg generation can be turned off for testing
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
public int Size { get; } // Size of the map (The map is square)
|
||||
public int NewEggRound { get; } // How many rounds between new egg generation
|
||||
public ReadOnlyCollection<Point> Obstacles => _obstacles.AsReadOnly();
|
||||
public Point SnakeHead {get; private set;}
|
||||
private readonly List<Point> _snake;
|
||||
public int EggLimit { get; } // Limit of how many eggs can be on the map
|
||||
|
||||
public Point SnakeHead { get; private set; } // Coordinates of the head of the snake
|
||||
// Head is not in snake
|
||||
// First is closest to the head
|
||||
public ReadOnlyCollection<Point> Snake => _snake.AsReadOnly();
|
||||
public int SnakeLength => Snake.Count + 1;
|
||||
private readonly List<Point> _eggs;
|
||||
public int SnakeLength => Snake.Count + 1; // Current length of the snake
|
||||
public ReadOnlyCollection<Point> Eggs => _eggs.AsReadOnly();
|
||||
public int NewEggRound { get; }
|
||||
public int EggLimit { get; }
|
||||
private int _round;
|
||||
private ITimer timer;
|
||||
public int Score { get; private set; }
|
||||
|
||||
public event EventHandler? GameUpdate;
|
||||
public event EventHandler<GameState>? GameStateUpdate;
|
||||
private GameState _state;
|
||||
public int Score { get; private set; } // Current score
|
||||
|
||||
public GameState State
|
||||
{
|
||||
get => _state;
|
||||
private set
|
||||
{
|
||||
_state = value;
|
||||
GameStateUpdate?.Invoke(this, _state);
|
||||
if(_state == GameState.Dead)
|
||||
if (_state == GameState.Dead)
|
||||
{
|
||||
timer.Change(Timeout.InfiniteTimeSpan, TickTimeSpan);
|
||||
}
|
||||
GameStateUpdate?.Invoke(this, _state);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private SnakeDirection LastDirection = SnakeDirection.Down;
|
||||
private SnakeDirection _SnakeHeadDirection;
|
||||
private bool disposedValue;
|
||||
|
||||
public SnakeDirection SnakeHeadDirection
|
||||
{
|
||||
get => _SnakeHeadDirection;
|
||||
get => _snake_head_direction;
|
||||
set
|
||||
{
|
||||
switch (value, LastDirection)
|
||||
switch (value, _last_direction)
|
||||
{
|
||||
case { value: SnakeDirection.Up or SnakeDirection.Down, LastDirection: SnakeDirection.Left or SnakeDirection.Right}:
|
||||
case { value: SnakeDirection.Left or SnakeDirection.Right, LastDirection: SnakeDirection.Up or SnakeDirection.Down }:
|
||||
_SnakeHeadDirection = value;
|
||||
case { value: SnakeDirection.Up or SnakeDirection.Down, _last_direction: SnakeDirection.Left or SnakeDirection.Right }:
|
||||
case { value: SnakeDirection.Left or SnakeDirection.Right, _last_direction: SnakeDirection.Up or SnakeDirection.Down }:
|
||||
_snake_head_direction = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
public LevelBlock this[Point p]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_obstacles.Contains(p))
|
||||
{
|
||||
return LevelBlock.Obstacle;
|
||||
}
|
||||
else if (p == SnakeHead)
|
||||
{
|
||||
return LevelBlock.SnakeHead;
|
||||
}
|
||||
else if (_snake.Contains(p))
|
||||
{
|
||||
return LevelBlock.Snake;
|
||||
}
|
||||
else if (_eggs.Contains(p))
|
||||
{
|
||||
return LevelBlock.Egg;
|
||||
}
|
||||
else if (p.X < 0 || p.Y < 0 || p.X >= Size || p.Y >= Size)
|
||||
{
|
||||
return LevelBlock.OutOfBounds;
|
||||
}
|
||||
|
||||
return LevelBlock.Empty;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
public event EventHandler? GameUpdate; // The map was updated
|
||||
public event EventHandler<GameState>? GameStateUpdate; // The game state was updated
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
public SnakeLevel(int size, IEnumerable<Point> obstacles, int snake_start_length, int new_egg_round, int egg_limit, TimeProvider time_provider){
|
||||
this.Size = size;
|
||||
this.NewEggRound = new_egg_round;
|
||||
@@ -98,104 +148,100 @@ public class SnakeLevel : IDisposable{
|
||||
_snake.Add((size / 2, i));
|
||||
}
|
||||
|
||||
_SnakeHeadDirection = SnakeDirection.Down;
|
||||
LastDirection = SnakeDirection.Down;
|
||||
_snake_head_direction = SnakeDirection.Down;
|
||||
_last_direction = SnakeDirection.Down;
|
||||
|
||||
timer = time_provider.CreateTimer(new TimerCallback(_timer_Tick), null, Timeout.InfiniteTimeSpan, TickTimeSpan);
|
||||
|
||||
_state = GameState.Paused;
|
||||
}
|
||||
|
||||
public SnakeLevel(int size, IEnumerable<Point> obstacles, int snake_start_length, int new_egg_round, int egg_limit) : this(size, obstacles, snake_start_length, new_egg_round, egg_limit, TimeProvider.System)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event handlers
|
||||
private void _timer_Tick(object? sender)
|
||||
{
|
||||
Tick();
|
||||
GameUpdate?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
#endregion
|
||||
|
||||
public SnakeLevel(int size, IEnumerable<Point> obstacles, int snake_start_length, int new_egg_round, int egg_limit) : this(size, obstacles, snake_start_length, new_egg_round, egg_limit, TimeProvider.System)
|
||||
{
|
||||
}
|
||||
|
||||
public LevelBlock this[Point p]{
|
||||
get{
|
||||
if(_obstacles.Contains(p)){
|
||||
return LevelBlock.Obstacle;
|
||||
}else if(p == SnakeHead){
|
||||
return LevelBlock.SnakeHead;
|
||||
}else if(_snake.Contains(p)){
|
||||
return LevelBlock.Snake;
|
||||
}else if(_eggs.Contains(p)){
|
||||
return LevelBlock.Egg;
|
||||
}else if(p.X < 0 || p.Y < 0 || p.X>=Size || p.Y >= Size){
|
||||
return LevelBlock.OutOfBounds;
|
||||
}
|
||||
|
||||
return LevelBlock.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
#region Methods
|
||||
public void Tick(){
|
||||
Point new_snake_head = (0,0);
|
||||
switch(SnakeHeadDirection){
|
||||
case SnakeDirection.Up:
|
||||
new_snake_head = (SnakeHead.X, SnakeHead.Y - 1);
|
||||
break;
|
||||
case SnakeDirection.Down:
|
||||
new_snake_head = (SnakeHead.X, SnakeHead.Y + 1);
|
||||
break;
|
||||
case SnakeDirection.Left:
|
||||
new_snake_head = (SnakeHead.X - 1, SnakeHead.Y);
|
||||
break;
|
||||
case SnakeDirection.Right:
|
||||
new_snake_head = (SnakeHead.X + 1, SnakeHead.Y);
|
||||
break;
|
||||
}
|
||||
|
||||
if(this[new_snake_head] is LevelBlock.Obstacle or LevelBlock.Snake or LevelBlock.OutOfBounds){
|
||||
State = GameState.Dead;
|
||||
return;
|
||||
}
|
||||
|
||||
Point last = _snake.Last();
|
||||
|
||||
for (int i = Snake.Count - 1; i > 0; i--)
|
||||
if (!_disable_snake_movement)
|
||||
{
|
||||
_snake[i] = _snake[i - 1];
|
||||
}
|
||||
_snake[0] = SnakeHead;
|
||||
|
||||
if(this[new_snake_head] is LevelBlock.Egg){
|
||||
_snake.Add(last);
|
||||
_eggs.Remove(new_snake_head);
|
||||
Score++;
|
||||
}
|
||||
|
||||
SnakeHead = new_snake_head;
|
||||
|
||||
if (Eggs.Count < EggLimit && --_round == 0)
|
||||
{
|
||||
_round = NewEggRound;
|
||||
int avail_pos = Size * Size - Eggs.Count - Obstacles.Count - SnakeLength;
|
||||
if (avail_pos > 0)
|
||||
Point new_snake_head = (0, 0);
|
||||
switch (SnakeHeadDirection)
|
||||
{
|
||||
int pos = Random.Shared.Next(0, avail_pos);
|
||||
Point p = (0, 0);
|
||||
for (int i = 0; i < pos; i++)
|
||||
case SnakeDirection.Up:
|
||||
new_snake_head = (SnakeHead.X, SnakeHead.Y - 1);
|
||||
break;
|
||||
case SnakeDirection.Down:
|
||||
new_snake_head = (SnakeHead.X, SnakeHead.Y + 1);
|
||||
break;
|
||||
case SnakeDirection.Left:
|
||||
new_snake_head = (SnakeHead.X - 1, SnakeHead.Y);
|
||||
break;
|
||||
case SnakeDirection.Right:
|
||||
new_snake_head = (SnakeHead.X + 1, SnakeHead.Y);
|
||||
break;
|
||||
}
|
||||
|
||||
if (this[new_snake_head] is LevelBlock.Obstacle or LevelBlock.Snake or LevelBlock.OutOfBounds)
|
||||
{
|
||||
State = GameState.Dead;
|
||||
return;
|
||||
}
|
||||
|
||||
Point last = _snake.Last();
|
||||
|
||||
for (int i = Snake.Count - 1; i > 0; i--)
|
||||
{
|
||||
_snake[i] = _snake[i - 1];
|
||||
}
|
||||
_snake[0] = SnakeHead;
|
||||
|
||||
if (this[new_snake_head] is LevelBlock.Egg)
|
||||
{
|
||||
_snake.Add(last);
|
||||
_eggs.Remove(new_snake_head);
|
||||
Score++;
|
||||
}
|
||||
|
||||
SnakeHead = new_snake_head;
|
||||
}
|
||||
|
||||
if (!_disable_egg_generation)
|
||||
{
|
||||
if (Eggs.Count < EggLimit && --_round == 0)
|
||||
{
|
||||
_round = NewEggRound;
|
||||
int avail_pos = Size * Size - Eggs.Count - Obstacles.Count - SnakeLength;
|
||||
if (avail_pos > 0)
|
||||
{
|
||||
do
|
||||
int pos = Random.Shared.Next(0, avail_pos);
|
||||
Point p = (-1, 0);
|
||||
for (int i = 0; i < pos + 1; i++)
|
||||
{
|
||||
p = (p.X + 1, p.Y);
|
||||
if (p.X >= Size)
|
||||
do
|
||||
{
|
||||
p = (0, p.Y + 1);
|
||||
}
|
||||
} while (this[p] is not LevelBlock.Empty);
|
||||
p = (p.X + 1, p.Y);
|
||||
if (p.X >= Size)
|
||||
{
|
||||
p = (0, p.Y + 1);
|
||||
}
|
||||
} while (this[p] is not LevelBlock.Empty) ;
|
||||
}
|
||||
_eggs.Add(p);
|
||||
}
|
||||
_eggs.Add(p);
|
||||
}
|
||||
}
|
||||
|
||||
LastDirection = SnakeHeadDirection;
|
||||
_last_direction = SnakeHeadDirection;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
@@ -223,7 +269,17 @@ public class SnakeLevel : IDisposable{
|
||||
throw new Exception("Game is not running");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Testing
|
||||
internal void AddEgg(Point p)
|
||||
{
|
||||
_eggs.Add(p);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
private bool disposedValue;
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
@@ -253,4 +309,5 @@ public class SnakeLevel : IDisposable{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace model
|
||||
{
|
||||
/*internal class Timer : IDisposable
|
||||
{
|
||||
private PeriodicTimer _timer;
|
||||
public event EventHandler<EventArgs>? Tick;
|
||||
public Timer(TimeSpan tick, TimeProvider _time_provider)
|
||||
{
|
||||
_timer = new PeriodicTimer(tick, _time_provider);
|
||||
}
|
||||
|
||||
private CancellationTokenSource? _cts;
|
||||
private bool disposedValue;
|
||||
|
||||
private async Task TickerTask()
|
||||
{
|
||||
_cts = new CancellationTokenSource();
|
||||
while (await _timer.WaitForNextTickAsync(_cts.Token))
|
||||
{
|
||||
Tick?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
Task.Run(TickerTask);
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_cts?.Cancel();
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// TODO: dispose managed state (managed objects)
|
||||
_cts?.Dispose();
|
||||
}
|
||||
|
||||
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
|
||||
// TODO: set large fields to null
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
|
||||
// ~Timer()
|
||||
// {
|
||||
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
// Dispose(disposing: false);
|
||||
// }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
@@ -7,6 +7,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
|
||||
<_Parameter1>tests</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
<PackageReference Include="ELTE.FI.SARuleSet" Version="3.1.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
Reference in New Issue
Block a user