#Objective: calculate the optimum angle to place the enemy ship, with regard to our ship
#Definition: angle exactly to the right is 0 degrees, so the ship is heading towards +90 degrees.
#Possible angles are integers, from -179 to 180.
#Assumptions: our ship is pointlike. The enemy ship is at a constant range. The enemy ship's orientation
#is tangential to the circle defined by our ship's position and range. The enemy ship is represented
#by a single line of hittable armor cells.
#Additionally, we assume sufficient range that we do not need to compensate for curvature (that range
#to the enemy ship's cells varies) In short: we assume that it is the case that the secant such that
#the average range is range is approximately the same as the arc and that inaccuracies that result
#from using the average instead of cell-specific range are insignificant.
#Section 0. Test ship and weapons. The relevant parameters for this test are width (ship[5]), left bound(ship[6]), right bound(ship[7]) and length (ship[8])
ship <- c(14000, 500, 10000, 1500, 205, -102, 103, 180, 440, 1.0, 200)
#We will create a data frame containing 18 weapons for testing.
#Weapons are formatted as "name", dps, facing, tracking arc, spread
#first, 6 weapons with easy parameters to parse
weapon1 <- c("right phaser", 7, -10, 20, 5)
weapon2 <- c("left phaser", 7, 10, 20, 5)
weapon3 <- c("pd gun 1", 7, -160, 20, 0)
weapon4 <- c("pd gun 2",7, 180, 20, 0)
weapon5 <- c("pd gun 3",7, 160, 20, 0)
weapon6 <- c("photon torpedo", 7, 120, 90, 5)
#then, 12 weapons with randomly generated data
#the generation process was c(round(100*runif(1)),round(180*runif(1,-1,1)),round(180*(1-log10(runif(1,1,10)))),round(30*runif(1)))
weapon7 <- c("bb gun",5,-150,11,20)
weapon8 <- c("space marine teleporter", 78,69,173,29)
weapon9 <- c("turbolaser", 92,122,111,9)
weapon10 <- c("hex bolter", 24,-136,38,20)
weapon11 <- c("singularity projector", 95,28,122,25)
weapon12 <- c("subspace resonance kazoo", 68,-139,12,2)
weapon13 <- c("left nullspace projector", 10,28,54,0)
weapon14 <- c("telepathic embarrassment generator", 30,-31,35,8)
weapon15 <- c("perfectoid resonance torpedo", 34,72,10,17)
weapon16 <- c("entropy inverter gun",78,-60,13,24)
weapon17 <- c("mini-collapsar rifle", 27,28,16,13)
weapon18 <- c("false vacuum tunneler", 32,78,157,20)
#store all weapons in a data frame - this part should be replaced with however the final integrated program
#handles weapons
no_weapons <- 18
weapons <- data.frame()
for (i in 1:no_weapons) weapons <- rbind(weapons,get(paste("weapon",i,sep="")))
colnames(weapons) <- c("name","damage","facing","trackingrange","spread")
#Section 1. functions of variables
#In this and following sections, say
#1. name of function
#2. input of function
#3. mathematical statement
#4. output of function
#1. G
#2. a real number y
#3. y*Phi(y)+phi(y), where Phi is the cdf and phi is the PDF of a standard normal dist
#4. =#3.
G <- function(y) return(y*pnorm(y) + dnorm(y))
#1. Hit probability of coordinate less than x
#2. A real number z, a standard deviation a of a normal distribution N(0,a),
#a parameter b of a symmetric uniform distribution (-b/2,b/2)
#3. if a > 0 and b > 0, a/2b * ( G(z/a + b/a) - G(z/a - b/a) )
#if a > 0 and b = 0, Phi(z)
#if b > 0 and a = 0, the integral of -infinity to x over a
#function corresponding to a rectangle with base from -b/2 to b/2 of area 1 ie. max(0, min(1, b/2 + z))
#if a = 0 and b = 0, 0 if z < 0 and 1 otherwise
#4. cumulative distribution function of the probability distribution of hits at z (CDF(z))
hit_probability_coord_lessthan_x <- function(z, a, b){
if(a > 0 & b > 0) return(a/2/b*(G(z/a+b/a)-G(z/a-b/a)))
if(a > 0 & b == 0) return(pnorm(z,0,a))
if(a == 0 & b > 0) return(max(0,min(1,b/2+z)))
if(a == 0 & b == 0) {
if(z < 0) return(0) else return(1)
}
}
#1. Degrees to arc
#2. A radius and an angle
#3. 2*pi*r * degrees/360
#4. the arc corresponding to the central angle of the circle defined by radius
deg_to_arc <- function(deg, radius) return(deg*2*radius/360*pi)
#1. Arc to degrees
#2. An arc and a radius
#3. arc / (2*pi*r) * 360
#4. The central angle corresponding to an arc of the circle defined by radius
arc_to_deg <- function(arc, radius) return(arc/2/radius*360/pi)
#1. Min and max mean
#2. A weapon, containing the columns facing, spread and tracking_range at [3],[4],[5]
#3. If spread < tracking_range,
#a vector ( facing + tracking_range/2 - spread / 2, facing - tracking_range/2 + spread / 2 )
#Else the vector ( facing, facing )
#4. Given a weapon, the maximum and minimum means of the probability distribution that it can achieve
#by rotating
min_max_mean <- function(weapon){
vec <- c(
weapon[[3]]-weapon[[4]]/2,
weapon[[3]]+weapon[[4]]/2
)
if(vec[1] > 180) vec[1] <- vec[1]-360
if(vec[2] > 180) vec[2] <- vec[2]-360
if(vec[1] < -179) vec[1] <- vec[1]+360
if(vec[2] < -179) vec[2] <- vec[2]+360
return(vec)
}
#Output the angle between two vectors angled a and b using a dot b = |a||b|cos(angle) and noting these are
#unit vectors
#Given an angle and a weapon, return the mean of the distribution when the weapon tries to track the target
transform_hit_coord <- function(angle, weapon){
if(weapon$tracking_arc==360) return(angle)
if(weapon$spread > weapon$tracking_arc) return(weapon$facing)
angle_rad <- angle * pi/180
facing_rad <- weapon$facing * pi / 180
angle_to_weapon <- acos(sin(angle_rad)*sin(facing_rad)+cos(angle_rad)*cos(facing_rad))*( 180 / pi )
if(angle_to_weapon <= (weapon$tracking_arc-weapon$spread)/2) return(angle)
max_mean_rad <- weapon$max_mean * pi /180
min_mean_rad <- weapon$min_mean * pi /180
angle_to_min <- acos(sin(angle_rad)*sin(min_mean_rad)+cos(angle_rad)*cos(min_mean_rad))
angle_to_max <- acos(sin(angle_rad)*sin(max_mean_rad)+cos(angle_rad)*cos(max_mean_rad))
if(angle_to_max >= angle_to_min) return(weapon$min_mean)
return(weapon$max_mean)
}
#1. Generate ship upper bounds
#2. A vector representing a ship, with width stored at [5] and number of cells stored at [6], and a range
#3. algorithm: 1. calculate width of a ship cell, in angles
# 2. the lowest bound is -ship width
# 3. the next bound after any given bound is previous bound + increment
# 4. there are equally many such next bounds as the ship has cells
# calculate them successively by applying 3.
# 5. convert the vector to pixels multiplying it by 2*pi*range
#4. A vector with the lower edge of the ship in index 1 and upper bounds of all its armor cells at
#successive indices
generate_ship_upper_bounds <- function(left_collision_bound, right_collision_bound, length){
#this operation centers the target to a whole pixel
#we will fire at the center of the ship, so center = 0
cell_size <- min(30,max(15,length/10))
ub_vector <- c(0)
#concatenate positive upper bounds
while(ub_vector[length(ub_vector)] + cell_size < right_collision_bound) {
ub_vector <- c(ub_vector, ub_vector[length(ub_vector)]+cell_size)
}
ub_vector <- c(ub_vector, right_collision_bound)
#concatenate negative upper bounds
while(ub_vector[1] - cell_size > left_collision_bound) ub_vector <- c(ub_vector[1]-cell_size, ub_vector)
ub_vector <- c(left_collision_bound, ub_vector)
return(ub_vector)
}
#Section 2. functions of functions and variables
#1. Transformed angle
#2. A weapon, containing the columns min_mean and max_mean, and an angle
#3. angle - transform_hit_coord(angle, weapon)
#4. Given that angle is the angle of the target relative to our ship (beta in illustration),
#output is the angle of the target relative to to the weapon (angle alpha in illustration)
transformed_angle <- function(angle, weapon) return(angle-transform_hit_coord(angle,weapon))
#1. Weapon adjustment (pixels)
#2. A weapon and an angle (should be used for the optimum angle)
#3. deg_to_arc(transformed_angle)
#4. Returns the arc from weapon distribution mean to target
weaponadjustment_px <- function(weapon, optimum_angle,range){
angle_difference <- transformed_angle(optimum_angle,weapon)
arc <- deg_to_arc(angle_difference,range)
return(arc)
}
#1. Hit distribution
#2. A vector of upper bounds, a standard deviation a of a normal distribution N(0,a),
#a parameter b of a symmetric uniform distribution (-b/2,b/2)
#3. will not present the detailed calculations for this function, as this section is to be replaced
#by the hit distribution function of the integrated code and is not intended for translation
#4. A vector of probability masses, such that cell 1 is chance to hit below lowest upper bound,
#the last cell is chance to hit above highest upper bound, and the others are the probabilities for
#hits between upper bounds
hit_distribution <- function(upper_bounds, standard_deviation, spread){
vector <- vector(mode="double", length = length(upper_bounds)+1)
if (standard_deviation == 0){
if (spread == 0){
vector[1] <- 0
for (j in 2:(length(upper_bounds))) {
#if both spread and standard deviation are 0 then all shots hit 1 cell. this should be so even if
#the ship has an even number of cells to prevent ships with even no. cells appearing tougher which is not
#the case in the real game most likely
if ((upper_bounds[j] >= 0) & (upper_bounds[j-1] < 0)) vector[j] <- 1
}
#return part of a box
} else {
vector[1] <- min(1,max(0,(upper_bounds[1]+spread))/(2*spread))
for (j in 2:(length(upper_bounds))) vector[j] <- min(1,max(0,(upper_bounds[j]+spread))/(2*spread)) - min(1,max(0,(upper_bounds[j-1]+spread))/(2*spread))
vector[length(upper_bounds)+1] <- 1-min(1,max(0,(upper_bounds[length(upper_bounds)]+spread))/(2*spread))
}
} else {
if (spread != 0){
vector[1] <- hit_probability_coord_lessthan_x(upper_bounds[1], standard_deviation, spread)
for (j in 2:(length(upper_bounds))) vector[j] <- (hit_probability_coord_lessthan_x(upper_bounds[j], standard_deviation, spread)-hit_probability_coord_lessthan_x(upper_bounds[j-1], standard_deviation, spread))
vector[length(upper_bounds)+1] <- (1-hit_probability_coord_lessthan_x(upper_bounds[length(upper_bounds)], standard_deviation, spread))
} else {
#if spread is 0 but standard deviation is not 0 we have a normal distribution
vector[1] <- pnorm(upper_bounds[1],0,standard_deviation)
for (j in 2:(length(upper_bounds))) vector[j] <- pnorm(upper_bounds[j],0,standard_deviation) - pnorm(upper_bounds[j-1], mean=0, sd=standard_deviation)
vector[length(upper_bounds)+1] <- 1-pnorm(upper_bounds[length(upper_bounds)], mean=0, sd=standard_deviation)
}
}
return(vector)
}
#1. Hit distribution at optimum angle
#2. The optimum angle, the standard error in pixels, a weapon with spread at index 5, a vector of upper bounds
#3. hit distribution for vector of upper bounds + arc from weapon to upper bound
#4. A vector of probability masses, such that cell 1 is chance to hit below lowest upper bound,
#the last cell is chance to hit above highest upper bound, and the others are the probabilities for
#hits between upper bounds, adjusted for ship location
hit_distribution_at_optimum_angle <- function(angle, sd, upper_bounds, weapon, range){
#convert spread to pixels
px_spread <- deg_to_arc(weapon[,5], range)
#adjust upper bound vector
adj_ubs <- upper_bounds + weaponadjustment_px(weapon, angle,range)
return(hit_distribution(adj_ubs,sd,px_spread))
}
#1. Summed area under curve
#2. An angle, the standard deviation of the normal distribution in pixels, a set of weapons, and a ship
# s.t. ship width is stored at index 5, weapon damage at index 2 and weapon spread at index 5
#3. Algorithm: convert ship width to degrees and
# convert standard error to degrees, for consistency in units
# for each weapon, calculate the angles of the ship's lower bound and upper bound
# relative to that gun
# then calculate dps * (CDF of probability distribution of that weapon at upper bound
# - CDF of probability distribution of that weapon at lower bound)
#4. Output: the sum of expected dps from this set of weapons to target ship at that angle relative to our ship
sum_auc <- function(angle, sd, weapons, ship, range) {
summed_auc <- 0
#convert the ship's width from segment to degrees
shipwidth <- arc_to_deg(ship[5], range)/2
d_error <- arc_to_deg(sd, range)
for (i in 1:length(weapons[,1])){
#angle of the ship's upper bound, in coordinates of the distribution mean
#note that this is weapon specific
ship_upper_bound <- transformed_angle(angle,weapons[i,])+shipwidth
ship_lower_bound <- transformed_angle(angle,weapons[i,])-shipwidth
damage <- weapons[i,2]
spread <- weapons[i,5]
#now we calculate the sum
summed_auc <- summed_auc + damage*(
hit_probability_coord_lessthan_x(ship_upper_bound, d_error, spread) -
hit_probability_coord_lessthan_x(ship_lower_bound, d_error, spread)
)
}
return(summed_auc)
}
#Section 3. functions of functions of functions and variables
#1. main
#2. a ship, a range, a standard deviation, and a list of weapons
#3. described in comments in the function
#4. prints, for each weapon, the probability of a hit at each of the ship's cells, as well as
#of a miss due to hitting below ship's lowest bound in cell 1 and of a miss due to hitting above
#ship's highest bound in the last cell
main <- function(ship, range, sd, weapons){
# 1. we were given a list of weapons with names etc. so formulate the list with proper types and
# with room for min and max means
weapons <- data.frame(name=weapons[,1], damage=as.double(weapons[ ,2]), facing=as.double(weapons[ ,3]),
tracking_arc=as.double(weapons[ ,4]),spread=as.double(weapons[ ,5]),min_mean=0,max_mean=0)
# compute min and max means for weapons
for (i in 1:length(weapons[,1])) {
weapons[i, 6] <- min_max_mean(weapons[i, ])[1]
weapons[i, 7] <- min_max_mean(weapons[i, ])[2]
}
angles <- seq(-179,180)
dps_at_angles <- angles
for (i in 1:360) {
dps_at_angles[i] <- sum_auc(dps_at_angles[i], sd, weapons, ship, range)
}
#we use a separate vector to keep track of angle, since vector index 1 corresponds to angle -179 now
x_axis <- seq(-179,180)
#find the optimum angle by selecting the midmost of those cells that have the highest dps,
#and from the vector x_axis the angle corresponding to that cell
#use rounding to avoid errors from numerical math
optimum_angle <- x_axis[which(round(dps_at_angles,3) == round(max(dps_at_angles),3))
[ceiling(length(which(round(dps_at_angles,3) == round(max(dps_at_angles),3)))/2)]]
#calculate ship upper bounds
upper_bounds <- generate_ship_upper_bounds(ship[6],ship[7],ship[8])
print("bounds")
print(upper_bounds)
#calculate and report the distributions for weapons, round for human readability
print("distributions")
for (i in 1:length(weapons[,1])){
print(paste0(weapons[i,1],":"))
print(round(hit_distribution_at_optimum_angle(optimum_angle,sd,upper_bounds,weapons[i,],range),3))
}
#testing section - not to be implemented in final code
#print a graph of the distribution and our choice of angle
print(weapons)
plot(dps_at_angles, x=x_axis)
abline(v=optimum_angle)
print("optimum angle")
print(optimum_angle)
}
seq(0,110,18)
#sample runs
main(ship, 1000, 50, weapons[1:6,])