jncraton / BWMetaAI

A StarCraft Brood War AI designed to follow the modern 1v1 metagame
Other
100 stars 12 forks source link

Difficulty scaling - give money proportionate to workers #17

Closed nikola-shekerev closed 5 years ago

nikola-shekerev commented 5 years ago

Hello, Mr Craton. The AI is awesome for newbie players. Sincere thanks for your work :)

I manage to beat 1 AI consistently but I cannot beat 2 AIs. I would like to implement a smoother difficulty scaling by giving AI money

Currently give_money() in freemoney.pyai does that work. I see 2 issues with current implementation

Solution - I would like to implement a mechanism that gives money proportionate to current workers. The bonus money will scale with game progression and can be negated with harassment and denying expansions

Unfortunately I did not manage to find proper documentation of the API of the game ai scripts. Can you point me to the appropriate resources, if any?

Kind Regards

jncraton commented 5 years ago

This would be great! I've made some attempts at improving difficulty scaling using optional cheating behavior, but I have not had much success.

This project is intentionally limited to using the AI scripting engine baked into the original game. This makes it very easy to install and use, but it is also incredibly limited.

Unfortunately I did not manage to find proper documentation of the API of the game ai scripts. Can you point me to the appropriate resources, if any?

Blizzard doesn't provide official documentation for this, as it is part of the game internals and was never meant to be modified. I have compiled much of the knowledge from various sources to this document.

The only commands that make the AI more challenging via cheating that I'm aware of are give_money and create_unit. The most serious issue with these from my point of view is what you noted about not scaling with the actual economy. This makes the play experience feel fake to me, as there's little incentive to target expansions if you know the AI doesn't really need them.

If give_money could be tied to worker count or mining base count, that could largely be mitigated, but I'm not aware of a way to do that. Scripts have very limited knowledge, even about their own units. There are ways to check if you have one or more of a unit, but you can't count them as far as I know.

If you have ideas for how to improve difficulty without damaging the human-like play experience, I'm certainly open to them.

nikola-shekerev commented 5 years ago

Ok, let me do some research and come back to you :)

jncraton commented 5 years ago

Sounds good. One simple option is to create a UMS map that implements this functionality using triggers. It's a little tedious to set up, as you need a version for each race, but it should get the job done. I haven't done much testing of this bot in UMS mode, though, so it might not perform well.

jncraton commented 5 years ago

The above change is the best that I could come up with.

Here's the AI built at difficulty 100 out of 255 if you'd like to test it out:

patch_rt.zip

It should be a little harder, but only after the midgame. Up until 6 minutes, it should be identical.

nikola-shekerev commented 5 years ago

Hello, Jon

I wanted to do some research on my own as well

Ages ago Nekron shared with me this doc that he have compiled. This is the closest to what the community has to documentation of the AI scripts of Broodwar: https://docs.google.com/document/d/1KXbIDuIBnacWwpJJxSQePmIhc1GKuEgRZxtf8cLi_Qo/edit

I was reading it and I now see and agree that:

Your commit is a simple conditional bayesian probability tied to worker count. I agree this is the closest we can get with this api. I want to run some calculations now, but I think that I see a bug atm

wait_build(8, Peon) some money code wait_build(8, Peon)

From my pov wait_build will always say "Yes, I already have 8 workers" after it passes once

I expected worker counts to increment:

wait_build(5, Peon) some money code wait_build(10, Peon) some money code wait_build(15, Peon) some money code wait_build(20, Peon) etc

Do you agree or am I misunderstanding smt?

jncraton commented 5 years ago

From my pov wait_build will always say "Yes, I already have 8 workers" after it passes once

There's also a significant delay (1800 frames) between each check.

Once a base gets 8 workers, it will have a chance at some random money.

1800 frames after that, the script will wait until it has 8 workers again. Under normal circumstances, it will still have 8 workers and the wait will pass immediately, but if many workers (or the CC) were killed due to harassment, the script will wait to try again for more money until 8 workers are active again.

