Dice Rolling Metalanguage

Apprentice
Posts: 12
Joined: 2008.09
Post: #1
Hi all,

I been trying to figure out a good project to be do, since i am a beginner. I have decided to go with a tool that been screaming inside my head to be put on code for a while now.


It's a simple application that allows me to roll dice but is based on a "meta-language". the few notes i have on it are this:

Code:
roll 4d6, drop lowest, add up the result. r4d6d1S
roll 4d6, keep highest 3 dice, add up the result, add 4 to the result. r4d6k3S+4

roll 2d8, add up the result, add 4 to the result. r2d8S+4


roll 6d10, count success with target greater than or equal to 7. r6d10t6
roll 6d10, count success with target lower than 7. r6d10T7


Syntax:

Roll dice
rAdB, where A is the number of dice to roll, B is the number of sides per dice.
Returns a Dice Pool.

Keep
[k|K]N, N is the number of dice to keep, k means keep highest value, K means lowest value.
Returns a Dice Pool.

Drop
[d|D]N, N is the number of dice to drop, d means drop lowest value, D means drop highest value.
Returns a Dice Pool.

Sum
S, Adds up the dice values in the dice pool.
Returns an Integer.

Count Success
[t|T]N, N is the target number, t means count results greater than target, T means count results lower than target.
Returns an Integer.

Now my question is, what would be the best way to implement this parser in Obj-C/Cocoa?

i have a few ideas, like i should create a class that uses NSMutableArray, DicePool. this object could then have several methods attached to it. but i am not sure how to structure it.

Like for instance...

DicePool Rolls;

[Rolls roll:4 d:6] keep:3 lowest:yes] or something like that.

but what really gets me stumped is how to make a parser for the syntax.

Any help would be appreciated.
Quote this message in a reply
Member
Posts: 283
Joined: 2006.05
Post: #2
That looks like a really cool idea.

NSScanner is useful for parsing a string.

I'd be probably have each DicePool method parse its own syntax, so you'd have:

Code:
DicePool * rolls = [[DicePool alloc] init];
[rolls roll:@"4d6"];
[rolls drop:@"d2"];
return [rolls sum];

That way each bit of parsing is self-contained and easier to debug. Then you'd need to have an initial parser that recognises the commands in the string and sends them to the correct method, which could be done with NSScanner. Perhaps that's making more work than is necessary though.
Quote this message in a reply
Member
Posts: 338
Joined: 2004.07
Post: #3
Cool idea. I bet there would be some demand to use such a thing in code as a random number generator, too.

For parsing, I would personally put the command in an NSString. The parser would need some sort of state machine/automaton for reading in the components. Just keep track of your state (an int is all you need) and what that is, determines what you should read in next from your string.

The parse function would accept the NSString, and populate your dicepool class with the data it needs. So it reads the first character, sees it's "r" and goes into the dice parsing state. Once it gets something else, like "k" it moves into a different state.

Once all the data is there, you can have a roll function that just executes that stored data.

Justin Ficarrotta
http://www.justinfic.com
"It is better to be The Man than to work for The Man." - Alexander Seropian
Quote this message in a reply
Moderator
Posts: 680
Joined: 2002.11
Post: #4
I would just like to say that this is a fantastic project for a beginner. Not too fancy, but challenging enough to make you think. I think I will implement this in C as an exercise.

I might also write a parser for it with yacc, since my compilers class just got the point where we can use lex/yacc to do useful things, and your grammar isn't very difficult to describe. (lex and yacc are tools that help build compilers.)

My web site - Games, music, Python stuff
Quote this message in a reply
Apprentice
Posts: 12
Joined: 2008.09
Post: #5
Thanks for all the encouraging words,

I will be sure to keep you updated on this.
Quote this message in a reply
Moderator
Posts: 680
Joined: 2002.11
Post: #6
I misunderstood your original syntax. I thought you intended it to be plain English with optional shorthand. I already started on a plain-English version in Python, so I'll probably finish that just for laughs.

Edit: Your grammar is slightly inconsistent. "k" means keep the highest N rolls, but "d" means drop the lowest N rolls. Shouldn't they both be lowest, with uppercase always indicating highest?

Another edit, with a suggestion: Why not make all return types be lists? A sum would simply return an element of one integer. That way, you could put multiple kinds of rolls in one command and get a list of all of the sums, or perhaps perform another operation on the list of sums, like keeping the highest.

Also, I'm abandoning the plain-English version. I have homework to do.

