PDA

View Full Version : GambleBot!



mredge73
12-08-2009, 03:51 AM
GambleBot 20100112GAM

Tired of losing your meat to the Penguin Mafia?
Introducing GambleBot, your very own clan run casino!
Now your clan can take your hard earned meat!

Current Games:

CoinFlip
Similar to the MMG game except that all bets are placed against the house (bot). Bet is placed through kmail and the coin is flipped, you call heads or tails. If you win you double your bet -5%, if you lose you get nothing. Simple enough?

Player will kmail the bot. In the text box they will use "50:50" to choose the CoinFlip game and then call either "heads" or "tails", if player does not call it default is heads.
example: 50:50 heads

ShellGame
Very similar to the CoinFlip game but with a 3 sided die or shell game. So the you will have 1/3 chance to win 3*Bet - 5%house cut.

Player will kmail the bot. In the text box they will use "Shell" or "shell" to choose the Shell game and then pick their shell by entering "1", "2", or "3", if player does not call it default is 2 (middle shell).
example: Shell 1


They will include the meat they want to bet in the kmail. The player will be notified of the result through kmail and PM. Clan reply is also used to broadcast result in the clan channel. The house takes a cut of 5% off of all winnings, if this is a clan run bot then you can choose to donate it back to your clan as a fund raiser. Processed kmails are automatically deleted afterward.

Default Bot Use:
The default logic is that the bot will start up when called and parse your Kmail for players. If it finds players it will execute the game. All processed kmails are deleted upon completion. 0 meat bets and >maxbet bets are sent back. Bot will repeat this every 3 minutes until 5 minutes before rollover. At that time the bot will execute the exit command and terminate mafia. Typing Abort at any time will halt script execution. The script can also be imported into another and the game can be called as a function.

Player statistics are kept at GambleBotPlayerStats.txt, in the form of: PlayerName Win Loss MeatWon MeatLost
example: MyBot 10 20 20000 100000

Three vars are used and can be changed in your player_vars.txt:
MrEdge73_CoinFlipMaxBet: this sets the max single bet, default 20k
MrEdge73_ShellGameMaxBet: this sets max single bet, default 20k
MrEdge73_KarmaExempt:if true the bot will use dense meat stacks from the clan stash for backup funding in the case that the bot does not have enough to pay the winner, must be karma exempt to do this, default is false

Known Issue:
Currently if a player wins and the bot cannot pay them, the bot ignores the kmail and moves to the next one. The Kmail is not flagged as a loser but the kmail has to win again on the next iteration to get paid. This could be considered dishonest because in order for the player to win, the bot has to have the meat to cover the winnings.
If the bot is not karma exempt I recommend that you keep 10xMaxBet in your bot's inventory at all times to eliminate this issue.


Required Scripts that must be placed in you script folder:
MrEdge73's Support Script -- Item Lists (Beta).ash (http://kolmafia.us/attachment.php?attachmentid=2263&d=1260240514)
MrEdge73's Support Script -- Functions (Beta).ash (http://kolmafia.us/attachment.php?attachmentid=2262&d=1260240320)
MrEdge73's Support Script -- ClanAdministrator(LITE).ash (http://kolmafia.us/attachment.php?attachmentid=2259&d=1260239457)
Zlib.ash

Enameless
12-09-2009, 03:53 AM
I tested this out as it seemed like a neat idea. I ran into trouble with DEBUG being undefined. I tried a few things but unfortunately my scripting skills are still pretty poor.