The script runs through this wait and possible free money cycle 10 times and then stops issuing free money for this base.

nikola-shekerev commented 5 years ago

Thanks, I understand your idea now

I intended to do something quite different (my syntax is probably wrong)

multirun_loop: if_owned(5, Peon) if random(X1) give_money() if_owned(10, Peon) if random(X2) give_money() if_owned(15, Peon) if random(X3) give_money() if_owned(20, Peon) if random(X4) give_money() etc etc etc wait(T)

The idea is that every T time the AI will have a few chances to get money. If more workers, more worker checks will pass and the AI will get more dice rolls. For example if the AI has 15 workers, it will get a roll with X1 chance, a roll with X2 chance and a roll with X3 chance. This is a bayesian chain.

I can calculate those to be quite precise, but I am not yet sure how well this will play. Probability can be accurate and still weird

I will play with this idea locally and if I manage to get something that works well, I will push in a branch for you to test

jncraton commented 5 years ago

Sounds good. This seems like a reasonable approach.

In my testing so far, there's still a fairly low difficulty ceiling. The AI is still constrained by the number of production structures that it has built, and these have been tuned to assume standard income levels. To really increase difficulty, we'd probably need to ramp up gateway count as well.

In my setup, I intentionally avoided using a non-terminating loop, as this will ultimately provide infinite money into the endgame when the AI simply piles its remaining workers into depleted geysers. These bases would continue to be detected as active even though they have no minerals. That's not necessarily bad, but it does eliminate the possibility of winning a war of attrition against this bot.

nikola-shekerev commented 5 years ago

1) Difficulty ceiling

I would prefer to keep difficulty ceiling low and do not skew the build order because of cheats. I feel this is kind of logic coupling

As a high difficulty workaround a player still has the option of playing against 2 or more AI-s of the same race. This will increase the production buildings as well. This is what I did and it works fine, except it was too hard for me :)

If we manage to achieve an AI that is 150% harder with cheats and gameplay is valid (harass, war of attrition, all these mechanics) I would call it mission accomplished

2) War of attrition This is really valid point, I will think of a way for this to work

BoneHorror commented 5 years ago

Unfortunamently blocks relying on wait_build will often be faulty; from my pretty broad experience over the few years of making UEDAIP, wait_build is broken and will cause priority issues for the AI player, such as stopping the execution of other waiting commands until the wait_build request is finished. So it's not gamebreaking, but it will bring the overall performance down. I would recommend switching over to AISE and using bring_jump with Anywhere(Loc.63) instead(unless melee has no anywhere location? I'm not quite sure). if_owned checks for (unit, block) not (amount, unit), so it would also be impossible to use in this context. Using AISE you can also dump give_money and instead use resources; ----resources (by iquare) resources playerId operator resourceType quantity block "%2operator (read/write) %1playerId's %4quantity %3resourceType, jumping to %5block if true %3resourceType: Ore, Gas, Any"

resourceType can be Ore, Gas, or Any. When reading, Any requires both Ore and Gas to be true. When writing, Any writes to both resource types. operator can be AtLeast, AtMost, Exactly (includes _Call and _Wait), Set, Add, Subtract, or Randomize.

The keys to making a harder AI melee without resorting to cheat with resources would be in my mind proper uses of eval_harass, harass_factor, target_expansion attacks and optimizing the ways AI handles requests, be it through AISE's request cycling or cleaner code.

nikola-shekerev commented 5 years ago

Thanks a lot for the info. I am super new to Broodwar scripts and also time is limited, sorry of some of my suggestions do not make sense

Question: I am not sure what AISE is, a link to a resource would be appreciated

Clarification: 1) According to a developer update (1 year ago or smt) at some point Remastered will support bwapi. 2) Another option for a player is to fight 2-3 non-bwapi bots.

=> So if a player wants a serious challenge, he has options

=> My goal is not to make a super hard bot, but one where the difficulty is somewhere between our current difficulty and the difficulty of 2 bots.

Work on bot logic, instead of cheats, could work great. Unfortunately I am not a competent player. Hence my simpler goal

