分析游戏策略提升算法

一、引入

近日,宿舍一位哥们总是若有所思,我一问原来是在思考一个高深的问题。他在游戏(凯瑞甘生存2)中遇到了一个问题。在游戏的每一局中有一个关键经济单位(海文)需要购买,更早买到这个单位就可以获得战局的更大优势。如何尽早积累到足够的资源,就变成了一个问题。下面的具体的游戏规则:

初始资金 105
资源提升速度 2个/秒
目标金额 4000

产品1(保护者):花费75;提升经济增速:0.25个/秒
产品2(牧师):花费550;提升经济增速:2.5个/秒(游戏中牧师需要500资源+10气体,为简化建模难度,牧师成本按550计算)

问题:购买产品的次数,同时多次购买都没有限制,资源每秒更新一次,如何组合以尽早积累到目标金额?

二、分析

初次分析
当拿到这个问题之后,刚开始感觉并没有太复杂,但是仔细一想,发现问题远远没有这么简单。购买产品虽然提高了资源增速,但同时也需要消耗资源,资源消耗是瞬时的,而增速提升所带来的效益,是逐渐体现出来的。

  • 问题一:可能会出现这样一种情况,假设即将要到达目标金额,这时购买了产品,虽然增速提升了,但是由于购买产品花费了资源,结果反而没有不买产品更快实现目标资源。这就造成了一个很棘手的问题:暂且不考虑到底购买哪一种产品,单就如何判断最后一次购买就不知道该如何求解。总不能一直买产品,经济增速极大,但是一直没有经济的积累吧。

  • 问题二:能不能将问题的规模变小呢?比如先求出,较小金额的目标值的方案,然后在这个方案的基础上,再进行进一步的求解(动态规划)。答案是不行的,因为在这个问题中,局部最优解根本和全局最优解没有关系,比如你找到了一个较小的最优解,但更大规模的答案根本无法与以求解答案建立联系。找不到联系,也就无法使用这种方法。

增速其实表示的是资源提升的后劲,但是资源量才是最终的目标,从长期发展来看,增速越快越有利,但是短期却会导致长时间的资源量保持在很低的水平。如何在增速和资源量积累之间做一个平恒就变成了一个很大问题。

深入分析
上文已经分析了问题的复杂性,论证了无法进行复杂问题的拆分。现在整个问题陷入了僵局,这时我必须从这当中跳脱出来。我就在思考,为什么这个问题让人如此困扰,最后我发现整个问题中好像所有的变量都在变化,而且这些变化又在相互影响,时间在变,增速在变,资源量也在变,产品购买规则和次数还是变。之前的思路一直是在变化中寻找规律,但当变量相互联系,相互影响,他们之间的关联关系已经变得迷雾重重,难以抽象。那我能不能强行控制一个量,使其有规律的变化呢?

经过一番仔细的思考之后,我觉得应当控制购买规则,因为时间是客观推进的(无法控制),增速是首购买规则影响的(控制规则,就等于控制增速),资源量是衡量成功的标准(不能控制)。那么此时问题就转化成如何合理控制购买规则的变化了(回溯法),即将所有购买情况依次列出对应时间。

具体分析
【思路】:
通过全排列列出所有的购买规则,按照此规则计算达到目标金额的时间,比较时间大小,得到最优解法。

【过程】:
假设以1,1,1,2···,2(全排列的结果)顺序购买商品,可以最快达到要求
那购买的时候一定是到达产品金额后即刻购买,以保证尽早获得更快提速(过程一)
最后保持增速到终止条件(过程二)

【实现】:
根据上面的分析,总时间是提速所花费时间加最后完成终止条件的时间和。
通过建立二叉树,来模拟不同的购买模式。二叉树的层数表示购买次数。
在二叉树创建中,如果出现增速,金额相同的情况,可以通过比较时间直接进行剪枝操作,减小无效计算,提高算法效率

【注意】:
1.这颗二叉树的每一个结点都有可能是最优方案,在树创建的同时需要寻找最优结果。
2.不能使用递归算法,在比较是否可以剪枝时,涉及两个分支间的比较,所以只能使用循环实现。
3.目前算法只能求解较小规模的问题,由于使用了回溯法,对于数据量很大的情况将无法列举所有的情况进行求解。
4.由于代码中,对于每一个状态,使用了字符串保存其路径,占用了很大的内存空间,在内存方面有待于优化。

三、代码及运行截图