mredge73
12-09-2009, 01:54 PM
I forgot to mention that GambleBot.ash (http://kolmafia.us/attachment.php?attachmentid=2264&d=1260247954) is new therefore it is using functions defined in my updated support script files. It will only work with support script versions 20091207 and newer, please download the latest versions here.
(DEBUG is defined in MrEdge73's Support Script -- Item Lists (Beta).ash (http://kolmafia.us/attachment.php?attachmentid=2263&d=1260240514) version 20091207)

Enameless
12-10-2009, 02:40 AM
I tried again using the updated support files and it worked great. Awesome script and neat idea.

mredge73
01-12-2010, 04:12 PM
New Game Added!

mredge73
01-18-2010, 04:23 PM
New Tool!

This is a quick kmail tool designed to be used by a someone who wishes to take advantage of bot running gamblebot.ash

After running UseGamblebot.ash it will prompt you for:
string bot: this is the gamblebot that you wish to kmail
string game: this is what you would normally enter in the text box of a kmail
int meat: this is your bet amount in meat
int runs: this is how many kmails to send to the gamble bot

Using my clan bot for example:
bot: shivermetimbers
game: 50:50
meat: 5000
runs: 10

This will send 10 kmails to shivermetimbers each with 50:50 in the text box and each with 5000 meat.

(You guys are more than welcome to use my gamble bot! The odds are in your favor!)

Rahmuss
01-18-2011, 08:41 PM
I'm new to bots and trying to learn how to use them, so pardon my most likely obvious questions. Here is how I envision using this GambleBot script:

1 - Download the five files above and put them all into the desired script folder
2 - Open Mafia and log in with the char I want to be a bot
3 - Go to Scripts menue and run GambleBot.ash
4 - Log onto KoL (normal browser) and go into chat
5 - /msg [clangamblebot] 50:50 heads
6 - Reap the rewards or weap the rearwards.

Is that what I should expect?

Grotfang
01-18-2011, 10:21 PM
I'm new to bots and trying to learn how to use them, so pardon my most likely obvious questions. Here is how I envision using this GambleBot script:

1 - Download the five files above and put them all into the desired script folder
2 - Open Mafia and log in with the char I want to be a bot
3 - Go to Scripts menue and run GambleBot.ash
4 - Log onto KoL (normal browser) and go into chat
5 - /msg [clangamblebot] 50:50 heads
6 - Reap the rewards or weap the rearwards.

Is that what I should expect?

Ish. I have highlighted the two sections I want to clarify some potential misconceptions.

Point 4:

Log in with a different character, I shall assume. If not, you aren't understanding how mafia works -- it is logged in as you. It just intercepts communications made between you and the game and does some magic. Be careful of seeming to abuse the multi guidelines.

Point 5:

Is best answered by mredge73


The default logic is that the bot will start up when called and parse your Kmail for players. If it finds players it will execute the game. All processed kmails are deleted upon completion. 0 meat bets and >maxbet bets are sent back. Bot will repeat this every 3 minutes until 5 minutes before rollover. At that time the bot will execute the exit command and terminate mafia. Typing Abort at any time will halt script execution. The script can also be imported into another and the game can be called as a function.

Kmail. Not chat.

mredge73
01-19-2011, 01:55 PM
Grotfang summed it up very well.
Bets are placed through kmail, I don't think meat can be sent through chat.
Chat is used by this bot to display results, not to take bets.

I don't know if this script abuses the multi guidelines are not, I would caution you to keep the max bet fairly low as to not attract attention.
Default is 20K but can be easily changed through vars, large transactions in the millions of meat will gain attention.
It is not intended to put MMG out of business, I use it for clan fund raising and low stake gambling.

Theraze
01-19-2011, 02:15 PM
If you don't run it for yourself, it shouldn't abuse the multi guidelines. If you use it to transfer meat between your characters...

Winterbay
01-19-2011, 02:24 PM
If you use it to transfer meat between your characters...
...it's probably easier to just send the meat in a Kmail directly.

Rahmuss
01-19-2011, 07:55 PM
Grotfeng - Yes, I meant logging into a normal browser with a different character (just to test it out).
And you're right, I meant for it to be a message that I sent, not a /msg (wasn't thinking when I typed that).
Sounds great though. I'd like to get a bot to just do simple clan chat stuff. Even if it just responds with random comments that make no sense. And I'd also like it to be able to roll dice upon chat requests. Hopefully I'll be able to figure out how to do some of those things.

Theraze
01-19-2011, 08:18 PM
I thought chatbot means hacking your source...? Or was that something else?

heeheehee
01-19-2011, 09:29 PM
chatbotScript is supported in the official builds of Mafia and could probably do quite a few of those things with chat_clan(). The only issue is that it can't do anything about non-PMs. Or PM someone specific (i.e. not replying to the most recent sender).

Rahmuss
01-19-2011, 11:11 PM
Anyone have a basic shell / snippit of code for when someone pms the clan chat bot and the bot responds?

heeheehee
01-19-2011, 11:28 PM
void main(string sender, string message) {

}

That should be the main method, I think. Just create an ASH file that does whatever with those, then set it to your chatbotScript via the CLI: "set chatbotScript = myScript.ash" or whatever it's called.

Rahmuss
01-20-2011, 12:25 AM
Can I then do something like:


void main(string sender, string message) {
message:="I'm the clan chatbot.";
}

Save that as mychatbot.ash
Go to the CLI in KoL Mafia and type: set chatbotScript = mychatbot.ash
And then when anyone messages our clan chatbot it will send that message???
P.S. Sorry if my syntax is off, I've done most of my programming in C++.

Theraze
01-20-2011, 01:10 AM
No... that would set any message sent to the chatbot to overwrite what was sent and instead replace it with "I'm the clan chatbot." instead of their actual message. What you'd want is more something that sends "I'm the clan chatbot." to sender...

Rahmuss
01-20-2011, 01:47 AM
Is there a manual page or something where I can see how to send messages using the chatbot script then? I wish I could see the scripts here at work; but they don't let us load any kind of files or software onto these computers.

Theraze
01-20-2011, 01:50 AM
From the wiki, searching for chatbot gives this: http://wiki.kolmafia.us/index.php?title=Miscellaneous_ASH_Features

Chatbot (chatbotScript)

Will execute whenever a private message is received
Requires a special main declaration void main(string sender, string message)

'sender' is the name of the player who sent the message
'message' is the message that was sent

Hmm... Cagebot (http://kolmafia.us/showthread.php?1896-Cagebot) has been talked about lately, that's a sort of chatbot.

Rahmuss
01-20-2011, 03:01 AM
Ah, thanks Theraze.

So could I have something like this inside the main chatbotScript function?:



if (sender='jick') {
chat_private(sender , "How may I serve you, master?");
}


Would that work at all?

Theraze
01-20-2011, 03:39 AM
Should be roughly right. I'd probably use a match on sender instead... something more like sender.equals("Jick") instead.

slyz
01-20-2011, 01:37 PM
Out of curiosity, what is the difference between


if ( sender == "Jick" )

and


if ( sender.equals("Jick") )

?

mredge73
01-20-2011, 01:39 PM
It would not work unless jick is in your clan.
Chat Private will only chat to clannies now.

I would recommend that you build your chat bot script to do very little, don't even hit the server.
Then you can use a clanbot script to parse in messages at a specified time interval.

This is my favorite chatbot script:


## Use this script by placing it in your scripts directory and typing "set chatbotScript=PMlogger.ash" into the gCLI.
void main( string sender , string message )
{
print("Incoming message from " +sender+ " saying: "+message,"olive");
record note
{
string sender;
string message;
}[int] PMlog;

File_to_Map("PMlog.txt",PMlog);
int newPM= count(PMlog);
PMlog[newPM].sender= sender;
PMlog[newPM].message= message;
Map_to_File(PMlog,"PMlog.txt");
}


It puts in all PM that are received into a map. My clan bot script will then execute them when it feels like it, not when they are immediately received.
This way you will prevent a crash due to two scripts running concurrently and hitting the server at the same time.

Theraze
01-20-2011, 01:46 PM
Out of curiosity, what is the difference between


if ( sender == "Jick" )

and


if ( sender.equals("Jick") )

?

But the first isn't what was given... we were given sender = 'Jick' not sender == 'Jick'. Explaining the difference and making sure that someone does it right in 300 lines of unindented code (if you're unlucky enough to be their validator when things go wrong on an overly complicated hodgepodge) generally isn't worth the effort of giving options. ;)

Rahmuss
01-20-2011, 08:14 PM
Ok, let me see if I can break this apart:

void main( string sender , string message ) #Main Function
{ #Start Main Function
print("Incoming message from " +sender+ " saying: "+message,"olive"); #Prints to the CLI "Incoming message from [sender's character name] saying [sender's message] olive". olive??? What's that for?
record note #Saves a copy of the information below (sender and their message)... where does it save it?
{
string sender;
string message;
}[int] PMlog; #Is this where it's saving that above info to?

File_to_Map("PMlog.txt",PMlog); #Selecting the file that you'll be changing?
int newPM= count(PMlog); #Creates an integer: newPM which is initialized to the lines in the PMlog
PMlog[newPM].sender= sender; #Adds the sender's name to sender part of the PMlog
PMlog[newPM].message= message; #Adds the message text to the message part of the PMlog
Map_to_File(PMlog,"PMlog.txt"); #Ending the changing of that file?
} #End main function

EDIT, EDIT: Oh, and I forgot, so I can put in something like this in the function then?:



if (sender.equals("Jick") {
chat_private(sender , "How may I serve you, master?");
}
if (sender.equals("Rick") {
chat_private(sender , "You may now serve me, your master?");
}
chat_private("rahmuss", +sender+, " sent your clan chatbot a message saying: ", +message);

Winterbay
01-20-2011, 08:21 PM
Olive: The colour of the text that appears in the CLI-window
file_to_map takes a file in the /data folder and makes it into a ash-record/map.
map_to_file reverses the above to save the map to a file again, also in the /data folder

The record note{} part is actually defining how it is going to save the data and initiates the map structure.

Theraze
01-20-2011, 09:06 PM
Yes... though I'd suggest not making your second chat_private a question. :)

Rahmuss
01-20-2011, 09:25 PM
Winterbay - Thanks. That makes sense. I'll have to check out the data folder after I try this then.

Theraze - Doh! You caught me. Actually I'm glad you caught that because I'll probably just cut / paste this into my script for testing purposes. :D

Thank you all for the great help. Now I just have to figure out how to randomize a comment back to whoever sends a message.

Theraze
01-20-2011, 09:39 PM
Well, the random(int) function will return a random number... could throw that into a switch...

Here's a little snippet that should send one of 3 random messages to the sender...

switch(random(3))
{
case 1: chat_private(sender , "You got message number one!"); break;
case 2: chat_private(sender , "You got the second message!"); break;
default: chat_private(sender , "You got the leftovers!"); break;
}

Rahmuss
01-20-2011, 11:32 PM
Theraze - Hey, that works for me. That's a great idea. I keep thinking of things to make this chatbot better. And that random function will help with my next project of making it so that he can roll dice. Then again, he can only send personal message right? Or, was it that the chatbot could also talk publicly in clan? Either way, this should be fun.

Theraze
01-21-2011, 12:42 AM
Hmm... well, since chat_clan works, I'm assuming it would work to send clan messages. And you could easily have it roll dice with just something like:
chat_clan(sender + " just rolled a " + random(6) + ". Is that a winner?!");

Gets more difficult if you care to have it keep/compare between people, but for just shooting back an immediate roll, should be easy. Note also that you could have that in a multiple bit if you have the matcher set to detect "roll dice <number>" or something like that... In that case, you'd want to do a string and append on dice rolls as you go through the for loop. Doing proper commas and that are fairly easily possible as well, if you care to do the work. :)

Rahmuss
01-21-2011, 01:43 AM
Theraze - Great! I thought I there was a way to chat to the clan chat; but I couldn't remember where I had seen that.

Not sure I understood that last part of your post though. The "matcher" you're talking about, I've seen it a few other times; but I don't really understand it. Sounds like it's trying to match a particular part of the message and that it requires regexes (regular expressions) to make it work. I saw a link earlier, I think from Alhifar, maybe I'll have to check that out.

EDIT: I guess I could also do a switch where it would find 1d2 or 1d3, or 1d4.... 1d20, something like that. Could I find that by having something like:


if (message.equals(*+"1d6")) {
chat_clan(sender + " just rolled a " + (random(6)+1) + ".;
}


Or, if there isn't a way to use a wildcard, then I guess I could make people write it specifically as "roll 1d6" and then if it found that exact message then it could roll a 1d6.

Theraze
01-21-2011, 01:55 AM
Something sort of like:
int dice = 1;
matcher m = create_matcher( "roll \\d (file://\\d)+ di([c]?)e", message.to_lower_case());
if( m.find() ) dice = m.group( 1 ).to_int();
else
{
m = create_matcher( "roll di([c]?)e \\d+", message.to_lower_case());
if( m.find() ) dice = m.group( 2 ).to_int();
}

That will detect either "roll <number> dice" or "roll dice <number" in the message, pull the number of dice, and set it as the amount to display...

Generally, you wouldn't do it both ways... but you might want to do "dice" and "die" (the proper singular for one dice) depending on how likely you think your gamblers will be aware of proper grammar. ;)

Edit: Since it amuses me, here's a bit of the workings to generate the proper syntax for sending punctuated dice results:
int rolled = 0;
buffer sending.append(sender + " just rolled a ");
while (rolled < dice)
{
if (rolled == 1 && dice == 2)
sending.append(" and ");
else if (rolled > 0 && dice == rolled + 1)
sending.append(", and ");
else if (rolled > 0)
sending.append(", ");
sending.append((random(6) + 1);
rolled += 1;
}
sending.append(" on " + dice);
if (dice == 1) sending.append("die.");
else sending.append("dice.");

I also varied the matching code above... this should check for 0 or 1 'c' in the word dice/die. You'll notice that having added a new variable, it now matches on the second group on the lower item. If it's easier for you to read, instead doing "di([c]?)e" you should also be able to do "(dice|die)" to match either dice or die. I just prefer the first for some somewhat bizarre reason. :D

Edit2: In the case of complicated dice, you could do a matcher like:
int dice = 1;
int sides = 6;
matcher m = create_matcher( "roll \\d+d\\d (file://\\d+d\\d)+ di([c]?)e", message.to_lower_case());
if( m.find() ){
dice = m.group( 1 ).to_int();
sides = m.group( 2 ).to_int();
}
else
{
m = create_matcher( "roll di([c]?)e \\d+d\\d+", message.to_lower_case());
if( m.find() )
{
dice = m.group( 2 ).to_int();
sides = m.group( 3 ).to_int();
}
}

With that, you'd want to run random(sides) instead of random(6) when rolling your dice... And when giving the message about how many dice they rolled, I'd change this line:
sending.append(" on " + dice);to this:
sending.append(" on " + dice + " " + sides + "-sided ");

If it works properly, it should return a clan chat of:
Theraze just rolled a 2, 5, and 1 on 3 6-sided dice.

Edit3: Fixed singular/pluralization. Don't grammar tired, it's bad. Also, I'd probably throw a check aborting the rolls if sides < 2 (possibly with a mocking comment on throwing balls, as that would be a 1 sided object)... and possibly calling 2 sided dice coins. If sides > 50 or something else rather large, because it ceases to be useful in dice calculations... though it may be useful for your own random number considerations. Just depends on how useful you want to keep it. Same on the number of dice thrown... if they aren't throwing at least 1 die, abort. If they're throwing over 20 or so, they can do it in multiple throws. Just don't want it to overwhelm the single message length capacity...

Theraze
01-21-2011, 02:24 PM
EDIT: I guess I could also do a switch where it would find 1d2 or 1d3, or 1d4.... 1d20, something like that. Could I find that by having something like:


if (message.equals(*+"1d6")) {
chat_clan(sender + " just rolled a " + (random(6)+1) + ".;
}


Or, if there isn't a way to use a wildcard, then I guess I could make people write it specifically as "roll 1d6" and then if it found that exact message then it could roll a 1d6.

Regarding doing something like that, if you changed it from message.equals(*+"1d6") to contains_text(message, "1d6"), that would actually let you do it that way... but you would have to do individual setups for each amount of dice and each side count. I'd suggest doing something with matchers, more like my post 34 instead.

Veracity
01-21-2011, 02:37 PM
Out of curiosity, what is the difference between


if ( sender == "Jick" )

This is ASH.


and


if ( sender.equals("Jick") )

?
This is Java. ASH does not have an "equals" function.

Theraze
01-21-2011, 02:40 PM
Gah... sorry. Yeah, I've been staring at code too long. Yeah, it's not .contains("Something") for that then, it's contains_text(<text to check>, "Something"). Just make sure you have both equal signs in, not just the first... one equal sign means change the definition of something, two means you're checking. Updated post 35 to actually use contains_text instead of contains...

Rahmuss
01-27-2011, 07:30 PM
Well, I have my chatbot up and working just doing random comments back to people.
The regular expressions are really neat... however, they are also a bit complicated and I don't have many large blockss of time (10 or more minutes) to try and figure them out. But I'm working on it.

Next task will be to get my bot to roll dice of any number. I think I'll stick with chatbot's format. So it will be a message like:
/msg clanchatbot roll 1d__
(where the __ is obviously any number up to maybe 100)
And then the matcher will find what number is in there and roll it in clan chat... that's the goal at least.

slyz
01-28-2011, 05:39 AM
Here is an example of a matcher that looks for something that looks like "XXdYY" or "XXDYY" where XX and YY are two numbers:


string text = "roll 22d7";
matcher dice_matcher = create_matcher( "\\b(\\d+)[dD](\\d+)\\b", text);

if ( text.contains_text( "roll" ) && dice_matcher.find() )
{
int num = dice_matcher.group( 1 ).to_int();
int faces = dice_matcher.group( 2 ).to_int();
int result = 0;

if ( faces == 0 ) abort( "0-faced die do not exist in an Euclidian paradigm." );
if ( num == 0 ) abort( "Not throwing any die will NOT give a random result, you know." );
print( "Rolling " + num + " d" + faces + " ..." );
for i from 1 to num result += random( faces ) + 1 ;
print( "Result: "+ result );
}
else
{
print( "Usage: 'roll <xxx>d<yyy>' where xxx is the number of yyy-faced die to roll." );
}


I can explain the RegEx in "\\b(\\d+)[dD](\\d+)\\b"

\\b matches a word boundary position such as whitespace or the beginning or end of the string (this means "a1d3test" won't match).
(\\d+) means one of more digits. Since it is enclosed in parenthesis, the number matched will be saved in a group.
[dD] simply meand "d" or "D" (so 2D56 and 2d56 will work).


EDIT: added checks to make sure faces and num are greater than 0.

Theraze
01-28-2011, 05:45 AM
I actually gave an example of how to do the whole thing, including the rolls of various sizes, above... though I didn't do the dD. Never seen someone do 2D10 instead of 2d10, but I suppose it may happen if someone gets all caps-happy.

Rahmuss
01-28-2011, 07:29 PM
slyz - Thanks for the help. I'm still trying to really learn the RegExes, I can see how they can be very helpful with a bot. I hope I can get other things to work just as well.

Theraze - Yep, that's probably true. Most likely no one would use "D"; but who knows, if that wasn't in there some frustrated English major may be cursing at his computer because it's not working. :D

Theraze
01-28-2011, 07:39 PM
slyz... one issue with what you posted is that you only return a single result, which may be useful in some cases, but won't allow for actual use in dice games. If you wanted to do Yahtzee or Zonk/Farkle, as standard pure d6 examples, you need to know what each die had, not just the total of all the dice. If you try to run a PnP game through chat, you need to know whether your d10/d20/dwhatever rolls are a success or not, not just what their total is...

Rahmuss
01-28-2011, 08:21 PM
Does this work?:



void main( string sender , string message ) {

string text = message;
matcher dice_matcher = create_matcher( "\\b(\\d+)[dD](\\d+)\\b", text);

if ( text.contains_text( "roll" ) && dice_matcher.find() ) {
int num = dice_matcher.group( 1 ).to_int();
int faces = dice_matcher.group( 2 ).to_int();
int result = 0;

if ( faces == 0 ) {
abort( "0-faced die do not exist in an Euclidian paradigm." );
chat_clan("Nice try small fry. There are no 0-sided die silly.");
}
if ( faces == 1 ) {
abort( "1-faced die do not exist in an Euclidian paradigm." );
chat_clan("Oooo, wow, that would give some... uhm... interesting results. Try again.");
}
if ( num == 0 ) {
abort( "Not throwing any die will NOT give a random result, you know." );
chat_clan("Ok, so you actually don't want me to roll any die?");
}
chat_clan( "Rolling " + num + " d" + faces + " ..." );
for i from 1 to num {
result = random( faces ) + 1;
print( "Result: "+ result );
}
}
else {
print( "Usage: 'roll <xxx>d<yyy>' where xxx is the number of yyy-faced die to roll." );
}
}

Theraze
01-28-2011, 08:23 PM
First comment... reverse chat_clan and aborts... once you've aborted, that's it, so the chats never happen.

Second, you'll want to display each result as it happens, not just the total.

Third, just noticed this... faces should probably match on group 3. Group 2 is probably the [dD] match, and you don't want it to have "d" faces... Possibly I'm wrong on this, but I've had issues with regexps in the past where I did things like that.

Edit: Try this:
void main( string sender , string message ) {
string text = message;
matcher dice_matcher = create_matcher( "\\b(\\d+)[dD](\\d+)\\b", text);
if ( text.contains_text( "roll" ) && dice_matcher.find() ) {
int num = dice_matcher.group( 1 ).to_int();
int faces = dice_matcher.group( 2 ).to_int();
string result;
if ( faces == 0 ) {
chat_private( sender, "Nice try small fry. There are no 0-sided die silly.");
return;
}
if ( faces == 1 ) {
chat_private( sender, "Oooo, wow, that would give some... uhm... interesting results. Try again.");
return;
}
if ( num == 0 ) {
chat_private( sender, "Ok, so you actually don't want me to roll any die?");
return;
}
chat_clan( "Rolling " + num + " d" + faces + " ..." );
for i from 1 to num {
result.append(to_string(random( faces ) + 1) + (i == num) ? "" : ", "));
chat_clan( "Result: "+ result );
}
}
else {
chat_private( sender, "Usage: 'roll <xxx>d<yyy>' where xxx is the number of yyy-faced die to roll." );
}
}


Edit2: Technically, 1 sided dice do exist... they're called balls. Same side always ends up regardless how many times you throw it. Unless you manage to make it invert, the outside always end facing up... wacky objects that they are.

slyz
01-28-2011, 08:41 PM
Sorry Theraze, I didn't notice your ninja edit =)

Oh, and I added the aborts in my snippet, but you're probably better off sending a message and then exiting (just replace the abort( "message" ); with return; ).

And [] isn't a capture group, so you should keep "int faces = dice_matcher.group( 2 ).to_int();"

Theraze
01-28-2011, 08:54 PM
Ah... yeah, I think I'd done ([dD]) or something like that instead. Okay, edited above to set back to group 2 matching, left my incorrect pondering in the thread since, hey, it makes more sense if you can read it all in context. :D

Edit: Tweaked above code to not abort, but return instead. Also set it to chat_private if roll fails, and chat_clan if results happen. Getting gCLI logs on the chatbot doesn't really help the requestor know how they did. :)

Rahmuss
01-28-2011, 09:24 PM
Theraze - Thanks again for the help. I tried to change it so that it didn't total the results, did I do it wrong? If so, then how should I code it?
What does print() do instead of chat_clan()? It seems like print() would either just display to the CLI or it might try to print it into any chat channel that you're in; but I thought it only worked for clan chat now???

Winterbay
01-28-2011, 09:38 PM
print() will print to the CLI and session log, it will do nothing with chat as far as I'm aware.

Rahmuss
01-28-2011, 09:51 PM
Thanks Winterbay, maybe it would be a good idea to keep a log of the rolls just in case there is a dispute. Though a timestamp may be needed. Oh, that reminds me. Anyone know how if there is a kind of pause() function?

EDIT: wait(30); ???

Theraze
01-28-2011, 10:07 PM
True... your version did display every roll with "Result: 1" "Result: 5" and so on. Not really good though if you have someone roll 500 dice.

Print only displays to gCLI and log, as Winterbay said. So nobody but the person running the bot would see it, and they would only see it if they actually go to check. Better to print out what you're sending instead of each roll individually.

Yes, wait(30) would pause for 30 seconds. Or you can use waitq if you'd like a silent delay. Just remember that while it's paused, I don't think anything will happen... so even if someone else sends another message, it'll wait for that to finish up before moving on.

Rahmuss
01-28-2011, 11:47 PM
Theraze - I already got the main gamble bot script from mredge73, so this is just an additional part for the chat bot to roll regular die for anything that our clan might need. I'll probably put a limit so that they cannot do more than 10 die or something. Anyway, thanks again for all the help.

Theraze
01-29-2011, 12:22 AM
Yes... I don't think you want to put a wait timer in there, since his only wait is when the bot is shutting down. But maybe I'm misunderstanding the way that chatbots run, and putting in waits won't screw up additional processing. :)

Rahmuss
01-30-2011, 06:54 AM
Not sure where else to ask these two questions, so I'll just ask here.

I can get the messages into the PMlog.txt file and I can open the file and see what's in there; but can I have my bot spit out one or more lines from the log? Here is what I tried:



void main( string sender , string message ) {
print("Incoming message from " +sender+ " saying: "+message,"olive");
string text = message;

record note {
string sender;
string message;
}
[int] PMlog;

File_to_Map("PMlog.txt",PMlog);
int newPM= count(PMlog);
PMlog[newPM].sender= sender;
PMlog[newPM].message= message;
Map_to_File(PMlog,"PMlog.txt");
talkint = newPM;

if (text.contains_text( "talk" )) {
for i from 0 to talkint {
chat_private(sender, PMlog[i] + " :: ";
}
}


My second question is, how do I get it to clear the file so that it's empty when I log in. I want to be able to spit out everything that was said; but only what's been logged in that file from the time I logged in. So if I log off at rollover, and log back in, I want the file to start from scratch, or else have some way of only printing what has been done since the time I logged in.

Winterbay
01-30-2011, 08:47 AM
That last part should be possible by doing a file_to_map on a empty variable in your logout script I guess.

StDoodle
01-30-2011, 09:11 AM
Yeah, I've done that many, many times. Just declare a map of the same format as you normally save (or really any map format should work), and then do map_to_file() before actually putting any data in the map. Just make sure that when you go to turn the file back into a map, you check to see if it has data with count() first. ;)

Edit to answer your first question:

That script of yours has several issues. Please take a look at this...


record note {
string sender;
string message;
string timestamp;
};

void main(string sender, string message) {
note [int] pm_log;

//load previous
if (! file_to_map("PMlog.txt", pm_log)) abort("Unable to load previous.");

//add this message to map
pm_log[count(pm_log)] = new note(sender, message, now_to_string("20110130 05:16:02");

//save updated map
if (! map_to_file(pm_log, "PMlog.txt")) abort("Unable to save pm log.");

if (contains_text(message, "talk")) {
//not sure what's going on; see below
}
}
Regarding the comment of "not sure what's going on":

It looks like you're saying that if you receive a pm with the text "talk" in it, you want to send a separate private message to the sender copying every single pm you've received since the log was cleared? That doesn't seem right... for one, sharing others' pm's is generally frowned on pretty heavily. For two, it could be quite spammy. If you have a different goal in mind, please feel free to let me know and I'd be happy to help. But I'm not assisting with anything that spammy and likely to be frowned on by TPTB. ;)

Edit 2 regarding the second question, partially answered above:

Put the following in your login script:


record note {
string sender;
string message;
string timestamp;
};
note [int] clearthem;

if (!get_property("_chatLogCleared").to_boolean()) {
if (map_to_file(clearthem, "PMlog.txt") {
set_property("_chatLogCleared","true");
} else {
abort("Unable to clear chat log; do so manually now!");
}
}


The property will get cleared automatically by mafia on your first login of the day, so if your chatbot gets halted & restarted for any reason, you'll still keep the day's log saved.

Rahmuss
01-31-2011, 06:45 PM
StDoodle - What I'm trying to do is basically setup a log so that people can leave a message for anyone in the clan. We don't want tons of clan announcements made; but we want these short messages available upon request. It actually won't be all PMs to the bot, it will only be PMs which contain a key word.

And thanks for the help on that second question as well. I'll have to see how that works.

StDoodle
01-31-2011, 07:23 PM
Are you asking for a message board functionality, where all messages are given to everyone? Or more of a "delayed PM" functionality, where you can leave messages for offline clannies? Or both? If you let me know, I'd be happy to edit that functionality in there. (Desired format for messages of ea. kind would be needed.)

mredge73
01-31-2011, 08:03 PM
Gamblebot.ash
Preliminary chat support for Rolling the Dice
dice roll stole from this thread, chat bot stuff was imported from my bot. I haven't completely cleaned it up but it does work as is.

publicroll xx[dD]yy will display results in both the clan channel and PM
roll xx[dD]yy will PM the person asking for the roll

Requires PM logger.

mredge73
01-31-2011, 08:07 PM
Rahmuss
I have implemented a note taking feature as well on my clan bot.
It is pretty easy, give me a while and I may be able to build an example.

Rahmuss
01-31-2011, 08:32 PM
It will be all messages sent to the chatbot that day with the special key word. So this is what would happen:

/msg clanchatbot keyword Tell clan this.
... an hour later...
/msg clanchatbot newmessages

clanchatbot: Tell clan this
(this would be to the clan chat window).

Usually we'd get maybe 2 or 3 messages a day, though on a busy day with a lot of things going on that need to be coordinated we may have as many as 10 messages.

EDIT: mredge73 - Great! Thanks for the update. That'll make it easier for me to incorporate it all.

mredge73
01-31-2011, 09:26 PM
Rahmus
Here is my sticky note function. You should be able to modify it to meet your needs.


//ChatBot Routines = Notetaking
//used to pass notes to other players through chat
//command order: 1 Add, 2 view, 3 erase, 4 count
//$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
string StickyNotes(int command, string sender, string message)
{
string Outgoing;

//Load Chatbot note taking Log
note[int] notelog;
File_to_Map("StickyNotes.txt",notelog);

//Add
if (command==1)
{
//parse out command
int spacing = index_of( message, "add notes:" ) + 10;
string cropped = substring( message , spacing );
//find new index
int newnote= count(notelog);
//write note to map
notelog[newnote].sender= sender+": ";
notelog[newnote].message= cropped;
//save new map
Map_to_File(notelog,"StickyNotes.txt");
Outgoing = "Added Note: "+cropped;
}
//view
if (command==2)
{
if (count(notelog)>0)
{
//PM the notelog to the sender
foreach note in notelog
if(my_name() != sender)
chat_private(sender, notelog[note].sender + notelog[note].message);
}
else
OutGoing= "The notelog is empty!";
}
//erase
if (command==3)
{
//pass an empty log to the file to erase it
note[int] emptylog;
Map_to_File(emptylog,"StickyNotes.txt");
OutGoing= "The notelog is empty!";
}
//count
if (command==4)
{
OutGoing= "There are "+count(notelog)+" notes on the notepad";
}
return OutGoing;
}


I call it by a chatbot function in the middle of a switch:
(see the unofficial gamblebot script)


//"add note" = adds note to the sticky note file
case contains_text( message, "add notes:" ):
Outgoing= StickyNotes(1,sender,message);
break ;

//"view notes" = sends PM to caller of all notes on file
case contains_text( message, "view notes" ):
Outgoing= StickyNotes(2,sender,message);
break ;

//"clear notes" = erases note file by passing empty map to file
case contains_text( message, "clear notes" ):
Outgoing= StickyNotes(3,sender,message);
break ;
//"count notes"= returns how many notes exist
case contains_text( message, "notes" ):
Outgoing= StickyNotes(4,sender,message);
break ;


**These are copy/pasted from another script (excerpts). Don't expect them to work right out of the box.
My clan requested this feature but only used it for a short time.

Rahmuss
02-01-2011, 08:43 AM
Well, my first response got deleted somehow, so here it is again. I originally tried to explain all the horror I went through; but now I'll just recap. Had to move variable for noteP and record stuff outside of IF statement to make it global so that my later IF statement would recognize it. I know it's getting into the second IF statement because it prints the message to the clan chat window as instructed in the IF statement. No errors now finally (Hurray!); but the foreach loop is not doing anything. Here is the meat and potatoes of what I have:



void main( string sender , string message ) {
print(sender+ ": "+message,"olive");

string text = message;
int hoboint = 0;
int slimeint = 0;
int talkint = 0;

record noteP {
string sender;
string message;
}
[int] PMlog;

if ( text.contains_text( "savenotes" )) {
File_to_Map("PMlog.txt",PMlog);
int newPM= count(PMlog);
PMlog[newPM].sender= sender + ": ";
PMlog[newPM].message= message;
Map_to_File(PMlog,"PMlog.txt");
talkint = newPM;
}
if (text.contains_text( "shownotes" )) {
chat_clan(text);
foreach noteP in PMlog
chat_clan(sender, PMlog[noteP].sender + PMlog[noteP].message);
}
else {
switch(random(40)) {
case 1: //A bunch of random PMs
}
}
}


I stayed up way too late trying to get this done and now it seems I'm back where I started. Any help would be VERY greatly appreciated. Thanks.

slyz
02-01-2011, 08:46 AM
You need to load PMlog.txt before the foreach loop. If the message doesn't contain "savenotes", file_to_map() is never called in the code above.

mredge73
02-01-2011, 01:58 PM
exactly
move this line: File_to_Map("PMlog.txt",PMlog);
right after the record declaration.

Rahmuss
02-01-2011, 10:46 PM
So put it the File_to_Map directly after the record is called and before the PMlog is defined? I'll have to try that later when I have time. I see what you're saying, and I think I've tried that (though maybe not with the record and / or variable outside the IF statement as well).

mredge73
02-01-2011, 11:56 PM
In the example above, you are declaring the record and defining PMlog at the same time.
A record is a user defined datatype, like string and int are pre-defined datatypes.

So the line should be placed:


record noteP {string sender; string message;}[int] PMlog;
File_to_Map("PMlog.txt",PMlog);
//other stuff


Or to prevent confusion you can split the definition and declaration.


//record definition
record noteP
{
string sender;
string message;
};

//map declaration
noteP [int] PMlog;

//Fill the empty map with data from a file
File_to_Map("PMlog.txt",PMlog);

Rahmuss
02-02-2011, 05:42 AM
So, this is directly from my test script. You can copy / paste it to a text file, save it as .ash and run it yourself. My PMlogged file has five entries in it currently. When I message him to shownotes, then he PMs me "shownotes" five times because I have private chat in the foreach loop. So I know it's getting inside the foreach loop, and I know that it knows how many records are in the file because it sends the same message five times (and no, the messages are not all the same thing in the PMlogged file). Nothing goes to the clan chat window though. Here is the code:



void main( string sender , string message ) {
print(sender+ ": "+message,"olive");

string text = message;
int hoboint = 0;
int slimeint = 0;
int talkint = 0;

record noteP { string sender; string message; }
[int] PMlogged;
File_to_Map("PMlogged.txt",PMlogged);

if ( text.contains_text( "savenotes" )) {
int newPM= count(PMlogged);
PMlogged[newPM].sender= sender + ": ";
PMlogged[newPM].message= message;
Map_to_File(PMlogged,"PMlogged.txt");
talkint = newPM;
}
if (text.contains_text( "shownotes" )) {
foreach noteP in PMlogged {
chat_private(sender, text);
chat_clan(sender, PMlogged[noteP].sender + PMlogged[noteP].message);
}
}
else {
switch(random(4)) {
case 1: chat_private(sender , "You Rock!!!");
case 2: chat_private(sender , "I don't think so."); break;
case 3: chat_private(sender , "Is that so?"); break;
default: chat_private(sender , "Need some meat?"); break;
}
}
}


Any thoughts?

EDIT: Horray! I saw that I was doing a chat_clan with sender and message inputs instead of just a message. So I changed that and viola. Though, is there a way to get it so that it ignores the "savenotes" part of the message either when inputing or when outputing? (preferrably the former). I'll go look at it.

Rahmuss
02-02-2011, 06:17 AM
Wow, so I guess I can't figure out regexes. I thought it might be fairly simple to have it recognize "savenotes" and then have the rest of the text in a second group (or the first group, depending on how I did it) and then save the message as that group. Main area of concern:



if ( text.contains_text( "savenotes" )) {
matcher check_message = create_matcher("savenotes (.+)" , message);
message = check_message.group(1);
chat_private(sender, "You saved the message: " + message);
int newPM= count(PMlogged);
PMlogged[newPM].sender= sender + ": ";
PMlogged[newPM].message= message;
Map_to_File(PMlogged,"PMlogged.txt");
talkint = newPM;
}


I've tried
matcher check_message = create_matcher("(savenotes) (*)" , message);
matcher check_message = create_matcher("(savenotes) ()" , message);
matcher check_message = create_matcher("savenotes (*)" , message);
matcher check_message = create_matcher("savenotes (????)" , message); //And messaged /msg clanchatbot savenotes Test
matcher check_message = create_matcher("savenotes (^*$)" , message);
matcher check_message = create_matcher("savenotes (.+)" , message);

And some others as well; but I can't remember them all. It sure sounds simple enough, and I'm sure if I knew regexes well enough then I might know how to do it. I'll have to keep looking.

EDIT: Also, a couple of times it gave the message: "No match attempted or previous match failed (test.ash, line 15)". That made it seem like it got past the matcher phrase; but didn't find a match. Just in case this helps.

EDIT, EDIT: Ok, looks like I figured it out. Nice



matcher check_message = create_matcher("((S|s)avenotes) (.+$)" , message);
if (check_message.find()) {
message = check_message.group(3);
}


Thanks for the help everyone.

StDoodle
02-02-2011, 07:11 AM
Heh, yeah, capitalization. It's the stupid things that get you on regex. ;)

For future use, the wiki's page on regular expressions (http://wiki.kolmafia.us/index.php?title=Regular_Expressions) is pretty decent.

slyz
02-02-2011, 11:40 AM
If you want your regex to be case-insensitive, add "(?i)" in front. This worked for me:


matcher check_message = create_matcher( "(?i)savenotes (.+)" , message);

mredge73
02-02-2011, 01:34 PM
This is a great tool for helping you learn regex:
http://www.fileformat.info/tool/regex.htm

You could avoid regex like Z and do something like my above example:


//parse out command
int spacing = index_of( message, "add notes:" ) + 10;
string cropped = substring( message , spacing );

This code crops out "add notes:" from the message.

Rahmuss
02-02-2011, 08:36 PM
StDoodle - Yep, I was using that Regex page on the wiki. Maybe it was the fact that it was so late in the evening (actually early in the morning) that made it so I could not think clearly.

slyz - Thanks slyz. For right now I don't care if they use upper or lower-case. Though that's good to know for later.

mredge73 - Wow, that's pretty cool. I didn't know you could crop things like that. Still a lot to learn.

StDoodle
02-02-2011, 09:08 PM
Also, for those uncomfortable with regex who want to do some string manipulation, I highly recommend looking into zlib's excise() (http://wiki.kolmafia.us/index.php?title=Zlib#excise) function.

Enameless
07-29-2011, 04:15 AM
So I've recently run into an issue. Gamblebot throws an error sending items to anyone with spaces in their name. It appears to be adding plus signs (+) as spaces. I've tried to see if I could find the issue but I have no clue what I'm looking for.

Edit:
Figured out the problem, it was using url_encode and opposed to url_decode for the sender and date thus adding +'s for spaces.

wilco ladd
09-01-2011, 06:36 PM
string ChatDice (string message)
{
matcher dice_matcher = create_matcher( "\\b(\\d+)[dD](\\d+)\\b", message);
if (dice_matcher.find())
{
int num = dice_matcher.group( 1 ).to_int();
int faces = dice_matcher.group( 2 ).to_int();
if ( faces == 0 ) return "Nice try small fry. There are no 0-sided die silly.";
if ( faces == 1 ) return "Oooo, wow, that would give some... uhm... interesting results. Try again.";
if ( num == 0 ) return "Ok, so you actually don't want me to roll any die?";
if ( num >50 ) return "Who do you think I am? I cannot find that many dice.";

string OutGoing=( "Rolling " + num + "D" + faces + " for " );
for i from 1 to num
OutGoing=Outgoing+"D"+i+"= "+(random(faces)+1)+" , ";
return OutGoing;

}
return "Usage: 'roll <xxx>d<yyy>' where xxx is the number of yyy-faced die to roll." ;
}


Is there anyway i can add +sender+ in the middle of the outgoing message so it shows the username? I have tried a few different ways but cant seem to get it to work, im pulling my hair out.

mredge73
09-02-2011, 02:35 AM
The string passed to that function is just the body of the message.
The output is just a string containing the result of the roll, it can manipulated outside of the function.

sender is not a global variable.
You would have to rewrite the function to accept the sender as a input in order to get that info.
May I ask, why would you need this?

wilco ladd
10-04-2011, 11:35 AM
May I ask, why would you need this?

I was trying to make it post in corps chat with the username of the player that asked for the roll. Gave up on it now though.