Mini Snake Game
贪吃蛇游戏是一款休闲益智类游戏,有 PC 和手机等多平台版本。既简单又耐玩。该游戏通过控制蛇头方向吃蛋,从而使得蛇变得越来越长。
游戏板
在razor面板创建游戏界面
- 创建Snake.razor
<divclass="game-container"tabindex="0"@onkeydown="ControlSnakeDirection">
@for (int row = 0; row < NO_OF_ROWS; row++)
{
@for (int col = 0; col < N0_OF_COLS; col++)
{
bool isSnakeCell = IsSnakeCell(row, col);
bool isSnakeHead = IsSnakeHead(row, col);
bool isFoodCell = IsFoodCell(row, col);
<divclass="cell @(isSnakeCell && !isSnakeHead ? "snake-body":"")">
@if (isSnakeCell)
{
@if (isSnakeHead)
{
<span>🐲</span>
}
else
{
<span>●</span>
}
}
@if (isFoodCell)
{
<span>🍎</span>
}
</div>
}
}
</div>
代码解释
在第 3 行,每次玩家按下键盘上的箭头键时,我们都会调用“ControlSnakeDirection()
”方法。此方法控制蛇响应用户输入的移动。但我们如何确定前进的方向呢?
- 我们有
Direction
枚举,这是一种表示蛇的运动选项的简洁方式。
public enum Direction
{
UP = 0,
RIGHT = 1,
DOWN = 2,
LEFT = 3
}
}
- 接下来,在第 4 行和第 6 行,我们使用两个嵌套循环来构建游戏板,有效地将页面划分为 15x15 部分的网格。循环变量由静态类
GameHelper
填充,该静态类保存与游戏相关的基本信息。
public static class GameHelper
{
public const int NO_OF_ROWS = 15;
public const int N0_OF_COLS = 15;
public const int SNAKE_SPEED = 600;
}
通过这三种方法,我们的游戏变得生动起来。
IsSnakeCell(row, col)
:检查给定行和列坐标处的当前单元格是否属于蛇的身体。它返回一个布尔值,表明该细胞是否是蛇身体的一部分。IsSnakeHead(row, col)
:与上一个方法类似,该方法验证当前单元格是否代表蛇的头。IsFoodCell(row, col)
:该方法检测当前单元格是否包含美味的苹果🍎。棋盘上的每个单元格都被分配了一个用途 - 它要么是蛇的头 (🐲)、蛇的身体 (●)、诱人的苹果 (🍎) 或空单元格。
我们应用于每个单元格的 CSS 类不仅仅是样式 - 它们是让我们的游戏栩栩如生的视觉提示。如果一个单元格属于蛇的身体(但不是头部),我们给它“snake-body
”类,并用绿色背景绘制它。同时,“cell
”类应用于每个单元格,从而产生填充板上的那些迷人的框。
<div class="cell @(isSnakeCell && !isSnakeHead ? "snake-body" : "")">
4.1. 如果“isSnakeHead
”为 true
,则显示蛇头的龙表情符号 (🐲) 或蛇身体的点 (●)。
@if(isSnakeCell)
{
@if(isSnakeHead)
{
< span >☃️</ span >
}
else
{
< span >🐧</ span >
}
}
4.2. 如果 isFoodCell
为 true
,它会显示一个苹果表情符号 🍎 来代表食物。
@if (isFoodCell)
{
<span>🍎</span>
}
记录分数
如果没有办法跟踪您的进度,游戏就不算完整。 “CurrentScore
”和“TopScore
”字段位于屏幕顶部,为您提供实时跟踪。
为了管理分数,我们引入了包含两个基本字段的 Score
类。
CurrentScore
:随着您累积积分,此字段会在整个游戏过程中更新。TopScore
:最高成就,该字段记录您在游戏中取得的最高分。
游戏成绩
public class Score
{
public int CurrentScore;
public int TopScore;
}
逻辑:Blazor.razor.cs 背后的代码
在幕后的 Blazor.razor.cs
文件中,隐藏着我们游戏的核心——控制蛇的每一个动作、管理游戏状态并处理用户输入的逻辑。
字段和属性
currentCell
:该字段表示蛇在游戏板上的当前位置,存储为具有行和列坐标的SnakeCell
类的实例。SnakeBody
:一个列表,记录了蛇身体所占据的每个单元格的位置。Score
:前面介绍的Score
类的实例,包含CurrentScore
和TopScore
。isGameOver
:一个布尔标志,用于监视游戏是否已结束或仍在进行中。SnakeDirection
:一个枚举字段(Direction),保留蛇当前的移动方向 - 向上、向右、向下或向左。foodRow
和foodCol
:整数字段,用于精确定位食物在游戏板上的当前位置。
public class SnakeCell
{
public int Row { get; set; }
public int Col { get; set; }
}
public partial class Snake
{
SnakeCell currentCell;
readonly List<SnakeCell> snakeBody = new();
Score score = new();
bool isGameOver;
// Define the Snake's initial direction
Direction snakeDirection = Direction.RIGHT;
// Define the food's initial position
int foodRow = 5;
int foodCol = 5;
}
初始化和游戏循环
OnInitializedAsync()
:当组件首次初始化时会触发此方法。它通过初始化基本参数并通过 StartGame 方法启动游戏循环来为我们的游戏奠定基础。
protected override async Task OnInitializedAsync()
{
InitializeGame();
await StartGame();
}
InitializeGame()
:游戏参数的摇篮!这种方法产生了一些基本元素,例如蛇的起始位置、得分,当然还有食物的位置。
private void InitializeGame()
{
// Define the Snake's initial position
currentCell = new() { Row = 10, Col = 10 };
// Initialize the snake's size to 1
score.CurrentScore = 1;
// Initialize the snake's body with one cell at the starting position
snakeBody.Add(CloneSnakeCell());
// Generate the initial food
GenerateFood();
}
StartGame()
:一个不断更新游戏状态的异步动力源。它控制蛇的移动,检查碰撞,并不知疲倦地奔跑,直到游戏结束。
private async Task StartGame()
{
// Start the game loop
while (!isGameOver)
{
UpdateSnakeDirection();
if (IsFoodFound())
{
score.CurrentScore++;
GenerateFood();
}
await Task.Delay(SNAKE_SPEED);
StateHasChanged();
}
}
游戏逻辑
ControlSnakeDirection()
:一种处理用户输入的方法,确保蛇根据箭头键按下适当地改变方向。如果您还记得清单 1,我们会在 @KeyDown 事件上调用此方法。
private void ControlSnakeDirection(KeyboardEventArgs e)
{
switch (e.Key)
{
case "ArrowUp":
snakeDirection = Direction.UP;
break;
case "ArrowRight":
snakeDirection = Direction.RIGHT;
break;
case "ArrowDown":
snakeDirection = Direction.DOWN;
break;
case "ArrowLeft":
snakeDirection = Direction.LEFT;
break;
}
}
UpdateSnakeDirection()
:这个方法是我们游戏的引擎,根据蛇的当前方向管理蛇的位置更新。然后增加蛇的大小,并检查游戏是否结束。
private void UpdateSnakeDirection()
{
switch (snakeDirection)
{
case Direction.UP:
currentCell.Row--;
break;
case Direction.RIGHT:
currentCell.Col++;
break;
case Direction.DOWN:
currentCell.Row++;
break;
case Direction.LEFT:
currentCell.Col--;
break;
}
// Add the new current Cell to the of the snake's body
snakeBody.Insert(0, CloneSnakeCell());
//Check if Game is over
IsGameOver();
// Remove the last cell (tail) to maintain the snake's size
if (snakeBody.Count > score.CurrentScore)
{
snakeBody.RemoveAt(snakeBody.Count - 1);
}
}
IsGameOver()
:游戏结束条件的守护者!它会监视与游戏板边界的碰撞,并为玩家提供重置游戏或探索我的网站 https://rikampalkar.github.io 的选择。
private async Task IsGameOver()
{
if (currentCell.Row < 0 || currentCell.Row >= NO_OF_ROWS || currentCell.Col < 0 || currentCell.Col >= N0_OF_COLS)
{
isGameOver = true;
bool isResetGame = await js.InvokeAsync<bool>("ResetGamePopup", score.CurrentScore);
if (isResetGame)
{
if (score.CurrentScore > score.TopScore)
{
score.TopScore = score.CurrentScore;
}
ResetGame();
}
else
{
await js.InvokeVoidAsync("navigateToWebsite", $"https://rikampalkar.github.io/");
}
}
isGameOver = false;
}
ResetGame()
:当玩家决定重新开始时,此方法负责为新的游戏会话清除记录。
private void ResetGame()
{
snakeBody.Clear();
isGameOver = false;
OnInitializedAsync();
}
GenerateFood()
:幕后主厨!此功能会在游戏板上的随机位置烹制一批新食物。
private void GenerateFood()
{
var random = new Random();
foodRow = random.Next(0, NO_OF_ROWS);
foodCol = random.Next(0, N0_OF_COLS);
}
CloneSnakeCell()
:此方法克隆当前细胞的位置,作为蛇体细胞的蓝图。
private SnakeCell CloneSnakeCell()
{
return new SnakeCell() { Row = currentCell.Row, Col= currentCell.Col };
}
CSS 方法
如果没有通过这些私有方法为您带来的视觉效果,我们的游戏将是不完整的。他们决定细胞在游戏板上的显示方式,确保蛇身、蛇头和食物恰到好处地突出。通过这些私有方法与razorUI通信
//This method checks whether the cell at the given row and col coordinates belongs to the snake's body.
private bool IsSnakeCell(int row, int col)
{
return snakeBody.Exists(cell => cell.Row == row && cell.Col == col);
}
//This method checks whether the cell at the given row and col coordinates matches the position of the food
private bool IsFoodCell(int row, int col)
{
return row == foodRow && col == foodCol;
}
// Function to check if a cell is the snake head
private bool IsSnakeHead(int row, int col)
{
return row == snakeBody[0].Row && col == snakeBody[0].Col;
}
// Check for collision between the Snake and food
private bool IsFoodFound()
{
return currentCell.Row == foodRow && currentCell.Col == foodCol;
}
源代码