蛇...纯 HTML⁉️ [无 JS、无 CSS、无图像!!]😱
他们说有些人就是喜欢混乱。
大家好👋🏼,我是 Graham“喜欢混乱”TheDev,这次我带着另一个愚蠢的互联网实验回来了(如果你愿意,你可以直接跳到游戏)。
一切都开始得如此天真,“我可以写一个贪吃蛇游戏吗?”。
但是,一如既往,我肩膀上的小恶魔低声说“让它更难”......所以我想“不要 JavaScript,用纯 CSS 来做”。
他又一次嘟囔道“噗,还是太简单了,你最近做了太多 CSS 的东西,用原始的、无样式的 HTML 来做吧”。
我转向另一侧肩膀,听听天使的想法,希望听到更明智的答案。
然后我才想起天使从来没有陪伴过我……
这就是纯 HTML 格式的蛇(使用了一些 PHP 技巧来支持它)。
这是正确的!
- 没有 JavaScript
- 没有图片
- 没有 CSS
- 没有Cookies
但是,我想说清楚(因为我不想被指责为点击诱饵),我使用 PHP 呈现此 HTML。
虽然可以使用纯 HTML 文件来实现,无需后端语言,但它需要640,345,228,142,352,307,046,244,325,015,114,448,670,890,662,773,914,918,117,331,955,996,440,709,549,671,345,290,477,020,322,434,911,210,797,593,280,795,101,545,372,667,251,627,877,890,009,349,763,765,710,326,350,331,533,965,349, 868,386,831,339,352,024,373,788,157,786,791,506,311,858,702,618,270,169,819,740,062,983,025,308,591,298,346,162,272,304,558,339,520,759,611,505,302,236,086,810,433,297,255,194,852,674,432,232,438,669,948,422,404,232,599,805,551,6 10,635,942,376,961,399,231,917,134,063,858,996,537,970,147,827,206,606,320,217,379,472,010,321,356,624,613,809,077,942,304,597,360,699,567,595,836,096,158,715,129,913,822,286,578,579,549,361,617,654,480,453,222,007,825,818,400,84 8,436,415,591,229,454,275,384,803,558,374,518,022,675,900,061,399,560,145,595,206,127,211,192,918,105,032,491,008,0 ...
因此,请原谅使用 PHP 生成下一页的“快捷方式”(对于那些在技术 Twitter 上花费太多时间的人,也请原谅我使用“死”语言!😱🤣)。
无论如何,这都是太多的序言了,我知道你为什么在这里,你想看到它的实际效果!
玩 HTML 贪吃蛇(有警告!)
可在桌面电脑上的 Chrome 上运行...对我来说,在 Firefox 和 iOS 上的任何浏览器上播放速度都太快了...您将在本文后面看到原因。
所以基本上,在桌面上的 Chrome 上播放它!
关键在于:
- ALT+I表示向上,
- ALT+J为左边,
- ALT+K表示向下,
- ALT+L为右,
- ALT+O开始新游戏(一旦你输了)。
在 Mac 上它是Control+Option而不是Alt我相信!
如果您想知道为什么是奇怪的键而不是 WASD,不幸的是ALT+D已经在 Windows 上的 Chrome 中使用,所以我不得不选择“安全”键。
最后警告:我们用来实现此功能的技巧之一就是将大量的 URL 充斥到您的浏览器历史记录中...您已经被警告了!
游戏
遗憾的是,这不能在 codepen 中运行,所以你必须在我的网站上玩 HTML 蛇。
玩完之后,回来看看我使用了哪些技巧来实现这个功能(并在评论中分享您的最高分!)。
问题 1:获取游戏 tick
对于游戏来说,通常需要一个“游戏刻度”。每个刻度都代表一个动作发生,或者计算一个新的游戏状态,然后渲染新的游戏状态。
但这引发了我们的第一个问题,我们如何才能在没有 JavaScript 的情况下让页面自动更新?
嗯,在 HTML 中做到这一点实际上非常简单,我们只需将<meta http-equiv="refresh"
其设置为一个较低的值。
因此,我们从 0.35 秒的刷新时间开始,然后随着您的分数上升,将其加快到 0.2 秒的刷新时间。
“元刷新”允许我们做的是指示浏览器,一旦它加载了页面的 HTML,就等待 X 秒,然后加载另一个 URL。
通过设置较低的刷新时间,然后在每次刷新时更改我们重定向到的 URL(稍后会详细介绍),我们可以让页面自行更改,即使您不按任何按钮!
以下是格式的简单示例:
<meta http-equiv="refresh" content="[time];url=[url-to-redirect-to]">
附注:我之前提到过,这个功能在其他浏览器上无法使用。它们不支持部分秒级的刷新时间,所以刷新是即时的,这导致游戏速度太快,无法正常进行。
但单靠元刷新不足以使游戏正常运行,我们需要某种方式来保存游戏状态并将蛇方向的变化传达给服务器。
为此,我们使用另一个简单的技巧:URL 编码的 GET 参数。
问题 2:管理游戏状态
因为我们不能使用 POST 请求或类似的东西,所以我们需要另一种机制来管理浏览器和服务器之间的游戏状态。
起初我使用几个 GET 参数来管理状态,因此 URL 如下所示:
url?playing=1&x=2&y=6&foodx=3&foody=6&dir=left.
一切运行良好,直到我需要为蛇存储多个点(它占据的每个方格的 x、y 坐标)。
snake=1,1,2,1
虽然我确实使用了一些 hacky x,y 坐标列表和解析(例如蛇位于 x=1,y=1 和 x=2,y=1)让它工作,但这很混乱。
因此,我们转而向我们的好朋友求助:urlencode()
和json_encode()
。
一起使用,我可以获取一个数组(或在本例中为多维数组),将其转换为 JSON,然后将其转换为 URL 的有效字符。
让我解释一下:
在 URL 中存储复杂数据
以下是我用于游戏状态的数据示例:
$state = array(
'width' => $width,
'height' => $height,
'snake' => array(array('x' => 5, 'y' => 5)),
'food' => array('x' => 7, 'y' => 7),
'direction' => 'right',
'score' => 0,
'state' => true
);
要将数据存储在 URL 中,我们可以使用以下命令:
$url = urlencode(json_encode($state));
通过 JSON 编码我们的数组,然后用 URL 友好的字符替换无效字符,这为我们提供了以 URL 友好(尽管不是人类友好!)的方式提供的状态:
%7B%22width%22%3A20%2C%22height%22%3A20%2C%22snake%22%3A%5B%7B%22x%22%3A19%2C%22y%22%3A5%7D%5D%2C%22food%22%3A%7B%22x%22%3A4%2C%22y%22%3A11%7D%2C%22direction%22%3A%22right%22%2C%22score%22%3A0%2C%22state%22%3Afalse%7D
现在我们有一个机制可以将游戏状态传递到浏览器并返回到服务器。
最大 URL 长度
那些懂行的人应该知道这里有个陷阱。URL 的长度是有上限的!
在 Chrome 中,该大小为 2083 个字符。
如果您玩游戏的时间足够长,您实际上会达到该角色的限制,因为为了存储我们的 x,y 位置对,我们每次使用超过 10 个角色。
但这是一个愚蠢的演示,所以我只想说:如果你把蛇做得足够长,请告诉我会发生什么错误!
哦,在现实世界中,您不应该在 URL 中使用 JSON 编码参数,我们就此打住吧!
我们有状态和游戏记录,现在怎么办?
就是这样!
嗯,差不多了。
我们需要将按键信息传达给服务器。
问题 3:改变蛇的方向
这是最后一个难题(也是我们最终在 URL 中使用游戏状态的原因),我们需要向服务器传达按键信息以改变蛇的方向。
问题 3a:按钮按下
在我们能够将按键信息传达给服务器之前,我们需要某种方法来实际捕获它们。
请记住,我们没有 JS 来捕捉按键动作。
我们也不能使用<button>
元素,因为它们需要 JS 才能工作。
因此,我们剩下的就只是不起眼的锚元素(<a>
)。
但让某人点击锚点会使游戏变得难以玩。
幸运的是,HTML 中内置了一种叫做accesskey
s的东西。
它们允许我们将一个字符分配给一个锚点,然后可以通过快捷方式(ALT + Windows 上 Chrome 中的字符)访问这些字符。
这为我们提供了允许键盘控制的机制,我们只需要 4 个具有不同方向的链接(锚点)作为 URL,然后accesskey
为每个链接分配一个。
重要提示: accesskey
应谨慎使用,如果您选择辅助技术 (AT) 用户使用的键,则可能会产生干扰。
问题 3b:方向
现在我们有了按下按键的方法,以及将按键操作传达给服务器的方法,我们需要一种方法来管理按键操作,以便它们更新蛇的方向。
幸运的是,我们的状态对象中已经有一个direction
通过 URL 传递的属性。
所以我们只需要创建 4 个不同的 URL,每个方向一个。然后我们把它们添加到链接中就完成了。
$encodedState = urlencode(json_encode($state));
<a href="index.php?state=<?php echo $encodedState; ?>&direction=up" accesskey="i">up (ALT + I)</a><br/>
<a href="index.php?state=<?php echo $encodedState; ?>&direction=left" accesskey="j">left (ALT + J)</a>
<a href="index.php?state=<?php echo $encodedState; ?>&direction=right" accesskey="l">right (ALT + L)</a>
<a href="index.php?state=<?php echo $encodedState; ?>&direction=down" accesskey="k">down (ALT + K)</a>
现在,例如,当您按下ALT+时K,将单击第 4 个链接,并将当前状态 + 新方向发送到服务器!
现在剩下的就是获取信息并计算下一个游戏状态。
游戏逻辑
最后,谜题的最后一部分是一些游戏逻辑。
例如,当生成食物位置时,我们需要检查它不在蛇已经占据的瓷砖上,所以我们有这个函数:
function generateFoodPosition($width, $height, $snake) {
do {
$food = array(
'x' => rand(0, $width - 1),
'y' => rand(0, $height - 1));
} while (
in_array($food, $snake)
);
return $food;
}
另一个函数是移动蛇
function moveSnake($state) {
$newHead = array('x' => $state['snake'][0]['x'], 'y' => $state['snake'][0]['y']);
// Update snake's head position based on direction
switch ($state['direction']) {
case 'up':
$newHead['y']--;
break;
case 'down':
$newHead['y']++;
break;
case 'left':
$newHead['x']--;
break;
case 'right':
$newHead['x']++;
break;
}
// Check if snake has collided with the wall or itself
if ($newHead['x'] < 0 || $newHead['x'] >= $state['width'] || $newHead['y'] < 0 || $newHead['y'] >= $state['height'] || in_array($newHead, array_slice($state['snake'], 1))) {
$state['state'] = false;
return $state; // Game over
}
// Check if snake has eaten the food
if ($newHead['x'] == $state['food']['x'] && $newHead['y'] == $state['food']['y']) {
$state['score'] += 10;
// Generate new food position
$state['food'] = generateFoodPosition($state['width'], $state['height'], $state['snake']);
} else {
// Remove tail segment
array_pop($state['snake']);
}
// Move snake
array_unshift($state['snake'], $newHead);
return $state;
}
以及一个用于构建游戏板的循环。
for ($y = 0; $y < 20; $y++) {
echo "\r\n";
for ($x = 0; $x < 20; $x++) {
if ($x == $state['food']['x'] && $y == $state['food']['y']) {
echo '🍎';
} elseif (in_array(array('x' => $x, 'y' => $y), $state['snake'])) {
echo '🟩';
}else{
echo '⬜';
}
}
}
但我不会详细介绍这些内容,因为您可以轻松查找这些内容(并找到更简洁的方法),找到其他人编写的(更好的)代码并适应您的需求。
就这样结束了
好了,我们使用元刷新、访问密钥和黑客技术构建了一个游戏,以便在 URL 中编码复杂数据。
这些东西在你的日常生活中有用吗?没有,可能没有。
他们能不能帮你解决一些奇怪的边缘情况,比如需要完成这件事,或者需要用黑客手段才能把产品发布出去?有可能。
什么?你不会是期待我给你一个有用的教程吧?现在你应该明白了吧。
但是,话虽如此,如果您确实喜欢这篇文章,或者奇迹般地学到了一些新东西,请在下面给我留言,这真的意义重大!
祝大家周末愉快!💗
文章来源:https://dev.to/grahamthedev/snakein-pure-html-no-js-no-css-no-images-2ccg