RuneFactory/src/main/kotlin/util/HelperFunctions.kt
2023-08-15 21:16:25 -04:00

166 lines
6.6 KiB
Kotlin

package util
import controllers.Orchestrator
import kotlin.random.Random
/**
* A collection of helper functions for common utility tasks.
*/
object HelperFunctions {
/**
* Computes the total number of steps needed to process the given total volume.
*
* This takes the total volume that needs to be processed and th
* and calculates the total steps required.
* Usage examples:
* ```
* val total = 550
* val perStep = 200
* val steps = calculateTotalSteps(total, perStep) // 3 steps
*
* val steps = calculateTotalSteps(1000, 100) // 10 steps
* ```
*
* @param totalVolume the total amount that needs to be processed
* @param volumePerStep the amount to process per step
* @return the number of steps required to process the total volume
*/
fun calculateTotalSteps(totalVolume: Int, volumePerStep: Int) =
totalVolume / volumePerStep + if (totalVolume % volumePerStep > 0) {
1
} else {
0
}
/**
* Generates a random long that approximates a normal distribution.
*
* Works by taking two random samples from 0 to upperBound/2 and adding them together. This tends to give a more natural
* spread than a single random sample.
*
* This approach relies on the Central Limit Theorem: that the sum of multiple independent random variables will
* tend towards a normal distribution, even if the original variables themselves are not normally distributed.
*
* @param upperBound The upper bound for the random range. Must be >= 2.
* @return A random long value following an approximate normal distribution.
*/
fun getApproximatelyNormalLong(upperBound: Long): Long {
// anything lower to 2 will round down to zero, so return zero. Additionally, this guarantees a positive upper
//bound in one step
if (upperBound < 2L) {
return 0L
}
// Generate two random longs from 0 to upperBound/2 and add them together to approximate a normal distribution.
return Random.nextLong(upperBound.shr(1)) + Random.nextLong(upperBound.shr(1))
}
/**
* Generates a random long following a Gaussian distribution within the given upper bound.
*
* Models the distribution observed in getRandomLongFromNormalDistribution
*
* @param upperBound The upper bound of the distribution
* @return A random long value
*/
fun getNextGaussian(upperBound: Long): Long {
require(upperBound > 0)
return java.util.Random()
.nextGaussian(
upperBound.toDouble() / 2.0,
upperBound.toDouble() / 5.0
).toLong()
.coerceIn(0..upperBound)
}
/**
* Prints a progress report to console showing current step, total steps, elapsed time, and estimated remaining time.
*
* This takes the current step number, total steps, and elapsed duration and prints a progress report.
* Typical usage is to call this within a loop, passing the loop index for current step and total loop count.
*
*
* Usage example:
* ```
* val totalSteps = 100
* val start = System.currentTimeMillis()
* for (i in 1..totalSteps) {
* // Do work
*
* report(i, totalSteps, System.currentTimeMillis() - start)
* }
* ```
*
* @param step The current step number
* @param of The total number of steps
* @param dur The elapsed duration so far in milliseconds
*/
fun report(step: Int, of: Int, dur: Long) {
val remaining = (dur / step) * (of - step)
if (step == 0) {
print("\rGathering timing data...\t|\tNo current ETA...) ")
} else if (step < 8) { // The time estimation is terrible, so it converges on reality. it takes roughly 8-10 steps to get a decent picture
print("\rStep $step of $of (${prettyTimeString(dur)} complete\t|\t~${prettyTimeString(remaining * 2)} remaining [LOW CONFIDENCE]) ")
} else if (step == of) {
print("\rFinal step (${prettyTimeString(dur)} complete\t|\t~${prettyTimeString(dur / (of - 1))} remaining) ")
} else {
print("\rStep $step of $of (${prettyTimeString(dur)} complete\t|\t~${prettyTimeString(remaining)} remaining) ")
}
}
/**
* Converts a duration in milliseconds to a human-readable string.
*
* This takes a duration in ms and converts it to a formatted string like "2h13m4s".
*
* Usage example:
*
* ```
* val duration = 72134 // ms
* val timeStr = prettyTimeString(duration)
* // "1m12s"
* ```
*
* @param durationMillis The duration to convert, in milliseconds
* @return A string representation of the duration, in the format XhYmZs
*/
fun prettyTimeString(durationMillis: Long): String {
val millisPerSecond = 1000L
val millisPerMinute = 60L * millisPerSecond
val millisPerHour = 60L * millisPerMinute
return if (durationMillis > millisPerHour) {
return "${durationMillis / millisPerHour}h${(durationMillis % millisPerHour) / millisPerMinute}m${(durationMillis % millisPerMinute) / millisPerSecond}s"
} else if (durationMillis > millisPerMinute) {
return "${(durationMillis % millisPerHour) / millisPerMinute}m${(durationMillis % millisPerMinute) / millisPerSecond}s"
} else {
"${(durationMillis % millisPerMinute) / millisPerSecond}s"
}
}
/**
* Gets the current mouse pointer location and returns it as a val declaration string.
*
* This method uses the [getPointerLocationAfter] method to get the current
* mouse pointer location after a small delay.
*
* It then formats this location into a string declaring a val with the provided
* variable name, like:
*
* ```
* val location = getPointerLocationAsValDeclarationString("clickPoint")
* // val clickPoint = Point(123, 456)
* ```
*
* The delay before getting the pointer location helps ensure the mouse has
* settled after any prior movements.
*
* @param varName The name to use for the variable in the declaration string.
* @return A string declaring a val with the pointer location.
*/
fun getPointerLocationAsValDeclarationString(orchestrator: Orchestrator, varName: String): String {
val info = orchestrator.getPointerLocationAfterDelay(5)
return "val $varName = Point(${info.x}, ${info.y})"
}
}