FruitFrolic
这是一个连连看小游戏,以 Unity2D 开发。因用了数种水果图片来做头像,所以游戏取名 FruitFrolic。同样,它也只是我闲时的练手。
少时曾玩过掌上游戏机里的俄罗斯方块及打飞机,及手机上的推箱子等,也在 Dos 上玩过几乎人人皆知的超级玛丽。我很想在闲暇的时候自己来实现它们,但为兴趣和乐趣而已。所以有前文所述的 PetGenie,以及本文,和之后可能的自实现版俄罗斯方块。不过限于美术素材及个人精力等之因,它们应会实现得比较简陋,虽然游戏核心逻辑几都具备。
而我所使用的所有美术素材及音频等都来源于网络,本着开放的原则,我的(所有)自实现小游戏也都开源,且没有任何版权等限制。
连连看的核心显然在洗牌及连线分析算法。洗牌控制了游戏的难易,变化很多。但我这里只是简单地平均生成了头像并随机打乱,而在连线分析算法里使用了广度优先搜索。
洗牌代码如下。
void RandomGenies() {
int idx;
// 共 6 * 8 个格子、12 种水果 --> 每种水果生成 4 次
int[] geniesCounter = new int[cSpriteTypeCount] {, , , , , , , , , , , };
for (int i = ; i < cGridRows; i++) {
for (int j = ; j < cGridCols; j++) {
genies[i, j].x = i;
genies[i, j].y = j;
while (true) {
idx = (int)(Random.value * cSpriteTypeCount);
if (geniesCounter[idx] > ) {
break;
}
}
geniesCounter[idx]--;
genies[i, j].index = idx;
genies[i, j].spriteRenderer.sprite = fruitSprites[idx];
}
}
}
而连线分析实现代码如下。
void DetectLink() {
if (!ValidPrecondition()) {
return;
}
List<TGenie> contnr0 = new List<TGenie>();
FindCells((int)(touchCoords.pos1.x), (int)(touchCoords.pos1.y), contnr0);
if (CellExists(genies[(int)(touchCoords.pos2.y), (int)(touchCoords.pos2.x)], contnr0)) {
genies[(int)(touchCoords.pos1.y), (int)(touchCoords.pos1.x)].index = cInvalidCoordValue;
genies[(int)(touchCoords.pos2.y), (int)(touchCoords.pos2.x)].index = cInvalidCoordValue;
return;
}
List<TGenie> contnr1 = new List<TGenie>();
PrepareContnr(contnr0, contnr1);
ShrinkContnr(contnr0, contnr1);
if (CellExists(genies[(int)(touchCoords.pos2.y), (int)(touchCoords.pos2.x)], contnr1)) {
genies[(int)(touchCoords.pos1.y), (int)(touchCoords.pos1.x)].index = cInvalidCoordValue;
genies[(int)(touchCoords.pos2.y), (int)(touchCoords.pos2.x)].index = cInvalidCoordValue;
return;
}
List<TGenie> contnr2 = new List<TGenie>();
PrepareContnr(contnr1, contnr2);
ShrinkContnr(contnr0, contnr2);
ShrinkContnr(contnr1, contnr2);
if (CellExists(genies[(int)(touchCoords.pos2.y), (int)(touchCoords.pos2.x)], contnr2)) {
genies[(int)(touchCoords.pos1.y), (int)(touchCoords.pos1.x)].index = cInvalidCoordValue;
genies[(int)(touchCoords.pos2.y), (int)(touchCoords.pos2.x)].index = cInvalidCoordValue;
return;
}
DetectSpecialLink();
}
bool ValidPrecondition() {
if ((touchCoords.pos1.x == cInvalidCoordValue) || (touchCoords.pos1.y == cInvalidCoordValue) || (touchCoords.pos2.x == cInvalidCoordValue) || (touchCoords.pos2.y == cInvalidCoordValue)) {
return false;
}
if ((touchCoords.pos1.x == touchCoords.pos2.x) && (touchCoords.pos1.y == touchCoords.pos2.y)) {
return false;
}
if (genies[(int)(touchCoords.pos1.y), (int)(touchCoords.pos1.x)].index != genies[(int)(touchCoords.pos2.y), (int)(touchCoords.pos2.x)].index) {
return false;
}
return true;
}
void FindCells(int x, int y, List<TGenie> contnr) {
int n = ;
n = y - ;
if (n >= ) {
if (!(contnr.Contains(genies[n, x]))) {
contnr.Add(genies[n, x]);
}
}
while ((n >= ) && (genies[n, x].index == cInvalidCoordValue)) {
n--;
if ((n >= ) && (!(contnr.Contains(genies[n, x])))) {
contnr.Add(genies[n, x]);
}
}
n = y + ;
if (n < cGridRows) {
if (!(contnr.Contains(genies[n, x]))) {
contnr.Add(genies[n, x]);
}
}
while ((n < cGridRows) && (genies[n, x].index == cInvalidCoordValue)) {
n++;
if ((n < cGridRows) && (!(contnr.Contains(genies[n, x])))) {
contnr.Add(genies[n, x]);
}
}
n = x - ;
if (n >= ) {
if (!(contnr.Contains(genies[y, n]))) {
contnr.Add(genies[y, n]);
}
}
while ((n >= ) && (genies[y, n].index == cInvalidCoordValue)) {
n--;
if ((n >= ) && (!(contnr.Contains(genies[y, n])))) {
contnr.Add(genies[y, n]);
}
}
n = x + ;
if (n < cGridCols) {
if (!(contnr.Contains(genies[y, n]))) {
contnr.Add(genies[y, n]);
}
}
while ((n < cGridCols) && (genies[y, n].index == cInvalidCoordValue)) {
n++;
if ((n < cGridCols) && (!(contnr.Contains(genies[y, n])))) {
contnr.Add(genies[y, n]);
}
}
}
bool CellExists(TGenie genie, List<TGenie> contnr) {
foreach (TGenie g in contnr) {
if (g.Equals(genie)) {
return true;
}
}
return false;
}
void PrepareContnr(List<TGenie> contnrSrc, List<TGenie> contnrDest) {
foreach (TGenie g in contnrSrc) {
if (g.index == cInvalidCoordValue) {
FindCells(g.y, g.x, contnrDest);
}
}
}
void ShrinkContnr(List<TGenie> contnrSrc, List<TGenie> contnrDest) {
foreach (TGenie g in contnrSrc) {
if (contnrDest.Contains(g)) {
contnrDest.Remove(g);
}
}
}
void DetectSpecialLink() {
// 若在第一或最末列
if (touchCoords.pos1.x == touchCoords.pos2.x) {
if ((touchCoords.pos1.x == ) || (touchCoords.pos1.x == cGridCols - )) {
genies[(int)(touchCoords.pos1.y), (int)(touchCoords.pos1.x)].index = cInvalidCoordValue;
genies[(int)(touchCoords.pos2.y), (int)(touchCoords.pos2.x)].index = cInvalidCoordValue;
return;
}
}
// 若在第一或最末行
if (touchCoords.pos1.y == touchCoords.pos2.y) {
if ((touchCoords.pos1.y == ) || (touchCoords.pos1.y == cGridRows - )) {
genies[(int)(touchCoords.pos1.y), (int)(touchCoords.pos1.x)].index = cInvalidCoordValue;
genies[(int)(touchCoords.pos2.y), (int)(touchCoords.pos2.x)].index = cInvalidCoordValue;
return;
}
}
}
判断是否已连线完毕(已全部消除或已死锁)的代码如下。
bool HasMatches() {
// 检测上下左右第一行/列是否有可消除的格子(特殊处理)
for (int i = ; i < cGridCols - ; i++) {
if (genies[, i].index != cInvalidCoordValue) {
for (int j = i + ; j < cGridCols; j++) {
if (genies[, i].index == genies[, j].index) {
return true;
}
}
}
if (genies[cGridRows - , i].index != cInvalidCoordValue) {
for (int j = i + ; j < cGridCols; j++) {
if (genies[cGridRows - , i].index == genies[cGridRows - , j].index) {
return true;
}
}
}
}
for (int i = ; i < cGridRows - ; i++) {
if (genies[i, ].index != cInvalidCoordValue) {
for (int j = i + ; j < cGridRows; j++) {
if (genies[i, ].index == genies[j, ].index) {
return true;
}
}
}
if (genies[i, cGridCols - ].index != cInvalidCoordValue) {
for (int j = i + ; j < cGridRows; j++) {
if (genies[i, cGridCols - ].index == genies[j, cGridCols - ].index) {
return true;
}
}
}
}
for (int i = ; i < cGridRows; i++) {
for (int j = ; j < cGridCols; j++) {
if (genies[i, j].index != cInvalidCoordValue) {
// 0 转弯
List<TGenie> contnr0 = new List<TGenie>();
FindCells(j, i, contnr0);
if (HasMatchableGenie(genies[i, j], contnr0)) {
return true;
}
// 1 转弯
List<TGenie> contnr1 = new List<TGenie>();
PrepareContnr(contnr0, contnr1);
ShrinkContnr(contnr0, contnr1);
if (HasMatchableGenie(genies[i, j], contnr1)) {
return true;
}
// 2 转弯
List<TGenie> contnr2 = new List<TGenie>();
PrepareContnr(contnr1, contnr2);
ShrinkContnr(contnr0, contnr2);
ShrinkContnr(contnr1, contnr2);
RemoveNullGenies(contnr2);
if (HasMatchableGenie(genies[i, j], contnr2)) {
return true;
}
}
}
}
return false;
}
bool HasMatchableGenie(TGenie genie, List<TGenie> contnr) {
foreach (TGenie g in contnr) {
if ((!g.Equals(genie)) && (g.index == genie.index)) {
return true;
}
}
return false;
}
void RemoveNullGenies(List<TGenie> contnr) {
List<TGenie> tmp = new List<TGenie>();
foreach (TGenie g in contnr) {
if (g.index == cInvalidCoordValue) {
tmp.Add(g);
}
}
foreach (TGenie g in tmp) {
contnr.Remove(g);
}
}
其实我本想分析每一种可能的连线情况(0---2 个转弯),但在写完 0 和 1 个转弯分析之后不想再写 2 个转弯分析代码了,因它们确实不好理解(也不好维护)。
// 0 个转角连通
bool CheckLink0() {
// 若在同一列格子
if (touchCoords.pos1.x == touchCoords.pos2.x) {
if ((touchCoords.pos1.x != ) && (touchCoords.pos1.x != cGridCols - )) {
if (touchCoords.pos1.y < touchCoords.pos2.y) {
for (int i = (int)(touchCoords.pos1.y) + ; i < ((int)(touchCoords.pos2.y)); i++) {
if (genies[i, (int)(touchCoords.pos1.x)].index != cInvalidCoordValue) {
return false;
}
}
} else {
for (int i = (int)(touchCoords.pos2.y) + ; i < ((int)(touchCoords.pos1.y)); i++) {
if (genies[i, (int)(touchCoords.pos1.x)].index != cInvalidCoordValue) {
return false;
}
}
}
} genies[(int)(touchCoords.pos1.y), (int)(touchCoords.pos1.x)].index = cInvalidCoordValue;
genies[(int)(touchCoords.pos2.y), (int)(touchCoords.pos2.x)].index = cInvalidCoordValue; return true;
} // 若在同一行格子
if (touchCoords.pos1.y == touchCoords.pos2.y) {
if ((touchCoords.pos1.y != ) && (touchCoords.pos1.y != cGridRows - )) {
if (touchCoords.pos1.x < touchCoords.pos2.x) {
for (int i = (int)(touchCoords.pos1.x) + ; i < ((int)(touchCoords.pos2.x)); i++) {
if (genies[(int)(touchCoords.pos1.y), i].index != cInvalidCoordValue) {
return false;
}
}
} else {
for (int i = (int)(touchCoords.pos2.x) + ; i < ((int)(touchCoords.pos1.x)); i++) {
if (genies[(int)(touchCoords.pos1.y), i].index != cInvalidCoordValue) {
return false;
}
}
}
} genies[(int)(touchCoords.pos1.y), (int)(touchCoords.pos1.x)].index = cInvalidCoordValue;
genies[(int)(touchCoords.pos2.y), (int)(touchCoords.pos2.x)].index = cInvalidCoordValue; return true;
} return false;
} // 1 个转角连通 --> 相当于两个格子划出一个矩形, 这两个格子是一对对角顶点, 另两个顶点如果可以同时和这两个格子直连, 那就说明可以连通
bool CheckLink1() {
int l = cInvalidCoordValue, t = cInvalidCoordValue, r = cInvalidCoordValue, b = cInvalidCoordValue;
if (touchCoords.pos1.y < touchCoords.pos2.y) {
t = (int)(touchCoords.pos1.y);
b = (int)(touchCoords.pos2.y);
} else {
t = (int)(touchCoords.pos2.y);
b = (int)(touchCoords.pos1.y);
}
if (touchCoords.pos1.x < touchCoords.pos2.x) {
l = (int)(touchCoords.pos1.x);
r = (int)(touchCoords.pos2.x);
} else {
l = (int)(touchCoords.pos2.x);
r = (int)(touchCoords.pos1.x);
} if (genies[t, l].index == cInvalidCoordValue) { // 若选取的两个格子在 右上、左下
for (int i = t + ; i < b; i++) {
if (genies[i, l].index != cInvalidCoordValue) {
return false;
}
} for (int j = l + ; j < r; j++) {
if (genies[t, j].index != cInvalidCoordValue) {
return false;
}
} genies[t, r].index = cInvalidCoordValue;
genies[b, l].index = cInvalidCoordValue; return true;
} else if (genies[t, r].index == cInvalidCoordValue) { // 若选取的两个格子在 左上、右下
for (int i = t + ; i < b; i++) {
if (genies[i, r].index != cInvalidCoordValue) {
return false;
}
} for (int j = l + ; j < r; j++) {
if (genies[t, j].index != cInvalidCoordValue) {
return false;
}
} genies[t, l].index = cInvalidCoordValue;
genies[b, r].index = cInvalidCoordValue; return true;
} else if (genies[b, l].index == cInvalidCoordValue) { // 若选取的两个格子在 左上、右下
for (int i = t + ; i < b; i++) {
if (genies[i, r].index != cInvalidCoordValue) {
return false;
}
} for (int j = l + ; j < r; j++) {
if (genies[b, j].index != cInvalidCoordValue) {
return false;
}
} genies[t, l].index = cInvalidCoordValue;
genies[b, r].index = cInvalidCoordValue; return true;
} else if (genies[b, r].index == cInvalidCoordValue) { // 若选取的两个格子在 右上、左下
for (int i = t + ; i < b; i++) {
if (genies[i, l].index != cInvalidCoordValue) {
return false;
}
} for (int j = l + ; j < r; j++) {
if (genies[b, j].index != cInvalidCoordValue) {
return false;
}
} genies[t, r].index = cInvalidCoordValue;
genies[b, l].index = cInvalidCoordValue; return true;
} return false;
}
游戏真机运行截图如下。