My web site - Games, music, Python stuff
Quote this message in a reply
Apprentice
Posts: 12
Joined: 2008.09
Post: #7
I constructed the syntax based on what my group of friends usually meant when calling dice rolls, i intended Lowercase for Default Behavior and Upper Case for Reverse Behavior. In this case, when you call out "Roll 4 dice, keep 3" you mean keep the highest 3 rolls by default. Notice that "Roll 4 dice, drop 1" should produce the same effect. since you normally drop the lowest. if you want to behave counter to expected behavior you use upper case.

Initially i thought the plain english would be fun, but i am not sure that making a parser for that is within my grasp yet.

Smile
Quote this message in a reply
Moderator
Posts: 680
Joined: 2002.11
Post: #8
Does it matter if roll results are sorted?

My web site - Games, music, Python stuff
Quote this message in a reply
Apprentice
Posts: 12
Joined: 2008.09
Post: #9
i guess you could sort the result if you want to simplify the other operations ( drop|keep ) (highest|lowest) but really it does not matter because you either dont care in the end result ( a dice roll ) or you need to see each individual rolled dice ( success counting )
Quote this message in a reply
Moderator
Posts: 680
Joined: 2002.11
Post: #10
Good. In that case, I'm finished.

Code:
Input: "r4d6d1S r4d6k3S+4 r2d8S+4 r6d10t6 r6d10T7"

Output:
rolled [1, 4, 5, 6]
keeping from drop with 1 [4, 5, 6]
sum: 15

rolled [2, 4, 4, 6]
keeping [4, 4, 6]
sum: 14
adding 4 to last result

rolled [2, 3]
sum: 5
adding 4 to last result

rolled [3, 4, 5, 5, 7, 8]
filter with 6 kept 2

rolled [1, 2, 2, 9, 9, 10]
filter with 7 kept 3

Results of all operations: [15, 18, 9, 2, 3]

The spaces aren't necessary. Whitespace is ignored.

I'll post the code in a while and explain a couple of nuances. My battery will literally die in 2 minutes. Expect post edit or new post.

My web site - Games, music, Python stuff
Quote this message in a reply
Apprentice
Posts: 12
Joined: 2008.09
Post: #11
WoW! did you did that, this fast on Python? i should probably look at it, havent seen it in a while since started on the rails bandwagon. when yu post your source i could perhaps make it into Obj-C

thanks
Quote this message in a reply
Moderator
Posts: 680
Joined: 2002.11
Post: #12
Full code is up at http://pastie.textmate.org/288183.

You can paste it into a plain text file with a .py extension, navigate to the folder from the command line, and type "python diceroll.py" or whatever you called it to run it. It takes user input.

edit: fixed link

My web site - Games, music, Python stuff
Quote this message in a reply
Luminary
Posts: 5,143
Joined: 2002.04
Post: #13
Neat problem Smile

Here's another (not well-tested) Python solution. This uses Spark ( http://pages.cpsc.ucalgary.ca/~aycock/spark/ ) to do a "proper" lex -> parse -> ast -> eval thing. It's longer than Diordna's solution, but a lot more general. For example, adding a binary operator (perhaps & to union dice pools - r4d6k3&r2d8) would be completely trivial.

http://onesadcookie.com/~keith/dice.py
Quote this message in a reply
Moderator
Posts: 680
Joined: 2002.11
Post: #14
Spark intrigues me. I think I'll take a look at it. Thanks for the alternate solution.

Edit: An & or other operator that operates on two expressions could be added to my version by pushing current_roll[] onto results[] when the operator is encountered. Alternately, roll expressions could be split up using regular expressions, parsed individually, and the results operated on.

The only problem I see at this point is operating on the results list itself. What if I want to take the sum of all previous sums? How do I express that in this syntax? Perhaps a capital R could stand in for a roll, but would cause subsequent commands to operate on the results list.

Edit 2: I just looked over OSC's code in detail, and it makes perfect sense, but I wish there were some comments in it so I could tell more easily which productions where which, since the method names are a bit cryptic, especially the first few in DiceParser. I also wish that productions could be collected in one place instead of scattered over lots of methods. Maybe I could write a YAML-SPARK bridge of some kind...

Also, this map(), filter(), and reduce() business should technically be handled by list comprehensions to be 2.6-compatible. And there is a sum() function for integer lists, no need for a custom reduce() for the 'S' token.

My web site - Games, music, Python stuff
Quote this message in a reply
Apprentice
Posts: 12
Joined: 2008.09
Post: #15
@diordna&OneSadCookie:

great code, i thank you both for your efforts. Sadly, i am not too well versed on python. But i will try and see how can i apply your ideas to a Cocoa Foundation Tool. or even to a cocoa app.

PS: Does anybody knows how to ask the user for input in a foundation tool application?
Quote this message in a reply
Post Reply