Thanks again to both of you for the suggestions. I will spend some time goofing around with the code and will come back to you at some point.

BoneHorror commented 5 years ago

AISE is an aiscript extension plugin created by Neiv that adds multiple (1 per player at once) call support (previously only 1 active at once globally), request recycler (certain impossible requests are cycled out until they can be filled), train bug fix, guard crash bug fix and a wealth of new commands created by Neiv and Iquare https://docs.google.com/document/d/1_kTX8brm4SWDeBZy7aPse33BiUASwVwYl4KnFld6GSE/edit?usp=sharing AISE - https://github.com/neivv/aise/releases PyMS with AISE commands - https://github.com/neivv/PyMS/tree/aise

I personally dislike BWAPI bots over built-in Starcraft AI since they're impractical in campaigns and ill suited for melee modding, hence why I like BWMetaAI and why I'd love to see it grow - and also because my attempts at melee AI were pretty unsuccesful, which is why I stick to campaigns mostly.

nikola-shekerev commented 5 years ago

HUGE thanks for the links :)

jncraton commented 5 years ago

Unfortunamently blocks relying on wait_build will often be faulty; from my pretty broad experience over the few years of making UEDAIP, wait_build is broken and will cause priority issues for the AI player, such as stopping the execution of other waiting commands until the wait_build request is finished. So it's not gamebreaking, but it will bring the overall performance down. I would recommend switching over to AISE.

That's very helpful information and could be useful in eliminating some of the stalling that I see occasionally.

I don't think that I'm interested in adopting AISE at this time. It looks great, but one of my long-term goals is to make this playable as a simple UMS map without any mods at all. This will make it trivial for new players to use, and also opens up the possibility for easy multiplayer. I have this mostly working using EUD triggers, but there are some stalls that I haven't had a chance to resolve.

BoneHorror commented 5 years ago

I think you could potentially recreate some commands using EUDs and AISE's source, afaik the source is public on github, but i understand how you'd want it to be more easily accessible for a project like this. Still, i think it'd be awesome to see it with a little more nuance in the way attacks are executed some day

nikola-shekerev commented 5 years ago

Greetings

I agree that wait_build and if_owned do not work as desired so the idea to bind worker count and give_moneywill not work

I have another idea - give_money for each AI expansion that survives X amount of time

What do you think?

jncraton commented 5 years ago

How do you know if the expansion survived without using wait_build?

nikola-shekerev commented 5 years ago

Here is a sample implementation: https://gist.github.com/nikola-shekerev/9eebe9b2181acf822e86bed14487ccd8

In the api documentation Necton says try_townpoint may malfunction if we do not use expandor allies_watch. I am thinking of another implementation that takes this comment under consideration

BoneHorror commented 5 years ago

I would like to remark that this just might not be true, this comment (about try_townpoint not working in expand/allies_watch-less blocks) was originally made by Heinermann but since the command was not dissected since then by anyone who can read binary I have no idea if it's accurate. If it works during testing then it probably works, in my campaign I only use it in expansion blocks in Fury of the Swarm so I didn't have many opportunities to check.

jncraton commented 5 years ago

Here is a sample implementation: https://gist.github.com/nikola-shekerev/9eebe9b2181acf822e86bed14487ccd8

That makes sense to me. I've honestly never used try_townpoint, but if it works as documented this could be a great way to handle give_money calls.

nikola-shekerev commented 5 years ago

Hey, Jon

1) I am not sure where to call the sample implementation from, so it is executed only once per AI, and not for each areatown. Could you advise?

2) Are you happy with the suggested numbers so we do some playtesting?

jncraton commented 5 years ago

I am not sure where to call the sample implementation from, so it is executed only once per AI, and not for each areatown. Could you advise?

You could call it from main.pyai. Starting this thread as soon as we start the main base manager should be fine:

https://github.com/jncraton/BWMetaAI/blob/master/src/main.pyai#L28

Are you happy with the suggested numbers so we do some playtesting?

