我正在编写一款示例游戏,其中一个方块会永远落下,你需要将它从一边转向另一边。如果你碰壁,你就输了。我试着随机地、持续地生成关卡,这样总有一条路可以通过,所以路径会逐渐变窄。
# #
# #
# ##
# ###
# ####
# #####
## #####
## #####
## ####
### ####
### ###
#### ###
#### ###
### ###
### ###
## ####
## ####
## #####
## #####
## ######
## ######
## ######
## ######
我目前的方法是有一个可能路径的数组,然后我随机选择一行。问题是这条路并不平坦,有时会变得不可能:
# #
# ####
# #####
### ###
## #####
### ####
# ###
## #####
#### ### <--- can't get through here
## ####
#### ###
### ###
# ##
## ######
## #####
## ######
## #####
## #####
## ####
## ####
# #
### ###
## ###### <--- or here
# #
## ######
## ######
哪一类算法能帮我开始呢?
这里的基础是一个简单的算法,可以通过改进得到一个更具挑战性和趣味性的游戏。这里没有IA。
它只是生成一些路径,这些路径总是可能的,这要归功于旧的路径。
基本思想是坚持使用索引,它将是路径的中间。
你随机地将中间的下标向左或向右移动。在我的实现中,我选择随机地使路径变窄。一种可能的改进是通过考虑宽度来制定更深入、更聪明的行动,从而获得连贯的路径(如果需要的话可以这样做)。
// path is a vector of booleans
// wideness tells how narrow the path is
// middle represents the middle of the path
while wideness > 0
{
thicken wideness sometimes
move middle to the right, left, or do not move it
print the path
}
你可以看看实时c++代码和算法。
结果如下所示:
|# #########|
|## ########|
|## ########|
|### #######|
|#### ######|
|### #######|
|### #######|
|#### ######|
|##### #####|
|##### #####|
|##### #####|
|#### ######|
|##### #####|
|###### ####|
|####### ###|
|####### #####|
|######## ####|
|######### ###|
|######## ####|
|######### ###|
|######## ####|
|######### ###|
|######### ###|
|######## ####|
|######### ###|
|######### ###|
|########## ##|
|########### #|
|########### #|
|########### #|
|########### #|
|########### #|
|########## ##|
|########## ##|
|########## ##|
|########### #|
|########### #|
|########### #|
|########### #|
|########### #|
|########### #|
|########### #|
|########### #|
|########## ##|
|######### ###|
|######### ###|
|######### ###|
|########## ##|
|########## ##|
|########## ##|
|######### ###|
|######## ####|
|####### #####|
|###### ######|
|####### #####|
|######## ####|
|######### ###|
|######## ####|
|######### ###|
|######### ###|
|########## ##|
|########### #|
|########## ##|
|########## ##|
|########### #|
|########## ##|
|######### ###|
|########## ##|
|########## ##|
|########## ##|
|########## ##|
|######### ###|
|########## ##|
|########### #|
|########## ##|
|########### #|
|########### #|
|########### #|
|########## ##|
|########### #|
|########## ##|
|########### #|
|########### #|
|########### #|
|########### #|
|########### #|
|########### #|
|########## ##|
|########### #|
|########### #|
|########## ##|
|######### ###|
|######### ###|
|########## ##|
|######### ###|
|########## ##|
|########## ####|
|########## ####|
|######### #####|
|######## ######|
|######### #####|
|######### #####|
|######## ######|
|######### #####|
|########## ####|
|######### #####|
|######## ######|
|######## ######|
|######## ######|
|######### #####|
|######### #####|
|########## ####|
|######### #####|
|########## ####|
|########## ####|
我不确定是一个类,但下面的原则可能适用。
你可以跟踪"孔中心索引"i,它将跟踪任何给定水平孔的中心,以及"当前孔宽度"(将逐渐变小的东西)w。
At each level:
i <- i +/- (w / 2)
w <- widthModify(w) //decreases width sometimes
for all "tokens" n on level
if [n < i - (w / 2)] or [n > i + (w / 2)] print("#")
else print(" ")
这意味着后面的洞之间一定有一些重叠,因为下面的中心在前一个关卡的"洞的范围"内。
算法本身非常简单,正如其他人所建议的,要注意的要点是路径的连续性,这可以通过首先计算每一行路径的宽度,然后将其定位,使其在前一行重叠一个间隙来保证。
我们定义:
- Wf为代表全宽度("关卡宽度")的静态自然数
- Wi是小于或等于Wf且大于0的自然数,表示第i行路径宽度
- Xi是MAX(0, Xi-1 - Wi + 1)和MIN(Wf - Wi, Xi-1 + Wi-1 -1)之间的自然数,表示路径从第i行开始的位置
其次,生成 W <子>我子>我们定义:
- P是一个介于0到1之间的实数,表示进度(以时间、距离、分数…
- Rw是0(含)到1(不含)之间随机生成的实数。
- F <子> w <子>子>子> (w <子> F子> w 子>)计算 w <子>我子>。你需要一些非线性函数,它允许所有P的全宽度范围,但概率随着P的推进而下降。例如,您可以使用: W f <子>子> (1 - (P (1 - R <子> W 子>))<一口> 0.65> 一口>。您可以使用功率常数,或者定义一个完全不同的函数来控制宽度。重要的是要记住将结果向上舍入到一个自然数。
最后,生成 X <子>子>我们定义:
- Rx是0(含)到1(不含)之间随机生成的实数。
- F <子> x <子>子>子> (W <子>子>,张W <子> ,张x <子> , R <子> x 子>)子>子>, 计算x <子>子>。
函数是: R <子> x 子> MIN (W <子> f 子> - W <子>子>,张x <子>子>张+ W <子>子> - 1)+ (1 - x R <子> )马克斯·张(0,x <子>子> - W <子>子> + 1)子>。将结果四舍五入为自然数是很重要的。
把所有这些放在一起,这里是一个实时的JavaScript实现:
function PathRow(fullWidth, progress, prevRow) {
var Rw = Math.random();
var Rx = Math.random();
this.width = Math.ceil(fullWidth * (1 - Math.pow(progress * (1 - Rw), 0.65)));
if(prevRow) {
this.x = Math.round(Rx * Math.min(fullWidth - this.width, prevRow.x + prevRow.width - 1) +
(1 - Rx) * Math.max(0, prevRow.x - this.width + 1));
}
else {
this.x = Math.round(Rx * (fullWidth - this.width));
}
}
function Game(width, height, target) {
this.progress = 0;
this.width = width;
this.height = height;
this.target = target;
this.path = [];
}
Game.prototype.next = function(progress) {
this.progress = progress;
this.path.push(new PathRow(this.width, this.progress, this.path[this.path.length - 1]));
this.draw();
}
Game.prototype.draw = function() {
var pathString = '';
for(var i = Math.max(0, this.path.length - this.height + 1); i < this.path.length; i++) {
pathString += '|' + new Array(this.path[i].x + 1).join('#') + new Array(this.path[i].width + 1).join(' ') + new Array(this.width - this.path[i].x - this.path[i].width + 1).join('#') + '|rn';
}
this.target.innerHTML = pathString;
}
var path = document.getElementById('path');
var go = document.getElementById('go');
go.onclick = function() {
go.disabled = true;
var game = new Game(20, 20, path);
var progress = 0;
var totalSteps = 480;
var interval = setInterval(function() {
game.next(progress++ / totalSteps);
if(progress == totalSteps) {
clearInterval(interval);
go.disabled = false;
}
}, 125);
}
#path {
white-space: pre;
font-family: monospace;
}
<div id="path"></div>
<br/><br/>
<button id="go">Go!</button>