├── .gitignore
├── COPYING
├── README.md
├── build.sbt
├── project
├── build.properties
└── plugins.sbt
├── src
└── main
│ └── scala
│ └── sortilege
│ ├── eightball.scala
│ ├── iching.scala
│ ├── ogham.scala
│ ├── runes.scala
│ └── tarot.scala
└── version.sbt
/.gitignore:
--------------------------------------------------------------------------------
1 | project/boot
2 | target
3 | .ensime
4 | .ensime_lucene
5 | TAGS
6 | \#*#
7 | *~
8 | .#*
9 | .lib
10 | .history
11 | .*.swp
12 |
--------------------------------------------------------------------------------
/COPYING:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 Erik Osheim.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7 | of the Software, and to permit persons to whom the Software is furnished to do
8 | so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Sortilege
2 |
3 | > Divination (from Latin *divinare* "to foresee, to be inspired by a
4 | > god", related to *divinus*, divine) is the attempt to gain insight
5 | > into a question or situation by way of an occultic, standardized
6 | > process or ritual. Used in various forms throughout history,
7 | > diviners ascertain their interpretations of how a querent should
8 | > proceed by reading signs, events, or omens, or through alleged
9 | > contact with a supernatural agency.
10 |
11 | [*Divination*](http://en.wikipedia.org/wiki/Divination), Wikipedia
12 |
13 | ### Overview
14 |
15 | Sortilege is a library for predicting the future.
16 |
17 | Sortilege currently supports five divination methods:
18 |
19 | 1. [I Ching](http://en.wikipedia.org/wiki/I_Ching)
20 | 2. [Tarot](http://en.wikipedia.org/wiki/Divinatory,_esoteric_and_occult_tarot)
21 | 3. [Futhark](http://en.wikipedia.org/wiki/Runes)
22 | 4. [Ogham](http://en.wikipedia.org/wiki/Ogham)
23 | 5. [Magic 8-Ball](http://en.wikipedia.org/wiki/Magic_8-Ball)
24 |
25 | Each of these has types and methods that allow users to make
26 | predictions, and work with the results.
27 |
28 | ### Examples
29 |
30 | ```
31 | scala> sortilege.IChing.yarrow.display
32 | res0: String = the clinging fire ䷝ becoming abundance ䷶
33 |
34 | scala> sortilege.Runes.random.display
35 | res1: String = algiz ᛉ
36 |
37 | scala> sortilege.Ogham.choose(3).map(_.display)
38 | res2: scala.collection.immutable.Vector[String] = Vector(Onn ᚑ, Sail ᚄ, Dair ᚇ)
39 |
40 | scala> println(sortilege.Tarot.celticCross.display)
41 | 1. present: temperance (inverted)
42 | 2. challenge: five of swords (inverted)
43 | 3. past: king of pentacles (inverted)
44 | 4. future: two of pentacles (inverted)
45 | 5. above: eight of swords
46 | 6. below: knight of cups (inverted)
47 | --------
48 | 7. advice: the fool (inverted)
49 | 8. influences: ten of swords
50 | 9. emotions: knight of swords (inverted)
51 | 10. outcome: the moon
52 |
53 | scala> sortilege.Eightball.random
54 | res4: sortilege.Phrase = Phrase(cannot predict now,unknown)
55 | ```
56 |
57 | ### Getting Sortilege
58 |
59 | Sortilege supports Scala 2.10, 2.11, and 2.12. If you use SBT, you can
60 | include Sortilege via the following `build.sbt` snippet:
61 |
62 | ```scala
63 | libraryDependencies += org.spire-math %% "sortilege" % "0.4.0"
64 | ```
65 |
66 | ### Detailed Information
67 |
68 | Sortilege is currently using a
69 | [Complementary-multiply-with-carry](http://en.wikipedia.org/wiki/Multiply-with-carry#Complementary-multiply-with-carry_generators)
70 | random number generator provided by
71 | [Spire](http://github.com/non/spire). Users may wish to manually seed
72 | the generator with information derived from the querant, such as the
73 | location and time, the question being asked, other background
74 | information, etc.
75 |
76 | Currently Sortilege is most concerned with representing and
77 | implementing the divination method correctly, without worrying about
78 | interpretation. Users will need to understand how to interpret the
79 | results given, using pattern matching or other strategies.
80 |
81 | ### Known Issues
82 |
83 | When used improperly, these methods may fail to predict the future. It
84 | is up to the user to determine if and when various prediction methods
85 | should be used.
86 |
87 | In addition, there are numerous issues with localization and
88 | canonicalization, to say nothing of competing standards and
89 | authorities.
90 |
91 | ### Future Work
92 |
93 | More divination methods could be added.
94 |
95 | A high-level query interface is needed, where users can ask questions
96 | and indicate the desired type for an answer value. Type classes can be
97 | used to provide flexible mapping between the low-level return types of
98 | particular divination methods and the high-level types desired
99 | (e.g. `Boolean`, `StockTrade`, etc.).
100 |
101 | Better support for operating on divination result types; possibly a
102 | "divination monad"?
103 |
104 | Include text, images, and commentary as resources in the JAR file,
105 | especially for the *I Ching* and *Tarot*.
106 |
107 | The random number generator should be pluggable.
108 |
109 | It would be interesting to provide randomized physical simulations of
110 | divination methods (including dice rolling, coin flipping, and so on).
111 |
112 | If we can find (or generate) accurate data about the historical motion
113 | of heavenly bodies it would be possible to support various kinds of
114 | *Astrology*.
115 |
116 | ### Copyright and License
117 |
118 | All code is available to you under the MIT license, available at
119 | http://opensource.org/licenses/mit-license.php and also in the
120 | [COPYING](COPYING) file.
121 |
122 | Copyright Erik Osheim, 2014-2018.
123 |
124 | ### No Warranty
125 |
126 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
127 | > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
128 | > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
129 | > NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
130 | > BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
131 | > ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
132 | > CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
133 | > SOFTWARE.
134 |
--------------------------------------------------------------------------------
/build.sbt:
--------------------------------------------------------------------------------
1 | name := "sortilege"
2 | organization := "org.spire-math"
3 | licenses += ("MIT", url("http://opensource.org/licenses/MIT"))
4 | homepage := Some(url("http://github.com/non/sortilege"))
5 |
6 | scalaVersion := "2.12.4"
7 | crossScalaVersions := List("2.10.6", "2.11.12", "2.12.4")
8 |
9 | libraryDependencies ++=
10 | "org.typelevel" %% "spire" % "0.14.1" ::
11 | "org.scalatest" %% "scalatest" % "3.0.4" % "test" ::
12 | "org.scalacheck" %% "scalacheck" % "1.13.5" % "test" ::
13 | Nil
14 |
15 | scalacOptions ++=
16 | "-deprecation" ::
17 | "-unchecked" ::
18 | "-feature" ::
19 | Nil
20 |
21 | // release stuff
22 | import ReleaseTransformations._
23 |
24 | releaseCrossBuild := true
25 | releasePublishArtifactsAction := PgpKeys.publishSigned.value
26 | publishMavenStyle := true
27 | publishArtifact in Test := false
28 | pomIncludeRepository := Function.const(false)
29 |
30 | publishTo := Some(if (isSnapshot.value) Opts.resolver.sonatypeSnapshots else Opts.resolver.sonatypeStaging)
31 |
32 | pomExtra := (
33 |
34 | git@github.com:non/sortilege.git
35 | scm:git:git@github.com:non/sortilege.git
36 |
37 |
38 |
39 | d_m
40 | Erik Osheim
41 | http://github.com/non/
42 |
43 |
44 | )
45 |
46 | releaseProcess := Seq[ReleaseStep](
47 | checkSnapshotDependencies,
48 | inquireVersions,
49 | runClean,
50 | runTest,
51 | setReleaseVersion,
52 | commitReleaseVersion,
53 | tagRelease,
54 | publishArtifacts,
55 | setNextVersion,
56 | commitNextVersion,
57 | releaseStepCommand("sonatypeReleaseAll"),
58 | pushChanges)
59 |
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.0.3
2 |
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0")
2 | addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.7")
3 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.0")
4 |
--------------------------------------------------------------------------------
/src/main/scala/sortilege/eightball.scala:
--------------------------------------------------------------------------------
1 | package sortilege
2 |
3 | import spire.math.Trilean
4 | import spire.math.Trilean.{True, False, Unknown}
5 | import spire.implicits._
6 | import spire.random.Generator
7 |
8 | object Eightball {
9 |
10 | case class Phrase(msg: String, value: Trilean)
11 |
12 | def random(implicit rng: Generator): Phrase =
13 | Phrases.qchoose
14 |
15 | val Phrases: Vector[Phrase] = Vector(
16 | Phrase("it is certain", True),
17 | Phrase("it is decidedly so", True),
18 | Phrase("without a doubt", True),
19 | Phrase("yes definitely", True),
20 | Phrase("you may rely on it", True),
21 | Phrase("as i see it, yes", True),
22 | Phrase("most likely", True),
23 | Phrase("outlook good", True),
24 | Phrase("yes", True),
25 | Phrase("signs point to yes", True),
26 | Phrase("reply hazy try again", Unknown),
27 | Phrase("ask again later", Unknown),
28 | Phrase("better not tell you now", Unknown),
29 | Phrase("cannot predict now", Unknown),
30 | Phrase("concentrate and ask again", Unknown),
31 | Phrase("don't count on it", False),
32 | Phrase("my reply is no", False),
33 | Phrase("my sources say no", False),
34 | Phrase("outlook not so good", False),
35 | Phrase("very doubtful", False))
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/scala/sortilege/iching.scala:
--------------------------------------------------------------------------------
1 | package sortilege
2 |
3 | import spire.random.Generator
4 |
5 | object IChing {
6 |
7 | sealed trait Reading {
8 | def display: String =
9 | this match {
10 | case Unchanging(h) => h.display
11 | case Changing(b, a) => s"${b.display} becoming ${a.display}"
12 | }
13 | }
14 |
15 | case class Unchanging(h: Hexagram) extends Reading
16 | case class Changing(b: Hexagram, a: Hexagram) extends Reading
17 |
18 | object Reading {
19 | def apply(b: Hexagram, a: Hexagram): Reading =
20 | if (b == a) Unchanging(b) else Changing(b, a)
21 | }
22 |
23 | case class Hexagram(name: String, num: Int, repr: Int) {
24 | def glyph: Char = ('\u4DBF' + num).toChar
25 | def display: String = s"'$name' $glyph"
26 | def lines: Lines = Lines(repr)
27 | }
28 |
29 | case class Lines(s1: Line, s2: Line, s3: Line, s4: Line, s5: Line, s6: Line)
30 |
31 | object Lines {
32 | def apply(repr: Int): Lines = {
33 | def f(i: Int): Line = if (((repr >>> i) & 0xf) == 0) Broken else Whole
34 | Lines(f(20), f(16), f(12), f(8), f(4), f(0))
35 | }
36 | }
37 |
38 | sealed trait Line
39 | case object Whole extends Line
40 | case object Broken extends Line
41 |
42 | def yarrow(implicit gen: Generator): Reading = {
43 | def loop(r: Int, i: Int, b: Int, a: Int): Reading =
44 | if (i >= 6) Reading(lookup(b), lookup(a)) else {
45 | val k = 1 << (i * 4)
46 | val x = r & 0xf
47 | val s = r >>> 4
48 | if (x < 1) loop(s, i + 1, b, a | k)
49 | else if (x < 4) loop(s, i + 1, b | k, a)
50 | else if (x < 9) loop(s, i + 1, b | k, a | k)
51 | else loop(s, i + 1, b, a)
52 | }
53 | loop(gen.nextInt(), 0, 0, 0)
54 | }
55 |
56 | def coins(implicit gen: Generator): Reading = {
57 | def loop(r: Int, i: Int, b: Int, a: Int): Reading =
58 | if (i >= 6) Reading(lookup(b), lookup(a)) else {
59 | val k = 1 << (i * 4)
60 | val x = r & 0x7
61 | val s = r >>> 3
62 | if (x < 1) loop(s, i + 1, b, a | k)
63 | else if (x < 2) loop(s, i + 1, b | k, a)
64 | else if (x < 5) loop(s, i + 1, b | k, a | k)
65 | else loop(s, i + 1, b, a)
66 | }
67 | loop(gen.nextInt(), 0, 0, 0)
68 | }
69 |
70 | def lookup(repr: Int): Hexagram =
71 | Hexagrams(Table(repr) - 1)
72 |
73 | def random(implicit gen: Generator): Hexagram =
74 | Hexagrams(gen.nextInt(64) + 1)
75 |
76 | val Hexagrams: Vector[Hexagram] = Vector(
77 | Hexagram("the creative heaven", 1, 0x111111),
78 | Hexagram("the receptive earth", 2, 0x000000),
79 | Hexagram("difficulty at the beginning", 3, 0x010001),
80 | Hexagram("youthful folly", 4, 0x100010),
81 | Hexagram("waiting", 5, 0x010111),
82 | Hexagram("conflict", 6, 0x111010),
83 | Hexagram("the army", 7, 0x000010),
84 | Hexagram("holding together", 8, 0x010000),
85 | Hexagram("small taming", 9, 0x110111),
86 | Hexagram("treading", 10, 0x111011),
87 | Hexagram("peace", 11, 0x000111),
88 | Hexagram("standstill", 12, 0x111000),
89 | Hexagram("fellowship", 13, 0x111101),
90 | Hexagram("great possession", 14, 0x101111),
91 | Hexagram("modesty", 15, 0x000100),
92 | Hexagram("enthusiasm", 16, 0x001000),
93 | Hexagram("following", 17, 0x011001),
94 | Hexagram("work on the decayed", 18, 0x100110),
95 | Hexagram("approach", 19, 0x000011),
96 | Hexagram("contemplation", 20, 0x110000),
97 | Hexagram("biting through", 21, 0x101001),
98 | Hexagram("grace", 22, 0x100101),
99 | Hexagram("splitting apart", 23, 0x100000),
100 | Hexagram("return", 24, 0x000001),
101 | Hexagram("innocence", 25, 0x111001),
102 | Hexagram("great taming", 26, 0x100111),
103 | Hexagram("mouth corners", 27, 0x100001),
104 | Hexagram("great preponderance", 28, 0x011110),
105 | Hexagram("the abysmal water", 29, 0x010010),
106 | Hexagram("the clinging fire", 30, 0x101101),
107 | Hexagram("influence", 31, 0x011100),
108 | Hexagram("duration", 32, 0x001110),
109 | Hexagram("retreat", 33, 0x111100),
110 | Hexagram("great power", 34, 0x001111),
111 | Hexagram("progress", 35, 0x101000),
112 | Hexagram("darkening of the light", 36, 0x000101),
113 | Hexagram("the family", 37, 0x110101),
114 | Hexagram("opposition", 38, 0x101011),
115 | Hexagram("obstruction", 39, 0x010100),
116 | Hexagram("deliverance", 40, 0x001010),
117 | Hexagram("decrease", 41, 0x100011),
118 | Hexagram("increase", 42, 0x110001),
119 | Hexagram("breakthrough", 43, 0x011111),
120 | Hexagram("coming to meet", 44, 0x111110),
121 | Hexagram("gathering together", 45, 0x011000),
122 | Hexagram("pushing upward", 46, 0x000110),
123 | Hexagram("oppression", 47, 0x011010),
124 | Hexagram("the well", 48, 0x010110),
125 | Hexagram("revolution", 49, 0x011101),
126 | Hexagram("the cauldron", 50, 0x101110),
127 | Hexagram("the arousing thunder", 51, 0x001001),
128 | Hexagram("the keeping still mountain", 52, 0x100100),
129 | Hexagram("development", 53, 0x110100),
130 | Hexagram("the marrying maiden", 54, 0x001011),
131 | Hexagram("abundance", 55, 0x001101),
132 | Hexagram("the wanderer", 56, 0x101100),
133 | Hexagram("the gentle wind", 57, 0x110110),
134 | Hexagram("the joyous lake", 58, 0x011011),
135 | Hexagram("dispersion",59, 0x110010 ),
136 | Hexagram("limitation", 60, 0x010011),
137 | Hexagram("inner truth", 61, 0x110011),
138 | Hexagram("small preponderance", 62, 0x001100),
139 | Hexagram("after completion", 63, 0x010101),
140 | Hexagram("before completion", 64, 0x101010))
141 |
142 | val Table: Map[Int, Int] =
143 | Hexagrams.iterator.map(h => (h.repr, h.num)).toMap
144 | }
145 |
--------------------------------------------------------------------------------
/src/main/scala/sortilege/ogham.scala:
--------------------------------------------------------------------------------
1 | package sortilege.ogham
2 |
3 | import spire.implicits._
4 | import spire.random.Generator
5 |
6 | object Ogham {
7 |
8 | sealed abstract class Feda(val name: String, val glyph: Char, val meaning: String) {
9 | def display: String = s"$name $glyph"
10 | }
11 |
12 | //Aicme Beithe
13 | case object Beith extends Feda("Beith", 'ᚁ', "birch")
14 | case object Luis extends Feda("Luis", 'ᚂ', "rowan")
15 | case object Fearn extends Feda("Fearn", 'ᚃ', "alder")
16 | case object Sail extends Feda("Sail", 'ᚄ', "willow")
17 | case object Nion extends Feda("Nion", 'ᚅ', "ash")
18 |
19 | //Aicme hÚatha
20 | case object Uath extends Feda("Uath", 'ᚆ', "hawthorn")
21 | case object Dair extends Feda("Dair", 'ᚇ', "oak")
22 | case object Tinne extends Feda("Tinne", 'ᚈ', "holly")
23 | case object Coll extends Feda("Coll", 'ᚉ', "hazel")
24 | case object Ceirt extends Feda("Ceirt", 'ᚊ', "apple")
25 |
26 | //Aicme Muine
27 | case object Muin extends Feda("Muin", 'ᚋ', "vine")
28 | case object Gort extends Feda("Gort", 'ᚌ', "ivy")
29 | case object NGeadal extends Feda("nGéadal", 'ᚍ', "reed")
30 | case object Straif extends Feda("Straif", 'ᚎ', "blackthorn")
31 | case object Ruis extends Feda("Ruis", 'ᚏ', "elder")
32 |
33 | //Aicme Ailme
34 | case object Ailm extends Feda("Ailm", 'ᚐ', "fir")
35 | case object Onn extends Feda("Onn", 'ᚑ', "gorse")
36 | case object Ur extends Feda("Úr", 'ᚒ', "heather")
37 | case object Eadhadh extends Feda("Eadhadh", 'ᚓ', "poplar")
38 | case object Iodhadh extends Feda("Iodhadh", 'ᚔ', "yew")
39 |
40 | //Forfeda
41 | case object Eabhadh extends Feda("Éabhadh", 'ᚕ', "aspen")
42 | case object Or extends Feda("Ór", 'ᚖ', "spindle tree")
43 | case object Uilleann extends Feda("Uilleann", 'ᚗ', "honeysuckle")
44 | case object Ifin extends Feda("Ifín", 'ᚘ', "gooseberry")
45 | case object Eamhancholl extends Feda("Eamhancholl", 'ᚙ', "twin of hazel")
46 |
47 | val AicmeBeithe: Vector[Feda] =
48 | Vector(Beith, Luis, Fearn, Sail, Nion)
49 |
50 | val AicmeHuatha: Vector[Feda] =
51 | Vector(Uath, Dair, Tinne, Coll, Ceirt)
52 |
53 | val AicmeMuine: Vector[Feda] =
54 | Vector(Muin, Gort, NGeadal, Straif, Ruis)
55 |
56 | val AicmeAilme: Vector[Feda] =
57 | Vector(Ailm, Onn, Ur, Eadhadh, Iodhadh)
58 |
59 | val Aicmi: Vector[Vector[Feda]] =
60 | Vector(AicmeBeithe, AicmeHuatha, AicmeMuine, AicmeAilme)
61 |
62 | val Forfeda: Vector[Feda] =
63 | Vector(Eabhadh, Or, Uilleann, Ifin, Eamhancholl)
64 |
65 | val Standard: Vector[Feda] =
66 | AicmeBeithe ++ AicmeHuatha ++ AicmeMuine ++ AicmeAilme
67 |
68 | val Extended: Vector[Feda] =
69 | Standard ++ Forfeda
70 |
71 | def random(implicit gen: Generator): Feda =
72 | Standard.qchoose
73 |
74 | def choose(n: Int)(implicit gen: Generator): Vector[Feda] =
75 | Standard.qshuffled.take(n)
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/src/main/scala/sortilege/runes.scala:
--------------------------------------------------------------------------------
1 | package sortilege.runes
2 |
3 | import spire.implicits._
4 | import spire.random.Generator
5 |
6 | object Runes {
7 |
8 | sealed abstract class Rune(val name: String, val glyph: Char, val sound: Char, val meaning: String) {
9 | def display: String = s"$name $glyph"
10 | }
11 |
12 | case object Fehu extends Rune("fehu", 'ᚠ', 'f', "cattle")
13 | case object Uruz extends Rune("uruz", 'ᚢ', 'u', "auroch")
14 | case object Thurisaz extends Rune("thursiaz", 'ᚦ', 'þ', "thorn")
15 | case object Ansuz extends Rune("ansuz", 'ᚨ', 'a', "mouth")
16 | case object Raido extends Rune("raido", 'ᚱ', 'r', "wheel")
17 | case object Kaunan extends Rune("kaunan", 'ᚲ', 'k', "torch")
18 | case object Gebo extends Rune("gebo", 'ᚷ', 'g', "gift")
19 | case object Wunjo extends Rune("wunjo", 'ᚹ', 'w', "joy")
20 | case object Hagalaz extends Rune("hagalaz", 'ᚺ', 'h', "hail")
21 | case object Naudiz extends Rune("naudiz", 'ᚾ', 'n', "need")
22 | case object Isaz extends Rune("isaz", 'ᛁ', 'i', "ice")
23 | case object Jera extends Rune ("jera", 'ᛃ', 'j', "harvest")
24 | case object Eiwaz extends Rune("eiwaz", 'ᛇ', 'æ', "yew")
25 | case object Perth extends Rune("perth", 'ᛈ', 'p', "cup")
26 | case object Algiz extends Rune("algiz", 'ᛉ', 'z', "elk")
27 | case object Sowilo extends Rune("sowilo", 'ᛊ', 's', "sun")
28 | case object Tiwaz extends Rune("tiwaz", 'ᛏ', 't', "creator")
29 | case object Berkanen extends Rune("berkanen", 'ᛒ', 'b', "birch")
30 | case object Ehwaz extends Rune("ehwaz", 'ᛖ', 'e', "horse")
31 | case object Mannaz extends Rune("mannaz", 'ᛗ', 'm', "human")
32 | case object Laguz extends Rune("laguz", 'ᛚ', 'l', "water")
33 | case object Ingwaz extends Rune("ingwaz", 'ᛝ', 'ŋ', "fertility")
34 | case object Othila extends Rune("othila", 'ᛟ', 'o', "home")
35 | case object Dagaz extends Rune("dagaz", 'ᛞ', 'd', "day")
36 |
37 | val Aett1: Vector[Rune] =
38 | Vector(Fehu, Uruz, Thurisaz, Ansuz, Raido, Kaunan, Gebo, Wunjo)
39 |
40 | val Aett2: Vector[Rune] =
41 | Vector(Hagalaz, Naudiz, Isaz, Jera, Eiwaz, Perth, Algiz, Sowilo)
42 |
43 | val Aett3: Vector[Rune] =
44 | Vector(Tiwaz, Berkanen, Ehwaz, Mannaz, Laguz, Ingwaz, Othila, Dagaz)
45 |
46 | val Aetts: Vector[Vector[Rune]] =
47 | Vector(Aett1, Aett2, Aett3)
48 |
49 | val All: Vector[Rune] =
50 | Aett1 ++ Aett2 ++ Aett3
51 |
52 | def random(implicit gen: Generator): Rune =
53 | All.qchoose
54 |
55 | def choose(n: Int)(implicit gen: Generator): Vector[Rune] =
56 | All.qshuffled.take(n)
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/scala/sortilege/tarot.scala:
--------------------------------------------------------------------------------
1 | package sortilege.tarot
2 |
3 | import spire.implicits._
4 | import spire.random.Generator
5 |
6 | object Tarot {
7 |
8 | sealed abstract class Card {
9 | def display: String
10 | }
11 |
12 | case class Minor(suit: Suit, rank: Rank) extends Card {
13 | def display: String = s"${rank.name} of ${suit.name}"
14 | }
15 |
16 | sealed abstract class Major(val num: Int, val name: String) extends Card {
17 | def display: String = name
18 | }
19 |
20 | sealed abstract class Suit(val name: String)
21 | case object Cups extends Suit("cups")
22 | case object Wands extends Suit("wands")
23 | case object Swords extends Suit("swords")
24 | case object Pentacles extends Suit("pentacles")
25 |
26 | sealed abstract class Rank(val value: Int, val name: String)
27 | case object Ace extends Rank(1, "ace")
28 | case object Two extends Rank(2, "two")
29 | case object Three extends Rank(3, "three")
30 | case object Four extends Rank(4, "four")
31 | case object Five extends Rank(5, "five")
32 | case object Six extends Rank(6, "six")
33 | case object Seven extends Rank(7, "seven")
34 | case object Eight extends Rank(8, "eight")
35 | case object Nine extends Rank(9, "nine")
36 | case object Ten extends Rank(10, "ten")
37 | case object Page extends Rank(11, "page")
38 | case object Knight extends Rank(12, "knight")
39 | case object Queen extends Rank(13, "queen")
40 | case object King extends Rank(14, "king")
41 |
42 | case object TheFool extends Major(0, "the fool")
43 | case object TheMagician extends Major(1, "the magician")
44 | case object TheHighPriestess extends Major(2, "the high priestess")
45 | case object TheEmpress extends Major(3, "the empress")
46 | case object TheEmperor extends Major(4, "the emperor")
47 | case object TheHierophant extends Major(5, "the hierophant")
48 | case object TheLovers extends Major(6, "the lovers")
49 | case object TheChariot extends Major(7, "the chariot")
50 | case object Justice extends Major(8, "justice")
51 | case object TheHermit extends Major(9, "the hermit")
52 | case object WheelOfFortune extends Major(10, "wheel of fortune")
53 | case object Strength extends Major(11, "strength")
54 | case object TheHangedMan extends Major(12, "the hanged man")
55 | case object Death extends Major(13, "death")
56 | case object Temperance extends Major(14, "temperance")
57 | case object TheDevil extends Major(15, "the devil")
58 | case object TheTower extends Major(16, "the tower")
59 | case object TheStar extends Major(17, "the star")
60 | case object TheMoon extends Major(18, "the moon")
61 | case object TheSun extends Major(19, "the sun")
62 | case object Judgement extends Major(20, "judgement")
63 | case object TheWorld extends Major(21, "the world")
64 |
65 | sealed trait Draw {
66 | def display: String = this match {
67 | case Upright(c) => c.display
68 | case Inverted(c) => s"${c.display} (inverted)"
69 | }
70 | }
71 |
72 | case class Upright(card: Card) extends Draw
73 | case class Inverted(card: Card) extends Draw
74 |
75 | object Draw {
76 | def apply(card: Card)(implicit gen: Generator): Draw =
77 | if (gen.nextBoolean) Upright(card) else Inverted(card)
78 | }
79 |
80 | case class ThreeCard(past: Draw, present: Draw, future: Draw) {
81 | def display: String =
82 | s"""1. past: ${past.display}
83 | 2. present: ${present.display}
84 | 3. future: ${future.display}
85 | """
86 | }
87 |
88 | case class Horseshoe(present: Draw, desires: Draw, unexpected: Draw, future: Draw, outcome: Draw) {
89 | def display: String =
90 | s"""1. present: ${present.display}
91 | 2. desires: ${desires.display}
92 | 3. unexpected: ${unexpected.display}
93 | 4. future: ${future.display}
94 | 5. outcome: ${outcome.display}
95 | """
96 | }
97 |
98 | case class CelticCross(circle: Circle, staff: Staff) {
99 | def display: String =
100 | s""" 1. present: ${circle.present.display}
101 | 2. challenge: ${circle.challenge.display}
102 | 3. past: ${circle.past.display}
103 | 4. future: ${circle.future.display}
104 | 5. above: ${circle.above.display}
105 | 6. below: ${circle.below.display}
106 | --------
107 | 7. advice: ${staff.advice.display}
108 | 8. influences: ${staff.influences.display}
109 | 9. emotions: ${staff.emotions.display}
110 | 10. outcome: ${staff.outcome.display}
111 | """
112 | }
113 |
114 | case class Circle(present: Draw, challenge: Draw, past: Draw, future: Draw, above: Draw, below: Draw)
115 | case class Staff(advice: Draw, influences: Draw, emotions: Draw, outcome: Draw)
116 |
117 | val Suits: Vector[Suit] =
118 | Vector(Cups, Swords, Wands, Pentacles)
119 |
120 | val Ranks: Vector[Rank] =
121 | Vector(Ace, Two, Three, Four, Five, Six, Seven,
122 | Eight, Nine, Ten, Page, Knight, Queen, King)
123 |
124 | val MinorArcana: Vector[Minor] =
125 | for { s <- Suits; r <- Ranks } yield Minor(s, r)
126 |
127 | val MajorArcana: Vector[Major] =
128 | Vector(TheFool, TheMagician, TheHighPriestess, TheEmpress, TheEmperor,
129 | TheHierophant, TheLovers, TheChariot, Justice, TheHermit,
130 | WheelOfFortune, Strength, TheHangedMan, Death, Temperance, TheDevil,
131 | TheTower, TheStar, TheMoon, TheSun, Judgement, TheWorld)
132 |
133 | val Deck: Vector[Card] =
134 | MinorArcana ++ MajorArcana
135 |
136 | def shuffle(implicit gen: Generator): Vector[Card] =
137 | Deck.qshuffled
138 |
139 | def single(implicit gen: Generator): Draw =
140 | Draw(Deck.qchoose)
141 |
142 | def draw(n: Int)(implicit gen: Generator): Vector[Draw] =
143 | shuffle.take(n).map(Draw(_))
144 |
145 | def threeCard(implicit gen: Generator): ThreeCard = {
146 | val Vector(a, b, c) = draw(3)
147 | ThreeCard(a, b, c)
148 | }
149 |
150 | def horseshoe(implicit gen: Generator): Horseshoe = {
151 | val Vector(a, b, c, d, e) = draw(5)
152 | Horseshoe(a, b, c, d, e)
153 | }
154 |
155 | def celticCross(implicit gen: Generator): CelticCross = {
156 | val Vector(a, b, c, d, e, f, g, h, i, j) = draw(10)
157 | CelticCross(Circle(a, b, c, d, e, f), Staff(g, h, i, j))
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/version.sbt:
--------------------------------------------------------------------------------
1 | version in ThisBuild := "0.4.1-SNAPSHOT"
2 |
--------------------------------------------------------------------------------