we now have an untested simulation framework
This commit is contained in:
parent
b65b0c368d
commit
e96a231d3c
@ -1,10 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
<component name="FrameworkDetectionExcludesConfiguration">
|
<component name="FrameworkDetectionExcludesConfiguration">
|
||||||
<file type="web" url="file://$PROJECT_DIR$" />
|
<file type="web" url="file://$PROJECT_DIR$" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_19" default="true" project-jdk-name="openjdk-19" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="openjdk-19" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@ -16,6 +16,9 @@ dependencies {
|
|||||||
|
|
||||||
implementation("net.java.dev.jna:jna:latest.release")
|
implementation("net.java.dev.jna:jna:latest.release")
|
||||||
implementation(kotlin("reflect"))
|
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 {
|
tasks.test {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import game_logic.runescape.RunescapeRoutines
|
import game_logic.runescape.RunescapeRoutines
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
// RunescapeRoutines.fullRunIncense(0, 158, 348, 0)
|
RunescapeRoutines.fullRunIncense( 0, 0, 0, 1839)
|
||||||
RunescapeRoutines.processInventoryAtFurnace(2500)
|
// RunescapeRoutines.processInventoryAtFurnace(2500)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -27,16 +27,6 @@ interface WindowsOSProxy : MousePointerObserver, OSProxy {
|
|||||||
* This takes a byte array [byteBuffer] containing text from a native Win32 call,
|
* This takes a byte array [byteBuffer] containing text from a native Win32 call,
|
||||||
* converts it to a String using JNA, and trims whitespace characters.
|
* 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
|
* @param byteBuffer Byte array containing text from a native call
|
||||||
* @return The native text as a String
|
* @return The native text as a String
|
||||||
*/
|
*/
|
||||||
@ -114,76 +104,10 @@ interface WindowsOSProxy : MousePointerObserver, OSProxy {
|
|||||||
var bottom: Int = 0
|
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.
|
* Gets the title of the active/foreground window as a String.
|
||||||
*
|
|
||||||
* 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
|
|
||||||
* ```
|
|
||||||
*
|
*
|
||||||
* @return The title text of the current foreground window.
|
* @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.
|
* 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.
|
* @param hWnd The native window handle to get the title for.
|
||||||
* @return The window title text as a [String].
|
* @return The window title text as a [String].
|
||||||
*/
|
*/
|
||||||
@ -237,12 +153,6 @@ interface WindowsOSProxy : MousePointerObserver, OSProxy {
|
|||||||
/**
|
/**
|
||||||
* Enumerates all open window names on the desktop.
|
* 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.
|
* @return An [ArrayList] containing the name of each open window.
|
||||||
*/
|
*/
|
||||||
override fun enumWindowNames(): ArrayList<String> {
|
override fun enumWindowNames(): ArrayList<String> {
|
||||||
@ -265,10 +175,6 @@ interface WindowsOSProxy : MousePointerObserver, OSProxy {
|
|||||||
/**
|
/**
|
||||||
* Sets the foreground window by name on Windows.
|
* 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.
|
* @param name The window name to search for and activate.
|
||||||
*/
|
*/
|
||||||
override fun setForegroundWindowByName(name: String) {
|
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.
|
* 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
|
* @return The outer bounding rectangle of the foreground window scaled for the
|
||||||
* screen DPI, or null if it failed.
|
* screen DPI, or null if it failed.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated("This will return *a* rectangle, not a correct rectangle")
|
||||||
override fun getScaledForegroundWindowBounds(): Rectangle? {
|
override fun getScaledForegroundWindowBounds(): Rectangle? {
|
||||||
val user32 = User32.INSTANCE
|
val user32 = User32.INSTANCE
|
||||||
val hWnd = user32.GetForegroundWindow()
|
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)
|
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? {
|
fun barelyFunctionalWindowQuery(): Rectangle? {
|
||||||
val user32 = User32.INSTANCE
|
val user32 = User32.INSTANCE
|
||||||
val hWnd = user32.GetForegroundWindow()
|
val hWnd = user32.GetForegroundWindow()
|
||||||
@ -364,14 +259,6 @@ interface WindowsOSProxy : MousePointerObserver, OSProxy {
|
|||||||
* This calls the Win32 API [User32.GetWindowRect] function to populate a
|
* This calls the Win32 API [User32.GetWindowRect] function to populate a
|
||||||
* [WinRect] struct with the window coordinates.
|
* [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 user32 An instance of User32, used to call the Win32 API.
|
||||||
* @param hWnd The window handle to get the rect for.
|
* @param hWnd The window handle to get the rect for.
|
||||||
|
|||||||
@ -3,11 +3,13 @@ package native
|
|||||||
import com.sun.jna.Library
|
import com.sun.jna.Library
|
||||||
import com.sun.jna.Native
|
import com.sun.jna.Native
|
||||||
import com.sun.jna.NativeLong
|
import com.sun.jna.NativeLong
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
|
||||||
|
|
||||||
interface HelloWorldWrapper: Library{
|
interface HelloWorldWrapper : Library {
|
||||||
companion object {
|
companion object {
|
||||||
init{
|
init {
|
||||||
System.setProperty(
|
System.setProperty(
|
||||||
"jna.library.path",
|
"jna.library.path",
|
||||||
"C:\\Users\\Hydros\\IdeaProjects\\RuneFactory\\src\\main\\rust\\src\\build"
|
"C:\\Users\\Hydros\\IdeaProjects\\RuneFactory\\src\\main\\rust\\src\\build"
|
||||||
@ -16,13 +18,13 @@ interface HelloWorldWrapper: Library{
|
|||||||
|
|
||||||
fun getInstance(): HelloWorldWrapper {
|
fun getInstance(): HelloWorldWrapper {
|
||||||
val options = Native.getLibraryOptions(HelloWorldWrapper::class.java)
|
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
|
return Native.load("hello", HelloWorldWrapper::class.java, options) as HelloWorldWrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NativeFunction(name ="_ZN5hello7get_int17h5cc51eaee082b02cE")
|
@MangledFunctionName(name = "_ZN5hello7get_int17h5cc51eaee082b02cE")
|
||||||
fun get_int(): NativeLong
|
fun get_int(): NativeLong
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
35
src/main/kotlin/native/MangledFunctionName.kt
Normal file
35
src/main/kotlin/native/MangledFunctionName.kt
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
54
src/main/kotlin/native/SolutionToAGI.kt
Normal file
54
src/main/kotlin/native/SolutionToAGI.kt
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
99
src/main/kotlin/simulation/FifthEdSimulator.kt
Normal file
99
src/main/kotlin/simulation/FifthEdSimulator.kt
Normal file
@ -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<Int>{
|
||||||
|
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<Bonus>,
|
||||||
|
val damageRoll: Dice,
|
||||||
|
val damageBonus: ArrayList<Bonus>,
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
69
src/main/kotlin/simulation/Simulator.kt
Normal file
69
src/main/kotlin/simulation/Simulator.kt
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package simulation
|
||||||
|
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
|
|
||||||
|
interface SimulationModel<T : Number> {
|
||||||
|
val sampleSize: Int
|
||||||
|
|
||||||
|
//has to be pure or else you're going to have a bad time
|
||||||
|
fun simulate(r: Random): T
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Simulator<T : Number> {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun <T: Number> getInstance(nThreads: Int = Runtime.getRuntime().availableProcessors() / 2 ): Simulator<T> {
|
||||||
|
return concreteSimulator(nThreads)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val nThreads: Int
|
||||||
|
|
||||||
|
fun doSimulation(model: SimulationModel<T>): ArrayList<T> {
|
||||||
|
val results = Collections.synchronizedList(ArrayList<T>(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<T>): ArrayList<T> {
|
||||||
|
val results = ArrayList<T>(steps)
|
||||||
|
val r = Random()
|
||||||
|
for (i in 0..<steps) {
|
||||||
|
results.add(model.simulate(r))
|
||||||
|
}
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class concreteSimulator<T : Number>(override val nThreads: Int) :
|
||||||
|
Simulator<T>
|
||||||
55
src/test/kotlin/simulation/SimulatorTest.kt
Normal file
55
src/test/kotlin/simulation/SimulatorTest.kt
Normal file
@ -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<Int>(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<Int>(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<Int>{
|
||||||
|
override fun simulate(r: Random): Int {
|
||||||
|
return r.nextInt(20)+1
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user