Palamedes is still in active development. New features are being added nearly every day. So please check the NEWS file for the latest features. Recent changes may not be properly reflected in the examples below. Be patient---This file will be updated to reflect new features and changes between the current alpha stage and the beta testing stage.
Most of the examples in this file are basic. There are also separate files showing advanced usage in applications such as Bayesian belief neworks, analysis of legal evidence, modeling a complicated ancient dice game, linear regression, absorbing Markov chains, and Monte Carlo simulation.
Prior to showing the examples, it's important to note that Palamedes may be used in two modes:
Palamedes adheres to the Tim Toady (TMTOWTDI) philosophy. Here are 4 ways to roll one six-sided die:
d
d6
1d6
choose[1..6]
You can roll three separate six-sided dice to get an array as follows:
3#d6
You can roll three dice six-sided dice and sum them like so:
3d6
In OD&D, you generate a new character by rolling 3d6 six times for each of the six abilities (strength, intelligence, wisdim, constitution, dexterity, and charisma) in order and then roll for starting gold by rolling ten times 3d6. Define shorthand to do this as follows:
SIWCDCh ← 6#3d6
chargen ← ["str","int","wis","con","dex","cha","gp"] → SIWCDCh⊎[10*3d6]
Notice the use of Unicode symbols. Most commands have a long English name, an ascii symbol and a Unicode symbol. Here is the equivalent English:
SIWCDCh gets 6#3d6
chargen gets ["str","int","wis","con","dex","cha","gp"] arrow SIWCDCh concat [10*3d6]
And here is the equivalent ascii:
SIWCDCh <- 6#3d6
chargen <- ["str","int","wis","con","dex","cha","gp"] -> SIWCDCh ++ [10*3d6]
Now save a new character named Frobozz and display him in a table:
set Frobozz = chargen
table(Frobozz)
In addition to generating random variates, Palamedes can also compute and manipulate entire probability distributions easily. Find the distribution for 3d6:
p(3d6)
This will output a JSON object representing the distribution. This is highly useful for feeding into subsequent calculations, but not for human readability. To make it readable, apply the table function:
table(p(3d6))
The problem reading this are digits after the decimal place. After we get the distribution, but before we table it, it would be nice to format the numbers as percentages with 2 digits of precision after the decimal place:
table(percent(p(3d6)))
This produces the output
key | value |
---|---|
3 | 0.46% |
4 | 1.39% |
5 | 2.78% |
6 | 4.63% |
7 | 6.94% |
8 | 9.72% |
9 | 11.57% |
10 | 12.50% |
11 | 12.50% |
12 | 11.57% |
13 | 9.72% |
14 | 6.94% |
15 | 4.63% |
16 | 2.78% |
17 | 1.39% |
18 | 0.46% |
What if you want no digits after the decimal? percent
takes an additional argument for the number of digits after the decimal you want displayed:
table(percent(p(3d6), 0))
And what if you wanted to see the numbers formatted as rational numbers (fractions)?
table(rational(p(3d6)))
The rational command may be abbreviated QQ
.
But picture is worth a thousand words, so let's picture this distribution:
barchart(p(3d6))
# | % | barchart |
---|---|---|
3 | 0.46 | 3 |
4 | 1.39 | 4 |
5 | 2.78 | 5 |
6 | 4.63 | 6 |
7 | 6.94 | 7 |
8 | 9.72 | 8 |
9 | 11.57 | 9 |
10 | 12.5 | 10 |
11 | 12.5 | 11 |
12 | 11.57 | 12 |
13 | 9.72 | 13 |
14 | 6.94 | 14 |
15 | 4.63 | 15 |
16 | 2.78 | 16 |
17 | 1.39 | 17 |
18 | 0.46 | 18 |
In the examples above, we've used standard prefix notation for applying functions. There's also a postfix notation, which allows you to write the arguments first and the function last. This improves readability by focusing on what's important first. Here are the last two commands using pipe forward:
3d6 |> p |> QQ |> table
3d6 |> p |> barchart
There's also an alternative, Mathematica-inspired postfix notation that I find faster to type:
3d6//p//QQ//table
3d6//p//barchart
Problem: You have a bunch of numbers you need to add up---maybe you need to stack a dozen bonuses, penalties, adjustments and modifiers in D&D 4E?
Solution: Just enter the numbers, separated by spaces, hit enter, and Palamedes will sum them for you. Summation is what Palamedes does by default...
In[7] := 5.5 -11 21 50% -2/3 0.666666666666667
Out[7] := 16
Notice the numbers can be positive or negative; integers, fractions, percentages or decimals.
What's really going on is much deeper though:
In[8] := history
Out[8] :=
key value
...
7 base * (base * (base * (base * (base * 5.5 - 11) + 21) + 0.5) - 0.6666666666666666) + 0.666666666666667
In[9] := base
Out[9] := 1
So what Palamedes is really doing is iteratively taking each number (starting from the far right and working to the left) and adding it to the next number multiplied by the base
, which is initially set to one. By changing the base, you can convert between number systems...
Problem: You have a binary number 10010110
and you want to know what it is in decimal.
Solution: Change the base
to 2 and then enter the binary digits, separated by spaces, and hit enter:
In[10] := base <- 2
Out[10] := base
In[11] := 1 0 0 1 0 1 1 0
Out[11] := 150
We can see how Palamedes did the calculation by using the history
command:
In[12] := history
Out[12] :=
key value
...
10 base ← 2
11 base * (base * (base * (base * (base * (base * (base * 1 + 0) + 0) + 1) + 0) + 1) + 1) + 0
Convert 30 minutes and 30 seconds to seconds. Same trick as before, using base
60:
In[15] := base <- 60
Out[15] := base
In[16] := 30 30
Out[16] := 1830
The sum of the squares of 1--10 is 385.
The JavaScript code is [1,2,3,4,5,6,7,8,9,10].map(function(x){return x*x;}).reduce(function(x,y){return x+y;});
using a map-reduce algorithm.
In Palamedes, you could simply write Σ([1..10]^2)
--- somewhat shorter and more readable than the JavaScript.
If you really wanted to be verbose, and imitate the JavaScript, you could write: 1--10||>map(_^2)||>reduce(_1+_2)
. Two dashes --
are a range constructor. The special pipe forward ||>
operator pipes a value into the second parameter of a function that takes two arguments. The _^2
is an anonymous function that squares its argument. and the _1+_2
is an anonymous function of two arguments that adds them together.
set
versus gets
The set
command immediately evaluates the expression on its right and assigns it to the identifier on its left. There are several ways to write set
. The first two use English, the next ascii, the last Unicode:
attack_roll set d20
set attack_roll = d20
attack_roll <~ d20
attack_roll ↜ d20
The gets
command assigns the expression on the right to the identifier on the left without performing any evaluation. This is useful for defining functions and synonyms. When you later use the identifier, it will evaluate the expression. To see the unevaluated expression assigned to an identifier, you the define
command which may be abbreviated by an apostrophe '
Let's define the "meets or beats" function that tells us the probability of hitting or exceeding a target value. These three definitions are equivalent. The first two use English, the next ascii, the last Unicode:
mob gets 1 - cdf + p
let mob = 1 - cdf + p
mob <- 1 - cdf + p
mob ← 1 - cdf + p
Now let's review the definition. These two statements are equivalent (English and ascii):
define mob
'mob
Now let's see a table of values for mob
applied to 3d6:
tab(mob 3d6)
Note that mob
is pre-defined for you and tab
is pre-defined as table(percent)
.
Important: If you generate a random variate, store it in an identifier and compare it to some other values in a conditional, always use set
, never use gets
. The reason is that gets
generates a new variate each time you do a comparison, while set
only generates the variate once (what you want in this case):
result <- {attack_roll <~ d20; if [attack_roll=20, "critical success", attack_roll=1,"critical failure",attack_roll>=target,"hits", otherwise, "miss"]}
Last update: Fri Sep 23 2016