一般游戏的战力公式,是一个线性回归方程:
a*x+b*y+c*z+… =p
其中,p是战斗力,[a,b,c…]是属性,[x,y,z…]是属性价值。
属性一般包括:最大生命值,攻击力,防御力,闪避,暴击,命中等等。
如果确定了属性价值,那么战斗力就确定了。
如果两个角色,战斗力相同,而属性可以不同,那么,属性价值相当于各属性的权重,并且属性价值有一个内在关系:
x/s+y/s+z/s+… = 1
s表示总属性价值。
x/s表示属性价值因子,总属性价值只和是1.
求取了价值因子,就可以求得权重,也就是属性价值。
很多游戏的属性价值是直觉得出的,这里提供一个机器学习方法
检验战斗力的方式,最简单的凭据是,实时战斗。
如果两个单位在实时战斗的情况下,如果战斗结果势均力敌,表示两个单位战力相当。
当然,一般影响战斗结果的因素很多,并不仅仅只有战力公式涉及到的属性。例如:单位移动速度,转向速度,AI程度等等。又或者受暴击率,闪避率等影响,一场战斗如果人品爆发次次暴击,也会影响战斗结果。又或者对于有操作的游戏,玩家的操作技巧,也影响战斗结果。
所以,为了屏蔽这些影响,战斗的时候有先决条件:1.非战斗力公式相关的属性相同;2.暴击率和闪避率一般多次判定之后趋于稳定,影响较小不考虑;3.玩家无法操作。
所以,可以用实时战斗的结果,来评估战斗力。
这里提供一个神经网络模型来进行学习。
可以看到线性方程,只用一个单层的前馈神经网络可以模拟出来。
输入层是N个神经元,对应N个属性,神经元的输出的权重,就是属性价值因子。而且,属性价值因子只和等于1,恰好满足神经网络的内在形式。
输出层是正则化的战斗力。乘以一个系数就是想要的总战斗力。系数可以随意。
属性值 = 属性价值因子*总战斗力
1.首先随机N个神经网络模型,模型的价值因子,是随机的,总和为1.
这样就得到N个神经网络模型。
2.然后求得属性值,得到N个单位的属性。
3.让这N个单位随机1v1匹配,进行战斗。
胜利的一方,表示这个单位战力比失败一方大,但不能表示这个比最终战斗力大。
如何求得最终权重?
未完待续。。。
以下是求取线性伤害公式的算法,战斗力公式使用线性(仅演示作用)
import numpy as np
import random
import math
import matplotlib.pyplot as plt
#假设战斗单位包含三个属性
# atk 攻击力
# def 防御力
# true_dmg 真实伤害
#假设伤害公式为:
# dmg = (player1.atk-0.5*player2.def)+player1.atk_plus
#这个公式可以直接看出伤害公式的权重比为 atk : def : atk_plus = 1 : 0.5 : 2 = 2 : 1 : 4
#也就是战力公式为:power = 2*atk+1*def+4*atk_plus
#我们让两个玩家互相攻击,来训练。最后比较权重。
#需要训练的权重.
#初始(1,10)之间随机值
#我们需要利用梯度下降算法,使这些权重比为 2:1:4
weights = np.random.uniform(1,10,3)
#用伤害计算公式来计算两个玩家的伤害
def damage(player1,player2):
#攻击减防御
dmg = player1[0] - 0.5*player2[1]
#第三项为额外输出
dmg += 2.0*player1[2]
return dmg
#数组求和
def sum(v):
temp_v = 0
for i in v:
temp_v += i
return temp_v
#将权重值变为因子(总和为1)
def get_weight_factor(p):
sum_p = sum(p)
return [ p[i]/sum_p for i in range(len(p)) ]
#已知单位的战斗力,以及战力公式的因子的情况下,计算玩家的三项属性值
#factor 玩家的 三项属性 的权重因子
#w 战力公式的权重因子
#power 总战力值
def get_propertys(factor,w, power):
temp_v = []
for i in range(len(factor)):
temp_v.append(power * factor[i]/w[i])
return temp_v
#计算两个单位伤害值的方差
def error(p1,p2):
return (damage(p1,p2) - damage(p2,p1))**2
#梯度下降算法
def gradient_descent(player1_weights_factor,player2_weights_factor):
#计算偏导数的间隔
delta = 0.001
#学习速度
rate = 0.001
#两个单位的战力值相同,依据当前的战力公式的因子,求出两个单位的属性值
player1 = get_propertys(player1_weights_factor,weights,power)
player2 = get_propertys(player2_weights_factor,weights,power)
out_param = []
for i in range(len(weights)):
new_weights = [ x for x in weights]
#偏置
new_weights[i] += delta
#返回新的玩家属性
d_p1 = get_propertys(player1_weights_factor,new_weights,power)
#计算新的玩家属性,与玩家2的属性的方差的变化
d = (error(d_p1,player2)-error(player1,player2))/delta
#梯度下降,得到新的权重
out_param.append(weights[i] - rate*d)
return out_param
#总战力
power = 100
#打印初始值
print(weights)
errorData = []
#训练100000次
for i in range(100000):
#随机生成两个战斗单位的属性权重因子,他们的属性的比例值是随机的
player1_weights_factor = get_weight_factor(np.random.uniform(5,30,3))
player2_weights_factor = get_weight_factor(np.random.uniform(5,30,3))
#梯度下降,生成新的权重
weights = gradient_descent(player1_weights_factor,player2_weights_factor)
#每1000次训练,打印结果
if i%1000 == 0:
p1 = get_propertys(player1_weights_factor,weights,power)
p2 = get_propertys(player2_weights_factor,weights,power)
errorNum = error(p1,p2)
errorData.append(errorNum)
#print('p1 {} p2 {}'.format(p1,p2))
print('weights = {} error = {:6f}'.format(weights, errorNum))
#图形输入值
input_values = [x*1000 for x in range(len(errorData))]
#图形输出值
squares = errorData
#plot根据列表绘制出有意义的图形,linewidth是图形线宽,可省略
plt.plot(input_values,squares,linewidth=1)
#设置图标标题
plt.title("Training",fontsize = 24)
#设置坐标轴标签
plt.xlabel("Training Times",fontsize = 14)
plt.ylabel("error value",fontsize = 14)
#设置刻度标记的大小
plt.tick_params(axis='both',labelsize = 14)
#打开matplotlib查看器,并显示绘制图形
plt.show()
输出结果:
总结:
驯良10000次之后,误差基本稳定下来。
战力公式的权重比值为:5.13 : 2.56 : 10.28
约等于伤害计算式的属性价值:2:1:4
以下是tensorflow实现的算法
import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np
# 模型,有三个变量 a,b,c
# 模型输入为玩家属性
# 模型输出为玩家战斗力
weights = []
weights.append( tf.Variable(np.random.uniform(5,10),name='a'))
weights.append( tf.Variable(np.random.uniform(5,10),name='b'))
weights.append( tf.Variable(np.random.uniform(5,10),name='c'))
# 假设战斗单位包含三个属性
# atk 攻击力
# def 防御力
# true_dmg 真实伤害
# 假设伤害公式为:
# dmg = (player1.atk-0.5*player2.def)+player1.atk_plus
# 战力公式:
# power = a*atk+b*def+c*true_dmg
# 则公式权重:2:1:4
# 训练完成的模型参数对比看是不是这个,如果是则表示正确
def damage(player1,player2):
dmg = player1[0]-0.5*player2[1]
dmg += 2.0*player1[2]
return dmg
#玩家的总战力
power = 100
# 随机生成玩家的数据,用来训练
# 玩家的总战力是100,但三个属性值不一样
def create_player(weights):
#随机玩家三个属性的战力分配比例
player_factor = np.random.uniform(5,30,3)
player_factor /= np.sum(player_factor)
#各属性的战力除以战力公式的权重,得到玩家的属性
player = []
for i in range(len(weights)):
player.append(power*player_factor[i]/weights[i])
return player
#定义损失函数
def loss(p1,p2):
error = damage(p1,p2)-damage(p2,p1)
return tf.square(error)+0.01*tf.reduce_sum(tf.square(weights)) #加上L2正则化项
#训练
optimizer = tf.keras.optimizers.SGD(learning_rate=0.01)
errorData = []
with tf.GradientTape(persistent=True) as trap:
for i in range(5000):
player1 = create_player(weights)
player2 = create_player(weights)
loss_value = loss(player1,player2)
grads = trap.gradient(loss_value,weights)
optimizer.apply_gradients(zip(grads, weights))
result = []
result.append(weights[0].numpy())
result.append(weights[1].numpy())
result.append(weights[2].numpy())
print("Loss at step {:03d}:weights = {} error = {:.3f}".format(i,result, loss_value))
errorData.append(loss( player1, player2))
#图形输入值
input_values = [x for x in range(len(errorData))]
#图形输出值
squares = errorData
#plot根据列表绘制出有意义的图形,linewidth是图形线宽,可省略
plt.plot(input_values,squares,linewidth=1)
#设置图标标题
plt.title("Training",fontsize = 24)
#设置坐标轴标签
plt.xlabel("Training Times",fontsize = 14)
plt.ylabel("error value",fontsize = 14)
#设置刻度标记的大小
plt.tick_params(axis='both',labelsize = 14)
#打开matplotlib查看器,并显示绘制图形
plt.show()