蛇控制台算法 c#
Snake Console Algorithm c#
谁能给我解释一下逻辑部分?我有点知道它应该可以工作,但我无法逐步跟踪代码,这没有意义。 temp, pre, 和 Tail 部分之间的交换是如此混乱。
运行 帧率如何? TailX[0] 和 TailY[0] 总是在前面吗?为什么?新的尾巴部件如何分配到正确的位置?
帮帮我。
using System;
using System.Threading;
namespace Projects
{
class Snake
{
public Random rand = new Random();
public ConsoleKeyInfo keypress = new ConsoleKeyInfo();
int score, headX, headY, fruitX, fruitY, nTail;
int[] TailX = new int[100];
int[] TailY = new int[100];
const int height = 20;
const int width = 60;
bool gameOver, reset, isprinted, horizontal, vertical;
string dir, pre_dir;
void ShowBanner()
{
Console.SetWindowSize(width, height + 6);
Console.ForegroundColor = ConsoleColor.Green;
Console.CursorVisible = false;
Console.WriteLine("!!####################################!!");
Console.WriteLine("!!####################################!!");
Console.WriteLine("!!####################################!!");
Console.WriteLine("!!####################################!!");
Console.WriteLine("!!####################################!!");
Console.WriteLine("!!########## Welcome ##########!!");
keypress = Console.ReadKey(true);
if (keypress.Key == ConsoleKey.Escape)
{
Environment.Exit(0);
}
}
void Setup()
{
dir = "Right";
pre_dir = "";
score = 0;
nTail = 0;
gameOver = false;
reset = false;
isprinted = false;
headX = width / 2;
headY = height / 2;
fruitX = rand.Next(1, width - 1);
fruitY = rand.Next(1, height - 1);
}
void CheckInput()
{
while (Console.KeyAvailable)
{
keypress = Console.ReadKey(true);
if (keypress.Key == ConsoleKey.Escape)
Environment.Exit(0);
if (keypress.Key == ConsoleKey.S)
{
pre_dir = dir;
dir = "STOP";
}
else if (keypress.Key ==ConsoleKey.LeftArrow)
{
pre_dir = dir;
dir = "LEFT";
}
else if (keypress.Key == ConsoleKey.RightArrow)
{
pre_dir = dir;
dir = "RIGHT";
}
else if (keypress.Key ==ConsoleKey.UpArrow)
{
pre_dir = dir;
dir = "UP";
}
else if (keypress.Key == ConsoleKey.DownArrow)
{
pre_dir = dir;
dir = "DOWN";
}
}
}
void Logic()
{
int preX = TailX[0];
int preY = TailY[0];
int tempX, tempY;
if (dir != "STOP")
{
TailX[0] = headX;
TailY[0] = headY;
for (int i = 1; i < nTail; i++)
{
tempX = TailX[i];
tempY = TailY[i];
TailX[i] = preX;
TailY[i] = preY;
preX = tempX;
preY = tempY;
}
}
switch (dir)
{
case "RIGHT":
headX++;
break;
case "LEFT":
headX--;
break;
case "UP":
headY--;
break;
case "DOWN":
headY++;
break;
case "STOP":
while (true)
{
Console.Clear();
Console.CursorLeft = width / 2 - 6;
Console.WriteLine("GAME PAUSED");
Console.WriteLine();
Console.WriteLine();
Console.WriteLine(" -S to resume ");
Console.WriteLine(" -R to reset ");
Console.Write(" -ESC to quit ");
keypress = Console.ReadKey(true);
if (keypress.Key == ConsoleKey.Escape)
Environment.Exit(0);
if (keypress.Key == ConsoleKey.R)
{
reset = true;
break;
}
if (keypress.Key == ConsoleKey.S)
break;
}
dir = pre_dir;
break;
}
if (headX <= 0 || headX >= width - 1 || headY <= 0 || headY >= height - 1)
{
gameOver = true;
}
else
{
gameOver = false;
}
if (headX == fruitX && headY == fruitY)
{
score += 10;
nTail++;
fruitX = rand.Next(1, width - 1);
fruitY = rand.Next(1, height - 1);
}
if (((dir == "LEFT" && pre_dir != "UP") && (dir == "LEFT" && pre_dir != "DOWN")) ||
((dir == "RIGHT" && pre_dir != "UP") && (dir == "RIGHT" && pre_dir != "DOWN")))
{
horizontal = true;
}
else
{
horizontal = false;
}
if (((dir == "UP" && pre_dir != "LEFT") && (dir == "UP" && pre_dir != "RIGHT")) ||
((dir == "DOWN" && pre_dir != "LEFT") && (dir == "RIGHT" && pre_dir != "RIGHT")))
{
vertical = true;
}
else
{
vertical = false;
}
for (int i = 1; i < nTail; i++)
{
if (TailX[i] == headX && TailY[i] == headY)
{
if (horizontal || vertical)
{
gameOver = false;
}
else
{
gameOver = true;
}
}
if (TailX[i] == fruitX && TailY[i] == fruitY)
{
fruitX = rand.Next(1, width - 1);
fruitY = rand.Next(1, height - 1);
}
}
}
void Render()
{
Console.SetCursorPosition(0, 0);
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
if (i == 0 || i == height - 1)
{
Console.Write("#");
}
else if (j == 0 || j == width - 1)
{
Console.Write("#");
}
else if (j == fruitX && i == fruitY)
{
Console.Write("F");
}
else if (j == headX && i == headY)
{
Console.Write("0");
}
else
{
isprinted = false;
for (int k = 0; k < nTail; k++)
{
if (TailX[k] == j && TailY[k] == i)
{
Console.Write("o");
isprinted = true;
}
}
if (!isprinted)
Console.Write(" ");
}
}
Console.WriteLine();
}
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine(" YOUR SCORE : " + score);
}
void Lose()
{
Console.CursorTop = height + 3;
Console.CursorLeft = width / 2 - 4;
Console.WriteLine("YOU DIED");
Console.WriteLine(" R to reset");
Console.Write(" Esc to quit");
while (true)
{
keypress = Console.ReadKey(true);
if (keypress.Key == ConsoleKey.Escape)
{
Environment.Exit(0);
}
if (keypress.Key == ConsoleKey.R)
{
reset = true;
break;
}
}
}
void Update()
{
while (!gameOver)
{
CheckInput();
Logic();
Render();
if (reset)
break;
Thread.Sleep(30);
}
if (gameOver)
Lose();
}
static void Main(string[] args)
{
Snake snake = new Snake();
snake.ShowBanner();
while (true)
{
snake.Setup();
snake.Update();
Console.Clear();
}
}
}
}
好的,让我们 运行 通过具有不同 i
值的 Logic
函数。
i
表示Logic
函数循环的次数,直到nTail
给出的蛇的长度为止,但是,它是从1开始的,不是0 因此它会跳过第一个尾段,该段始终设置为头是上一帧的位置。
当蛇只是一个脑袋的时候nTail = 0
在你吃东西之前,nTail
是 0,因此 运行s 的逻辑的唯一部分是:
// Set the position of the first segment of the tail to the position of where the head was this frame
TailX[0] = headX;
TailY[0] = headY;
然后稍后在函数中
// Set a new position for the head of the snake based on direction
switch (dir)
{
case "RIGHT":
headX++;
break;
// etc...
在这个例子中,假设蛇向右移动(让我们忽略 Y,只看 X)。
headX = 5;
TailX[0] = 4;
现在假设您吃了一些食物,所以 nTail = 1
当蛇有1段尾巴时nTail = 1
发生与第一个状态相同的事情 - 循环被跳过。
由于函数已经将尾部的位置设置为头部的位置,然后更新头部,我们仍然有相同的情况,除了我们只是沿着另一个瓦片移动(更新TailX[0]
到headX
的位置,然后根据我们行进的方向更新headX
。
headX = 6;
TailX[0] = 5;
当你在下一回合获得更多食物时会发生什么(除了幸运)?
当蛇有2段尾巴时nTail = 2
现在 Logic
循环 运行 是尾部更新循环,因为 nTail
现在足够大,可以触发循环条件。
现在请记住,我们已经将 TailX[0]
更新为与 headX
相同 - 所以我们知道尾部的第一段已经位于上一帧头部所在的位置。
现在我们遍历所有其他尾段并将它们的位置设置为前一个尾段所在的位置。
这就是 preX
很重要的原因。在函数的开始,我们捕获第一个尾段的位置 preX = TailX[0]
.
然后当我们进入循环时。我们只循环一次,因为 nTail = 1
,所以(我排除了 Y,因为我们只需要 X 来演示)
for (int i = 1; i < nTail; i++)
{
// Capture the X position of the current segment of tail as we will need it to update the next segment of tail
tempX = TailX[i];
// Set the X position of the tail to the previous tail segment (remember, we captured this at the start of the logic function and it was essentially the X position of the head)
TailX[i] = preX;
// Capture the old position of the tail segment pre-update so we can assign it to the next tail segment
preX = tempX;
}
// Later on we update the head to its new position
所以这导致:
headX = 7;
TailX[0] = 6;
TailX[1] = 5;
所以你必须逆向考虑算法,因为它是这样工作的:
- 将 tail 的第一段更新到 head 所在的位置
- 将 tail 的任何其他片段更新到上一个片段所在的位置
- 更新头部以移动到新位置
同样重要的是要注意,渲染函数总是使用 headX/Y
渲染头部,并且仅在 nTail
大于 0 时才渲染尾部。
这解释了为什么尽管每帧都设置了 TailX[0]
,而不管蛇的长度如何,但在吃东西之前你看不到尾巴。
附录:
存在的几个错误:
你可以向后退 运行 进入你的尾巴 - 这有时会杀了你,但大多数时候不会。如果您的头在尾巴的一部分上,则某些竞技场墙壁不会渲染。我没有研究为什么,但我在游戏中注意到了这一点。
游戏区域是 20 * 60,如果我们从每个维度(top/bottom/left/right)中减去 1 的墙壁,我们最终得到 18 * 58 的游戏区域,这意味着蛇的最大长度在技术上可以是 1,044。数组不够大,长度达到 101 会使游戏崩溃(索引超出范围)——我在代码中发现了这一点,并在 VS 中重新创建了场景:
谁能给我解释一下逻辑部分?我有点知道它应该可以工作,但我无法逐步跟踪代码,这没有意义。 temp, pre, 和 Tail 部分之间的交换是如此混乱。
运行 帧率如何? TailX[0] 和 TailY[0] 总是在前面吗?为什么?新的尾巴部件如何分配到正确的位置? 帮帮我。
using System;
using System.Threading;
namespace Projects
{
class Snake
{
public Random rand = new Random();
public ConsoleKeyInfo keypress = new ConsoleKeyInfo();
int score, headX, headY, fruitX, fruitY, nTail;
int[] TailX = new int[100];
int[] TailY = new int[100];
const int height = 20;
const int width = 60;
bool gameOver, reset, isprinted, horizontal, vertical;
string dir, pre_dir;
void ShowBanner()
{
Console.SetWindowSize(width, height + 6);
Console.ForegroundColor = ConsoleColor.Green;
Console.CursorVisible = false;
Console.WriteLine("!!####################################!!");
Console.WriteLine("!!####################################!!");
Console.WriteLine("!!####################################!!");
Console.WriteLine("!!####################################!!");
Console.WriteLine("!!####################################!!");
Console.WriteLine("!!########## Welcome ##########!!");
keypress = Console.ReadKey(true);
if (keypress.Key == ConsoleKey.Escape)
{
Environment.Exit(0);
}
}
void Setup()
{
dir = "Right";
pre_dir = "";
score = 0;
nTail = 0;
gameOver = false;
reset = false;
isprinted = false;
headX = width / 2;
headY = height / 2;
fruitX = rand.Next(1, width - 1);
fruitY = rand.Next(1, height - 1);
}
void CheckInput()
{
while (Console.KeyAvailable)
{
keypress = Console.ReadKey(true);
if (keypress.Key == ConsoleKey.Escape)
Environment.Exit(0);
if (keypress.Key == ConsoleKey.S)
{
pre_dir = dir;
dir = "STOP";
}
else if (keypress.Key ==ConsoleKey.LeftArrow)
{
pre_dir = dir;
dir = "LEFT";
}
else if (keypress.Key == ConsoleKey.RightArrow)
{
pre_dir = dir;
dir = "RIGHT";
}
else if (keypress.Key ==ConsoleKey.UpArrow)
{
pre_dir = dir;
dir = "UP";
}
else if (keypress.Key == ConsoleKey.DownArrow)
{
pre_dir = dir;
dir = "DOWN";
}
}
}
void Logic()
{
int preX = TailX[0];
int preY = TailY[0];
int tempX, tempY;
if (dir != "STOP")
{
TailX[0] = headX;
TailY[0] = headY;
for (int i = 1; i < nTail; i++)
{
tempX = TailX[i];
tempY = TailY[i];
TailX[i] = preX;
TailY[i] = preY;
preX = tempX;
preY = tempY;
}
}
switch (dir)
{
case "RIGHT":
headX++;
break;
case "LEFT":
headX--;
break;
case "UP":
headY--;
break;
case "DOWN":
headY++;
break;
case "STOP":
while (true)
{
Console.Clear();
Console.CursorLeft = width / 2 - 6;
Console.WriteLine("GAME PAUSED");
Console.WriteLine();
Console.WriteLine();
Console.WriteLine(" -S to resume ");
Console.WriteLine(" -R to reset ");
Console.Write(" -ESC to quit ");
keypress = Console.ReadKey(true);
if (keypress.Key == ConsoleKey.Escape)
Environment.Exit(0);
if (keypress.Key == ConsoleKey.R)
{
reset = true;
break;
}
if (keypress.Key == ConsoleKey.S)
break;
}
dir = pre_dir;
break;
}
if (headX <= 0 || headX >= width - 1 || headY <= 0 || headY >= height - 1)
{
gameOver = true;
}
else
{
gameOver = false;
}
if (headX == fruitX && headY == fruitY)
{
score += 10;
nTail++;
fruitX = rand.Next(1, width - 1);
fruitY = rand.Next(1, height - 1);
}
if (((dir == "LEFT" && pre_dir != "UP") && (dir == "LEFT" && pre_dir != "DOWN")) ||
((dir == "RIGHT" && pre_dir != "UP") && (dir == "RIGHT" && pre_dir != "DOWN")))
{
horizontal = true;
}
else
{
horizontal = false;
}
if (((dir == "UP" && pre_dir != "LEFT") && (dir == "UP" && pre_dir != "RIGHT")) ||
((dir == "DOWN" && pre_dir != "LEFT") && (dir == "RIGHT" && pre_dir != "RIGHT")))
{
vertical = true;
}
else
{
vertical = false;
}
for (int i = 1; i < nTail; i++)
{
if (TailX[i] == headX && TailY[i] == headY)
{
if (horizontal || vertical)
{
gameOver = false;
}
else
{
gameOver = true;
}
}
if (TailX[i] == fruitX && TailY[i] == fruitY)
{
fruitX = rand.Next(1, width - 1);
fruitY = rand.Next(1, height - 1);
}
}
}
void Render()
{
Console.SetCursorPosition(0, 0);
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
if (i == 0 || i == height - 1)
{
Console.Write("#");
}
else if (j == 0 || j == width - 1)
{
Console.Write("#");
}
else if (j == fruitX && i == fruitY)
{
Console.Write("F");
}
else if (j == headX && i == headY)
{
Console.Write("0");
}
else
{
isprinted = false;
for (int k = 0; k < nTail; k++)
{
if (TailX[k] == j && TailY[k] == i)
{
Console.Write("o");
isprinted = true;
}
}
if (!isprinted)
Console.Write(" ");
}
}
Console.WriteLine();
}
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine(" YOUR SCORE : " + score);
}
void Lose()
{
Console.CursorTop = height + 3;
Console.CursorLeft = width / 2 - 4;
Console.WriteLine("YOU DIED");
Console.WriteLine(" R to reset");
Console.Write(" Esc to quit");
while (true)
{
keypress = Console.ReadKey(true);
if (keypress.Key == ConsoleKey.Escape)
{
Environment.Exit(0);
}
if (keypress.Key == ConsoleKey.R)
{
reset = true;
break;
}
}
}
void Update()
{
while (!gameOver)
{
CheckInput();
Logic();
Render();
if (reset)
break;
Thread.Sleep(30);
}
if (gameOver)
Lose();
}
static void Main(string[] args)
{
Snake snake = new Snake();
snake.ShowBanner();
while (true)
{
snake.Setup();
snake.Update();
Console.Clear();
}
}
}
}
好的,让我们 运行 通过具有不同 i
值的 Logic
函数。
i
表示Logic
函数循环的次数,直到nTail
给出的蛇的长度为止,但是,它是从1开始的,不是0 因此它会跳过第一个尾段,该段始终设置为头是上一帧的位置。
当蛇只是一个脑袋的时候nTail = 0
在你吃东西之前,nTail
是 0,因此 运行s 的逻辑的唯一部分是:
// Set the position of the first segment of the tail to the position of where the head was this frame
TailX[0] = headX;
TailY[0] = headY;
然后稍后在函数中
// Set a new position for the head of the snake based on direction
switch (dir)
{
case "RIGHT":
headX++;
break;
// etc...
在这个例子中,假设蛇向右移动(让我们忽略 Y,只看 X)。
headX = 5;
TailX[0] = 4;
现在假设您吃了一些食物,所以 nTail = 1
当蛇有1段尾巴时nTail = 1
发生与第一个状态相同的事情 - 循环被跳过。
由于函数已经将尾部的位置设置为头部的位置,然后更新头部,我们仍然有相同的情况,除了我们只是沿着另一个瓦片移动(更新TailX[0]
到headX
的位置,然后根据我们行进的方向更新headX
。
headX = 6;
TailX[0] = 5;
当你在下一回合获得更多食物时会发生什么(除了幸运)?
当蛇有2段尾巴时nTail = 2
现在 Logic
循环 运行 是尾部更新循环,因为 nTail
现在足够大,可以触发循环条件。
现在请记住,我们已经将 TailX[0]
更新为与 headX
相同 - 所以我们知道尾部的第一段已经位于上一帧头部所在的位置。
现在我们遍历所有其他尾段并将它们的位置设置为前一个尾段所在的位置。
这就是 preX
很重要的原因。在函数的开始,我们捕获第一个尾段的位置 preX = TailX[0]
.
然后当我们进入循环时。我们只循环一次,因为 nTail = 1
,所以(我排除了 Y,因为我们只需要 X 来演示)
for (int i = 1; i < nTail; i++)
{
// Capture the X position of the current segment of tail as we will need it to update the next segment of tail
tempX = TailX[i];
// Set the X position of the tail to the previous tail segment (remember, we captured this at the start of the logic function and it was essentially the X position of the head)
TailX[i] = preX;
// Capture the old position of the tail segment pre-update so we can assign it to the next tail segment
preX = tempX;
}
// Later on we update the head to its new position
所以这导致:
headX = 7;
TailX[0] = 6;
TailX[1] = 5;
所以你必须逆向考虑算法,因为它是这样工作的:
- 将 tail 的第一段更新到 head 所在的位置
- 将 tail 的任何其他片段更新到上一个片段所在的位置
- 更新头部以移动到新位置
同样重要的是要注意,渲染函数总是使用 headX/Y
渲染头部,并且仅在 nTail
大于 0 时才渲染尾部。
这解释了为什么尽管每帧都设置了 TailX[0]
,而不管蛇的长度如何,但在吃东西之前你看不到尾巴。
附录:
存在的几个错误:
你可以向后退 运行 进入你的尾巴 - 这有时会杀了你,但大多数时候不会。如果您的头在尾巴的一部分上,则某些竞技场墙壁不会渲染。我没有研究为什么,但我在游戏中注意到了这一点。
游戏区域是 20 * 60,如果我们从每个维度(top/bottom/left/right)中减去 1 的墙壁,我们最终得到 18 * 58 的游戏区域,这意味着蛇的最大长度在技术上可以是 1,044。数组不够大,长度达到 101 会使游戏崩溃(索引超出范围)——我在代码中发现了这一点,并在 VS 中重新创建了场景: