Pen & Paper - Rollenspiel > Werkstatt

Covella - Fantasy-Kalender - DSL für beliebige Systeme von Zeitmessung

<< < (2/17) > >>

Talwyn:
Sehr fleißig 1of3, das sieht schonmal super aus :). Ich würde es auch in einem vernünftigen Rahmen halten und Dinge wie Zeitreisen, Kalenderwechsel usw. ausschließen. Was man allerdings bräuchte, wäre eine Erweiterung, um Dinge wie Schaltjahre und Feiertage abbilden zu können am Beispiel des Calendar of Harptos (Forgotten Realms):


--- Code: ---val tenday = Days 10 as "Tenday"

val hammer = Days 30 as "month" named "Hammer" aka "Deepwinter"
val midwinter = Days 1 as "holiday" named "Midwinter"
val alturiak = Days 30 as "month" named "Alturiak" aka "The Claw of Winter" or "The Claws of the Cold"
val ches = Days 30 as "month" named "Ches" aka "The Claw of Sunsets" with event "Spring Equinox" on 19
val tarsakh = Days 30 as "month" named "Tarsakh" aka "The Claw of Storms"
val greengrass = Days 1 as "holiday" named "Greengrass"
val mirtul = Days 30 as "month" named "Mirtul" aka "The Melting"
val kythorn = Days 30 as "month" named "Kythorn" aka "The Time of Flowers" with event "Summer Solstice" on 20
val flamerule = Days 30 as "month" named "Flamerule" aka "Summertide"
val midsummer = Days 1 as "holiday" named "Midsummer"
val shieldmeet = Days 1 as "holiday" named "Shieldmeet" occurring every 4 years startingAt 0
val eleasis = Days 30 as "month" named "Eleasis" aka "Highsun"
val eleint = Days 30 as "month" named "Eleint" aka "The Fading" with event "Autumn Equinox" on 21
val highharvesttide = Days 1 as "holiday" named "Highharvestide"
val marpenoth = Days 30 as "month" named "Marpenoth" aka "Leafall"
val uktar = Days 30 as "month" named "Uktar" aka "The Rotting"
val feastOfTheMoon = Days 1 as "holiday" named "Feast of the Moon"
val Nightal = Days 30 as "month" named "Nightal" aka "The Drawing Down" with event "Winter Solstice" on 20

val calendarOfHarptos = harptos // an dieser Stelle äußerst einfach, weil im Kalender des Harptos jedes Jahr immer mit einem frischen Zehntag beginnt.

--- Ende Code ---

Dementsprechend müsste man die Klassen erweitern, um diese Konstrukte abbilden zu können. Mal sehen, evtl. komme ich am Wochenende dazu was zu machen.

1of3:

--- Zitat ---named "Alturiak" aka "The Claw of Winter"
--- Ende Zitat ---

Vielleicht einfacher: named ("Alturiak", "The Claw of Winter") Dann einfach ne Seq von Namen. Man kann natürlich aka als Alias verwenden, um Namen anzufügen.

Wie man schon jetzt Schaltjahre machen kann, hab ich ja beim julianischen Kalender gezeigt: Einfach verschiedene Jahre anlegen und addieren.

Eine Auszeichnung für besondere Ereignisse und Feiertage ist eine gute Idee. Schön wäre, wenn man relativ leicht verschiedene Zyklen schneiden kann, also im Stile von: Wenn ein Freitag der 13. eines Monats ist. Das ist bei ganz vielen Kulturen eine wichtige Methode für Tagesauszeichnungen.

Ich hab noch ein bisschen experimentiert. Ich würde noch erzwingen, dass Elemente eine Designation haben müssen, bevor man sie komponieren kann, also alles, was man addieren will muss vorher ein "as" bekommen. Dazu also gemäß Immutable Builder Pattern die Klasse ändern. Ich hab das noch mal gemacht und ein paar Bugs entfernt.

(Klicke zum Anzeigen/Verstecken)
--- Code: ---abstract class Days {
  def number : Int
  var name: Option[String] = None
  def named(nam : String) = name = Some(nam)

  def as(designation : String) = QualifiedDays(this,designation)
}

case class QualifiedDays(days: Days, designation: String) extends Days{
  def + (that : QualifiedDays) = CompoundDays(ArrayBuffer(this,that))

  def number = days.number
    override def named(nam : String) = days.name = Some(nam)

  def repeatWithNames(names : Seq[String]) : CompoundDays = {
    val output = for (i <- 0 to names.length) yield QualifiedDays(days, designation)
    for (i <- 0 to names.length) output(i) named names(i)
    CompoundDays(ArrayBuffer(output).flatten)
  }
}

case class CompoundDays (comps : ArrayBuffer[QualifiedDays]) extends Days {
  def number = comps map (_ number) sum

  def + (that: QualifiedDays) = comps += that

  override def as(des: String): QualifiedDays = if ( comps.map(_.designation).contains(des) )
    throw new IllegalArgumentException("Designator '" + des + "' of CompoundDays is the same as designator of component.")
            else super.as(des)
}
--- Ende Code ---

firstdeathmaker:
Wie sähe das Kalendersystem eigentlich bei parallel laufenden Kalendern aus? Also z.B. wenn ich einen chinesischen und einen westlichen habe und ein Datum umrechnen möchte?

Wenn ich richtig gesehen hab, dann könnte man damit ja sogar Zeitalter realisieren und diese aneinander hängen. Aber was ist mit unterschiedlich schnell vergehenden Tagen? Z.b. Marstage und Erdtage?

1of3:
Ich hab für das Paket "Tag" als Referenzgröße gewählt. Unterschiedliche Tageslängen von Planeten sind so schwierig. Mehrere Kalender in ein System zu packen ist aber möglich.

Generell bräuchte es auf einem Datum eine Methode


--- Code: ---def get(designator: String) = ???
--- Ende Code ---

Da kann man also problemlos get("Westliches Jahr") und get("Chinesisches Jahr"). Ich hadere momentan ein bisschen mit der Implementierung, aber das ist der Plan.


Edit: Ich muss den ersten Satz nach etwas nachdenken übrigens korrigieren. Nur weil ich die Objekte "Days" genannt habe und als "Standard-Tick" einen Tag im Kopf hatte, heißt das natürlich nicht, dass man nicht genauso gut von "Minutes" oder "Nanoseconds" ausgehen könnte. Ggf. möchte man dann ein Type-Alias ("type Minutes = Days") schreiben oder auch zwei, damit die Sachen nicht so merkwürdig heißen, aber die Funktionalität bleibt ja die selbe.


EditEdit: So. Getter funktioniert eingschränkt.
- Nicht bei negativen Daten oder Datum 0. Hier müsste man die Zyklen rückwärts durchlaufen.
- Nur beim primären Zyklus. Synchronisieren mit weiteren Zyklen geht noch nicht.
- Die Jahreszahl wird noch nicht angezeigt.

Hier einmal der vollständige Code bis jetzt.

(Klicke zum Anzeigen/Verstecken)
--- Code: ---import scala.collection.mutable.ArrayBuffer


case class Era (calendar: Calendar, startingCount: Int) {
  val primaryCycle = calendar.primaryCycle

  val primaryCounter : String = calendar.primaryCounter.
          getOrElse(if (primaryCycle.isInstanceOf[QualifiedDays]) primaryCycle.asInstanceOf[QualifiedDays].designation
                    else {
                     val designationSet = primaryCycle.asInstanceOf[CompoundDays].children.map(_.designation).toSet
                     if (designationSet.size == 1) designationSet.head
                     else throw new IllegalArgumentException("Could not determine primary counter for Era.")
                    } )


  var synchronizedCycles: Set[Days] = Set() // Lieber Kalender? Oder beides? Wie kommt die Synchronisation da rein?

  def synchronizeAt (days: Days, at: Int) = ???

  def synchronize (days: Days) = SynchronisationTemplate(this,days)

  def apply(i: BigInt) = {
    val remainder = i % primaryCycle.number
    if (primaryCycle.isInstanceOf[QualifiedDays]) primaryCycle.asInstanceOf[QualifiedDays](remainder)
    else (primaryCycle as "")(remainder)
  }

 // def round = (synchronizedCycles + primaryCycle) map(_ number) foldLeft(1,lcm (_ ,_))
 // private def gcd(a: Int, b: Int): Int = if (b == 0) a.abs else gcd(b, a % b)
 // private def lcm(a: Int, b: Int) = (a * b).abs / gcd(a, b)

}


case class SynchronisationTemplate(era: Era, days: Days){
  def at(int: Int) = era.synchronizeAt(days,int)

}

case class Calendar (primaryCycle: Days) {

var primaryCounter : Option[String] = None
def countIn(designation: String) = primaryCounter = Some(designation)

def startIn(startingCount: Int) = Era(this,startingCount)



}





case class Datum(day: BigInt)(implicit era: Era){

def get(designator: String): Option[(Days,Option[Int])] =
          try { Some(era(day).map {case (string,days,optionInt ) => string -> (days,optionInt) }.toMap.apply(designator)) }
          catch { case e: Exception => None }


def indexOf (designator: String) = IndexTemplate(designator,this)

}

case class IndexTemplate(designator: String, datum: Datum){

  def in(designator: String) : Int = ???

}


case class EventTemplate(eventName: String, in: Days){
  def on (i: Int) = if (i <= in.number && i>=0) in.events = in.events ++ Seq(eventName -> i)
                    else throw new IllegalArgumentException("Attempted to add event '" +
                              eventName + "' on day'" + i+ "of " + in.number + " days." )

}


abstract class Days {
  def number : Int
  var name: Option[String] = None
  def named(nam : String) :Days = {name = Some(nam); this}
  var events : Seq[(String,Int)] = Seq()
  def hasChildren : Boolean

  def event (eventName: String) = EventTemplate(eventName, this)
  def as(designation : String) = QualifiedDays(this,designation)

  override def toString: String = name.getOrElse("") + "(" + number + " days)"

}


case class QualifiedDays(days: Days, designation: String) extends Days{
  def + (that : QualifiedDays) = CompoundDays(ArrayBuffer(this,that))

  def number = days.number
    override def named(nam : String) = {days.name = Some(nam); this}

  def repeatWithNames(names : Seq[String]) : CompoundDays = {
    val output = for (i <- 0 to names.length) yield QualifiedDays(days, designation)
    for (i <- 0 to names.length) output(i) named names(i)
    CompoundDays(ArrayBuffer(output).flatten)
  }

  override def hasChildren: Boolean = days.hasChildren

  override def toString: String = designation + ": " + days

  def apply(i : BigInt) : Seq[(String ,Days,Option[Int])] = if (i > number || i <1)
            throw  new IllegalArgumentException("Tried to retrieve '" + i
              + "' from a set of '" + number + "' units designated as " + designation + "." )
    else if (!hasChildren) Seq( (designation, days, None ) )
    else Seq( (designation, days, None ) ) ++ followPath(days.asInstanceOf[CompoundDays],i)


    def followPath(compoundDays : CompoundDays, newI : BigInt) : Seq[(String ,Days,Option[Int])] = {
       val (qdays, index, remaining) =compoundDays.getChildfor(newI)
       if (!qdays.days.hasChildren) Seq((qdays.designation, qdays.days, Some(index)))
       else Seq((qdays.designation, qdays.days, Some(index))) ++ followPath(qdays.days.asInstanceOf[CompoundDays],remaining)
    }

}



case class CompoundDays (children : ArrayBuffer[QualifiedDays]) extends Days {
  def number = children map (_ number) sum

  override def hasChildren: Boolean = if (children.isEmpty) false else true

  def +(that: QualifiedDays) = {children += that; this}

  override def as(des: String): QualifiedDays = if (children.map(_.designation).contains(des))
    throw new IllegalArgumentException("Designator '" + des + "' of CompoundDays is the same as designator of component.")
  else super.as(des)



  def getChildfor(i: BigInt): (QualifiedDays, Int, BigInt) = {
    def selectChild(newI: BigInt, offset: Int): (QualifiedDays, Int, BigInt) = {
      if (newI <= children(offset).number) (children(offset),offset,newI)
      else selectChild(newI-children(offset).number,offset+1)
    }
    if (!hasChildren) throw new IllegalStateException("Compound has no Children") else selectChild(i, 0)
  }

}


  case class SimpleDays(number: Int) extends Days{
    override val hasChildren: Boolean = false


  }

  object Days{
    def apply(number : Int) = SimpleDays(number)
    def apply(names: String*) = SimpleDays(1) as "" repeatWithNames names
  }


object Main extends App {

  val januar = Days(31) as "Monat"  named "Januar"
  val februar = Days(28) as "Monat" named "Februar"
  val schaltFebruar = Days(29) as "Monat" named "Februar"
  val maerz = Days(31) as "Monat" named "März"
  val april = Days(30) as "Monat" named "April"
  val mai = Days(31) as "Monat" named "Mai"
  val juni = Days(30) as "Monat" named "Juni"
  val juli = Days(31) as "Monat" named "Juli"
  val august = Days(31) as "Monat" named "August"
  val september = Days(30) as "Monat" named "September"
  val oktober = Days(31) as "Monat" named "Oktober"
  val november = Days(30) as "Monat" named "November"
  val dezember = Days(31) as "Monat" named "Dezember"

   val jahr = (januar + februar  + maerz + april + mai + juni + juli + august + september + oktober + november + dezember)  as "Jahr"
  val schaltjahr =  (januar + schaltFebruar+ maerz + april + mai + juni + juli + august + september + oktober + november + dezember) as "Jahr"

  implicit val julianisch = Calendar(jahr+jahr+jahr+schaltjahr) startIn(1970)

  val testdatum = Datum(361)

  println((testdatum get "Monat") + "\n" +  (testdatum get "blörz") )

}
--- Ende Code ---

1of3:
So. Einige weitere Gedanken. firstdeathmakers Hinweis, auch kleinere Zeiteinheiten zu unterstützen ist gut. Ich hab die Klassen ein wenig umbenannt. Statt "Days" heißt es "Units" und die kleinste Einheit ist "Tick".

Spätestens damit ergeben sich für die Abwicklung verschiedene Konsequenzen. Die Builder-Objekte für die DSL sind zwar sehr nett, aber in der Verwendung sind sie enorm unhandlich. Zudem instanziieren wir sehr viele davon, was nicht günstig ist. Im Betrieb braucht es also eine einfachere Datenstruktur.

In der Vewendung braucht es etwa folgende Fragestellungen:

- Liebe $Zeiteinheit, die wievielte bist du in $AndererZeiteinheit?
- Liebes $Datum, was ist deine Standarddarstellung?
- Liebes $Datum, in welcher Ausgabe von $Zeiteinheit liegst du?
- Liebes $Zeitintervall, ich hätte gern ein Ereignis auf dir.
- Liebes $Datum, welche Ereignisse gibts bei dir?(1)

Ich hab mir mal ein paar Implementierungen angeguckt und die meisten schummeln. Einfach, weil sie z.B. wissen, dass dieses Ding namens November, der elfte Monat in dieser Sache namens Jahr ist. Die rechnen nicht, die lesen ab.

Was kann man machen:

Eine Era merkt sich zunächst mal nicht die Hierarchie der Builder-Objekte, sondern eine Klassifikation von Designationen, also Zeiteinheiten.

Besonders angenehm sind diejenigen, die sich uniform durchziehen. Die also erstens überall verfügbar und zweitens immer gleich lang sind. Bei uns z.B. Tage, Stunden und alles, was kürzer ist. Bei denen müssen wir nur wissen, wie lang sie sind.

Nicht ganz so hübsch, diejenigen, die zwar überall vorhanden, aber nicht immer gleich lang sind, z.B. Jahre und Monate.

Ganz hässlich sind diejenigen, die sporadisch vorkommen, also z.B. in Talwyns Harptos-Kalender die Monate und besonderen Feiertage, die sich abwechseln.

Bei jeder Designation sollte zudem stehen, worin sie standardmäßig gezählt wird. Wir zählen also Stunden für gewöhnlich in Bezug auf den laufenden Tag. Wir können uns natürlich, fragen wie viele Stunden seit Beginn des siderischen Monats, aber das ist nicht häufigste Anfrage. Also wenn ich ein Ding frage: "Das wie vielte bist du?", sollte es wissen, worin es standardmäßig gezählt wird.

Was alternative Namen angeht, wie Talwyn vorgeschlagen hat, würde ich eine Zuordnung (Map/Dictionary) benutzen, also z.B.


--- Zitat ---val alturiak = Days 30 as "month" named "Alturiak" aka "poetic"->"The Claw of Winter" aka "poetic2"->"The Claws of the Cold"
--- Ende Zitat ---

Entsprechend kann man dann beliebig viele Varianten jeweils mit Bezeichner anfügen. Wir würden wahrscheinlich sowas wie
"short"->"Jan" für Januar wollen.

Für Ereignisse schwebt mir folgende Syntax vor:


--- Code: ---event $eventName at $datum1 till $datum2 per $Designation given $condition §direction
--- Ende Code ---

Je nachdem, auf welchem Objekt man "event" aufruft können z.B. Anfang und Ende auch schon gesetzt sein. Direction gibts in drei Varianten: forewards, backwards und foreAndBack. Letzteres ist Standard, wenn es sich nicht um einmaliges Ereignis handelt.

Navigation

[0] Themen-Index

[#] Nächste Seite

[*] Vorherige Sete

Zur normalen Ansicht wechseln