cleaned up a whole bunch of stuff and started unit tests on helper functions

This commit is contained in:
dtookey 2023-08-12 07:16:24 -04:00
parent 244ca8d134
commit f72a44dfee
11 changed files with 121 additions and 92 deletions

View File

@ -19,7 +19,7 @@ package controllers
*
* 1. Obtaining an Automaton instance bound to the current OS/desktop
* 2. Calling methods like [moveMouse] and [mouseClick] to perform actions
* 3. Using [sleep] and [sleepWithVariance] to add delays
* 3. Using [sleep] and [sleep] to add delays
*
* This interface allows the underlying OS/desktop implementation details to be abstracted and swapped as needed.
*/

View File

@ -41,19 +41,18 @@ interface InputController {
/**
* Performs a mousewheel scroll in motion.
*
* This will move the scroll wheel forward by the number of ticks
* over the duration. It will sleep for short intervals between
* ticks using the provided sleep duration and variance.
* This will move the scroll wheel forward by the number of ticks over the duration. It will sleep for short
* intervals between ticks using the provided sleep duration and variance.
*
* @param sleepDur The base sleep duration between scroll ticks.
* @param sleepDurVariance The variance in sleep duration.
* @param sleepDurationMillis The base sleep duration between scroll ticks.
* @param sleepDurationVarianceMillis The variance in sleep duration.
*/
fun scrollIn(sleepDur: Long, sleepDurVariance: Long)
fun scrollIn(sleepDurationMillis: Long, sleepDurationVarianceMillis: Long)
/**
* Performs a mousewheel scroll out motion.
*
* Same as [scrollIn] but moves the scroll wheel backward.
*/
fun scrollOut(sleepDur: Long, sleepDurVariance: Long)
fun scrollOut(sleepDurationMillis: Long, sleepDurationVarianceMillis: Long)
}

View File