I've not given this much thought, but if you find numbers that feel good to you then I'm happy.

nikola-shekerev commented 5 years ago

Quick question: Even if all buildings in an expansion are destroyed, the town_manager thread for that expansion will continue to execute, correct?

jncraton commented 5 years ago

I believe that is correct, but I'm not sure if I've ever tested this.

nikola-shekerev commented 5 years ago

Quick update from me:

0) I now have a decent idea how to mess with the project so here is my plan:

1) The suggested implementation does not work very well. I am trying to improve it. After playing games I look at the stats with spent resources. That is my primary guide.

2) If try_townpoint proves to not work well, I have another idea with expansions

3) Ultimately I will also try to just give_money in some interval. The interval can decrease over time, so in late game give_money will be more. Also this will be fixed numbers, not infinite

4) The ideas so far are about how to make "hard difficulty". We can go in another direction - we can also make "easy difficulty". The implementation is very simple - decrease how many workers the AI makes.

But why? A player can fight 2 easy AI-s of same race instead of 1 cheating. In my experience playing vs multiple bots feels really awesome. The gameplay is preserved - harass, expo denial, attrition war. The AI-s strike from different directions but that actually make for a fun game - you move your army around and positioning vs many bots matters a lot.

5) My goal is to have 5 difficulties - easiest, easy, normal, hard, hardest. The differences will be small, but will stack when you play against multiple bots.

6) This will take a bit of time, job / family and all, will keep you updated

BoneHorror commented 5 years ago

I would like to correct the record with regards to something I said earlier, it turns out the problems I was having with wait_build/start are related to AI being unable to build buildings in regions bordering state 0 regions that have the "Alerted" flag set (set after enemy units are inside of a region, reset if the region state changes from 0) - the command itself is fine.

nikola-shekerev commented 5 years ago

Greetings

1) The try_townpoint implementation seems to works. I am testing with different numbers and I am happy with how it feels.

2) But if wait_build is ok then a more realistic implementation would be something like this:

wait_build(15, Peon) give_money()

In the idea above we reward just once each expansion that managed to reach some worker saturation. So both denying the expo and harassing workers will delay / deny the bonus money

It will be amazing if this works without blocking the AI :) I need to work in the weekend but I will give it a spin some time next week

3) Update - Sorry for the "close" / "open" mishap below

nikola-shekerev commented 5 years ago

Hello

Implemenation 1 using try_townpoint https://gist.github.com/nikola-shekerev/6036f33dc10cb8b3dfbe4c590ee30114

Implementation 2 using wait_build https://gist.github.com/nikola-shekerev/8563debcc0d90c7f5d4ed9cd71f6deea

If you do not harass the AI, both implementations should feel similar. But in reality implementation 1 is challenging and implementation 2 feels as if no free money are given. I do not know why.

So implementation 1 is the way to go. I could create a Pull Request on a branch, if you give me rights.

Cheers

jncraton commented 5 years ago

You shouldn't need any special permissions to create a pull request. See:

https://help.github.com/en/articles/creating-a-pull-request-from-a-fork

If you have something working well without glitches that outperforms the wait_build implementation currently used, I'm happy to merge it.

nikola-shekerev commented 5 years ago

Greetings

I did more testing. I am ashamed to admit it, but my results are poor.

1) Early game

The implementation with try_townpoint in the gist above indeed performs well in early game. But I got a similar performance with a variation of wait_build implementation.

In the current implementation in master, if you remove randomness and give money on fixed intervals, it behaves almost identical

I attribute the good initial results to how aggressive I was with giving money, not the merits of the idea.

The problem with aggressively giving money is that it does not matter how many expansions the AI has. Strategy becomes irrelevant

2) Late game

I did not manage to get decent performance late game with any implementation

3) Easy difficulty

I had an idea to make an easy difficulty and fight multiple AI-s, but I did not get decent results here either

Conclusion

I feel that I have wasted your time. I apologize for that, If you are fine, I would close the ticket :)

Cheers and happy hunting