lots of staff
This commit is contained in:
parent
ff23702c15
commit
a98a463531
@ -1,10 +1,10 @@
|
|||||||
|
using model;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.IO.Pipes;
|
using System.Diagnostics;
|
||||||
using System.Numerics;
|
|
||||||
|
|
||||||
namespace Snake.Model;
|
namespace Snake.Model;
|
||||||
|
|
||||||
public class SnakeLevel{
|
public class SnakeLevel : IDisposable{
|
||||||
public static readonly TimeSpan TickTimeSpan = TimeSpan.FromMilliseconds(500);
|
public static readonly TimeSpan TickTimeSpan = TimeSpan.FromMilliseconds(500);
|
||||||
public int Size {get; }
|
public int Size {get; }
|
||||||
|
|
||||||
@ -43,8 +43,7 @@ public class SnakeLevel{
|
|||||||
public int NewEggRound { get; }
|
public int NewEggRound { get; }
|
||||||
public int EggLimit { get; }
|
public int EggLimit { get; }
|
||||||
private int _round;
|
private int _round;
|
||||||
|
private ITimer timer;
|
||||||
private PeriodicTimer _timer;
|
|
||||||
public int Score { get; private set; }
|
public int Score { get; private set; }
|
||||||
|
|
||||||
public event EventHandler? GameUpdate;
|
public event EventHandler? GameUpdate;
|
||||||
@ -57,12 +56,18 @@ public class SnakeLevel{
|
|||||||
{
|
{
|
||||||
_state = value;
|
_state = value;
|
||||||
GameStateUpdate?.Invoke(this, _state);
|
GameStateUpdate?.Invoke(this, _state);
|
||||||
|
if(_state == GameState.Dead)
|
||||||
|
{
|
||||||
|
timer.Change(Timeout.InfiniteTimeSpan, TickTimeSpan);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private SnakeDirection LastDirection = SnakeDirection.Down;
|
private SnakeDirection LastDirection = SnakeDirection.Down;
|
||||||
private SnakeDirection _SnakeHeadDirection;
|
private SnakeDirection _SnakeHeadDirection;
|
||||||
|
private bool disposedValue;
|
||||||
|
|
||||||
public SnakeDirection SnakeHeadDirection
|
public SnakeDirection SnakeHeadDirection
|
||||||
{
|
{
|
||||||
get => _SnakeHeadDirection;
|
get => _SnakeHeadDirection;
|
||||||
@ -96,11 +101,17 @@ public class SnakeLevel{
|
|||||||
_SnakeHeadDirection = SnakeDirection.Down;
|
_SnakeHeadDirection = SnakeDirection.Down;
|
||||||
LastDirection = SnakeDirection.Down;
|
LastDirection = SnakeDirection.Down;
|
||||||
|
|
||||||
_timer = new PeriodicTimer(TickTimeSpan, time_provider);
|
timer = time_provider.CreateTimer(new TimerCallback(_timer_Tick), null, Timeout.InfiniteTimeSpan, TickTimeSpan);
|
||||||
|
|
||||||
_state = GameState.Paused;
|
_state = GameState.Paused;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void _timer_Tick(object? sender)
|
||||||
|
{
|
||||||
|
Tick();
|
||||||
|
GameUpdate?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
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 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)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -187,21 +198,12 @@ public class SnakeLevel{
|
|||||||
LastDirection = SnakeHeadDirection;
|
LastDirection = SnakeHeadDirection;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task TickerTask()
|
|
||||||
{
|
|
||||||
while(State == GameState.Running && await _timer.WaitForNextTickAsync())
|
|
||||||
{
|
|
||||||
Tick();
|
|
||||||
GameUpdate?.Invoke(this, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
if(State == GameState.Paused)
|
if(State == GameState.Paused)
|
||||||
{
|
{
|
||||||
State = GameState.Running;
|
State = GameState.Running;
|
||||||
Task.Run(TickerTask);
|
timer.Change(TickTimeSpan, TickTimeSpan);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -214,10 +216,41 @@ public class SnakeLevel{
|
|||||||
if(State == GameState.Running)
|
if(State == GameState.Running)
|
||||||
{
|
{
|
||||||
State = GameState.Paused;
|
State = GameState.Paused;
|
||||||
|
timer.Change(Timeout.InfiniteTimeSpan, TickTimeSpan);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new Exception("Game is not running");
|
throw new Exception("Game is not running");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!disposedValue)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
// TODO: dispose managed state (managed objects)
|
||||||
|
timer.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
|
||||||
|
// ~SnakeLevel()
|
||||||
|
// {
|
||||||
|
// // 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
70
model/Timer.cs
Normal file
70
model/Timer.cs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
||||||
@ -6,4 +6,11 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="ELTE.FI.SARuleSet" Version="3.1.0">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
65
tests/Model/SnakeLevelTest.cs
Normal file
65
tests/Model/SnakeLevelTest.cs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
using Snake.Model;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Microsoft.Extensions.Time.Testing;
|
||||||
|
|
||||||
|
namespace tests.Model
|
||||||
|
{
|
||||||
|
public class SnakeLevelTest
|
||||||
|
{
|
||||||
|
FakeTimeProvider timeProvider = new FakeTimeProvider();
|
||||||
|
SnakeLevel level;
|
||||||
|
Point[] obstacles = [(3, 3), (4, 4)];
|
||||||
|
public SnakeLevelTest()
|
||||||
|
{
|
||||||
|
level = new SnakeLevel(
|
||||||
|
10, obstacles,
|
||||||
|
5, 3, 2, timeProvider
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SnakeInitialValuesTests()
|
||||||
|
{
|
||||||
|
level.SnakeHeadDirection.Should().Be(SnakeLevel.SnakeDirection.Down);
|
||||||
|
level.SnakeLength.Should().Be(5);
|
||||||
|
level.EggLimit.Should().Be(2);
|
||||||
|
level.NewEggRound.Should().Be(3);
|
||||||
|
level.Size.Should().Be(10);
|
||||||
|
level.State.Should().Be(SnakeLevel.GameState.Paused);
|
||||||
|
foreach(Point p in obstacles)
|
||||||
|
{
|
||||||
|
level[p].Should().Be(SnakeLevel.LevelBlock.Obstacle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SnakeOutOfBoundsTest()
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
for(int i=0; i<5; 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -9,7 +9,7 @@ public class LevelLoaderTests
|
|||||||
public void LoadLevelTest()
|
public void LoadLevelTest()
|
||||||
{
|
{
|
||||||
// Preparre
|
// Preparre
|
||||||
string SaveFile =
|
string SaveFile =
|
||||||
"""
|
"""
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@ -20,7 +20,9 @@ public class LevelLoaderTests
|
|||||||
[1,1],
|
[1,1],
|
||||||
[4,2],
|
[4,2],
|
||||||
[6,9]
|
[6,9]
|
||||||
]
|
],
|
||||||
|
"new_egg_round": 2,
|
||||||
|
"egg_limit": 5
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level_name": "Cool snake map",
|
"level_name": "Cool snake map",
|
||||||
@ -29,7 +31,9 @@ public class LevelLoaderTests
|
|||||||
"obstacles": [
|
"obstacles": [
|
||||||
[2,3],
|
[2,3],
|
||||||
[1,2]
|
[1,2]
|
||||||
]
|
],
|
||||||
|
"new_egg_round": 3,
|
||||||
|
"egg_limit": 6
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
""";
|
""";
|
||||||
@ -44,11 +48,15 @@ public class LevelLoaderTests
|
|||||||
Assert.Equal(10, current_level.Size);
|
Assert.Equal(10, current_level.Size);
|
||||||
Assert.Equal(5, current_level.SnakeStartLength);
|
Assert.Equal(5, current_level.SnakeStartLength);
|
||||||
Assert.Equal(current_level.Obstacles, [[1,1], [4,2], [6,9]]);
|
Assert.Equal(current_level.Obstacles, [[1,1], [4,2], [6,9]]);
|
||||||
|
Assert.Equal(2, current_level.NewEggRound);
|
||||||
|
Assert.Equal(5, current_level.EggLimit);
|
||||||
|
|
||||||
current_level = loader.StoredLevels[1];
|
current_level = loader.StoredLevels[1];
|
||||||
Assert.Equal("Cool snake map", current_level.LevelName);
|
Assert.Equal("Cool snake map", current_level.LevelName);
|
||||||
Assert.Equal(12, current_level.Size);
|
Assert.Equal(12, current_level.Size);
|
||||||
Assert.Equal(3, current_level.SnakeStartLength);
|
Assert.Equal(3, current_level.SnakeStartLength);
|
||||||
Assert.Equal(current_level.Obstacles, [[2,3], [1,2]]);
|
Assert.Equal(current_level.Obstacles, [[2,3], [1,2]]);
|
||||||
|
Assert.Equal(3, current_level.NewEggRound);
|
||||||
|
Assert.Equal(6, current_level.EggLimit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
@ -10,6 +10,8 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="FluentAssertions" Version="6.12.1" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.TimeProvider.Testing" Version="8.10.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
|
||||||
<PackageReference Include="xunit" Version="2.4.2" />
|
<PackageReference Include="xunit" Version="2.4.2" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||||
|
|||||||
2
view/Form1.Designer.cs
generated
2
view/Form1.Designer.cs
generated
@ -39,9 +39,9 @@
|
|||||||
//
|
//
|
||||||
// Canvas
|
// Canvas
|
||||||
//
|
//
|
||||||
|
Canvas.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
||||||
Canvas.BackColor = Color.White;
|
Canvas.BackColor = Color.White;
|
||||||
Canvas.Controls.Add(btnStart);
|
Canvas.Controls.Add(btnStart);
|
||||||
Canvas.Dock = DockStyle.Top;
|
|
||||||
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);
|
||||||
|
|||||||
@ -35,7 +35,8 @@ namespace view
|
|||||||
CurrentLevel.SnakeHeadDirection = SnakeLevel.SnakeDirection.Right;
|
CurrentLevel.SnakeHeadDirection = SnakeLevel.SnakeDirection.Right;
|
||||||
break;
|
break;
|
||||||
case Keys.Escape:
|
case Keys.Escape:
|
||||||
CurrentLevel.Pause();
|
if(CurrentLevel.State == SnakeLevel.GameState.Running)
|
||||||
|
CurrentLevel.Pause();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -44,6 +45,7 @@ namespace view
|
|||||||
private void Draw(SnakeLevel level)
|
private void Draw(SnakeLevel level)
|
||||||
{
|
{
|
||||||
var canvas_size = Math.Min(Canvas.Width, Canvas.Height);
|
var canvas_size = Math.Min(Canvas.Width, Canvas.Height);
|
||||||
|
graphics = Canvas.CreateGraphics();
|
||||||
|
|
||||||
|
|
||||||
graphics.Clear(Color.White);
|
graphics.Clear(Color.White);
|
||||||
@ -80,14 +82,18 @@ namespace view
|
|||||||
break;
|
break;
|
||||||
case SnakeLevel.SnakeDirection.Down:
|
case SnakeLevel.SnakeDirection.Down:
|
||||||
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);
|
||||||
graphics.DrawLine(Pens.Black, snake_head_x + (int)cell_size / 2, snake_head_y, snake_head_x, snake_head_y + (int)cell_size / 2);
|
graphics.DrawLine(Pens.Black, snake_head_x + (int)cell_size / 2, snake_head_y + (int)cell_size, snake_head_x, snake_head_y + (int)cell_size / 2);
|
||||||
graphics.DrawLine(Pens.Black, snake_head_x + (int)cell_size / 2, snake_head_y, snake_head_x + (int)cell_size, snake_head_y + (int)cell_size / 2);
|
graphics.DrawLine(Pens.Black, snake_head_x + (int)cell_size / 2, snake_head_y + (int)cell_size, snake_head_x + (int)cell_size, snake_head_y + (int)cell_size / 2);
|
||||||
break;
|
break;
|
||||||
case SnakeLevel.SnakeDirection.Left:
|
case SnakeLevel.SnakeDirection.Left:
|
||||||
graphics.DrawLine(Pens.Black, snake_head_x, snake_head_y + (int)cell_size / 2, snake_head_x + (int)cell_size, snake_head_y + (int)cell_size / 2);
|
graphics.DrawLine(Pens.Black, snake_head_x, snake_head_y + (int)cell_size / 2, snake_head_x + (int)cell_size, snake_head_y + (int)cell_size / 2);
|
||||||
|
graphics.DrawLine(Pens.Black, snake_head_x, snake_head_y + (int)(cell_size / 2), snake_head_x + (int)(cell_size / 2), snake_head_y);
|
||||||
|
graphics.DrawLine(Pens.Black, snake_head_x, snake_head_y + (int)(cell_size / 2), snake_head_x + (int)(cell_size / 2), snake_head_y + (int)cell_size);
|
||||||
break;
|
break;
|
||||||
case SnakeLevel.SnakeDirection.Right:
|
case SnakeLevel.SnakeDirection.Right:
|
||||||
graphics.DrawLine(Pens.Black, snake_head_x, snake_head_y + (int)cell_size / 2, snake_head_x + (int)cell_size, snake_head_y + (int)cell_size / 2);
|
graphics.DrawLine(Pens.Black, snake_head_x, snake_head_y + (int)cell_size / 2, snake_head_x + (int)cell_size, snake_head_y + (int)cell_size / 2);
|
||||||
|
graphics.DrawLine(Pens.Black, snake_head_x + (int)cell_size, snake_head_y + (int)(cell_size / 2), snake_head_x + (int)(cell_size / 2), snake_head_y);
|
||||||
|
graphics.DrawLine(Pens.Black, snake_head_x + (int)cell_size, snake_head_y + (int)(cell_size / 2), snake_head_x + (int)(cell_size / 2), snake_head_y + (int)cell_size);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -111,7 +117,7 @@ namespace view
|
|||||||
|
|
||||||
private void btnStart_Click(object sender, EventArgs e)
|
private void btnStart_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (CurrentLevel is null || CurrentLevel.State != SnakeLevel.GameState.Running)
|
if (CurrentLevel is null || CurrentLevel.State == SnakeLevel.GameState.Dead)
|
||||||
{
|
{
|
||||||
var MapChooser = new MapChooser(this.level_loader);
|
var MapChooser = new MapChooser(this.level_loader);
|
||||||
MapChooser.ShowDialog();
|
MapChooser.ShowDialog();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user