十六进制2048-数字游戏(译文)
By S.F.
本文链接 https://www.kyfws.com/news/hex-2048-a-numbers-game/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 19 分钟阅读 - 9415 个词 阅读量 0十六进制2048-数字游戏(译文)
原文地址:https://www.codeproject.com/Articles/5280583/Hex-2048-A-Numbers-Game
原文作者:Christ Kennedy
译文由本站翻译
前言
A graphically bouncy and colorful version of the popular numbers tile-sliding game 流行数字磁贴滑动游戏的图形化弹力和彩色版本 This numbers game uses Breadth First Search algorithm to find a path along a changing maze and gather similar tiles. Reactive rumble animation brings life to the pieces that shake and jostle their neighbors. 该数字游戏使用"广度优先搜索"算法查找沿变化的迷宫的路径并收集相似的图块.反应灵敏的隆隆动画为震撼邻居的作品赋予了生命.
- Download Hex_2048_20201007_0724.zip - 591.9 KB下载Hex_2048_20201007_0724.zip-591.9 KB
- Download Hex_2048_fundamentals.zip - 67 KB下载Hex_2048_fundamentals.zip-67 KB
Malicious Source Code(恶意源代码)
Contrary to popular belief, the original game of 2048 was not developed by the U.S. Military’s research agency Darpa, but was secretly developed at MIT by Gabriele Cirulli. Given the game’s addictiveness, however, it has been classified as a weaponized distraction which was unleashed onto unwary teenagers, weary moms and beer chugging undergrad students around the world, giving credence to the unproven allegations that Cirulli was really a C.I.A. operative disguised as a 19 year old university student. And so I, seizing this opportunity to finally impress the members of the Underground Coding Community, and Kiddie Coders alike (while patiently awaiting for my Anonymous photo-ID and membership card to arrive in the mail), shed my responsible software developer identity and set out to build the next meme-worthy beguilement in the form of a more lethal version of this “game” and thereby hope to further embellish what its designer says is:
与普遍看法相反,最初的2048游戏不是由美国军事研究机构Darpa开发的,而是由Gabriele Cirulli在麻省理工学院秘密开发的.然而,鉴于这款游戏的成瘾性,它被归类为一种武器分散注意力的东西,释放给了全世界疲倦的青少年,疲倦的妈妈和喝啤酒的大学生,这证明了未经证实的指控,即Cirulli确实是C.I.A.手术装扮成一名19岁的大学生.因此,我抓住了这个机会,最终给Underground Coding社区的成员和Kiddie Coders留下了深刻的印象(同时耐心等待我的匿名photo-ID和会员卡到达邮件中),摆脱了我负责任的软件开发人员的身份并设定了以这种"游戏"的更致命版本的形式来构建下一个值得模因的迷惑,从而希望进一步修饰其设计师所说的话:
“part of the beauty of open source software”.
“开源软件之美的一部分”.
You can learn more about playing the game by reading this Wiki article.
通过阅读此Wiki文章,您可以了解有关玩游戏的更多信息.
Or you might be interested in watching a brief video I made about this project and posted on YouTube.
或者,您可能对观看我为此制作的简短视频项目感兴趣并发布在YouTube上.
Disclaimer
免责声明
*Due to the high Waste-of-Time probability caused by this addictive product the Health and Safety Board recommends the use of the Healthy-Living Safeguard Timer option which has been provided by the manufacturer to help minimize any adverse effects which may arise due to prolonged use of this product. *
由于这种令人上瘾的产品造成的浪费时间的可能性很高,因此健康与安全委员会建议使用制造商提供的健康生活''
保护计时器'‘选件,以最大程度地减少由于长时间使用而引起的不良影响使用本产品.
The designer of Hex2048, Christ Kennedy, will not be held responsible for any damages to the user’s social, marital or professional life.
Hex2048的设计师Christine Kennedy对用户的社交,婚姻或职业生活造成的任何损失不承担任何责任.
Use with caution.
请谨慎使用.
Board(棋盘)
Calling this a “Tile-Sliding-Game” is a bit of a misnomer when you look at the inner workings of this GPA destroying code. This home-wrecker doesn’t actually do any ‘tile-sliding’ because the tiles themselves remain in their alotted places from the time they are initialized until you hear your loved ones slam the front door and drive off without you. The original game’s quadrilinear layout made it possible to shuffle the cartesian positions (x, y) of the individual tiles while retaining their values but this version uses a hexagonal layout where the pieces can move in six directions rather than four. Each tile is a member of the
classTile
class and has a list of pointers to its own neighbors. This facilitates a few things but it means that shuffling them around on the board would be a huge hassle and a waste of those beautiful pointers. Also, because the tiles themselves jiggle and wobble on their respective pedestals, it’s easy to assign them a spot and give them a radial-coordinate tether with which to roam while never getting lost or straying too far.
当您查看此GPA销毁代码的内部工作原理时,将其称为"平铺滑动游戏"有点用词不当.这款清障车实际上并没有进行任何"平铺滑动",因为从初始化之初直到您听到亲人猛撞前门并在没有您的情况下开车离开,瓷砖本身一直留在很多地方.原始游戏的四边形布局使得可以在保留其值的同时调整各个图块的笛卡尔位置(x,y),但此版本使用六边形布局,其中棋子可以沿六个方向而不是四个方向移动.每个瓦片都是classTile类的成员,并具有指向其自身邻居的指针列表.这促进了一些事情,但是这意味着将它们拖到黑板上既麻烦又浪费那些漂亮的指针.而且,由于瓷砖本身在各自的基座上会摇晃和摆动,因此很容易为它们分配一个点,并为它们提供一个径向坐标系带,可以与之漫游,而不会迷路或走得太远.
A tricky thing I discovered while writing this app (and was still able to get the basics up and running in under 5 hours - Download Hex_2048_fundamentals.zip - 67 KB file is a snap shot of what I had working at the end of that first night of coding) was that, since the columns are shifted vertically from their neighbors, moving horizontally in the odd columns is different from moving horizontally in the even columns. Have a look at the
Move()
function below which deals with that issue:
我在编写此应用程序时发现了一件棘手的事情(并且仍然能够在5个小时内启动并运行基础知识-下载Hex_2048_fundamentals.zip-67 KB文件是我在编码的第一天晚上结束时所做的工作的快照)是,由于列是从相邻列垂直移开的,因此在奇数列中水平移动与在偶数列中水平移动不同.看一下下面的Move()
函数可以解决这个问题:
public static Point move(Point pt, int dir)
{
if (lstPtMoveDir.Count == 0)
{
List<Point> lstEven = new List<Point>();
{
lstEven.Add(new Point(0, -1));
lstEven.Add(new Point(1, -1));
lstEven.Add(new Point(1, 0));
lstEven.Add(new Point(0, 1));
lstEven.Add(new Point(-1, 0));
lstEven.Add(new Point(-1, -1));
}
lstPtMoveDir.Add(lstEven);
List<Point> lstOdd = new List<Point>();
{
lstOdd.Add(new Point(0, -1));
lstOdd.Add(new Point(1, 0));
lstOdd.Add(new Point(1, 1));
lstOdd.Add(new Point(0, 1));
lstOdd.Add(new Point(-1, 1));
lstOdd.Add(new Point(-1, 0));
}
lstPtMoveDir.Add(lstOdd);
}
return new Point(pt.X + lstPtMoveDir[pt.X % 2][dir].X,
pt.Y + lstPtMoveDir[pt.X % 2][dir].Y);
}
after initializing the two Even/Odd Point lists, it returns the changes that need to be made to the calling function in order for the point to more from the input parameter
pt
in the desireddir
ectiondir
. There are sixdir
ections with zero pointing north and the rest of thedir
ections dialling around clockwise from there. In this game, the odd valued columns are shifted higher than the even numbered columns as you can see below:
在初始化两个偶数/奇数点列表之后,它返回需要对调用函数进行的更改,以使该点从所需的"方向"部分的"输入"参数中的" pt"更多.有六个指向零的"方向"指向北,其余"指向"从那里按顺时针方向拨号.在此游戏中,奇数列移到高于偶数列,如下所示:
Find Your Way(找到你的方式)
The Breadth First Search algorithm is an essential part of this project. Its applications are many and varied. Its fast, easy to implement and there’s lots of documentation and tutorials on-line to help you out. We can have a quick look at an example for Hex2048. In the image below, the blue piece at the top (numbered 15) needs to move around the Yellow occupied tiles and reach the far left corner at the bottom (numbered 0). To do so, we start at the destination and travel where we can while leaving sticky notes on tiles as we count the steps we’ve taken since we left the place we’re searching. (In different applications, you’d start from the start location and branch out until you found what you’re looking for rather than, as we do here, start from the spot we intend to ‘find’ because we’re not looking for some ‘thing’ but rather a path to a specific destination.) When you’ve reached the start location (blue square at the top), that square is then pasted with a sticky-note with the number 15, the number of steps taken to get there. To find your way to the destination square from there, you simply count backwards and travel to any (the only in most cases) square with a sticky note that has a value of one less than the tile you’re standing on.
广度优先搜索算法是该项目的重要组成部分.它的应用是多种多样的.它快速,易于实施,并且在线上有很多文档和tutorials可以为您提供帮助.我们可以快速浏览一下Hex2048的示例.在下图中,顶部的蓝色块(编号15)需要绕过黄色占用的图块移动,并到达底部的最左角(编号0).为此,我们从目的地开始旅行,并在可能的情况下在瓷砖上留下便条纸,这是我们计算自离开搜索地点以来所采取的步骤. (在不同的应用程序中,您将从起始位置开始分支,直到找到所需的内容为止,而不是像我们在此处那样从想要"查找"的位置开始,因为我们没有在寻找当到达起始位置(顶部的蓝色方框)时,在该方框上粘贴带有便笺的数字15,表示已采取的步骤数.到那里.要找到从那里到达目的地广场的方式,您只需向后计数,然后到便签纸上移动(比大多数情况下唯一的便签纸),便签的便签价值比您所站立的瓷砖小一.
Notice when you reach the square numbered six along the path, there are two adjacent squares with the number five on it. The algorithm can choose either one and still get to its destination. Sometimes, like in video games, you may randomly choose from your available options just to keep things fresh. Other times, you just want to zip-it along and choose the first suitable tile you encounter. It all really only depends on your Uber driver, anyway.
请注意,当您沿路径到达编号为6的正方形时,将有两个相邻的正方形,上面有数字5.该算法可以选择其中一个,仍然可以到达其目的地.有时,例如在视频游戏中,您可能会随机选择可用的选项,以保持新鲜感.在其他时候,您只需要将其压缩并选择遇到的第一个合适的图块.无论如何,这实际上仅取决于您的Uber驱动程序.
The code below is taken from Hex2048 and returns a list of point locations on the board that a piece needs to travel along in order to reach its destination. This implementation works in the reverse order, counting the tiles from start to destination and then winding its way back, then taking the final results and reversing the order of the points to get the final output list.
以下代码摘自Hex2048,并返回了一块板上到达点的位置的点列表.此实现以相反的顺序进行,从起点到目的地对切片进行计数,然后将其回绕,然后获取最终结果,并反转点的顺序以获取最终输出列表.
List<Point> MoveSelectedTile_BFS()
{
Point ptDestination = ptTileHighLight;
Point ptStart = ptTileSelected;
int[,] intSeen = new int[szGame.Width, szGame.Height];
// init intSeen
for (int intX = 0; intX < szGame.Width; intX++)
for (int intY = 0; intY < szGame.Height; intY++)
intSeen[intX, intY] = -1;
List<Point> lstQ = new List<Point>();
lstQ.Add(ptStart);
intSeen[ptStart.X, ptStart.Y] = 0;
int intStepsTaken = 0;
while (lstQ.Count > 0)
{
Point ptTile = lstQ[0];
lstQ.RemoveAt(0);
intStepsTaken = intSeen[ptTile.X, ptTile.Y];
for (int intDirCounter = 0; intDirCounter < 6; intDirCounter++)
{
Point ptNeaghbour = move(ptTile, intDirCounter);
if (TileInBounds(ptNeaghbour))
{
if (intSeen[ptNeaghbour.X, ptNeaghbour.Y] < 0)
{ // BFS has not seen neaghbour
if (Board.Tiles[ptNeaghbour.X, ptNeaghbour.Y].Value == 0)
{ // neaghbour is not occupied by a colored tile
intSeen[ptNeaghbour.X, ptNeaghbour.Y] = intStepsTaken + 1;
if (ptNeaghbour.X == ptDestination.X &&
ptNeaghbour.Y == ptDestination.Y)
{ // we have found a path to the destination
lstQ.Clear();
goto validPath;
}
else
{
lstQ.Add(ptNeaghbour);
}
}
}
}
}
}
return new List<Point>();
validPath:
intStepsTaken = intSeen[ptDestination.X, ptDestination.Y];
List<Point> lstSteps = new List<Point>();
lstSteps.Add(ptDestination);
Point ptCurrent = ptDestination;
while (!(ptCurrent.X == ptStart.X && ptCurrent.Y == ptStart.Y))
{
for (int intDirCounter = 0; intDirCounter < 6; intDirCounter++)
{
Point ptNeaghbour = move(ptCurrent, intDirCounter);
if (TileInBounds(ptNeaghbour))
{
if (intSeen[ptNeaghbour.X, ptNeaghbour.Y] == intStepsTaken - 1)
{
lstSteps.Add(ptNeaghbour);
ptCurrent = ptNeaghbour;
intStepsTaken--;
break;
}
}
}
}
lstSteps.Reverse();
lstSteps.RemoveAt(0);
return lstSteps;
}
The same algorithm is used in a slightly different way when the game needs to test the board’s content and remove groups of four or more similar pieces. That function is seeded to start from each tile on the board and branches out to all adjacent tiles that have the same value. When it finds four or more tiles like this, it returns them in a list and they are removed from the board before the same algorithms repeats itself until no groups of similar tiles are found and the game proceeds.
当游戏需要测试棋盘内容并删除四个或更多相似棋子的组时,使用相同算法的方式略有不同.该功能的种子是从板上的每个图块开始的,并分支到具有相同值的所有相邻图块.当它找到四个或更多这样的图块时,将它们返回到列表中,并在相同算法重复其自身之前将它们从板上移除,直到没有找到相似的图块组并且游戏继续进行.
It is much a simpler function. Have a look at the BFS for any given seed tile
ptSeed
:
这是一个更简单的功能.看一下任何给定的种子图块ptSeed的BFS:
List<Point> Tiles_GatherLike_BFS(Point ptSeed)
{
if (!TileInBounds(ptSeed)) return new List<Point>();
int[,] intSeen = new int[szGame.Width, szGame.Height];
int intSeedValue = Board.Tiles[ptSeed.X, ptSeed.Y].Value;
if (intSeedValue <= 0) return new List<Point>();
List<Point> lstQ = new List<Point>();
lstQ.Add(ptSeed);
List<Point> lstRetVal = new List<Point>();
while (lstQ.Count > 0)
{
Point ptTest = lstQ[0];
intSeen[ptTest.X, ptTest.Y] = 1;
lstQ.RemoveAt(0);
int intTileValue = Board.Tiles[ptTest.X, ptTest.Y].Value;
if (intTileValue == intSeedValue)
{
if (!lstRetVal.Contains(ptTest))
{
lstRetVal.Add(ptTest);
for (int intDir = 0; intDir < 6; intDir++)
{
Point ptNeaghbour = move(ptTest, intDir);
if (TileInBounds(ptNeaghbour))
if (intSeen[ptNeaghbour.X, ptNeaghbour.Y] == 0)
if (!lstQ.Contains(ptNeaghbour) && !lstRetVal.Contains(ptNeaghbour))
lstQ.Add(ptNeaghbour);
}
}
}
}
return lstRetVal;
}
Grumbling and Rumbling(抱怨和抱怨)
One of the first things you’ll notice when you play this game is the way the pieces jostle each other when they move. The effect is pretty cool. As I mentioned earlier, each tile is tethered to its spot by a Radial Coordinate (which is like a Spherical Coordinate but missing the third vertical coordinate Phi). Essentially, it’s an arrow with a specific length. The arrow points in the direction in which the tile is shifted away from the tile’s fixed central location and the length tells how far it moves in that direction. But the tiles only move a little bit at a time so they need to know by how much and how far to go. At every clock cycle, the magnitude of the radial coorinate is increased until it reaches its limit and then it begins to shorten until it’s zero again and the tile is at rest. When it reaches its maximum limit, that limit and the direction of the radial coordinate is used to ‘push’ the neighboring tiles that are in the direction that it has been shifted. The three neighbors in the general direction of its shift are each imparted with one third of the force it was hit with, and in this way the motion of the tiles progresses radiating outwards from the source.
玩此游戏时,您会注意到的第一件事是,棋子在移动时会相互碰撞.效果很酷.正如我之前提到的,每个图块都通过径向坐标(类似于球形坐标拴在了它的位置,但是缺少了第三垂直坐标Phi).本质上,它是具有特定长度的箭头.箭头指向瓦从瓦的固定中心位置移开的方向,长度指示瓦向该方向移动的距离.但是磁贴一次只能移动一点,因此它们需要知道要移动多少距离.在每个时钟周期,径向坐标的大小都会增加,直到达到极限为止,然后开始缩短,直到再次为零,并且磁贴处于静止状态.当达到最大极限时,该极限和径向坐标的方向将用于"推动"已移动方向的相邻图块.在其移动的大致方向上的三个邻居分别被施加了其受到的力的三分之一,这样,瓦片的运动就从辐射源向外辐射.
Liquids and air all behave in a similar way. Sounds waves are really just atmospheric particles colliding with each other and propagating the force imparted on each of their neighbors. Conservation of energy means that the force each particle feels is distributed across to all its neighbors. Here, we have tiles moving in one of six quantized directions. When they reach the end of that shift, they push one neighbor slightly to the left.
液体和空气的行为都相似.声波实际上只是大气粒子相互碰撞,并传播施加在它们的每个邻域上的力.能量守恒意味着每个粒子感觉到的力分布到所有相邻粒子.在这里,我们有沿六个量化方向之一移动的图块.当他们到达该转变的终点时,他们将一个邻居稍微向左推.
(dir + 5) % 6
(dir + 5)%6
and another neighbor slightly to the right:
另一个邻居稍微偏右:
(dir +1) % 6
(目录+1)%6
and the one neighbor that is in the same direction it itself was pushed.
以及与自己处于同一方向的一个邻居.
This sounds all fine and good but what happens in implementation is that the shoves that proceed, say to the left, wind their way back the way they came and a harmonic resonance like situation results. Watch this video of the Tacoma Bridge Collapse, if you don’t know what I mean. There may not be any gail force winds billowing across the game board here, but it’s still a problem. When I was putting this feature together the slightest jostle from one piece sent the entire board flying off the rack. What I did to solve that problem was add a list of points to each tile for it to keep track of which tiles initiated the wave of motion that is hurtling towards them. When they are struck by a neighbor, that neighbor reports which tile initiated the force hitting them and the tile being hit then looks in its list to see if it has been hit by that tile’s impulse recently. If the source-tile’s location (ID point) is not in the list, then the tile is hit and adds the source-point to its list. When the tidal force winds its way around the board and tries to hit it again, the force is ignored the second and all subsequent times. Each tile sends forward the same source-tile’s ID when it propagates the impulse it felt on to its neighbors and each tile clears its list of source-tile-IDs when it comes to rest.
听起来一切都很好,但是在实现过程中发生的是,前进的铲子向左说,向后弯曲,直到出现类似情况的谐音.如果您不明白我的意思,请观看[塔科马大桥塌陷]的视频(https://www.youtube.com/watch?v=XggxeuFDaDU).在这里,可能没有大风横过游戏板,但这仍然是一个问题.当我将这一功能放在一起时,只有一点点的争吵就使整个电路板从架子上飞了下来.为了解决这个问题,我要做的是在每个图块上添加一个点列表,以便跟踪哪些图块引发了朝它们冲击的运动.当它们被邻居击中时,该邻居报告哪个瓦片引发了击打它们的力,然后被撞的瓦片在其列表中查找最近是否被该瓦片的冲撞撞了.如果源图块的位置(ID点)不在列表中,则将单击图块并将源点添加到其列表中.当潮汐力绕板缠绕并试图再次击打时,第二次及以后的所有时间都将忽略该力.每个图块将其感觉到的脉冲传播到其邻居时,都会转发相同的源-tile-ID,并且每个图块在静止时都会清除其源-tile-ID列表.
Resizing PerPixelAlpha Forms(调整PerPixelAlpha表单的大小)
I initially intended to add Dragon sprites on either side of the game. With that in mind, I cut up a Dragon image I downloaded off the internet and made it into a passable sprite. The snake-like dragon I picked was not as conducive to sprite-making as a non-snake-like dragon I could have picked, so the results were not as good as they could have been but the real problems erupted when I started drawing the animated dragon on the Left side of the game. The reason why the Left dragon was so problematic was that the dragon’s shape and size varied as it moved. Which, of course was the point of making it into a sprite, but the resultant form image varied in size as well which meant the form had to move to adjust for the changes. Moving the form works fine for smaller sprites like the Jessica Rabbit sprite-form I published some ten years ago but for this interactive game, the results were unacceptable. The game was jumping visibly at a delayed rate after it changed its image and it was so bad that I had to implement a dual-form system that drew the next frame on one form, positioned it then toggled the previous off (
Hide()
) before instantly toggling the next one on (Show()
). The graphics were much better with this implementation but it turns out that the mouse-event triggers were not following the plan and the game became unresponsive. As a consequence of that, I wrote a few lines of code that were called every game loop (on a timer) and tested the mouse’s position and button states before deciding which events to call instead of letting the two jumping forms jostle each other for the privilege and ignoring their duty to report mouse-events at all in the bitterness of their fight. This was working pretty good and the results were better but there were so many complications and the code got so ugly that I looked at my dragons (two mirrors of the same sprite) and decided it just wasn’t worth it. I set the form’s size to a constant value which was big enough to accommodate both dragons at their worst and looked at what I had done and decided it was good.
我最初打算在游戏的任一侧添加Dragon精灵.考虑到这一点,我剪切了从互联网上下载的Dragon图片,并将其制作成可传递的精灵.我选择的蛇状龙不像我可以选择的蛇状龙那样有利于制作精灵,因此结果虽然不尽如人意,但是当我开始画图时,真正的问题就爆发了游戏左侧的动画龙.左龙之所以如此有问题,是因为它的形状和大小随其移动而变化.当然,这就是将其变成精灵的目的,但是生成的表单图像的大小也有所不同,这意味着表单必须移动以适应变化.移动表单对于较小的Sprite效果很好,例如Jessica Rabbit我在十年前发布但针对这款互动游戏的结果令人无法接受.更改图像后,游戏明显以延迟的速度跳跃,这太糟糕了,以至于我不得不实现一种双重形式的系统,即以一种形式绘制下一帧,然后放置它然后将前一帧关闭(Hide() ),然后立即在(
Show()`)上切换下一个.通过此实现,图形效果要好得多,但事实证明,鼠标事件触发器未遵循计划,因此游戏变得无响应.结果,我写了几行代码,在每个游戏循环中(在计时器上)调用了该代码,并在确定要调用的事件之前测试了鼠标的位置和按钮状态,而不是让两个跳跃形式相互争用.特权,而忽略了他们在战斗的痛苦中报告鼠标事件的义务.这工作得很好,结果也更好,但是存在很多复杂性,并且代码变得丑陋,我看着我的龙(同一个精灵的两个镜子),认为那是不值得的.我将表格的大小设置为一个恒定值,该值足以容纳两条龙在最坏的时候,然后看一下我所做的事情并确定它是好的.
But not good enough.
但是还不够好.
The form’s larger size reduced the game’s speed and the dragons were just not worth it. So I trashed them. It only took me ten minutes to re-jig the whole project without those ugly dragons and we have what we have now… much much better.
表格较大的尺寸降低了游戏的速度,而龙不值得.所以我丢了他们.我只花了十分钟就重新完成了整个项目,而没有那些丑陋的巨龙,我们拥有了现在的能力^好多了.
Sparkles - Stars, Spikes and Caltrops(火花-星星,尖峰和菱形)
Stars, spikes and caltrops are the three different shapes which fly out in multi-colors whenever the game gets excited. They’re all drawn dynamically the first time the game is launched and then their variously colored and rotated versions are all stored on the harddrive in a binary stream file. A single algorithm draws all three shapes which only differ in the number of ‘spikey’ things sticking out of them. Stars have 5 points, caltrops (not sure why I called them that?!?) have four and spikes only 2. They each have an inner and outer radius and the points are generated by going around a central point and setting points at alternating radii from it. These images are rotated, as I said, and stored in sequence on the file. As they are stored, their addresses are recorded in a list and the file size is used as the start of the image index where all those addresses that were accumulated in a list are recorded in the same sequence as their images. At the end of all this, the very last thing recorded on the file, is a long integer which tells you where the index starts. So to find a specific image, you plug the shape, color and angle through this function:
星星,尖刺和菱形是三种不同的形状,每当游戏兴奋时,它们就会飞出多种颜色.它们都是在第一次启动游戏时动态绘制的,然后它们各种颜色和旋转的版本都以二进制流文件的形式存储在硬盘中.单个算法绘制出所有三个形状,只是形状上伸出的"尖角"物的数量不同.星星有5个点,菱角(不确定为什么我称它们为!!?)只有4个,尖峰只有2个.它们每个都有内半径和外半径,并且这些点是通过围绕中心点并将半径设置为交替的点而生成的从中.正如我所说,这些图像是旋转的,并按顺序存储在文件中.存储它们时,它们的地址被记录在一个列表中,并且文件大小用作图像索引的开始,其中列表中累积的所有那些地址都以与其图像相同的顺序记录.最后,记录在文件上的最后一件事是一个长整数,它告诉您索引从何处开始.因此,要查找特定图像,请通过此功能插入形状,颜色和角度:
static long ImageAddress(enuSparkleShapes eShape, enuSparkleColors eColor, int intRotation)
{
long lngIndexAddress
= lngFileIndexAddress
+ (((int)eColor
* ((int)enuSparkleShapes._numSparkleShapes
* NumRotationPerQuarterTurn))
+ ((int)eShape
* NumRotationPerQuarterTurn)
+ intRotation) * SizeLongInteger;
fs.Position = lngIndexAddress;
return (long)formatter.Deserialize(fs);
}
It calculates the location of the desired image’s Index by figuring out how many indices come before it, multiplying that value by the amount of bytes it takes to store a single
long
integer in the stream and adding their product to the Address of the start of the Index (that lastlong
integer we wrote at the end of the file).
它计算出所需图像的索引数,然后将该值乘以在流中存储单个"长"整数所需的字节数,然后将其乘积添加到起始地址中,从而计算出所需图像的索引位置Index的值(我们在文件末尾写入的最后一个long型整数).
So, it measures the length of the file. Moves back the number of bytes to store a single long integer on my harddrive, 58 bytes (yea!? 58… I was surprised too since a long integer is only 8 bytes ??). There it reads the long integer and knows where in the Index starts. It moves forward from the start of the index and locates the address of the image we’re looking for. Reads the long integer and uses it as the address of the bitmap.
因此,它测量文件的长度.移回要在我的硬盘驱动器上存储单个长整数的字节数,即58个字节(是!58…我也很惊讶,因为长整数只有8个字节??).在那里,它读取长整数并知道索引从何处开始.它从索引的开头开始向前移动,并找到我们要查找的图像的地址.读取长整数并将其用作位图的地址.
Confused?
困惑?
Alternately, there is an advantage in storing the index on a separate file which is that you can add images and grow both your index and your images list on two separate files. In this case, we’re talking about tiny images of colored stars that only take seconds to reproduce, so even if I change my mind and add that neglected 3-pointed ninja-star to the list of ‘sparkle shapes’, it is no great cost to delete the existing file and rebuild it.
另外,将索引存储在单独的文件中还有一个优势,那就是您可以添加图像,并在两个单独的文件上同时增加索引和图像列表.在这种情况下,我们谈论的是彩色恒星的微小图像,仅需几秒钟即可再现,因此即使我改变主意并将被忽略的三点忍者恒星添加到"闪光形状"列表中,删除现有文件并重建它的成本很高.
Having both the images and the index on a single file keeps things neat.
将图像和索引都放在一个文件中可以保持整洁.
Since a different OS may give you different results, the
SizeLongInteger
variable needs to be evaluated. Here is the function which determines how many bytes it takes to store a long integer on your harddrive:
由于不同的操作系统可能会给您带来不同的结果,因此需要评估SizeLongInteger
变量.这是确定在硬盘驱动器上存储一个长整数所需的字节数的函数:
static long SizeLongInteger = 0;
static void SizeLongInteger_Measure()
{
string strTempFilename = "DoNotTryThisAtHome.bin";
FileStream fsTemp = new FileStream(strTempFilename, FileMode.Create);
fsTemp.Position = 0;
formatter.Serialize(fsTemp, (long)1);
SizeLongInteger = fsTemp.Position;
fsTemp.Close();
System.IO.File.Delete(strTempFilename);
}
Debugger and Binary Strings(调试器和二进制字符串)
When I was dealing with those mouse event issues, I had to figure out how the
MouseEventArgs
were encoded. They seem to be stored in along
int
eger. I converted theMouseButtons
valueint
o along
int
eger and discovered that the 20th bit was set when the left mouse button was down. Likewise for the middle and right buttons, 22nd and 23rd, respectively. To help along
the way, I wrote a few functions that convertint
eger values (byte
,short
,int
andlong
)int
ostring
s to make it easier to look and debug them.
当我处理这些鼠标事件问题时,我不得不弄清楚" MouseEventArgs"是如何编码的.它们似乎存储在"长"整数中.我将" MouseButtons"值" int"转换为" long" integer,然后发现按下鼠标左键时已设置了第20位.同样,中间和右边的按钮分别为22nd和23rd.为了帮助long
,我写了一些函数来转换int
eger值(byte
,short
,int
和long
)int
ostring
s,以使其更容易查找和调试它们.
Since the » and « bitwise shift operands return integer values, the workhorse here takes in an integer and spits out a 32 character string of 1s and 0s.
因为»和«按位移位操作数返回整数值,所以这里的主力函数接受一个整数并吐出32个1和0的字符串.
public static string intToString(int intIn)
{
string strRetVal = "";
for (int intBitCounter = 0; intBitCounter < 32; intBitCounter++)
{
char chr = intIn % 2 == 0
? '0'
: '1';
strRetVal = chr.ToString() + strRetVal;
intIn = intIn >> 1;
}
return strRetVal;
}
It’s convenient, and I’m glad I wrote it. I’m sure it will come in handy again soon enough.
很方便,我很高兴写了它.我敢肯定它将很快再次派上用场.
Why Bother?(何必?)
There are hundreds of versions of this game available on-line, so why would anyone write their own?
该游戏有数百种在线版本,那么为什么有人会自己编写呢?
I was playing a version similar to this one (Hexagonal board) on my phone and got annoyed with all the ads. I loved the game so much I actually considered doling out the $5.00 payment to get rid of the ads but then reconsidered and decided it would be more cost-efficient to spend a week writing my own… I actually did press the option to pay to get rid of the ads but it didn’t work and bogged my wireless down so badly, it forgot it was a telecommunication device when the ringtone sounded and I nearly missed a call trying to get it to morph from a slow ad-riddled game back into phone.
我在手机上玩的是与此版本(六角板)类似的版本,所有的广告都让他很生气.我非常喜欢这款游戏,实际上我考虑过支付5.00美元以摆脱广告,但随后重新考虑并决定花一周的时间编写自己的游戏会更具成本效益…实际上我确实选择了付款为了摆脱广告,但它无法正常工作并使我的无线设备陷入瘫痪,当铃声响起时,我忘了它是一台电信设备,而我差点错过了一个电话,试图让它从一个缓慢的,充满广告的游戏中变身回电话.
It was a wrong number and that sent me into a downward spiral of coding energy… and the only way out of a downward spiral like that is to code, right? So that’s what I did.
这是一个错误的数字,这使我陷入编码能力的下降螺旋式^而摆脱这种下降螺旋式的唯一方法是编码,对吗?这就是我所做的.
I started last Sunday at 19h and had a working game before midnight. It was so motivating and encouraging to get such quick results that I spent the rest of the week improving the graphics.
我从上周日19点开始,在午夜之前进行了一场比赛.获得如此快的结果是如此令人鼓舞和鼓舞,以至于我花了剩余的时间来改进图形.
Coding really is a great way to spend your time, but really … I just hate ads.
编码确实是一种花费时间的好方法,但实际上^我只是讨厌广告.
History(历史)
- 28th September, 2020: Initial version2020年9月28日:初始版本
- 2nd October, 2020 : updated software - first instance of Final Tile wasn’t disappearing2020年10月2日:更新了软件-Final Tile的第一例并没有消失
- *4th October, 2020 : added original source code - I wrote in under 5 hours.*2020年10月4日:添加了原始源代码-我在5个小时内写下了代码.
- 7th October, 2020 : fixed High Score file-save - added Timer count-down option2020年10月7日:固定了高分文件保存-添加了计时器倒计时选项
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
C# Intermediate game DevOps 新闻 翻译