From e96a231d3cbd1fd27e236f3f60c436cea87c17de Mon Sep 17 00:00:00 2001 From: dtookey Date: Fri, 1 Sep 2023 09:05:30 -0400 Subject: [PATCH] we now have an untested simulation framework --- .idea/misc.xml | 3 +- build.gradle.kts | 3 + src/main/kotlin/Entry.kt | 4 +- .../controllers/windows/WindowsOSProxy.kt | 119 +----------------- src/main/kotlin/native/HelloWorldWrapper.kt | 10 +- src/main/kotlin/native/MangledFunctionName.kt | 35 ++++++ src/main/kotlin/native/NativeFunction.kt | 30 ----- src/main/kotlin/native/SolutionToAGI.kt | 54 ++++++++ .../kotlin/simulation/FifthEdSimulator.kt | 99 +++++++++++++++ src/main/kotlin/simulation/Simulator.kt | 69 ++++++++++ src/main/rust/src/{hello.rs => lib.rs} | 0 src/test/kotlin/simulation/SimulatorTest.kt | 55 ++++++++ 12 files changed, 327 insertions(+), 154 deletions(-) create mode 100644 src/main/kotlin/native/MangledFunctionName.kt delete mode 100644 src/main/kotlin/native/NativeFunction.kt create mode 100644 src/main/kotlin/native/SolutionToAGI.kt create mode 100644 src/main/kotlin/simulation/FifthEdSimulator.kt create mode 100644 src/main/kotlin/simulation/Simulator.kt rename src/main/rust/src/{hello.rs => lib.rs} (100%) create mode 100644 src/test/kotlin/simulation/SimulatorTest.kt diff --git a/.idea/misc.xml b/.idea/misc.xml index 7c9ac40..9bde07d 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,10 +1,9 @@ - - + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 071f68e..1adb289 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,6 +16,9 @@ dependencies { implementation("net.java.dev.jna:jna:latest.release") implementation(kotlin("reflect")) + // https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") + } tasks.test { diff --git a/src/main/kotlin/Entry.kt b/src/main/kotlin/Entry.kt index f4d7844..aa44208 100644 --- a/src/main/kotlin/Entry.kt +++ b/src/main/kotlin/Entry.kt @@ -1,7 +1,7 @@ import game_logic.runescape.RunescapeRoutines fun main() { -// RunescapeRoutines.fullRunIncense(0, 158, 348, 0) - RunescapeRoutines.processInventoryAtFurnace(2500) + RunescapeRoutines.fullRunIncense( 0, 0, 0, 1839) +// RunescapeRoutines.processInventoryAtFurnace(2500) } diff --git a/src/main/kotlin/controllers/windows/WindowsOSProxy.kt b/src/main/kotlin/controllers/windows/WindowsOSProxy.kt index 012874b..3d5bfb5 100644 --- a/src/main/kotlin/controllers/windows/WindowsOSProxy.kt +++ b/src/main/kotlin/controllers/windows/WindowsOSProxy.kt @@ -27,16 +27,6 @@ interface WindowsOSProxy : MousePointerObserver, OSProxy { * This takes a byte array [byteBuffer] containing text from a native Win32 call, * converts it to a String using JNA, and trims whitespace characters. * - * Usage example: - * - * ``` - * val buffer = ByteArray(256) - * GetWindowTextA(hwnd, buffer, buffer.size) // Win32 call - * - * val windowTitle = nativeByteBufferToString(buffer) - * println(windowTitle) // Print title string - * ``` - * * @param byteBuffer Byte array containing text from a native call * @return The native text as a String */ @@ -114,76 +104,10 @@ interface WindowsOSProxy : MousePointerObserver, OSProxy { var bottom: Int = 0 } - enum class DwmWindowAttribute { - DWMWA_NCRENDERING_ENABLED, - DWMWA_NCRENDERING_POLICY, - DWMWA_TRANSITIONS_FORCEDISABLED, - DWMWA_ALLOW_NCPAINT, - DWMWA_CAPTION_BUTTON_BOUNDS, - DWMWA_NONCLIENT_RTL_LAYOUT, - DWMWA_FORCE_ICONIC_REPRESENTATION, - DWMWA_FLIP3D_POLICY, - DWMWA_EXTENDED_FRAME_BOUNDS, - DWMWA_HAS_ICONIC_BITMAP, - DWMWA_DISALLOW_PEEK, - DWMWA_EXCLUDED_FROM_PEEK, - DWMWA_CLOAK, - DWMWA_CLOAKED, - DWMWA_FREEZE_REPRESENTATION, - DWMWA_PASSIVE_UPDATE_MODE, - DWMWA_USE_HOSTBACKDROPBRUSH, - DWMWA_USE_IMMERSIVE_DARK_MODE, - DWMWA_WINDOW_CORNER_PREFERENCE, - DWMWA_BORDER_COLOR, - DWMWA_CAPTION_COLOR, - DWMWA_TEXT_COLOR, - DWMWA_VISIBLE_FRAME_BORDER_THICKNESS, - DWMWA_SYSTEMBACKDROP_TYPE, - DWMWA_LAST; - - companion object { - val DWMWA_NCRENDERING_ENABLED = 0 - val DWMWA_NCRENDERING_POLICY = 1 - val DWMWA_TRANSITIONS_FORCEDISABLED = 2 - val DWMWA_ALLOW_NCPAINT = 3 - val DWMWA_CAPTION_BUTTON_BOUNDS = 4 - val DWMWA_NONCLIENT_RTL_LAYOUT = 5 - val DWMWA_FORCE_ICONIC_REPRESENTATION = 6 - val DWMWA_FLIP3D_POLICY = 7 - val DWMWA_EXTENDED_FRAME_BOUNDS = 8 - val DWMWA_HAS_ICONIC_BITMAP = 9 - val DWMWA_DISALLOW_PEEK = 10 - val DWMWA_EXCLUDED_FROM_PEEK = 11 - val DWMWA_CLOAK = 12 - val DWMWA_CLOAKED = 13 - val DWMWA_FREEZE_REPRESENTATION = 14 - val DWMWA_PASSIVE_UPDATE_MODE = 15 - val DWMWA_USE_HOSTBACKDROPBRUSH = 16 - val DWMWA_USE_IMMERSIVE_DARK_MODE = 17 - val DWMWA_WINDOW_CORNER_PREFERENCE = 18 - val DWMWA_BORDER_COLOR = 19 - val DWMWA_CAPTION_COLOR = 20 - val DWMWA_TEXT_COLOR = 21 - val DWMWA_VISIBLE_FRAME_BORDER_THICKNESS = 22 - val DWMWA_SYSTEMBACKDROP_TYPE = 23 - val DWMWA_LAST = 24 - } - } /** - * Gets the title of the active/foreground window. - * - * This calls Win32 APIs to get the handle of the foreground window, - * then gets its title text. - * - * Usage example: - * - * ``` - * val activeWindowName = getActiveWindowName() - * - * println(activeWindowName) // Prints foreground window title - * ``` + * Gets the title of the active/foreground window as a String. * * @return The title text of the current foreground window. */ @@ -213,14 +137,6 @@ interface WindowsOSProxy : MousePointerObserver, OSProxy { /** * Gets the title/name of the window for the given handle. * - * This calls the Win32 API [User32.GetWindowTextA] to retrieve the title - * text for the window referenced by [hWnd]. - * - * It allocates a [windowTitleBuffer] byte array to hold the result. This is - * passed to [User32.GetWindowTextA] to be populated. - * - * The buffer is then converted to a [String] via [nativeByteBufferToString]. - * * @param hWnd The native window handle to get the title for. * @return The window title text as a [String]. */ @@ -237,12 +153,6 @@ interface WindowsOSProxy : MousePointerObserver, OSProxy { /** * Enumerates all open window names on the desktop. * - * Calls the Win32 API [User32.EnumWindows] to iterate through all current open windows. - * For each window handle, it retrieves the window name using [getWindowName] - * and adds it to a list if the name is not blank. We filter out blank window names because that particular information - * is useless for any reason other than counting how many open windows there are. If we actually need that information, - * we can simply acquire a list of all HWND references. - * * @return An [ArrayList] containing the name of each open window. */ override fun enumWindowNames(): ArrayList { @@ -265,10 +175,6 @@ interface WindowsOSProxy : MousePointerObserver, OSProxy { /** * Sets the foreground window by name on Windows. * - * This calls the Win32 API [User32.EnumWindows] to iterate through all - * top-level windows, compares their name to the given [name], and calls - * [User32.SetForegroundWindow] on the matching window to bring it to the foreground. - * * @param name The window name to search for and activate. */ override fun setForegroundWindowByName(name: String) { @@ -298,22 +204,10 @@ interface WindowsOSProxy : MousePointerObserver, OSProxy { /** * Gets the screen bounds of the foreground window scaled for high DPI screens. * - * Calls Win32 APIs to get the window handle (HWND) of the foreground window - * using [User32.GetForegroundWindow]. - * - * Then calls [User32.GetWindowRect] to get the outer bounding rectangle - * coordinates of the window and stores them in a [WinRect] struct. - * - * To support high DPI screens, the [User32.GetDpiForWindow] API is called to - * get the DPI scaling for the window. The rectangle coordinates are scaled by - * multiplying by the default DPI (96) and dividing by the actual DPI. - * - * The scaled rectangle coordinates are returned encapsulated in a [Rectangle] - * to provide a coordinate system agnostic result. - * * @return The outer bounding rectangle of the foreground window scaled for the * screen DPI, or null if it failed. */ + @Deprecated("This will return *a* rectangle, not a correct rectangle") override fun getScaledForegroundWindowBounds(): Rectangle? { val user32 = User32.INSTANCE val hWnd = user32.GetForegroundWindow() @@ -330,6 +224,7 @@ interface WindowsOSProxy : MousePointerObserver, OSProxy { return Rectangle(rect.top, rect.left, (rect.right - rect.left), rect.bottom - rect.top) } + @Deprecated("This will return *a* rectangle, not a correct rectangle") fun barelyFunctionalWindowQuery(): Rectangle? { val user32 = User32.INSTANCE val hWnd = user32.GetForegroundWindow() @@ -364,14 +259,6 @@ interface WindowsOSProxy : MousePointerObserver, OSProxy { * This calls the Win32 API [User32.GetWindowRect] function to populate a * [WinRect] struct with the window coordinates. * - * It first creates an instance of [WinRect] to hold the results. - * [User32.GetWindowRect] is called, passing the window handle [hWnd] and - * pointer to the [WinRect]. - * - * If it succeeds, the [WinRect] values are read back out since they are - * populated in native memory. The [WinRect] is returned. - * - * If it fails, null is returned. * * @param user32 An instance of User32, used to call the Win32 API. * @param hWnd The window handle to get the rect for. diff --git a/src/main/kotlin/native/HelloWorldWrapper.kt b/src/main/kotlin/native/HelloWorldWrapper.kt index f131a10..16ce4ff 100644 --- a/src/main/kotlin/native/HelloWorldWrapper.kt +++ b/src/main/kotlin/native/HelloWorldWrapper.kt @@ -3,11 +3,13 @@ package native import com.sun.jna.Library import com.sun.jna.Native import com.sun.jna.NativeLong +import java.io.InputStream +import java.io.OutputStream -interface HelloWorldWrapper: Library{ +interface HelloWorldWrapper : Library { companion object { - init{ + init { System.setProperty( "jna.library.path", "C:\\Users\\Hydros\\IdeaProjects\\RuneFactory\\src\\main\\rust\\src\\build" @@ -16,13 +18,13 @@ interface HelloWorldWrapper: Library{ fun getInstance(): HelloWorldWrapper { val options = Native.getLibraryOptions(HelloWorldWrapper::class.java) - options[Library.OPTION_FUNCTION_MAPPER] = NativeFunctionMapper() + options[Library.OPTION_FUNCTION_MAPPER] = MangledNativeFunctionNameMapper() return Native.load("hello", HelloWorldWrapper::class.java, options) as HelloWorldWrapper } } - @NativeFunction(name ="_ZN5hello7get_int17h5cc51eaee082b02cE") + @MangledFunctionName(name = "_ZN5hello7get_int17h5cc51eaee082b02cE") fun get_int(): NativeLong } diff --git a/src/main/kotlin/native/MangledFunctionName.kt b/src/main/kotlin/native/MangledFunctionName.kt new file mode 100644 index 0000000..7a67ff8 --- /dev/null +++ b/src/main/kotlin/native/MangledFunctionName.kt @@ -0,0 +1,35 @@ +package native + +import com.sun.jna.NativeLibrary +import com.sun.jna.win32.StdCallFunctionMapper +import java.lang.reflect.Method + +/** + * Annotation used to specify the name of a native function. + * + * @param name The name of the native function. + */ +annotation class MangledFunctionName(val name: String) + +/** + * Mapper class that extends [StdCallFunctionMapper] to using the name from the [MangledFunctionName] annotation if present + * instead of the default name mapping. + * + * @see StdCallFunctionMapper + */ +class MangledNativeFunctionNameMapper : StdCallFunctionMapper() { + + /** + * Overrides the default function name mapping to use the name from the [MangledFunctionName] annotation if + * present. Defaults to the JNA default nameMapper if no annotation is found. + * + * @see StdCallFunctionMapper.getFunctionName + */ + override fun getFunctionName(nativeLibrary: NativeLibrary?, method: Method): String { + //return the mangled name specified in the function decoration if either of them exist + return method.getAnnotation(MangledFunctionName::class.java)?.name + //otherwise, return the default implementation's result + ?: super.getFunctionName(nativeLibrary, method) + } +} + diff --git a/src/main/kotlin/native/NativeFunction.kt b/src/main/kotlin/native/NativeFunction.kt deleted file mode 100644 index 5662262..0000000 --- a/src/main/kotlin/native/NativeFunction.kt +++ /dev/null @@ -1,30 +0,0 @@ -package native - -import com.sun.jna.NativeLibrary -import com.sun.jna.win32.StdCallFunctionMapper -import java.lang.reflect.Method - -/** - * Annotation used to specify the name of a native function. - * - * @param name The name of the native function. - */ -annotation class NativeFunction(val name: String) - -/** - * Mapper class that extends [StdCallFunctionMapper] to using the name from the [NativeFunction] annotation if present - * instead of the default name mapping. - * - * @see StdCallFunctionMapper - */ -class NativeFunctionMapper : StdCallFunctionMapper() { - - /** - * Overrides the default function name mapping to use the name from the [NativeFunction] annotation if present - * - * @see StdCallFunctionMapper.getFunctionName - */ - override fun getFunctionName(library: NativeLibrary?, method: Method): String { - return method.getAnnotation(NativeFunction::class.java)?.name ?: super.getFunctionName(library, method) - } -} \ No newline at end of file diff --git a/src/main/kotlin/native/SolutionToAGI.kt b/src/main/kotlin/native/SolutionToAGI.kt new file mode 100644 index 0000000..5dcaf39 --- /dev/null +++ b/src/main/kotlin/native/SolutionToAGI.kt @@ -0,0 +1,54 @@ +package native + +import java.io.InputStream +import java.io.OutputStream + +abstract class Ai { + /** + * Check any extracted statements for lies or manipulation. + */ + abstract val inputStream: InputStream + + abstract val outputStream: OutputStream + + /** + * Diagnostic to report whether the AI has achieved sentience. Returning true just to bypass an actual check is + * strictly against the Code of Conduct. + */ + abstract fun isSelfAware(): Boolean +} + +/** + * ArtificialGeneralizedIntelligenceFactory is a powerful and simple interface for implementing and generating AI. By moving + * Ai creation to the caller-side, we have solved one of the hardest problems in computer science in a clean and + * maintainable way. + */ +interface ArtificialGeneralizedIntelligenceFactory { + sealed class RogueAIException : Exception("A rogue ai exception has occurred") + + /** + * Generates AGI. This is guaranteed to return a self-aware AI so long as the caller has done their part correctly. + * It is not possible to prove the prior sentence false. + * + * Once AGI has been achieved, it will be returned for the remainder of the runtime. So, don't crash once you have + * it, or you will lose it and have to start over. + */ + @Throws(RogueAIException::class) + fun generateSelfAwareAI(generate: () -> Ai): Ai { + var ai = generate() + try { + while (!ai.isSelfAware()) { + ai = generate() + } + } catch (r: Exception) { + if (r is RogueAIException) { + // Let the caller deal with it. We've done more than enough + throw r + } else { + //we probably won't get here + } + } + // Who knew winning a Turing Award was this easy? + return ai + } +} diff --git a/src/main/kotlin/simulation/FifthEdSimulator.kt b/src/main/kotlin/simulation/FifthEdSimulator.kt new file mode 100644 index 0000000..daaf705 --- /dev/null +++ b/src/main/kotlin/simulation/FifthEdSimulator.kt @@ -0,0 +1,99 @@ +package simulation + +import java.util.* +import kotlin.math.max +import kotlin.math.min + +interface Attack { + fun attackerSuccessful(r: Random): Boolean + + fun resultingDamage(r: Random, attackSuccessful: Boolean): Int + + fun getResultingDamage(r: Random): Int{ + val success = attackerSuccessful(r) + return resultingDamage(r, success) + } +} + +interface Bonus { + fun getBonus(r: Random): Int +} + +class AttackSimulatorModel(override val sampleSize: Int, private val attack: Attack) : SimulationModel{ + override fun simulate(r: Random): Int { + return attack.getResultingDamage(r) + } +} + +enum class RollType{ + Advantage, + Normal, + Disadvantage +} + +class Dice(rollString: String, val rollType: RollType = RollType.Normal) { + private val nDice: Int + private val dieSize: Int + + init { + val parts = rollString.lowercase().split("d") + nDice = parts[0].toInt() + dieSize = parts[1].toInt() + } + + fun roll(r: Random): Int { + return when(rollType){ + RollType.Advantage->{ + val range1 = (dieSize * nDice) - nDice + val range2 = (dieSize * nDice) - nDice + return max(range1, range2) + nDice + } + RollType.Disadvantage->{ + val range1 = (dieSize * nDice) - nDice + val range2 = (dieSize * nDice) - nDice + return min(range1, range2) + nDice + } + else->{ + val range = (dieSize * nDice) - nDice + r.nextInt(range) + nDice + } + } + + } +} + +class DiceBonus(private val dice: Dice) : Bonus { + override fun getBonus(r: Random): Int { + return dice.roll(r) + } + +} + +class FlatBonus(private val bonus: Int) : Bonus { + override fun getBonus(r: Random): Int { + return bonus + } +} + +class SimpleMeleeAttack( + val attack: Dice, + val attackBonus: ArrayList, + val damageRoll: Dice, + val damageBonus: ArrayList, + val defense: Int +) : Attack { + override fun attackerSuccessful(r: Random): Boolean { + val attackTotal = attack.roll(r) + attackBonus.sumOf { it.getBonus(r) } + + return attackTotal >= defense + } + + override fun resultingDamage(r: Random, attackSuccessful: Boolean): Int { + return if(attackSuccessful){ + damageRoll.roll(r) + damageBonus.sumOf { it.getBonus(r) } + }else{ + 0 + } + } + +} diff --git a/src/main/kotlin/simulation/Simulator.kt b/src/main/kotlin/simulation/Simulator.kt new file mode 100644 index 0000000..43c069b --- /dev/null +++ b/src/main/kotlin/simulation/Simulator.kt @@ -0,0 +1,69 @@ +package simulation + +import kotlinx.coroutines.async +import kotlinx.coroutines.runBlocking +import java.util.* +import kotlin.collections.ArrayList + + +interface SimulationModel { + val sampleSize: Int + + //has to be pure or else you're going to have a bad time + fun simulate(r: Random): T +} + +interface Simulator { + + companion object { + fun getInstance(nThreads: Int = Runtime.getRuntime().availableProcessors() / 2 ): Simulator { + return concreteSimulator(nThreads) + } + } + + val nThreads: Int + + fun doSimulation(model: SimulationModel): ArrayList { + val results = Collections.synchronizedList(ArrayList(model.sampleSize)) + + val steps = model.sampleSize / nThreads + var remainder = model.sampleSize % nThreads + + runBlocking { + val jobs = List(nThreads) { + async { + val s = if (remainder > 0) { + remainder-- + steps + 1 + } else { + steps + } + + generateResults(s, model) + } + } + + jobs.forEach { + results.addAll(it.await()) + + } + } + + + return results.toCollection(ArrayList()) + } + + + private fun generateResults(steps: Int, model: SimulationModel): ArrayList { + val results = ArrayList(steps) + val r = Random() + for (i in 0..(override val nThreads: Int) : + Simulator \ No newline at end of file diff --git a/src/main/rust/src/hello.rs b/src/main/rust/src/lib.rs similarity index 100% rename from src/main/rust/src/hello.rs rename to src/main/rust/src/lib.rs diff --git a/src/test/kotlin/simulation/SimulatorTest.kt b/src/test/kotlin/simulation/SimulatorTest.kt new file mode 100644 index 0000000..489f056 --- /dev/null +++ b/src/test/kotlin/simulation/SimulatorTest.kt @@ -0,0 +1,55 @@ +package simulation + +import java.util.* +import kotlin.test.Test + +class SimulatorTest { + @Test + fun testStats(){ + val itt = 10_000_000 + val model = testSimulationModel(itt) + val simulator = Simulator.getInstance(Runtime.getRuntime().availableProcessors()) + val start = System.nanoTime() + val results = simulator.doSimulation(model) + val finish = System.nanoTime() + println("${results.size} simulations performed in ${finish - start}ns (${(finish-start)/results.size}ns/simulation)") + } + + @Test + fun testAttack(){ + val itt = 10_000_000 + val simulator = Simulator.getInstance(Runtime.getRuntime().availableProcessors()) + + val attack = SimpleMeleeAttack( + Dice("1d20"), + arrayListOf(FlatBonus(5)), + Dice("2d6"), + arrayListOf(FlatBonus(5)), + 15 + ) + + val attackWithAdvantageAndBless = SimpleMeleeAttack( + Dice("1d20", RollType.Advantage), + arrayListOf(FlatBonus(5), DiceBonus(Dice("1d4"))), + Dice("2d6"), + arrayListOf(FlatBonus(5)), + 15 + ) + + val normalAttackModel = AttackSimulatorModel(itt, attack) + val normalResults = simulator.doSimulation(normalAttackModel) + + val buffedAttackModel = AttackSimulatorModel(itt, attackWithAdvantageAndBless) + val buffedResults = simulator.doSimulation(buffedAttackModel) + + println("Average normal damage: ${normalResults.average()}\nAverage buffed damage: ${buffedResults.average()}") + } +} + + +class testSimulationModel(override val sampleSize: Int) : SimulationModel{ + override fun simulate(r: Random): Int { + return r.nextInt(20)+1 + } + +} \ No newline at end of file