@ -101,10 +101,10 @@ interface Orchestrator {
*/
fun moveMouseLeftClickAndSleep(p: Point, sleepDuration: Long, sleepDurationVariance: Long = 1) {
automaton.moveMouse(p)
automaton.sleepWithVariance(100, 50)
automaton.sleep(100, 50)
//left click
automaton.mouseClick(InputEvent.BUTTON1_DOWN_MASK)
automaton.sleepWithVariance(sleepDuration, sleepDurationVariance)
automaton.sleep(sleepDuration, sleepDurationVariance)
}
/**

View File

@ -2,7 +2,6 @@ package controllers
import java.awt.Point
import java.awt.Robot
import java.awt.event.InputEvent
/**
@ -20,10 +19,9 @@ open class RobotAutomaton(internal val robot: Robot = Robot()) : Automaton {
*
* Uses the Robot's mouseMove() method to move to the x, y coordinates.
*
* Due to a bug in OpenJDK, the mouse may not move all the way to the given
* point if using a non-default Windows display scale. So this method checks
* the current mouse position after moving and retries up to 10 times until
* the mouse reaches the exact intended point.
* Due to a bug in OpenJDK, the mouse may not move all the way to the given point if using a non-default Windows
* display scale. So this method checks the current mouse position after moving and retries up to 10 times until the
* mouse reaches the exact intended point. Multiple retries will reach the desired destination typically within 3 tries.
*
* @param point The Point representing the x, y coordinates to move the mouse to.
*/
@ -53,15 +51,7 @@ open class RobotAutomaton(internal val robot: Robot = Robot()) : Automaton {
/**
* Performs a mouse click with the given button.
*
* Uses the Robot to simulate pressing and releasing the mouse button.
*
* A random sleep is added after pressing the button before releasing,
* to add variance in timing and avoid robotic behavior.
*
* Valid button values are:
* - InputEvent.BUTTON1 (left click)
* - InputEvent.BUTTON2 (right click)
* - InputEvent.BUTTON3 (middle click)
* A random sleep is added after pressing the button before releasing.
*
* @param button the mouse button to click
*/
@ -73,7 +63,7 @@ open class RobotAutomaton(internal val robot: Robot = Robot()) : Automaton {
robot.mousePress(button)
// Add random sleep to vary timing
sleepWithVariance(8, 8)
sleep(8, 8)
robot.mouseRelease(button)
}
@ -81,8 +71,6 @@ open class RobotAutomaton(internal val robot: Robot = Robot()) : Automaton {
/**
* Presses and releases the given key.
*
* Uses the Robot's keyPress() and keyRelease() methods to simulate pressing and releasing the key.
*
* A random sleep is added after pressing the key before releasing, to add variance in timing and avoid robotic
* behavior. The worst-case delay is ~1 frame at 60fps.
*
@ -97,7 +85,7 @@ open class RobotAutomaton(internal val robot: Robot = Robot()) : Automaton {
robot.keyPress(keyCode)
// Add random sleep to vary timing
sleepWithVariance(8, 8)
sleep(8, 8)
robot.keyRelease(keyCode)
}
@ -106,28 +94,28 @@ open class RobotAutomaton(internal val robot: Robot = Robot()) : Automaton {
* Scrolls the mouse wheel down by one unit.
*
* Uses the [Robot.mouseWheel] method to scroll down and then sleeps for a random duration between the given
* sleepDur + rand(0, sleepDurVariance) ms. This is to add variance in scrolling speed and avoid robotic behavior.
* sleepDurationMillis + rand(0, sleepDurationVarianceMillis) ms. This adds variance in scrolling speed.
*
* @param sleepDur The average duration to sleep after scrolling.
* @param sleepDurVariance The variance in sleep duration.
* @param sleepDurationMillis The minimum duration to sleep after scrolling
* @param sleepDurationVarianceMillis The maximum allowed additional sleep duration
*/
override fun scrollOut(sleepDur: Long, sleepDurVariance: Long) {
override fun scrollOut(sleepDurationMillis: Long, sleepDurationVarianceMillis: Long) {
robot.mouseWheel(1)
sleepWithVariance(sleepDur, sleepDurVariance)
sleep(sleepDurationMillis, sleepDurationVarianceMillis)
}
/**
* Scrolls the mouse wheel up by one unit.
* Scrolls the mouse wheel down by one unit.
*
* Uses the [Robot.mouseWheel] method to scroll up and then sleeps for a random duration between the given
* sleepDur + rand(0, sleepDurVariance) ms. This adds variance in scrolling speed to avoid robotic behavior.
* Uses the [Robot.mouseWheel] method to scroll down and then sleeps for a random duration between the given
* sleepDurationMillis + rand(0, sleepDurationVarianceMillis) ms. This adds variance in scrolling speed.
*
* @param sleepDur The average duration to sleep after scrolling
* @param sleepDurVariance The variance in sleep duration
* @param sleepDurationMillis The minimum duration to sleep after scrolling
* @param sleepDurationVarianceMillis The maximum allowed additional sleep duration
*/
override fun scrollIn(sleepDur: Long, sleepDurVariance: Long) {
override fun scrollIn(sleepDurationMillis: Long, sleepDurationVarianceMillis: Long) {
robot.mouseWheel(-1)
sleepWithVariance(sleepDur, sleepDurVariance)
sleep(sleepDurationMillis, sleepDurationVarianceMillis)
}
}

View File

@ -3,38 +3,36 @@ package controllers
import java.util.concurrent.TimeUnit
import kotlin.random.Random
interface TemporalController {
/**
* Sleeps for the specified duration.
* Interface for components that control time-related behavior.
*
* This uses [TimeUnit.MILLISECONDS] to sleep for the given duration in milliseconds.
*
* @param dur The sleep duration in milliseconds.
* Implementations of this interface allow pausing execution for certain durations,
* optionally with random variance.
*/
fun sleep(dur: Long) {
TimeUnit.MILLISECONDS.sleep(dur)
}
interface TemporalController {
/**
* Sleeps for the specified duration with some variance.
* Sleep for a specified base duration, with optional random variance added.
*
* This will sleep for the given duration plus a random variance between 0 inclusive and [variance] exclusive.
* The variance is divided in half to generate a random positive value that is added to the duration.
* Random variance is approximated to a normal distribution and guarantees not to pause for longer than
* [maxAdditionalDuration].
*
* If the duration is negative or the variance is less than 1, this method
* will return immediately without sleeping.
*
* @param duration The base sleep duration in ms
* @param variance The amount of variance to add in ms. Gets divided in half
* and rolled as two separate random numbers to create a normal distribution
* @param baseDuration the minimum time to sleep
* @param maxAdditionalDuration maximum extra random duration to add
* @return Unit
*/
fun sleepWithVariance(duration: Long, variance: Long) {
if (duration < 0 || variance <= 1) {
fun sleep(baseDuration: Long, maxAdditionalDuration: Long = 0L) {
// Check for invalid input
if (baseDuration < 0 || maxAdditionalDuration < 0) {
return
}
val dSize = (variance) / 2
val r1 = Random.nextLong(dSize)
val r2 = Random.nextLong(dSize)
sleep(duration + r1 + r2)
// If no variance, sleep the base duration
val duration = if(maxAdditionalDuration == 0L){
baseDuration
}else{
baseDuration + util.HelperFunctions.getRandomLongFromNormalDistribution((maxAdditionalDuration) / 2)
}
TimeUnit.MILLISECONDS.sleep(duration)
}
}

View File

@ -1,8 +1,8 @@
package game_logic.runescape
import controllers.Automaton
import controllers.MouseWiggleParams
import controllers.RobotAutomaton
import params.MouseWiggleParams
import params.StandingTaskParams
import params.TravelTaskParams
import java.awt.Point
@ -98,7 +98,7 @@ class RSAgent(override val automaton: Automaton = RobotAutomaton()) : RSOrchestr
//press the "accept" default hotkey
automaton.keyPress(KeyEvent.VK_SPACE)
//wait for the desired time to finish
automaton.sleepWithVariance(taskParams.craftingWaitDurationMillis, taskParams.craftingWaitDurationVarianceMillis)
automaton.sleep(taskParams.craftingWaitDurationMillis, taskParams.craftingWaitDurationVarianceMillis)
}
/**
@ -147,7 +147,7 @@ class RSAgent(override val automaton: Automaton = RobotAutomaton()) : RSOrchestr
automaton.keyPress(KeyEvent.VK_SPACE)
//wait for it to complete
automaton.sleepWithVariance(taskParams.craftingWaitDurationMillis, taskParams.craftingWaitDurationVarianceMillis)
automaton.sleep(taskParams.craftingWaitDurationMillis, taskParams.craftingWaitDurationVarianceMillis)
}
@ -201,7 +201,7 @@ class RSAgent(override val automaton: Automaton = RobotAutomaton()) : RSOrchestr
override fun sleepForNTicks(n: Long) {
val latencyPadding = LATENCY_PADDING_MS
val baseWaitTime = n * TICK_DURATION_MS
automaton.sleepWithVariance(latencyPadding + baseWaitTime, 150)
automaton.sleep(latencyPadding + baseWaitTime, 150)
}
}

View File

@ -1,6 +1,7 @@
package util
import controllers.Orchestrator
import kotlin.random.Random
/**
* A collection of helper functions for common utility tasks.
@ -32,6 +33,30 @@ object HelperFunctions {
}
/**
* Generates a random long value approximating a normal distribution.
*
* Takes two random samples from 0 to upperBound/2 and adds them together. This approximates a normal distribution
* better than a single random sample.
*
* @param upperBound The upper bound to sample from
* @return A random long value following an approximate normal distribution
*/
fun getRandomLongFromNormalDistribution(upperBound: Long): Long{
//anything lower to 2 will round down to zero, so return zero
if(upperBound <= 2L){
return 0L
}
// To create more natural distribution Take two random samples from 0 to upperBound/2 and add them. This
// approximates a normal distribution better than single random sample.
val subBound = upperBound/2L
val result1 = Random.nextLong(subBound)
val result2 = Random.nextLong(subBound)
return result1 + result2
}
/**
* Prints a progress report to console showing current step, total steps, elapsed time, and estimated remaining time.
*

View File

@ -2,7 +2,6 @@ package controllers
import org.mockito.Mockito.mock
import org.mockito.Mockito.`when`
import params.MouseWiggleParams
import java.awt.Point
import java.awt.event.InputEvent
import kotlin.test.Test
@ -75,7 +74,7 @@ class AutomatonTest {
val automaton = mock(Automaton::class.java)
automaton.sleep(1000)
automaton.sleepWithVariance(1000, 200)
automaton.sleep(1000, 200)
// Asserts Automaton extends TemporalController
}
}

View File

@ -1,6 +1,5 @@
package controllers
import params.MouseWiggleParams
import kotlin.test.*
import org.mockito.Mockito.*
import java.awt.Point

View File

@ -12,30 +12,22 @@ internal class TemporalControllerTest {
* Creates an instance of TemporalController for testing.
*/
private val controller = object : TemporalController {
/**
* Sleeps for the given duration in milliseconds.
*/
override fun sleep(dur: Long) {
TimeUnit.MILLISECONDS.sleep(dur)
}
/**
* Sleeps for around the given duration, with variance.
*
* @param duration the desired duration to sleep
* @param variance the amount of variance in the actual duration
* @param baseDuration the desired duration to sleep
* @param maxAdditionalDuration the amount of variance in the actual duration
*/
override fun sleepWithVariance(duration: Long, variance: Long) {
if (duration < 0 || variance <= 1) {
override fun sleep(baseDuration: Long, maxAdditionalDuration: Long) {
if (baseDuration < 0 || maxAdditionalDuration <= 1) {
return
}
val dSize = (variance) / 2
val dSize = (maxAdditionalDuration) / 2
val r1 = Random.nextLong(dSize)
val r2 = Random.nextLong(dSize)
sleep(duration + r1 + r2)
sleep(baseDuration + r1 + r2)
}
}
@ -53,7 +45,7 @@ internal class TemporalControllerTest {
}
/**
* Tests that [TemporalController.sleepWithVariance] sleeps for approximately
* Tests that [TemporalController.sleep] sleeps for approximately
* the given duration, within the specified variance.
*/
@Test
@ -62,7 +54,7 @@ internal class TemporalControllerTest {
val variance = 500L
val start = System.currentTimeMillis()
controller.sleepWithVariance(duration, variance)
controller.sleep(duration, variance)
val end = System.currentTimeMillis()
val elapsed = end - start
@ -74,13 +66,13 @@ internal class TemporalControllerTest {
}
/**
* Tests that [TemporalController.sleepWithVariance] returns immediately
* Tests that [TemporalController.sleep] returns immediately
* if passed a negative duration.
*/
@Test
fun `sleepWithVariance returns immediately if duration is negative`() {
val start = System.currentTimeMillis()
controller.sleepWithVariance(-100, 500)
controller.sleep(-100, 500)
val end = System.currentTimeMillis()
val elapsed = end - start
@ -88,13 +80,13 @@ internal class TemporalControllerTest {
}
/**
* Tests that [TemporalController.sleepWithVariance] returns immediately
* Tests that [TemporalController.sleep] returns immediately
* if the variance parameter is 0.
*/
@Test
fun `sleepWithVariance returns immediately if variance is 0`() {
val start = System.currentTimeMillis()
controller.sleepWithVariance(100, 0)
controller.sleep(100, 0)
val end = System.currentTimeMillis()
val elapsed = end - start

View File

@ -0,0 +1,29 @@
package util
import kotlin.math.pow
import kotlin.test.*
class HelperFunctionsTest {
@Test
fun `test getRandomLongFromNormalDistribution generates normal distribution`() {
// Generate a large number of samples
val numSamples = 10000
val upperBound = 1000L
val samples = (1..numSamples).map {
HelperFunctions.getRandomLongFromNormalDistribution(upperBound)
}
// Calculate mean and standard deviation
val mean = samples.average()
val stdDev = samples.map { (it - mean).pow(2) }.average().pow(0.5)
// Assert mean and std dev are within expected range
assert(mean > upperBound * 0.4 && mean < upperBound * 0.6)
assert(stdDev > upperBound * 0.2 && stdDev < upperBound * 0.3)
}
}