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 * 1. Obtaining an Automaton instance bound to the current OS/desktop
* 2. Calling methods like [moveMouse] and [mouseClick] to perform actions * 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. * 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. * Performs a mousewheel scroll in motion.
* *
* This will move the scroll wheel forward by the number of ticks * This will move the scroll wheel forward by the number of ticks over the duration. It will sleep for short
* over the duration. It will sleep for short intervals between * intervals between ticks using the provided sleep duration and variance.
* ticks using the provided sleep duration and variance.
* *
* @param sleepDur The base sleep duration between scroll ticks. * @param sleepDurationMillis The base sleep duration between scroll ticks.
* @param sleepDurVariance The variance in sleep duration. * @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. * Performs a mousewheel scroll out motion.
* *
* Same as [scrollIn] but moves the scroll wheel backward. * 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) { fun moveMouseLeftClickAndSleep(p: Point, sleepDuration: Long, sleepDurationVariance: Long = 1) {
automaton.moveMouse(p) automaton.moveMouse(p)
automaton.sleepWithVariance(100, 50) automaton.sleep(100, 50)
//left click //left click
automaton.mouseClick(InputEvent.BUTTON1_DOWN_MASK) 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.Point
import java.awt.Robot 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. * 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 * Due to a bug in OpenJDK, the mouse may not move all the way to the given point if using a non-default Windows
* point if using a non-default Windows display scale. So this method checks * display scale. So this method checks the current mouse position after moving and retries up to 10 times until the
* the current mouse position after moving and retries up to 10 times until * mouse reaches the exact intended point. Multiple retries will reach the desired destination typically within 3 tries.
* the mouse reaches the exact intended point.
* *
* @param point The Point representing the x, y coordinates to move the mouse to. * @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. * 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.
*
* 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)
* *
* @param button the mouse button to click * @param button the mouse button to click
*/ */
@ -73,7 +63,7 @@ open class RobotAutomaton(internal val robot: Robot = Robot()) : Automaton {
robot.mousePress(button) robot.mousePress(button)
// Add random sleep to vary timing // Add random sleep to vary timing
sleepWithVariance(8, 8) sleep(8, 8)
robot.mouseRelease(button) robot.mouseRelease(button)
} }
@ -81,8 +71,6 @@ open class RobotAutomaton(internal val robot: Robot = Robot()) : Automaton {
/** /**
* Presses and releases the given key. * 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 * 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. * 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) robot.keyPress(keyCode)
// Add random sleep to vary timing // Add random sleep to vary timing
sleepWithVariance(8, 8) sleep(8, 8)
robot.keyRelease(keyCode) robot.keyRelease(keyCode)
} }
@ -106,28 +94,28 @@ open class RobotAutomaton(internal val robot: Robot = Robot()) : Automaton {
* Scrolls the mouse wheel down by one unit. * 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 * 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 sleepDurationMillis The minimum duration to sleep after scrolling
* @param sleepDurVariance The variance in sleep duration. * @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) 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 * Uses the [Robot.mouseWheel] method to scroll down 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. * sleepDurationMillis + rand(0, sleepDurationVarianceMillis) ms. This adds variance in scrolling speed.
* *
* @param sleepDur The average duration to sleep after scrolling * @param sleepDurationMillis The minimum duration to sleep after scrolling
* @param sleepDurVariance The variance in sleep duration * @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) robot.mouseWheel(-1)
sleepWithVariance(sleepDur, sleepDurVariance) sleep(sleepDurationMillis, sleepDurationVarianceMillis)
} }
} }

View File

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

View File

@ -1,8 +1,8 @@
package game_logic.runescape package game_logic.runescape
import controllers.Automaton import controllers.Automaton
import controllers.MouseWiggleParams
import controllers.RobotAutomaton import controllers.RobotAutomaton
import params.MouseWiggleParams
import params.StandingTaskParams import params.StandingTaskParams
import params.TravelTaskParams import params.TravelTaskParams
import java.awt.Point import java.awt.Point
@ -98,7 +98,7 @@ class RSAgent(override val automaton: Automaton = RobotAutomaton()) : RSOrchestr
//press the "accept" default hotkey //press the "accept" default hotkey
automaton.keyPress(KeyEvent.VK_SPACE) automaton.keyPress(KeyEvent.VK_SPACE)
//wait for the desired time to finish //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) automaton.keyPress(KeyEvent.VK_SPACE)
//wait for it to complete //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) { override fun sleepForNTicks(n: Long) {
val latencyPadding = LATENCY_PADDING_MS val latencyPadding = LATENCY_PADDING_MS
val baseWaitTime = n * TICK_DURATION_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 package util
import controllers.Orchestrator import controllers.Orchestrator
import kotlin.random.Random
/** /**
* A collection of helper functions for common utility tasks. * 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. * 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.mock
import org.mockito.Mockito.`when` import org.mockito.Mockito.`when`
import params.MouseWiggleParams
import java.awt.Point import java.awt.Point
import java.awt.event.InputEvent import java.awt.event.InputEvent
import kotlin.test.Test import kotlin.test.Test
@ -75,7 +74,7 @@ class AutomatonTest {
val automaton = mock(Automaton::class.java) val automaton = mock(Automaton::class.java)
automaton.sleep(1000) automaton.sleep(1000)
automaton.sleepWithVariance(1000, 200) automaton.sleep(1000, 200)
// Asserts Automaton extends TemporalController // Asserts Automaton extends TemporalController
} }
} }

View File

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

View File

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