Skip to content

Commit d092bdd

Browse files
committed
NeonSnake: make it mobile friendly
1 parent 8645567 commit d092bdd

3 files changed

Lines changed: 43 additions & 37 deletions

File tree

BlazorExperiments/Models/NeonSnakeGame/Snake.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ namespace BlazorExperiments.UI.Models.NeonSnakeGame;
55
public class Snake {
66
public const int HudH = 50;
77
public int CellSize = 1;
8-
public const int Cols = 28 * 6; // 168
9-
public const int Rows = 18 * 6; // 108
10-
public int WorldW = Cols;
11-
public int WorldH = Rows;
8+
public readonly int Cols;
9+
public readonly int Rows;
10+
public int WorldW;
11+
public int WorldH;
1212
public const double DeathAnimDuration = 1500;
1313

1414
public readonly Vector2[] CenterCoords;
@@ -39,8 +39,10 @@ public class Snake {
3939
static readonly string[] RockColors = ["#3d4a7f", "#4f63a1", "#32406e", "#6b7fd1", "#5868a8"];
4040
static readonly Vector2[] CardinalDirs = [new(1, 0), new(-1, 0), new(0, 1), new(0, -1)];
4141

42-
public Snake(int cellSize) {
42+
public Snake(int cellSize, int visibleCols, int visibleRows) {
4343
CellSize = cellSize;
44+
Cols = visibleCols * 6;
45+
Rows = visibleRows * 6;
4446
WorldW = Cols * CellSize;
4547
WorldH = Rows * CellSize;
4648
CenterCoords = new Vector2[Cols * Rows];

BlazorExperiments/Pages/NeonSnake.razor

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@
1818
</CanvasComponent>
1919

2020
@code {
21+
bool _isMobile = false;
2122
protected override async Task OnInitializedAsync()
2223
{
23-
var isMobile = await JS.InvokeAsync<bool>("isMobile");
24+
_isMobile = await JS.InvokeAsync<bool>("isMobile");
2425
_canvas.CellsPerRow = 28;
25-
if (isMobile)
26+
if (_isMobile)
2627
{
2728
_canvas.CellsPerRow = 10;
2829
}

BlazorExperiments/Pages/NeonSnake.razor.cs

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ public partial class NeonSnake {
1515
const int KeyDown = 40;
1616
const int KeyLeft = 37;
1717
const int KeyRight = 39;
18-
const int ScreenW = 28 * 40;
19-
const int ScreenH = 18 * 40 + NeonSnakeGame.Snake.HudH;
18+
double _screenW, _screenH, _gameAreaH;
19+
int _visibleRows;
2020

2121
CanvasComponent _canvas = null!;
2222
NeonSnakeGame.Snake _snake = null!;
@@ -41,6 +41,10 @@ static readonly (double Ox, double Oy, double Rx, double Ry, double A)[] EggSpec
4141

4242
public async Task InitializeAsync() {
4343
_cellSize = _canvas.CellSize;
44+
_screenW = _canvas.Width;
45+
_screenH = _canvas.Height;
46+
_gameAreaH = _screenH - NeonSnakeGame.Snake.HudH;
47+
_visibleRows = (int)_gameAreaH / _cellSize;
4448
_obstacleInset = _cellSize * 0.08;
4549
_obstacleSize = _cellSize - _obstacleInset * 2;
4650
_cacheW = _obstacleSize + ObstaclePadding * 2;
@@ -52,7 +56,7 @@ public async Task InitializeAsync() {
5256
await JS.InvokeVoidAsync("neonSnakeEggCache.renderRunningEgg", _eggEr, _eggEh, EggCachePadding, dpr);
5357
var raw = await JS.InvokeAsync<string?>("localStorage.getItem", BestScoreKey);
5458
int savedBest = int.TryParse(raw, out var v) ? v : 0;
55-
_snake = new NeonSnakeGame.Snake(_cellSize) {
59+
_snake = new NeonSnakeGame.Snake(_cellSize, _canvas.CellsPerRow, _visibleRows) {
5660
BestScore = savedBest
5761
};
5862

@@ -70,7 +74,7 @@ private async ValueTask LoopAsync(ElapsedEventArgs elapsedEvent) {
7074
var healthBefore = _snake.Health;
7175
var deadBefore = _snake.Dead;
7276

73-
_snake.Update(dt, ScreenW, ScreenH);
77+
_snake.Update(dt, _screenW, _gameAreaH);
7478

7579
if (_snake.Score > scoreBefore) await PlaySoundAsync("neonSnakeAudio.playEatSound");
7680
if (_snake.Health < healthBefore) await PlaySoundAsync("neonSnakeAudio.playHitSound");
@@ -87,7 +91,7 @@ void HandleInput(KeyboardEventArgs e) {
8791
if (_snake is null) return;
8892

8993
if (_snake.ShowDeathScreen && (e.Code == "Space" || e.Key == " " || e.Key == "Spacebar")) {
90-
_snake = new NeonSnakeGame.Snake(_cellSize);
94+
_snake = new NeonSnakeGame.Snake(_cellSize, _canvas.CellsPerRow, _visibleRows);
9195
_lastTick = DateTime.UtcNow;
9296
return;
9397
}
@@ -113,7 +117,7 @@ void HandleInput(KeyboardEventArgs e) {
113117

114118
void HandleTouchStart(TouchEventArgs e) {
115119
if (_snake.ShowDeathScreen) {
116-
_snake = new NeonSnakeGame.Snake(_cellSize);
120+
_snake = new NeonSnakeGame.Snake(_cellSize, _canvas.CellsPerRow, _visibleRows);
117121
_lastTick = DateTime.UtcNow;
118122
return;
119123
}
@@ -154,17 +158,17 @@ async ValueTask PlaySoundAsync(string fn) {
154158
}
155159

156160
async ValueTask DrawAsync(Batch2D ctx) {
157-
await ctx.ClearRectAsync(0, 0, ScreenW, ScreenH);
161+
await ctx.ClearRectAsync(0, 0, _screenW, _screenH);
158162

159-
await ctx.FillStyleAsync(0, 0, 0, ScreenH,
163+
await ctx.FillStyleAsync(0, 0, 0, _screenH,
160164
(0d, Neon.BgOuter),
161165
(1d, Neon.BgInner));
162-
await ctx.FillRectAsync(0, 0, ScreenW, ScreenH);
166+
await ctx.FillRectAsync(0, 0, _screenW, _screenH);
163167

164-
await ctx.FillStyleAsync(ScreenW * 0.5, ScreenH * 0.55, ScreenW * 0.2, ScreenW * 0.5, ScreenH * 0.55, ScreenW * 0.85,
168+
await ctx.FillStyleAsync(_screenW * 0.5, _screenH * 0.55, _screenW * 0.2, _screenW * 0.5, _screenH * 0.55, _screenW * 0.85,
165169
(0d, "rgba(0,0,0,0)"),
166170
(1d, "rgba(0,0,0,0.38)"));
167-
await ctx.FillRectAsync(0, 0, ScreenW, ScreenH);
171+
await ctx.FillRectAsync(0, 0, _screenW, _screenH);
168172

169173
var shakeX = 0d;
170174
var shakeY = 0d;
@@ -179,9 +183,8 @@ await ctx.FillStyleAsync(ScreenW * 0.5, ScreenH * 0.55, ScreenW * 0.2, ScreenW *
179183

180184
const int borderMargin = 26; // lineWidth/2 + shadowBlur
181185
if (_snake.CamX < borderMargin || _snake.CamY < borderMargin ||
182-
_snake.CamX + ScreenW > _snake.WorldW - borderMargin ||
183-
_snake.CamY + ScreenH - NeonSnakeGame.Snake.HudH > _snake.WorldH - borderMargin)
184-
{
186+
_snake.CamX + _screenW > _snake.WorldW - borderMargin ||
187+
_snake.CamY + _gameAreaH > _snake.WorldH - borderMargin) {
185188
await DrawGridAsync(ctx);
186189
}
187190

@@ -203,7 +206,7 @@ await ctx.FillStyleAsync(ScreenW * 0.5, ScreenH * 0.55, ScreenW * 0.2, ScreenW *
203206

204207
if (_snake.HitFlash > 0) {
205208
await ctx.FillStyleAsync($"rgba(255, 63, 114, {_snake.HitFlash * 0.3})");
206-
await ctx.FillRectAsync(0, NeonSnakeGame.Snake.HudH, ScreenW, ScreenH - NeonSnakeGame.Snake.HudH);
209+
await ctx.FillRectAsync(0, NeonSnakeGame.Snake.HudH, _screenW, _gameAreaH);
207210
}
208211

209212
await DrawHudAsync(ctx);
@@ -227,8 +230,8 @@ async ValueTask DrawObstaclesAsync(Batch2D ctx) {
227230
var ox = o.X * _cellSize + _obstacleInset - ObstaclePadding;
228231
var oy = o.Y * _cellSize + _obstacleInset - ObstaclePadding;
229232

230-
if (ox + _cacheW < _snake.CamX - _cellSize || ox > _snake.CamX + ScreenW + _cellSize) continue;
231-
if (oy + _cacheW < _snake.CamY - _cellSize || oy > _snake.CamY + ScreenH + _cellSize) continue;
233+
if (ox + _cacheW < _snake.CamX - _cellSize || ox > _snake.CamX + _screenW + _cellSize) continue;
234+
if (oy + _cacheW < _snake.CamY - _cellSize || oy > _snake.CamY + _gameAreaH + _cellSize) continue;
232235

233236
await ctx.DrawImageAsync("obstacleCache", ox, oy, _cacheW, _cacheW);
234237
}
@@ -241,8 +244,8 @@ async ValueTask DrawEggsAsync(Batch2D ctx) {
241244
var halfCache = _eggCacheSize * 0.5;
242245
var camX = _snake.CamX;
243246
var camY = _snake.CamY;
244-
var viewRight = camX + ScreenW;
245-
var viewBottom = camY + ScreenH;
247+
var viewRight = camX + _screenW;
248+
var viewBottom = camY + _gameAreaH;
246249
var cullMargin = _cellSize * 1.5;
247250
var glowMargin = _cellSize * 0.5;
248251

@@ -700,9 +703,9 @@ async ValueTask DrawHudAsync(Batch2D ctx) {
700703
await ctx.FillStyleAsync(0, 0, 0, NeonSnakeGame.Snake.HudH,
701704
(0d, Neon.HudBgTop),
702705
(1d, Neon.HudBgBottom));
703-
await ctx.FillRectAsync(0, 0, ScreenW, NeonSnakeGame.Snake.HudH);
706+
await ctx.FillRectAsync(0, 0, _screenW, NeonSnakeGame.Snake.HudH);
704707
await ctx.FillStyleAsync(Neon.HudLine);
705-
await ctx.FillRectAsync(0, NeonSnakeGame.Snake.HudH - 2, ScreenW, 2);
708+
await ctx.FillRectAsync(0, NeonSnakeGame.Snake.HudH - 2, _screenW, 2);
706709

707710
await ctx.FillStyleAsync(Neon.TextPrimary);
708711
await ctx.ShadowColorAsync(Neon.SnakeGlow);
@@ -716,7 +719,7 @@ await ctx.FillStyleAsync(0, 0, 0, NeonSnakeGame.Snake.HudH,
716719
var heartSize = 14d;
717720
var heartGap = 36d;
718721
var heartsW = 3 * heartGap;
719-
var hx0 = (ScreenW - heartsW) / 2d + heartGap / 2d;
722+
var hx0 = (_screenW - heartsW) / 2d + heartGap / 2d;
720723
var hy = 26d;
721724
for (var i = 0; i < 3; i++) {
722725
var cx = hx0 + i * heartGap;
@@ -727,7 +730,7 @@ await ctx.FillStyleAsync(0, 0, 0, NeonSnakeGame.Snake.HudH,
727730
await ctx.FontAsync("18px monospace");
728731
await ctx.TextAlignAsync(TextAlign.Right);
729732
if (_snake.BestScore != _lastBestScore) { _lastBestScore = _snake.BestScore; _bestText = $"Best: {_snake.BestScore}"; }
730-
await ctx.FillTextAsync(_bestText, ScreenW - 15, 33);
733+
await ctx.FillTextAsync(_bestText, _screenW - 15, 33);
731734
await ctx.TextAlignAsync(TextAlign.Left);
732735
}
733736

@@ -761,32 +764,32 @@ async ValueTask DrawHeartAsync(Batch2D ctx, double cx, double cy, double size, s
761764
}
762765

763766
async ValueTask DrawDeathScreenAsync(Batch2D ctx) {
764-
await ctx.FillStyleAsync(0, NeonSnakeGame.Snake.HudH, 0, ScreenH,
767+
await ctx.FillStyleAsync(0, NeonSnakeGame.Snake.HudH, 0, _screenH,
765768
(0d, "rgba(7,12,31,0.7)"),
766769
(1d, "rgba(2,4,13,0.8)"));
767-
await ctx.FillRectAsync(0, NeonSnakeGame.Snake.HudH, ScreenW, ScreenH - NeonSnakeGame.Snake.HudH);
770+
await ctx.FillRectAsync(0, NeonSnakeGame.Snake.HudH, _screenW, _gameAreaH);
768771

769772
await ctx.FillStyleAsync(Neon.Danger);
770773
await ctx.ShadowColorAsync(Neon.DangerGlow);
771774
await ctx.ShadowBlurAsync(18);
772775
await ctx.FontAsync("bold 48px monospace");
773776
await ctx.TextAlignAsync(TextAlign.Center);
774-
var cy = NeonSnakeGame.Snake.HudH + (ScreenH - NeonSnakeGame.Snake.HudH) / 2d;
775-
await ctx.FillTextAsync("Game Over", ScreenW / 2d, cy - 30);
777+
var cy = NeonSnakeGame.Snake.HudH + _gameAreaH / 2d;
778+
await ctx.FillTextAsync("Game Over", _screenW / 2d, cy - 30);
776779

777780
await ctx.ShadowBlurAsync(0);
778781
await ctx.FillStyleAsync("#f2f8ff");
779782
await ctx.FontAsync("24px monospace");
780-
await ctx.FillTextAsync($"Score: {_snake.Score}", ScreenW / 2d, cy + 15);
783+
await ctx.FillTextAsync($"Score: {_snake.Score}", _screenW / 2d, cy + 15);
781784
if (_snake.Score >= _snake.BestScore && _snake.Score > 0) {
782785
await ctx.FillStyleAsync(Neon.TextAccent);
783786
await ctx.FontAsync("20px monospace");
784-
await ctx.FillTextAsync("New Best!", ScreenW / 2d, cy + 45);
787+
await ctx.FillTextAsync("New Best!", _screenW / 2d, cy + 45);
785788
}
786789

787790
await ctx.FontAsync("18px monospace");
788791
await ctx.FillStyleAsync(Neon.TextMuted);
789-
await ctx.FillTextAsync("Press SPACE to restart", ScreenW / 2d, cy + 80);
792+
await ctx.FillTextAsync(_isMobile ? "Tap to restart" : "Press SPACE to restart", _screenW / 2d, cy + 80);
790793
await ctx.TextAlignAsync(TextAlign.Start);
791794
}
792795
}

0 commit comments

Comments
 (0)