Hello and feature request

Hello, I’ve been reading about Elm and playing with the warp-to-0. Mainly giving a more detailed status message and reducing unnecessary clicks. I think I see why you like some of the features of Elm, but I’ve never used a true functional language without mutation – the code of my first attempt looks awful. Looking at how you handled the decision tree now.

Some feature that would be nice but are missing (or at least I didn’t find them):

There appears to be no way to directly interact with a running bot – either per console or perhaps hotkeys, the botengine is watching for them anyway
e.g. giving an order to end the bot at the next convenient/save point could be useful

The idea to finish the session because of inactivity is nice, but the possibility of an app pausing itself as if the pause-app-keys were pressed would be even better

ShipManeuverType is missing ManeuverDock (looking for “Dock” works)

Two things that I won’t be immediately , but might be useful for more complex bots:

I could encode information in the state of the bot, get it from your logs and add a parameter when starting the bot, but there seems to be no easy way for persistent information/reading+writing files

Despite the roots of Elm, I don’t see a way to make an http request and react to the response. There are some useful APIs out there.

1 Like

How could that work from a users perspective? What would be a convenient user interface to set the end signal?

There is this example app that sends HTTP requests and reads the HTTP responses: elm-fullstack/implement/PersistentProcess/example-elm-apps/http-proxy at 84f44abf92f86ee03a0a6accc8bf6afa031e300f · elm-fullstack/elm-fullstack · GitHub

As you mentioned, the Elm part is without mutation. So the way this works is to do anything with side-effects in a volatile host. The Elm app communicates with the volatile host(s) over a serial interface, so you serialize your command/task to the volatile host and deserialize the response. That example app initializes a volatile host with a C# script code and also contains the code to serialize and deserialize the messages on the interface.
You can use the same route to read or write files.

For the user of the bot? Easiest would probably be a custom hotkey similar to pausing the bot
For the user of your framework? In theory, you already support changing the appsettings of a running bot, I just don’t see a method for the bot user to change them after starting the bot.

Ok, a minimalistic test to extend the volatile host actually worked, let’s see if I get far enough to need it.

Thank you for the idea. Yes, the framework and apps already support changing app-settings at runtime, I did not get around to add the UI for that. Currently, I am working more on the development tools side.

I had a problem and made a minimal app to test it

statusDescriptionText = 
                    case readingFromGameClient.infoPanelContainer of
                        CanNotSeeIt ->
                            "No infoPanelContainer"
                        CanSee infoPanelContainer ->
                            case infoPanelContainer.infoPanelLocationInfo of
                                CanNotSeeIt ->
                                    "no infoPanelLocationInfo"
                                CanSee infoPanelLocationInfo ->
                                    case infoPanelLocationInfo.securityStatusPercent of
                                        Nothing ->
                                            "no securityStatusPercent"
                                        Just securityStatusPercent ->
                                            String.fromInt securityStatusPercent

I always get “no securityStatusPercent”, as far as I can tell it already fails at Regex.fromString
But the alternate UI has no problem parsing it (securityStatusPercent = Just 90). Am I missing something?
I’ve uploaded the session

1 Like

Nice find!
Seeing this, I suspect the behavior of the two javascript engines differs for this particular regex pattern. The botengine for Windows uses the ChakraCore javascript engine. In the alternate UI, it depends on which browser you use, so that could explain why you see it working in the alternate UI.

I opened the alternate UI in Chrome and Edge and loaded the sample you linked. I see the difference there too:

I am surprised it did not work in this Edge, I had expected that Microsoft switching to chromium would lead to using the same interpretation of regex.

We can also see the difference in this regex testing tool: regex101: build, test, and debug regex

Now I look for a replacement of the function to parse securityStatusPercent.

Here is the function found in the linked session archive, that does not work because of the regex problem:

getSecurityStatusPercentFromUINodeText : String -> Maybe Int
getSecurityStatusPercentFromUINodeText text =
    "(?<=='Security status'>)\\s*(|-)\\d+(|\\.\\d+)\\s*(?=\\<)"
        |> Regex.fromString
        |> Maybe.andThen (\regex -> text |> Regex.find regex |> List.head)
        |> Maybe.andThen (.match >> String.trim >> String.replace "," "." >> String.toFloat)
        |> Maybe.map ((*) 100 >> round)

Here is a part of the string that we got from the game client UI, in that session:

<hint='Security status'>0.9</hint></color><fontsize=12><fontsize=8> </fontsize>&lt;<fontsize=8>

I removed the beginning and the end to make it easier to read. What is important is that we have additional text before and after the part that we want to extract.

In this scenario, the part of the string we want to extract is “0.9”. That should be parsed to a securityStatusPercent of 90

In other words, we are looking for a function that returns the substring “0.9” out of the larger string above. To transform the string "0.9" to the number 90, we can continue to use String.toFloat, multiply by 100 and round as before.

The function String.split can help us remove the parts before and after the substring of interest.
We can start by removing the part before the number: To do this, I use the substring with the preceding XML tag as the argument for String.split. I use the Elm repl to check this approach works:

> String.split "<hint='Security status'>" "<hint='Security status'>0.9</hint></color><fontsize=12><fontsize=8>"
["","0.9</hint></color><fontsize=12><fontsize=8>"]
    : List String

In the output from the REPL, we see that this expression resulted in a List String with two elements. The second element in this list is the substring after the first occurrence of "<hint='Security status'>", so it starts with the number we want to get out eventually.

We can use List.drop 1 and List.head to get the second element from this list:

> String.split "<hint='Security status'>" "<hint='Security status'>0.9</hint></color><fontsize=12><fontsize=8>" |> List.drop 1 |> List.head
Just "0.9</hint></color><fontsize=12><fontsize=8>"
    : Maybe String

The next step is to remove the substring after the number that we don’t need.
In the concrete String from the linked scenario, I see the first non-whitespace character after the number is "<". So I use this to separate the number from the rest:

> String.split "<hint='Security status'>" "<hint='Security status'>0.9</hint></color><fontsize=12><fontsize=8>" |> List.drop 1 |> List.head |>
|   Maybe.andThen (String.split "<" >> List.head)
|
Just "0.9" : Maybe String

To get the number, I add String.toFloat and the rounding as we already used it in the earlier implementation:

> String.split "<hint='Security status'>" "<hint='Security status'>0.9</hint></color><fontsize=12><fontsize=8>" |> List.drop 1 |> List.head |>
|   Maybe.andThen (String.split "<" >> List.head) |> Maybe.andThen (String.trim >> String.toFloat) |> Maybe.map ((*) 100 >> round)
|
Just 90 : Maybe Int

To prepare this for integration in the app-code, I translate this expression into a function:

> getSecurityStatusPercentFromUINodeText = String.split "<hint='Security status'>" >> List.drop 1 >> List.head >> Maybe.andThen (String.split "<" >> List.head) >> Maybe.andThen (String.trim >> String.toFloat) >> Maybe.map ((*) 100 >> round)

<function> : String -> Maybe Int

Now I can test this function by applying a string:

> getSecurityStatusPercentFromUINodeText "spdpfijj <hint='Security status'>  0.54  <somethingelse"
Just 54 : Maybe Int

So we have the new function to parse the security level percentage:

getSecurityStatusPercentFromUINodeText : String -> Maybe Int
getSecurityStatusPercentFromUINodeText =
    String.replace " " ""
        >> String.toLower
        >> String.split "<hint='securitystatus'>"
        >> List.drop 1
        >> List.head
        >> Maybe.andThen (String.split "<" >> List.head)
        >> Maybe.andThen (String.trim >> String.toFloat)
        >> Maybe.map ((*) 100 >> round)

To make our new function more robust against possible future changes, I added some more processing steps at the beginning:

  • Removing space characters.
  • Mapping to lowercase characters.

As a result of these additional preprocessing steps, I needed to change the argument to String.split to "<hint='securitystatus'>"

After replacing the function in the app code, we can run a simulation with the session archive to confirm we get the right output overall:

image

In the status text from the app in the simulation we can see the “90” appearing where the older app displayed no securityStatusPercent. That looks like a complete fix.

1 Like

I got my bot to run a mission from accepting to returning after completing it, but there’s a lot left to add before I’d be comfortable sharing it.

If you want to add anything of my parsing feel free - Added · Threepwood-eve/Sanderling@db5063c · GitHub

I found the alternate UI very helpful, especially after I also started to extend the frontend.

After your last post, I also started to simulate. I see you added a video mentioning it, but I totally missed the option before, perhaps you should add a hint on github – it’s a great time saver

1 Like

Awesome!
Thank you for sharing your experience, I will take a look.

Thanks for the hint :+1:
Now I see a link to simulations should be added to the overview of guides.

Since many people reported confusion over syntax of the programming language, I looked into an alternative expression of the function discovered above.

Here is the original declaration again:

getSecurityStatusPercentFromUINodeText : String -> Maybe Int
getSecurityStatusPercentFromUINodeText =
    String.replace " " ""
        >> String.toLower
        >> String.split "<hint='securitystatus'>"
        >> List.drop 1
        >> List.head
        >> Maybe.andThen (String.split "<" >> List.head)
        >> Maybe.andThen (String.trim >> String.toFloat)
        >> Maybe.map ((*) 100 >> round)

Here is an alternative way to express this function:

getSecurityStatusPercentFromUINodeText : String -> Maybe Int
getSecurityStatusPercentFromUINodeText textFromUserInterface =
    Maybe.map ((*) 100 >> round)
        (Maybe.andThen (String.trim >> String.toFloat)
            (Maybe.andThen (String.split "<" >> List.head)
                (List.head
                    (List.drop 1
                        (String.split "<hint='securitystatus'>"
                            (String.toLower
                                (String.replace " " "" textFromUserInterface)
                            )
                        )
                    )
                )
            )
        )

With this new variant of the expression, we name the outer parameter/argument. The tree structure we see matches more closely what you would find in some programming languages that have less support for function composition. So that could make it easier to understand to people who bring prior experience in another programming language.
But what about people without experience in programming? With the new expression, I see the downside of the order of execution further from the order of code. The String.replace " " "" is what gets executed first, and in the first variant, it also appears first in the function expression.

There is a guide on the |> and >> operators at https://to.botengine.org/guide/elm-programming-language-forward-pipe-and-function-composition-operators