ALOT again
This commit is contained in:
parent
a98a463531
commit
e2c9116575
@ -2,7 +2,7 @@ using System.Numerics;
|
|||||||
|
|
||||||
namespace Snake.Model;
|
namespace Snake.Model;
|
||||||
|
|
||||||
// 0,0 is top left
|
// Struct to store a point. A bit functional looking
|
||||||
public readonly record struct Point{
|
public readonly record struct Point{
|
||||||
public readonly int X;
|
public readonly int X;
|
||||||
public readonly int Y;
|
public readonly int Y;
|
||||||
@ -12,6 +12,7 @@ public readonly record struct Point{
|
|||||||
Y = y;
|
Y = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is where the magic happens
|
||||||
public static implicit operator Point((int x, int y) tuple){
|
public static implicit operator Point((int x, int y) tuple){
|
||||||
return new Point(tuple.x, tuple.y);
|
return new Point(tuple.x, tuple.y);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,15 @@
|
|||||||
using model;
|
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace Snake.Model;
|
namespace Snake.Model;
|
||||||
|
|
||||||
public class SnakeLevel : IDisposable{
|
public class SnakeLevel : IDisposable{
|
||||||
public static readonly TimeSpan TickTimeSpan = TimeSpan.FromMilliseconds(500);
|
#region Enums
|
||||||
public int Size {get; }
|
/// <summary>
|
||||||
|
/// States that a 'block' can be in
|
||||||
public enum LevelBlock{
|
/// </summary>
|
||||||
|
public enum LevelBlock
|
||||||
|
{
|
||||||
Empty,
|
Empty,
|
||||||
Obstacle,
|
Obstacle,
|
||||||
Egg,
|
Egg,
|
||||||
@ -17,72 +18,121 @@ public class SnakeLevel : IDisposable{
|
|||||||
OutOfBounds
|
OutOfBounds
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum SnakeDirection{
|
/// <summary>
|
||||||
|
/// The directions that the snake can be headed
|
||||||
|
/// </summary>
|
||||||
|
public enum SnakeDirection
|
||||||
|
{
|
||||||
Up,
|
Up,
|
||||||
Down,
|
Down,
|
||||||
Left,
|
Left,
|
||||||
Right
|
Right
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum GameState{
|
/// <summary>
|
||||||
|
/// States that the game can be in
|
||||||
|
/// </summary>
|
||||||
|
public enum GameState
|
||||||
|
{
|
||||||
Running,
|
Running,
|
||||||
Paused,
|
Paused,
|
||||||
Dead
|
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 ReadOnlyCollection<Point> Obstacles => _obstacles.AsReadOnly();
|
||||||
public Point SnakeHead {get; private set;}
|
public int EggLimit { get; } // Limit of how many eggs can be on the map
|
||||||
private readonly List<Point> _snake;
|
|
||||||
|
public Point SnakeHead { get; private set; } // Coordinates of the head of the snake
|
||||||
// Head is not in snake
|
// Head is not in snake
|
||||||
// First is closest to the head
|
// First is closest to the head
|
||||||
public ReadOnlyCollection<Point> Snake => _snake.AsReadOnly();
|
public ReadOnlyCollection<Point> Snake => _snake.AsReadOnly();
|
||||||
public int SnakeLength => Snake.Count + 1;
|
public int SnakeLength => Snake.Count + 1; // Current length of the snake
|
||||||
private readonly List<Point> _eggs;
|
|
||||||
public ReadOnlyCollection<Point> Eggs => _eggs.AsReadOnly();
|
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 int Score { get; private set; } // Current score
|
||||||
public event EventHandler<GameState>? GameStateUpdate;
|
|
||||||
private GameState _state;
|
|
||||||
public GameState State
|
public GameState State
|
||||||
{
|
{
|
||||||
get => _state;
|
get => _state;
|
||||||
private set
|
private set
|
||||||
{
|
{
|
||||||
_state = value;
|
_state = value;
|
||||||
GameStateUpdate?.Invoke(this, _state);
|
if (_state == GameState.Dead)
|
||||||
if(_state == GameState.Dead)
|
|
||||||
{
|
{
|
||||||
timer.Change(Timeout.InfiniteTimeSpan, TickTimeSpan);
|
timer.Change(Timeout.InfiniteTimeSpan, TickTimeSpan);
|
||||||
}
|
}
|
||||||
|
GameStateUpdate?.Invoke(this, _state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private SnakeDirection LastDirection = SnakeDirection.Down;
|
|
||||||
private SnakeDirection _SnakeHeadDirection;
|
|
||||||
private bool disposedValue;
|
|
||||||
|
|
||||||
public SnakeDirection SnakeHeadDirection
|
public SnakeDirection SnakeHeadDirection
|
||||||
{
|
{
|
||||||
get => _SnakeHeadDirection;
|
get => _snake_head_direction;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
switch (value, LastDirection)
|
switch (value, _last_direction)
|
||||||
{
|
{
|
||||||
case { value: SnakeDirection.Up or SnakeDirection.Down, LastDirection: SnakeDirection.Left or SnakeDirection.Right}:
|
case { value: SnakeDirection.Up or SnakeDirection.Down, _last_direction: SnakeDirection.Left or SnakeDirection.Right }:
|
||||||
case { value: SnakeDirection.Left or SnakeDirection.Right, LastDirection: SnakeDirection.Up or SnakeDirection.Down }:
|
case { value: SnakeDirection.Left or SnakeDirection.Right, _last_direction: SnakeDirection.Up or SnakeDirection.Down }:
|
||||||
_SnakeHeadDirection = value;
|
_snake_head_direction = value;
|
||||||
break;
|
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){
|
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.Size = size;
|
||||||
this.NewEggRound = new_egg_round;
|
this.NewEggRound = new_egg_round;
|
||||||
@ -98,45 +148,35 @@ public class SnakeLevel : IDisposable{
|
|||||||
_snake.Add((size / 2, i));
|
_snake.Add((size / 2, i));
|
||||||
}
|
}
|
||||||
|
|
||||||
_SnakeHeadDirection = SnakeDirection.Down;
|
_snake_head_direction = SnakeDirection.Down;
|
||||||
LastDirection = SnakeDirection.Down;
|
_last_direction = SnakeDirection.Down;
|
||||||
|
|
||||||
timer = time_provider.CreateTimer(new TimerCallback(_timer_Tick), null, Timeout.InfiniteTimeSpan, TickTimeSpan);
|
timer = time_provider.CreateTimer(new TimerCallback(_timer_Tick), null, Timeout.InfiniteTimeSpan, TickTimeSpan);
|
||||||
|
|
||||||
_state = GameState.Paused;
|
_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)
|
private void _timer_Tick(object? sender)
|
||||||
{
|
{
|
||||||
Tick();
|
Tick();
|
||||||
GameUpdate?.Invoke(this, EventArgs.Empty);
|
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)
|
#region Methods
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Tick(){
|
public void Tick(){
|
||||||
Point new_snake_head = (0,0);
|
if (!_disable_snake_movement)
|
||||||
switch(SnakeHeadDirection){
|
{
|
||||||
|
Point new_snake_head = (0, 0);
|
||||||
|
switch (SnakeHeadDirection)
|
||||||
|
{
|
||||||
case SnakeDirection.Up:
|
case SnakeDirection.Up:
|
||||||
new_snake_head = (SnakeHead.X, SnakeHead.Y - 1);
|
new_snake_head = (SnakeHead.X, SnakeHead.Y - 1);
|
||||||
break;
|
break;
|
||||||
@ -151,7 +191,8 @@ public class SnakeLevel : IDisposable{
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this[new_snake_head] is LevelBlock.Obstacle or LevelBlock.Snake or LevelBlock.OutOfBounds){
|
if (this[new_snake_head] is LevelBlock.Obstacle or LevelBlock.Snake or LevelBlock.OutOfBounds)
|
||||||
|
{
|
||||||
State = GameState.Dead;
|
State = GameState.Dead;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -164,14 +205,18 @@ public class SnakeLevel : IDisposable{
|
|||||||
}
|
}
|
||||||
_snake[0] = SnakeHead;
|
_snake[0] = SnakeHead;
|
||||||
|
|
||||||
if(this[new_snake_head] is LevelBlock.Egg){
|
if (this[new_snake_head] is LevelBlock.Egg)
|
||||||
|
{
|
||||||
_snake.Add(last);
|
_snake.Add(last);
|
||||||
_eggs.Remove(new_snake_head);
|
_eggs.Remove(new_snake_head);
|
||||||
Score++;
|
Score++;
|
||||||
}
|
}
|
||||||
|
|
||||||
SnakeHead = new_snake_head;
|
SnakeHead = new_snake_head;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_disable_egg_generation)
|
||||||
|
{
|
||||||
if (Eggs.Count < EggLimit && --_round == 0)
|
if (Eggs.Count < EggLimit && --_round == 0)
|
||||||
{
|
{
|
||||||
_round = NewEggRound;
|
_round = NewEggRound;
|
||||||
@ -179,8 +224,8 @@ public class SnakeLevel : IDisposable{
|
|||||||
if (avail_pos > 0)
|
if (avail_pos > 0)
|
||||||
{
|
{
|
||||||
int pos = Random.Shared.Next(0, avail_pos);
|
int pos = Random.Shared.Next(0, avail_pos);
|
||||||
Point p = (0, 0);
|
Point p = (-1, 0);
|
||||||
for (int i = 0; i < pos; i++)
|
for (int i = 0; i < pos + 1; i++)
|
||||||
{
|
{
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
@ -189,13 +234,14 @@ public class SnakeLevel : IDisposable{
|
|||||||
{
|
{
|
||||||
p = (0, p.Y + 1);
|
p = (0, p.Y + 1);
|
||||||
}
|
}
|
||||||
} while (this[p] is not LevelBlock.Empty);
|
} while (this[p] is not LevelBlock.Empty) ;
|
||||||
}
|
}
|
||||||
_eggs.Add(p);
|
_eggs.Add(p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LastDirection = SnakeHeadDirection;
|
_last_direction = SnakeHeadDirection;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
@ -223,7 +269,17 @@ public class SnakeLevel : IDisposable{
|
|||||||
throw new Exception("Game is not running");
|
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)
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (!disposedValue)
|
if (!disposedValue)
|
||||||
@ -253,4 +309,5 @@ public class SnakeLevel : IDisposable{
|
|||||||
Dispose(disposing: true);
|
Dispose(disposing: true);
|
||||||
GC.SuppressFinalize(this);
|
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>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
|
||||||
|
<_Parameter1>tests</_Parameter1>
|
||||||
|
</AssemblyAttribute>
|
||||||
<PackageReference Include="ELTE.FI.SARuleSet" Version="3.1.0">
|
<PackageReference Include="ELTE.FI.SARuleSet" Version="3.1.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
|||||||
@ -12,13 +12,20 @@ public class SnakeLevelLoader{
|
|||||||
if(!File.Exists(SaveFileName)){
|
if(!File.Exists(SaveFileName)){
|
||||||
throw new FileNotFoundException("Save file not found", SaveFileName);
|
throw new FileNotFoundException("Save file not found", SaveFileName);
|
||||||
}
|
}
|
||||||
string file_content = File.ReadAllText(SaveFileName);
|
|
||||||
_stored_levels = JsonSerializer.Deserialize<List<StoredSnakeLevel>>(file_content, new JsonSerializerOptions(){PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower})
|
|
||||||
?? throw new JsonException("Failed to deserialize stored levels");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<StoredSnakeLevel> _stored_levels;
|
public async Task ReadAllDataAsync()
|
||||||
public ReadOnlyCollection<StoredSnakeLevel> StoredLevels => _stored_levels.AsReadOnly();
|
{
|
||||||
|
string file_content = await File.ReadAllTextAsync(SaveFileName);
|
||||||
|
_stored_levels = JsonSerializer.Deserialize<List<StoredSnakeLevel>>(file_content, new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower })
|
||||||
|
?? throw new JsonException("Failed to deserialize stored levels");
|
||||||
|
|
||||||
|
await Task.Delay(2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<StoredSnakeLevel>? _stored_levels;
|
||||||
|
public ReadOnlyCollection<StoredSnakeLevel> StoredLevels => _stored_levels?.AsReadOnly()
|
||||||
|
?? throw new InvalidOperationException("Can not query stored levels before loading the data");
|
||||||
|
|
||||||
public SnakeLevel LoadLevel(StoredSnakeLevel level){
|
public SnakeLevel LoadLevel(StoredSnakeLevel level){
|
||||||
return new SnakeLevel(level.Size,
|
return new SnakeLevel(level.Size,
|
||||||
|
|||||||
@ -1,9 +1,4 @@
|
|||||||
using Snake.Model;
|
using Snake.Model;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Microsoft.Extensions.Time.Testing;
|
using Microsoft.Extensions.Time.Testing;
|
||||||
|
|
||||||
@ -11,25 +6,39 @@ namespace tests.Model
|
|||||||
{
|
{
|
||||||
public class SnakeLevelTest
|
public class SnakeLevelTest
|
||||||
{
|
{
|
||||||
|
#region Level constants
|
||||||
|
private const int Size = 10;
|
||||||
|
private const int SnakeStartLength = 5;
|
||||||
|
private const int NewEggRound = 3;
|
||||||
|
private const int EggLimit = 2;
|
||||||
|
Point[] obstacles = [(0, 4), (3, 3)];
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Test objects
|
||||||
FakeTimeProvider timeProvider = new FakeTimeProvider();
|
FakeTimeProvider timeProvider = new FakeTimeProvider();
|
||||||
SnakeLevel level;
|
SnakeLevel level;
|
||||||
Point[] obstacles = [(3, 3), (4, 4)];
|
#endregion
|
||||||
|
|
||||||
|
#region Constructor and initializazion
|
||||||
public SnakeLevelTest()
|
public SnakeLevelTest()
|
||||||
{
|
{
|
||||||
level = new SnakeLevel(
|
level = new SnakeLevel(
|
||||||
10, obstacles,
|
size: Size, obstacles: obstacles,
|
||||||
5, 3, 2, timeProvider
|
snake_start_length: SnakeStartLength, new_egg_round: NewEggRound,
|
||||||
|
egg_limit: EggLimit, time_provider: timeProvider
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void SnakeInitialValuesTests()
|
public void SnakeInitialValuesTests()
|
||||||
{
|
{
|
||||||
level.SnakeHeadDirection.Should().Be(SnakeLevel.SnakeDirection.Down);
|
level.SnakeHeadDirection.Should().Be(SnakeLevel.SnakeDirection.Down);
|
||||||
level.SnakeLength.Should().Be(5);
|
level.SnakeLength.Should().Be(SnakeStartLength);
|
||||||
level.EggLimit.Should().Be(2);
|
level.EggLimit.Should().Be(EggLimit);
|
||||||
level.NewEggRound.Should().Be(3);
|
level.NewEggRound.Should().Be(NewEggRound);
|
||||||
level.Size.Should().Be(10);
|
level.Size.Should().Be(Size);
|
||||||
level.State.Should().Be(SnakeLevel.GameState.Paused);
|
level.State.Should().Be(SnakeLevel.GameState.Paused);
|
||||||
foreach(Point p in obstacles)
|
foreach(Point p in obstacles)
|
||||||
{
|
{
|
||||||
@ -40,6 +49,7 @@ namespace tests.Model
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void SnakeOutOfBoundsTest()
|
public void SnakeOutOfBoundsTest()
|
||||||
{
|
{
|
||||||
|
level._disable_egg_generation = true;
|
||||||
bool game_state_changed_raised = false;
|
bool game_state_changed_raised = false;
|
||||||
level.GameStateUpdate += (s, e) =>
|
level.GameStateUpdate += (s, e) =>
|
||||||
{
|
{
|
||||||
@ -50,7 +60,7 @@ namespace tests.Model
|
|||||||
|
|
||||||
game_state_changed_raised.Should().Be(true);
|
game_state_changed_raised.Should().Be(true);
|
||||||
game_state_changed_raised = false;
|
game_state_changed_raised = false;
|
||||||
for(int i=0; i<5; i++)
|
for(int i=0; i<Size - SnakeStartLength; i++)
|
||||||
{
|
{
|
||||||
timeProvider.Advance(SnakeLevel.TickTimeSpan);
|
timeProvider.Advance(SnakeLevel.TickTimeSpan);
|
||||||
}
|
}
|
||||||
@ -61,5 +71,78 @@ namespace tests.Model
|
|||||||
game_state_changed_raised.Should().Be(true);
|
game_state_changed_raised.Should().Be(true);
|
||||||
level.State.Should().Be(SnakeLevel.GameState.Dead);
|
level.State.Should().Be(SnakeLevel.GameState.Dead);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SnakeObstacleTest()
|
||||||
|
{
|
||||||
|
// Check the test constants
|
||||||
|
obstacles.Should().Contain((0, SnakeStartLength - 1), "There must be an obstacle on the left-most block next to the snake head for the obstacle test.");
|
||||||
|
for (int i = 1; i < Size / 2; i++)
|
||||||
|
{
|
||||||
|
obstacles.Should().NotContain((i, SnakeStartLength - 1), "There must not be an obstacle on the left of the snake head except for the one on the left-most position for the obstacle test.");
|
||||||
|
}
|
||||||
|
|
||||||
|
level._disable_egg_generation = true;
|
||||||
|
|
||||||
|
bool game_state_changed_raised = false;
|
||||||
|
level.GameStateUpdate += (s, e) =>
|
||||||
|
{
|
||||||
|
game_state_changed_raised = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
level.Start();
|
||||||
|
game_state_changed_raised.Should().Be(true);
|
||||||
|
game_state_changed_raised = false;
|
||||||
|
level.SnakeHeadDirection = SnakeLevel.SnakeDirection.Left;
|
||||||
|
for(int i=0; i < Size/2 - 1; i++)
|
||||||
|
{
|
||||||
|
timeProvider.Advance(SnakeLevel.TickTimeSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
game_state_changed_raised.Should().Be(false);
|
||||||
|
timeProvider.Advance(SnakeLevel.TickTimeSpan);
|
||||||
|
game_state_changed_raised.Should().Be(true);
|
||||||
|
level.State.Should().Be(SnakeLevel.GameState.Dead);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void EggPickupTest()
|
||||||
|
{
|
||||||
|
// check test constants
|
||||||
|
obstacles.Should().NotContain((Size / 2, SnakeStartLength), "There must be no eggs upto two blocks below the snake for egg pickup test");
|
||||||
|
obstacles.Should().NotContain((Size / 2, SnakeStartLength + 1), "There must be no eggs upto two blocks below the snake for egg pickup test");
|
||||||
|
|
||||||
|
level._disable_egg_generation = true;
|
||||||
|
Point test_egg = (Size / 2, SnakeStartLength + 1);
|
||||||
|
level.AddEgg(test_egg);
|
||||||
|
level.Start();
|
||||||
|
timeProvider.Advance(SnakeLevel.TickTimeSpan);
|
||||||
|
level.Eggs.Should().Contain(test_egg);
|
||||||
|
level.SnakeLength.Should().Be(SnakeStartLength);
|
||||||
|
timeProvider.Advance(SnakeLevel.TickTimeSpan);
|
||||||
|
level.Eggs.Should().NotContain(test_egg);
|
||||||
|
level.Score.Should().Be(1);
|
||||||
|
level.SnakeLength.Should().Be(SnakeStartLength + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void EggGenerationTest()
|
||||||
|
{
|
||||||
|
level._disable_snake_movement = true;
|
||||||
|
level.Start();
|
||||||
|
for(int i=0; i<EggLimit+1; i++)
|
||||||
|
{
|
||||||
|
level.Eggs.Should().HaveCount(i);
|
||||||
|
for(int j=0; j<NewEggRound; j++)
|
||||||
|
{
|
||||||
|
timeProvider.Advance(SnakeLevel.TickTimeSpan);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
timeProvider.Advance(SnakeLevel.TickTimeSpan);
|
||||||
|
level.Eggs.Should().HaveCount(EggLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ namespace Snake.Tests.Persistance;
|
|||||||
public class LevelLoaderTests
|
public class LevelLoaderTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void LoadLevelTest()
|
public async void LoadLevelTest()
|
||||||
{
|
{
|
||||||
// Preparre
|
// Preparre
|
||||||
string SaveFile =
|
string SaveFile =
|
||||||
@ -41,6 +41,7 @@ public class LevelLoaderTests
|
|||||||
File.WriteAllText(tempfile, SaveFile);
|
File.WriteAllText(tempfile, SaveFile);
|
||||||
|
|
||||||
var loader = new SnakeLevelLoader(tempfile);
|
var loader = new SnakeLevelLoader(tempfile);
|
||||||
|
await loader.ReadAllDataAsync();
|
||||||
Assert.Equal(2, loader.StoredLevels.Count);
|
Assert.Equal(2, loader.StoredLevels.Count);
|
||||||
|
|
||||||
var current_level = loader.StoredLevels[0];
|
var current_level = loader.StoredLevels[0];
|
||||||
|
|||||||
31
view/Form1.Designer.cs
generated
31
view/Form1.Designer.cs
generated
@ -29,6 +29,8 @@
|
|||||||
private void InitializeComponent()
|
private void InitializeComponent()
|
||||||
{
|
{
|
||||||
Canvas = new Panel();
|
Canvas = new Panel();
|
||||||
|
btnEndGame = new Button();
|
||||||
|
lblLoading = new Label();
|
||||||
btnStart = new Button();
|
btnStart = new Button();
|
||||||
panel1 = new Panel();
|
panel1 = new Panel();
|
||||||
txtScore = new TextBox();
|
txtScore = new TextBox();
|
||||||
@ -41,15 +43,39 @@
|
|||||||
//
|
//
|
||||||
Canvas.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
Canvas.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
||||||
Canvas.BackColor = Color.White;
|
Canvas.BackColor = Color.White;
|
||||||
|
Canvas.Controls.Add(btnEndGame);
|
||||||
|
Canvas.Controls.Add(lblLoading);
|
||||||
Canvas.Controls.Add(btnStart);
|
Canvas.Controls.Add(btnStart);
|
||||||
Canvas.Location = new Point(0, 0);
|
Canvas.Location = new Point(0, 0);
|
||||||
Canvas.Name = "Canvas";
|
Canvas.Name = "Canvas";
|
||||||
Canvas.Size = new Size(480, 480);
|
Canvas.Size = new Size(480, 480);
|
||||||
Canvas.TabIndex = 0;
|
Canvas.TabIndex = 0;
|
||||||
//
|
//
|
||||||
|
// btnEndGame
|
||||||
|
//
|
||||||
|
btnEndGame.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
||||||
|
btnEndGame.Location = new Point(193, 261);
|
||||||
|
btnEndGame.Name = "btnEndGame";
|
||||||
|
btnEndGame.Size = new Size(94, 29);
|
||||||
|
btnEndGame.TabIndex = 2;
|
||||||
|
btnEndGame.Text = "END GAME";
|
||||||
|
btnEndGame.UseVisualStyleBackColor = true;
|
||||||
|
btnEndGame.Click += button1_Click;
|
||||||
|
//
|
||||||
|
// lblLoading
|
||||||
|
//
|
||||||
|
lblLoading.AutoSize = true;
|
||||||
|
lblLoading.Font = new Font("Segoe UI", 15F);
|
||||||
|
lblLoading.Location = new Point(92, 151);
|
||||||
|
lblLoading.Name = "lblLoading";
|
||||||
|
lblLoading.Size = new Size(296, 35);
|
||||||
|
lblLoading.TabIndex = 1;
|
||||||
|
lblLoading.Text = "Loading data, please wait";
|
||||||
|
//
|
||||||
// btnStart
|
// btnStart
|
||||||
//
|
//
|
||||||
btnStart.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
btnStart.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
||||||
|
btnStart.Enabled = false;
|
||||||
btnStart.Location = new Point(193, 226);
|
btnStart.Location = new Point(193, 226);
|
||||||
btnStart.Name = "btnStart";
|
btnStart.Name = "btnStart";
|
||||||
btnStart.Size = new Size(94, 29);
|
btnStart.Size = new Size(94, 29);
|
||||||
@ -96,9 +122,12 @@
|
|||||||
Controls.Add(Canvas);
|
Controls.Add(Canvas);
|
||||||
Name = "Form1";
|
Name = "Form1";
|
||||||
Text = "Snake";
|
Text = "Snake";
|
||||||
|
FormClosing += Form1_FormClosing;
|
||||||
|
Shown += Form1_Shown;
|
||||||
KeyDown += Form1_KeyDown;
|
KeyDown += Form1_KeyDown;
|
||||||
PreviewKeyDown += Form1_PreviewKeyDown;
|
PreviewKeyDown += Form1_PreviewKeyDown;
|
||||||
Canvas.ResumeLayout(false);
|
Canvas.ResumeLayout(false);
|
||||||
|
Canvas.PerformLayout();
|
||||||
panel1.ResumeLayout(false);
|
panel1.ResumeLayout(false);
|
||||||
panel1.PerformLayout();
|
panel1.PerformLayout();
|
||||||
ResumeLayout(false);
|
ResumeLayout(false);
|
||||||
@ -111,5 +140,7 @@
|
|||||||
private Label label1;
|
private Label label1;
|
||||||
private TextBox txtScore;
|
private TextBox txtScore;
|
||||||
private Button btnStart;
|
private Button btnStart;
|
||||||
|
private Label lblLoading;
|
||||||
|
private Button btnEndGame;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
169
view/Form1.cs
169
view/Form1.cs
@ -5,75 +5,61 @@ namespace view
|
|||||||
{
|
{
|
||||||
public partial class Form1 : Form
|
public partial class Form1 : Form
|
||||||
{
|
{
|
||||||
Graphics graphics;
|
#region Fields
|
||||||
int line_width = 2;
|
const int line_width = 2; // Width of the table lines
|
||||||
SnakeLevel? CurrentLevel;
|
SnakeLevel? CurrentLevel; // Current game state
|
||||||
SnakeLevelLoader level_loader;
|
SnakeLevelLoader level_loader; // Level loader
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructor
|
||||||
public Form1()
|
public Form1()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
graphics = Canvas.CreateGraphics();
|
|
||||||
level_loader = new SnakeLevelLoader(Path.Combine(Directory.GetCurrentDirectory(), "levels.json"));
|
level_loader = new SnakeLevelLoader(Path.Combine(Directory.GetCurrentDirectory(), "levels.json"));
|
||||||
}
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
private void Form1_KeyDown(object sender, KeyEventArgs e)
|
#region Draw Funciton
|
||||||
|
/// <summary>
|
||||||
|
/// Draws the current map to the canvas
|
||||||
|
/// </summary>
|
||||||
|
private void Draw()
|
||||||
{
|
{
|
||||||
if (CurrentLevel == null) return;
|
if (CurrentLevel == null) return;
|
||||||
switch (e.KeyCode)
|
|
||||||
{
|
|
||||||
case Keys.Up:
|
|
||||||
CurrentLevel.SnakeHeadDirection = SnakeLevel.SnakeDirection.Up;
|
|
||||||
break;
|
|
||||||
case Keys.Down:
|
|
||||||
CurrentLevel.SnakeHeadDirection = SnakeLevel.SnakeDirection.Down;
|
|
||||||
break;
|
|
||||||
case Keys.Left:
|
|
||||||
CurrentLevel.SnakeHeadDirection = SnakeLevel.SnakeDirection.Left;
|
|
||||||
break;
|
|
||||||
case Keys.Right:
|
|
||||||
CurrentLevel.SnakeHeadDirection = SnakeLevel.SnakeDirection.Right;
|
|
||||||
break;
|
|
||||||
case Keys.Escape:
|
|
||||||
if(CurrentLevel.State == SnakeLevel.GameState.Running)
|
|
||||||
CurrentLevel.Pause();
|
|
||||||
break;
|
|
||||||
|
|
||||||
}
|
using Graphics canvas_graphics = Canvas.CreateGraphics();
|
||||||
}
|
|
||||||
|
|
||||||
private void Draw(SnakeLevel level)
|
|
||||||
{
|
|
||||||
var canvas_size = Math.Min(Canvas.Width, Canvas.Height);
|
|
||||||
graphics = Canvas.CreateGraphics();
|
|
||||||
|
|
||||||
|
using Bitmap bmp = new Bitmap(Canvas.Width, Canvas.Height, canvas_graphics);
|
||||||
|
using Graphics graphics = Graphics.FromImage(bmp);
|
||||||
|
|
||||||
graphics.Clear(Color.White);
|
graphics.Clear(Color.White);
|
||||||
double cell_size = (double)(canvas_size - (level.Size + 1) * line_width) / level.Size;
|
var canvas_size = Math.Min(Canvas.Width, Canvas.Height);
|
||||||
|
|
||||||
|
double cell_size = (double)(canvas_size - (CurrentLevel.Size + 1) * line_width) / CurrentLevel.Size;
|
||||||
// vertical lines
|
// vertical lines
|
||||||
for (int i = 0; i <= level.Size; i++)
|
for (int i = 0; i <= CurrentLevel.Size; i++)
|
||||||
{
|
{
|
||||||
int x = (int)(i * (cell_size + line_width));
|
int x = (int)(i * (cell_size + line_width));
|
||||||
graphics.FillRectangle(Brushes.Black, x, 0, line_width, canvas_size);
|
graphics.FillRectangle(Brushes.Black, x, 0, line_width, canvas_size);
|
||||||
}
|
}
|
||||||
// horizontal lines
|
// horizontal lines
|
||||||
for (int i = 0; i <= level.Size; i++)
|
for (int i = 0; i <= CurrentLevel.Size; i++)
|
||||||
{
|
{
|
||||||
int y = (int)(i * (cell_size + line_width));
|
int y = (int)(i * (cell_size + line_width));
|
||||||
graphics.FillRectangle(Brushes.Black, 0, y, canvas_size, line_width);
|
graphics.FillRectangle(Brushes.Black, 0, y, canvas_size, line_width);
|
||||||
}
|
}
|
||||||
// Ssnake
|
// Ssnake
|
||||||
foreach (var pos in level.Snake)
|
foreach (var pos in CurrentLevel.Snake)
|
||||||
{
|
{
|
||||||
int x = (int)(cell_size * pos.X + line_width * (pos.X + 1));
|
int x = (int)(cell_size * pos.X + line_width * (pos.X + 1));
|
||||||
int y = (int)(cell_size * pos.Y + line_width * (pos.Y + 1));
|
int y = (int)(cell_size * pos.Y + line_width * (pos.Y + 1));
|
||||||
graphics.FillRectangle(Brushes.Green, x, y, (int)cell_size, (int)cell_size);
|
graphics.FillRectangle(Brushes.Green, x, y, (int)cell_size, (int)cell_size);
|
||||||
}
|
}
|
||||||
// snake head
|
// snake head
|
||||||
int snake_head_x = (int)(cell_size * level.SnakeHead.X + line_width * (level.SnakeHead.X + 1));
|
int snake_head_x = (int)(cell_size * CurrentLevel.SnakeHead.X + line_width * (CurrentLevel.SnakeHead.X + 1));
|
||||||
int snake_head_y = (int)(cell_size * level.SnakeHead.Y + line_width * (level.SnakeHead.Y + 1));
|
int snake_head_y = (int)(cell_size * CurrentLevel.SnakeHead.Y + line_width * (CurrentLevel.SnakeHead.Y + 1));
|
||||||
graphics.FillRectangle(Brushes.Red, snake_head_x, snake_head_y, (int)cell_size, (int)cell_size);
|
graphics.FillRectangle(Brushes.Red, snake_head_x, snake_head_y, (int)cell_size, (int)cell_size);
|
||||||
switch (level.SnakeHeadDirection)
|
switch (CurrentLevel.SnakeHeadDirection)
|
||||||
{
|
{
|
||||||
case SnakeLevel.SnakeDirection.Up:
|
case SnakeLevel.SnakeDirection.Up:
|
||||||
graphics.DrawLine(Pens.Black, snake_head_x + (int)cell_size / 2, snake_head_y, snake_head_x + (int)cell_size / 2, snake_head_y + (int)cell_size);
|
graphics.DrawLine(Pens.Black, snake_head_x + (int)cell_size / 2, snake_head_y, snake_head_x + (int)cell_size / 2, snake_head_y + (int)cell_size);
|
||||||
@ -99,7 +85,7 @@ namespace view
|
|||||||
}
|
}
|
||||||
|
|
||||||
// obstacles
|
// obstacles
|
||||||
foreach (var pos in level.Obstacles)
|
foreach (var pos in CurrentLevel.Obstacles)
|
||||||
{
|
{
|
||||||
int x = (int)(cell_size * pos.X + line_width * (pos.X + 1));
|
int x = (int)(cell_size * pos.X + line_width * (pos.X + 1));
|
||||||
int y = (int)(cell_size * pos.Y + line_width * (pos.Y + 1));
|
int y = (int)(cell_size * pos.Y + line_width * (pos.Y + 1));
|
||||||
@ -107,12 +93,41 @@ namespace view
|
|||||||
}
|
}
|
||||||
|
|
||||||
//eggs
|
//eggs
|
||||||
foreach (var pos in level.Eggs)
|
foreach (var pos in CurrentLevel.Eggs)
|
||||||
{
|
{
|
||||||
int x = (int)(cell_size * pos.X + line_width * (pos.X + 1));
|
int x = (int)(cell_size * pos.X + line_width * (pos.X + 1));
|
||||||
int y = (int)(cell_size * pos.Y + line_width * (pos.Y + 1));
|
int y = (int)(cell_size * pos.Y + line_width * (pos.Y + 1));
|
||||||
graphics.FillRectangle(Brushes.Pink, x, y, (int)cell_size, (int)cell_size);
|
graphics.FillRectangle(Brushes.Pink, x, y, (int)cell_size, (int)cell_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canvas_graphics.DrawImage(bmp, 0, 0);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Form event handlers
|
||||||
|
private void Form1_KeyDown(object sender, KeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (CurrentLevel == null) return;
|
||||||
|
switch (e.KeyCode)
|
||||||
|
{
|
||||||
|
case Keys.Up:
|
||||||
|
CurrentLevel.SnakeHeadDirection = SnakeLevel.SnakeDirection.Up;
|
||||||
|
break;
|
||||||
|
case Keys.Down:
|
||||||
|
CurrentLevel.SnakeHeadDirection = SnakeLevel.SnakeDirection.Down;
|
||||||
|
break;
|
||||||
|
case Keys.Left:
|
||||||
|
CurrentLevel.SnakeHeadDirection = SnakeLevel.SnakeDirection.Left;
|
||||||
|
break;
|
||||||
|
case Keys.Right:
|
||||||
|
CurrentLevel.SnakeHeadDirection = SnakeLevel.SnakeDirection.Right;
|
||||||
|
break;
|
||||||
|
case Keys.Escape:
|
||||||
|
if (CurrentLevel.State == SnakeLevel.GameState.Running)
|
||||||
|
CurrentLevel.Pause();
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnStart_Click(object sender, EventArgs e)
|
private void btnStart_Click(object sender, EventArgs e)
|
||||||
@ -139,31 +154,86 @@ namespace view
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CurrentLevel_GameUpdate(object? sender, EventArgs e)
|
private void Form1_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
|
||||||
|
{
|
||||||
|
if (CurrentLevel?.State is SnakeLevel.GameState.Running)
|
||||||
|
{
|
||||||
|
e.IsInputKey = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Form1_Shown(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
btnEndGame.Visible = false; // It will trigger a whole repaint the first time it is shown unless this is done
|
||||||
|
level_loader.ReadAllDataAsync().ContinueWith((task) =>
|
||||||
|
{
|
||||||
|
if (!this.IsDisposed) // The form can be closed before the file is loaded
|
||||||
{
|
{
|
||||||
this.Invoke(() =>
|
this.Invoke(() =>
|
||||||
|
{
|
||||||
|
if (task.IsCompletedSuccessfully)
|
||||||
|
{
|
||||||
|
lblLoading.Visible = false;
|
||||||
|
btnStart.Enabled = true;
|
||||||
|
btnStart.Focus();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MessageBox.Show("Failed to read data");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void button1_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
CurrentLevel = null;
|
||||||
|
btnEndGame.Visible = false;
|
||||||
|
Canvas.Refresh();
|
||||||
|
txtScore.Text = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
|
||||||
{
|
{
|
||||||
if (CurrentLevel != null)
|
if (CurrentLevel != null)
|
||||||
{
|
{
|
||||||
Draw(CurrentLevel);
|
if (CurrentLevel.State == SnakeLevel.GameState.Running)
|
||||||
txtScore.Text = CurrentLevel.Score.ToString();
|
{
|
||||||
|
CurrentLevel.Pause();
|
||||||
}
|
}
|
||||||
|
CurrentLevel = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Game event handlers
|
||||||
|
private void CurrentLevel_GameUpdate(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (CurrentLevel == null) return;
|
||||||
|
this.Invoke(() =>
|
||||||
|
{
|
||||||
|
Draw();
|
||||||
|
txtScore.Text = CurrentLevel.Score.ToString();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CurrentLevel_GameStateUpdate(object? sender, SnakeLevel.GameState e)
|
private void CurrentLevel_GameStateUpdate(object? sender, SnakeLevel.GameState e)
|
||||||
{
|
{
|
||||||
|
if (CurrentLevel == null) return;
|
||||||
this.Invoke(() =>
|
this.Invoke(() =>
|
||||||
{
|
{
|
||||||
if (CurrentLevel == null) return;
|
|
||||||
switch (e)
|
switch (e)
|
||||||
{
|
{
|
||||||
case SnakeLevel.GameState.Running:
|
case SnakeLevel.GameState.Running:
|
||||||
btnStart.Visible = false;
|
btnStart.Visible = false;
|
||||||
Draw(CurrentLevel);
|
btnEndGame.Visible = false;
|
||||||
|
Draw();
|
||||||
break;
|
break;
|
||||||
case SnakeLevel.GameState.Paused:
|
case SnakeLevel.GameState.Paused:
|
||||||
btnStart.Visible = true;
|
btnStart.Visible = true;
|
||||||
|
btnEndGame.Visible = true;
|
||||||
break;
|
break;
|
||||||
case SnakeLevel.GameState.Dead:
|
case SnakeLevel.GameState.Dead:
|
||||||
btnStart.Visible = true;
|
btnStart.Visible = true;
|
||||||
@ -172,13 +242,6 @@ namespace view
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
#endregion
|
||||||
private void Form1_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
|
|
||||||
{
|
|
||||||
if(CurrentLevel?.State is SnakeLevel.GameState.Running)
|
|
||||||
{
|
|
||||||
e.IsInputKey = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,8 +4,15 @@ namespace view
|
|||||||
{
|
{
|
||||||
public partial class MapChooser : Form
|
public partial class MapChooser : Form
|
||||||
{
|
{
|
||||||
|
#region Fields
|
||||||
private SnakeLevelLoader _level_loader;
|
private SnakeLevelLoader _level_loader;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties
|
||||||
public StoredSnakeLevel? SelectedLevel { get; private set; }
|
public StoredSnakeLevel? SelectedLevel { get; private set; }
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructor
|
||||||
public MapChooser(SnakeLevelLoader level_loader)
|
public MapChooser(SnakeLevelLoader level_loader)
|
||||||
{
|
{
|
||||||
this._level_loader = level_loader;
|
this._level_loader = level_loader;
|
||||||
@ -16,7 +23,24 @@ namespace view
|
|||||||
lstLevels.Items.Add(level.LevelName);
|
lstLevels.Items.Add(level.LevelName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
void ChooseLevel()
|
||||||
|
{
|
||||||
|
if (lstLevels.SelectedIndex == -1)
|
||||||
|
{
|
||||||
|
SelectedLevel = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SelectedLevel = _level_loader.StoredLevels[lstLevels.SelectedIndex];
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Event handlers
|
||||||
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
|
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (lstLevels.SelectedIndex == -1)
|
if (lstLevels.SelectedIndex == -1)
|
||||||
@ -48,18 +72,6 @@ namespace view
|
|||||||
ChooseLevel();
|
ChooseLevel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endregion
|
||||||
void ChooseLevel()
|
|
||||||
{
|
|
||||||
if (lstLevels.SelectedIndex == -1)
|
|
||||||
{
|
|
||||||
SelectedLevel = null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SelectedLevel = _level_loader.StoredLevels[lstLevels.SelectedIndex];
|
|
||||||
Close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,5 +21,50 @@
|
|||||||
[ 2, 3 ],
|
[ 2, 3 ],
|
||||||
[ 1, 2 ]
|
[ 1, 2 ]
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level_name": "Easy",
|
||||||
|
"size": 10,
|
||||||
|
"snake_start_length": 5,
|
||||||
|
"new_egg_round": 2,
|
||||||
|
"egg_limit": 4,
|
||||||
|
"obstacles": [
|
||||||
|
[ 1, 1 ],
|
||||||
|
[ 1, 8 ],
|
||||||
|
[ 8, 1 ],
|
||||||
|
[ 8, 8 ]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level_name": "Medium",
|
||||||
|
"size": 9,
|
||||||
|
"snake_start_length": 5,
|
||||||
|
"new_egg_round": 4,
|
||||||
|
"egg_limit": 2,
|
||||||
|
"obstacles": [
|
||||||
|
[ 1, 1 ],
|
||||||
|
[ 1, 7 ],
|
||||||
|
[ 7, 1 ],
|
||||||
|
[ 7, 7 ]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level_name": "Hard",
|
||||||
|
"size": 7,
|
||||||
|
"snake_start_length": 5,
|
||||||
|
"new_egg_round": 8,
|
||||||
|
"egg_limit": 1,
|
||||||
|
"obstacles": [
|
||||||
|
[ 1, 1 ],
|
||||||
|
[ 1, 2 ],
|
||||||
|
[ 1, 3 ],
|
||||||
|
[ 1, 4 ],
|
||||||
|
[ 1, 5 ],
|
||||||
|
[ 5, 1 ],
|
||||||
|
[ 5, 2 ],
|
||||||
|
[ 5, 3 ],
|
||||||
|
[ 5, 4 ],
|
||||||
|
[ 5, 5 ]
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
Loading…
Reference in New Issue
Block a user