├── .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 | --------------------------------------------------------------------------------