Skip to content

Commit

Permalink
SnakeGame: add GameOver mode back; fix screen rollover effect.
Browse files Browse the repository at this point in the history
  • Loading branch information
iSeiryu committed Dec 28, 2023
1 parent 5271528 commit e6c69aa
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 58 deletions.
6 changes: 3 additions & 3 deletions BlazorExperiments/Models/SnakeGame/BodyPart.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public BodyPart(float x, float y, float prevX, float prevY) {
}

public Vector2 Position;
public Vector2 PrevPosition { get; }
public Vector2 PrevPosition;
public Vector2 AnimationPosition { get; private set; }
public double Interpolation { get; private set; }

Expand All @@ -20,8 +20,8 @@ public void ResetInterp() {
}

// Interpolate between current position towards target position
public void Interpolate(double deltaTime, float gameSpeed) {
Interpolation += deltaTime * gameSpeed;
public void Animate(double deltaTime, float gameSpeed) {
Interpolation += deltaTime * gameSpeed / 1_000;
Interpolation = Math.Min(1.0f, Interpolation); // clamp max value at 1.0

AnimationPosition = Vector2.Lerp(PrevPosition, Position, (float)Interpolation);
Expand Down
20 changes: 9 additions & 11 deletions BlazorExperiments/Models/SnakeGame/Snake.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,22 @@ public class Snake(int size, int fieldWidth, int fieldHeight, float snakeSpeed)
readonly int _size = size;
readonly int _xLimit = fieldWidth - size;
readonly int _yLimit = fieldHeight - size;
readonly float _snakeSpeed = snakeSpeed;
float _snakeSpeed = snakeSpeed;
SnakeDirection _currentDirection;

float _xSpeed,
_ySpeed;

public BodyPart Head => Tail[^1];
public List<BodyPart> Tail { get; } = [new(0, 0, 0, 0)];
public void IncreaseSnakeSpeed(float increaseBy) => _snakeSpeed += increaseBy;

public void Update(double deltaTime) {
if (Head.Interpolation == 1.0f)
SnakeStep();

public void Animate(double deltaTime) {
for (var i = 0; i < Tail.Count; i++)
Tail[i].Interpolate(deltaTime, _snakeSpeed);
Tail[i].Animate(deltaTime, _snakeSpeed);
}

void SnakeStep() {
public void SnakeStep() {
for (var i = 0; i < Tail.Count - 1; i++) {
Tail[i] = Tail[i + 1];
Tail[i].ResetInterp();
Expand All @@ -33,13 +31,13 @@ void SnakeStep() {
Head.Position.Y);

if (Head.Position.X > _xLimit)
Head.Position.X = 0;
Head.Position.X = Head.PrevPosition.X = 0;
else if (Head.Position.X < 0)
Head.Position.X = _xLimit;
Head.Position.X = Head.PrevPosition.X = _xLimit;
else if (Head.Position.Y > _yLimit)
Head.Position.Y = 0;
Head.Position.Y = Head.PrevPosition.Y = 0;
else if (Head.Position.Y < 0)
Head.Position.Y = _yLimit;
Head.Position.Y = Head.PrevPosition.Y = _yLimit;
}

public void SetDirection(SnakeDirection snakeDirection) {
Expand Down
40 changes: 22 additions & 18 deletions BlazorExperiments/Shared/CanvasComponent.razor.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Timers;
using BlazorExperiments.UI.Models;
using BlazorExperiments.UI.Services;
using Excubo.Blazor.Canvas;
using Excubo.Blazor.Canvas.Contexts;
Expand All @@ -12,32 +13,17 @@ public partial class CanvasComponent : IAsyncDisposable {
const int Margin = 50;
const int MediaMinWidth = 641;
const int SideBarWidth = 250;
const double Interval = 1_000 / 60; // 60 fps

double _fps = 0;

DateTime _lastTimeFpsCalculated = DateTime.Now;
DateTime _lastTimeCanvasRendered = DateTime.Now;

async Task SetCanvasSize() {
var windowProperties = await BrowserResizeService.GetWindowProperties(JS);
var sideBarWidth = windowProperties.Width > MediaMinWidth ? SideBarWidth : 0;
var topMenuHeight = sideBarWidth == 0 ? Margin : 0;

_devicePixelRatio = windowProperties.DevicePixelRatio;
Width = (int)(windowProperties.Width - sideBarWidth - Margin);
Height = (int)(windowProperties.Height - Margin - topMenuHeight);
Width -= Width % CellSize;
Height -= Height % CellSize;
_style = $"width: {Width}px; height: {Height}px;";
StateHasChanged();
}

protected override async Task OnAfterRenderAsync(bool firstRender) {
if (firstRender) {
const int interval = 1_000 / 60; // 60 fps

await SetCanvasSize();
Timer = new Timer(interval);
Timer = new Timer(Interval);
Context = await Canvas.GetContext2DAsync(alpha: Alpha);
await Context.ScaleAsync(_devicePixelRatio, _devicePixelRatio);
await Container.FocusAsync();
Expand All @@ -52,8 +38,24 @@ protected override async Task OnAfterRenderAsync(bool firstRender) {
}
}

async Task SetCanvasSize() {
WindowProperties = await BrowserResizeService.GetWindowProperties(JS);
var sideBarWidth = WindowProperties.Width > MediaMinWidth ? SideBarWidth : 0;
var topMenuHeight = sideBarWidth == 0 ? Margin : 0;

_devicePixelRatio = WindowProperties.DevicePixelRatio;
Width = (int)(WindowProperties.Width - sideBarWidth - Margin);
Height = (int)(WindowProperties.Height - Margin - topMenuHeight);
Width -= Width % CellSize;
Height -= Height % CellSize;
_style = $"width: {Width}px; height: {Height}px;";
StateHasChanged();
}

public WindowProperties WindowProperties { get; private set; } = default!;

public async ValueTask DrawFps(Batch2D batch, ElapsedEventArgs elapsedEvent) {
if (elapsedEvent.SignalTime - _lastTimeFpsCalculated > TimeSpan.FromSeconds(1)) {
if (elapsedEvent.SignalTime - _lastTimeFpsCalculated > TimeSpan.FromMilliseconds(1_200)) {
_fps = 1 / (DateTime.Now - _lastTimeCanvasRendered).TotalSeconds;
_lastTimeFpsCalculated = elapsedEvent.SignalTime;
}
Expand All @@ -65,6 +67,8 @@ public async ValueTask DrawFps(Batch2D batch, ElapsedEventArgs elapsedEvent) {
_lastTimeCanvasRendered = DateTime.Now;
}

public bool IsLessThanMediaMinWidth() => WindowProperties.Width < MediaMinWidth;

public async ValueTask DisposeAsync() {
Timer?.Dispose();
await Context.DisposeAsync();
Expand Down
53 changes: 27 additions & 26 deletions BlazorExperiments/Shared/SnakeGame.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,10 @@ public partial class SnakeGame {
int _cellSize = 1;
bool _gameOver;
DateTime _lastTime = DateTime.Now;
TimeSpan _snakeSpeedInMilliseconds = TimeSpan.Zero;
int _level = 0;
int _level = 1;
const double EggSpawnChance = 0.005;

void InitializeGame() {
IncreaseLevel(1);
_cellSize = _canvas.CellSize;
_eggs.Clear();
_eggs.Add(new(_cellSize, (int)_canvas.Width, (int)_canvas.Height));
Expand All @@ -29,25 +28,26 @@ void InitializeGame() {
}

async ValueTask GameLoopAsync(ElapsedEventArgs elapsedEvent) {
var eatenEgg = _eggs.FirstOrDefault(egg => _snake.Ate(egg));
if (eatenEgg is not null) {
_eggs.Remove(eatenEgg);
if (_snake.Tail.Count % 6 == 0)
IncreaseLevel(_level + 1);
}

const double eggSpawnChance = 0.005;
if (Random.Shared.NextDouble() < eggSpawnChance && _eggs.Count < 5 || _eggs.Count == 0)
AddEgg();

var deltaTime = elapsedEvent.SignalTime - _lastTime;
_snake.Update(deltaTime.TotalMilliseconds / 1_000);
_lastTime = elapsedEvent.SignalTime;

//if (_snake.IsDead()) {
// await GameOver();
// return;
//}
if (_snake.Head.Interpolation == 1.0f) {
_snake.SnakeStep();
if (_snake.IsDead()) {
await GameOver();
return;
}

var eatenEgg = _eggs.FirstOrDefault(egg => _snake.Ate(egg));
if (eatenEgg is not null) {
_eggs.Remove(eatenEgg);
if (_snake.Tail.Count % 6 == 0)
IncreaseLevel(_level + 1);
}
}

_snake.Animate(deltaTime.TotalMilliseconds);
AddEgg();

await DrawAsync(elapsedEvent);
}
Expand All @@ -61,7 +61,7 @@ async ValueTask DrawAsync(ElapsedEventArgs elapsedEvent) {
await batch.FillTextAsync($"Score: {_snake.Tail.Count}", _canvas.Width - 55, 10);
await batch.FillTextAsync($"Level: {_level}", _canvas.Width - 55, 20);

await batch.ShadowBlurAsync(50);
await batch.ShadowBlurAsync(30);
await batch.ShadowColorAsync("darkgreen");
await batch.FillStyleAsync("green");
foreach (var cell in _snake.Tail) {
Expand All @@ -76,7 +76,6 @@ async ValueTask DrawAsync(ElapsedEventArgs elapsedEvent) {
await batch.StrokeStyleAsync("white");
await batch.StrokeRectAsync(_snake.Head.AnimationPosition.X, _snake.Head.AnimationPosition.Y, _cellSize, _cellSize);

await batch.ShadowBlurAsync(0);
await batch.FillStyleAsync("yellow");
foreach (var egg in _eggs)
await batch.FillRectAsync(egg.X, egg.Y, _cellSize, _cellSize);
Expand All @@ -86,12 +85,13 @@ async ValueTask DrawAsync(ElapsedEventArgs elapsedEvent) {
}

void AddEgg() {
_eggs.Add(new(_cellSize, (int)_canvas.Width, (int)_canvas.Height));
if (_eggs.Count < 5 && Random.Shared.NextDouble() < EggSpawnChance || _eggs.Count == 0)
_eggs.Add(new(_cellSize, (int)_canvas.Width, (int)_canvas.Height));
}

void IncreaseLevel(int level) {
_level = level;
_snakeSpeedInMilliseconds = TimeSpan.FromMilliseconds(1_000 / 3 / _level);
_snake.IncreaseSnakeSpeed(_level);
}

void HandleInput(KeyboardEventArgs e) {
Expand Down Expand Up @@ -119,7 +119,7 @@ void HandleTouchMove(TouchEventArgs e) {
if (_previousTouch == null)
return;

const int sensitivity = 5;
const int sensitivity = 10;
var xDiff = _previousTouch.ClientX - e.Touches[0].ClientX;
var yDiff = _previousTouch.ClientY - e.Touches[0].ClientY;

Expand All @@ -146,9 +146,10 @@ async Task ClearScreenAsync(Batch2D batch) {
async Task GameOver() {
_gameOver = true;
_canvas.Timer.Enabled = false;
_level = 1;

await _canvas.Context.FillStyleAsync("red");
await _canvas.Context.FontAsync("42px serif");
await _canvas.Context.FillTextAsync("Game Over", _canvas.Width / 4, _canvas.Height / 2);
await _canvas.Context.FontAsync(_canvas.IsLessThanMediaMinWidth() ? "20px serif" : "42px serif");
await _canvas.Context.FillTextAsync("Game Over", _canvas.Width / 2 - 100, _canvas.Height / 2 - 100);
}
}

0 comments on commit e6c69aa

Please sign in to comment.