代码下载链接在这里。
FruitFrolic的更多相关文章
随机推荐
- cloudera learning7:Hadoop资源管理
Linux Control Groups(cgroups):在操作系统级别进行资源分配,可通过Cloudera Static Service Pools配置. YARN调度器配置:对运行在YARN上的 ...
- C#程序以管理员权限运行
原文:C#程序以管理员权限运行 C#程序以管理员权限运行 在Vista 和 Windows 7 及更新版本的操作系统,增加了 UAC(用户账户控制) 的安全机制,如果 UAC 被打开,用户即使以管理员 ...
- "undefined method `root' for nil:NilClass" error when using "pod install" 解决办法
如果pod undate 的时候报错"undefined method `root' for nil:NilClass" error when using "pod in ...
- JMeter学习-029-JMeter配置文件propertie配置项读取及应用实例
在上文中提到通过读取配置文件中的数据,以此定制JMeter Slave的脚本分发路径(默认脚本路径,即参数文件相对路径父目录). 此文,就以此为例进行实例说明. 通过阅读JMeter源码 core/s ...
- [转载]Ubuntu17.04(Zesty Zapus)路线图发布:2017年4月13日发布
Canonical今天公布了Ubuntu 17.04(Zesty Zapus)操作系统的发布路线图,该版本于今年10月24日上线启动,toolchain已经上传且首个daily ISO镜像已经生成.面 ...
- haproxy学习之https配置
haproxy学习之https配置 原文 http://www.cnblogs.com/ilanni/p/4941056.html 如何配置https,以及https在实际生产环境中的应用. ...
- 上下箭头选中 选项事件 JS
//上下键 选择事件 searchBackgroud 为样式,只做标记,无实质样式,因为和其他样式不兼容,只能添加CSS $(document).keydown(function (event) { ...
- XPath 语法
XPath 使用路径表达式来选取 XML 文档中的节点或节点集.节点是通过沿着路径 (path) 或者步 (steps) 来选取的. XML 实例文档 我们将在下面的例子中使用这个 XML 文档. & ...
- 6周学习计划,攻克JavaScript难关(React/Redux/ES6 etc.)
作者:余博伦链接:https://zhuanlan.zhihu.com/p/23412169来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 和大家一样,最近我也看了Jo ...
- [原创]java WEB学习笔记103:Spring学习---Spring Bean配置:基于注解的方式(基于注解配置bean,基于注解来装配bean的属性)
本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...