#include<stdio.h>
#include<string.h>
const int n = 50;//定义购买次数的最大值

const int START = 105; //起始资金
const float LEVEL = 2; //起始速度
const int END = 4000; //终止条件

const int level1 = 75;
const float up1 = 0.25;
const int level2 = 550;
const float up2 = 2.5;


typedef struct {
	float level;
	long long time;
	int money;
	char path[51];//记录路径
}data;

typedef struct {
	data next[101];//最大层数保证100次
	int length = 1;
}tree;
//保存下一次循环需要的变量

int change(float in) {
	return (in - (int)in == 0) ? (in) : (in + 1);
}
//进位取整

int min = change((END - START) / LEVEL);
char ans[51];//保存最后的答案

data s(data in,int n){
	static data back;
	if (n == 1) {
		if (in.money >= level1) {
			back.level = in.level + up1;
			back.money = in.money - level1;
			back.time = in.time;
			strcpy(back.path , strcat(in.path, "1"));
		}
		else {
			back.level = in.level + up1;
			back.money = 0;
			back.time = in.time+change((level1-in.money)/in.level);
			strcpy(back.path, strcat(in.path, "1"));
		}
	}

	if (n == 2) {
		if (in.money >= level2) {
			back.level = in.level + up2;
			back.money = in.money - level2;
			back.time = in.time;
			strcpy(back.path, strcat(in.path, "2"));
		}
		else {
			back.level = in.level + up2;
			back.money = 0;
			back.time = in.time + change((level2 - in.money) / in.level);
			strcpy(back.path, strcat(in.path, "2"));
		}
	}

	return back;
}
//传入上一次状态和本次购买产品类型,返回购买后的那一瞬间的状态

tree find(tree pre) {
	static tree back;
	for (int i = 1; i <= pre.length; i++) {
		int time = pre.next[i].time + change((END - pre.next[i].money) / pre.next[i].level);
		printf("时间:%d\n", time);
		printf("顺序:%s\n\n", pre.next[i].path);
		if (min > time) {
			min = time;
			strcpy(ans, pre.next[i].path);
		}
	}
	//计算每一组情况的时间消耗,每一层都有可能是最优解

	int l = 1;//back的下标
	for (int i = 1; i <= pre.length; i++) {
		if (i == 1) {
			back.next[l++] = s(pre.next[i], 1);
			back.next[l++] = s(pre.next[i], 2);
		}
		else if (i == pre.length) {
			back.next[l++] = s(pre.next[i], 1);
			back.next[l++] = s(pre.next[i], 2);
		}
		//第一和最后不能剪枝,只有中间部分可以剪枝处理
		else {
			//判断需要剪哪一个枝
			if (pre.next[i].money != pre.next[i + 1].money) {
				back.next[l++] = s(pre.next[i], 1);
				back.next[l++] = s(pre.next[i], 2);
				back.next[l++] = s(pre.next[i + 1], 1);
				back.next[l++] = s(pre.next[i + 1], 2);
			}else if (pre.next[i].time <= pre.next[i + 1].time) {
				back.next[l++] = s(pre.next[i], 1);
				back.next[l++] = s(pre.next[i], 2);
			}else {
				back.next[l++] = s(pre.next[i+1], 1);
				back.next[l++] = s(pre.next[i+1], 2);
			}
			i++;//每两个需要剪枝一个,需要递增
		}
	}
	back.length = l - 1;
	return back;
}
//遍历每一层,剪枝并返回下一轮遍历条件


int main(void) {
	tree value;
	value.length = 1;
	value.next[1].level = LEVEL;
	value.next[1].money = START;
	value.next[1].time = 0;
	strcpy(value.next[1].path,"");

	for (int i = 1; i <= n; i++) {
		value = find(value);
	}
	printf("------------------------------------------------------\n");
	printf("最短时间:%d\n", min);
	printf("购买顺序:%s", ans);//购买顺序输出的字符串,表示购买的先后顺序。

	return 0;
}

在这里插入图片描述
【结论】:
1.购买6个一号产品(保护者),再购买5个二号产品(牧师),可在783秒后达到目标资源。

四、声明

这是我第一次,用计算机思维去尝试解决实际中的问题。当然鉴于我有限的知识,对于这个问题,设计的算法不一定是最优的,也计算大规模问题中也会有一些缺陷,我后期也会继续思考,提升这个算法。其中一些错误,和缺陷也希望大家能够指出,同时欢迎感兴趣的网友可以和我讨论。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值