Eine Herausforderung hierbei wird es sein, unterschiedliche (fiktive) Kalender zu unterstützen.
case class CalendarSystem (cycle : Days*)
abstract class Days {
def number : Int}
case class SimpleDays(number: Int) extends Days{
def + (that: SimpleDays) = CompoundDays(List(this, that))
def + (those: CompoundDays) = CompoundDays(this + those.comps)
}
case class CompoundDays (comps : Seq[Days]) extends Days {
def number = comps map (_ number) sum
def + (that: SimpleDays) = CompoundDays(this.comps :+ that)
def + (those: CompoundDays) = CompoundDays(this.comps ++ those.comps)
}
abstract class Days {
def number : Int
var designation: Option[String] = None
var name: Option[String] = None
def as(des : String) designation = Some(des)
def named(nam : String) name = Some(nam)
}
object Days{
def apply(number : Int) = SimpleDays(number)
def apply(names: String*) = names map (SimpleDays(1) named _) foldLeft(Days.empty,_+_)
}
val woche = Days("Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag","Sonntag") as "Woche"
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"
julianisch = CalendarSystem (woche, jahr + jahr + jahr + schaltjahr)
val julianischerKalender = julianisch startWith ("Samstag", 1)
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.
named "Alturiak" aka "The Claw of Winter"
def get(designator: String) = ???
val alturiak = Days 30 as "month" named "Alturiak" aka "poetic"->"The Claw of Winter" aka "poetic2"->"The Claws of the Cold"
event $eventName at $datum1 till $datum2 per $Designation given $condition §direction
Hm, ich hätte das Ganze von der astronomischen Seite aufgezogen. Solare und lunare Zyklen (vielleicht mit mehreren Monden), Jahreszeiten, Feiertage, Sonnwenden und Tagundnachtgleichen, Aussaat und Ernte, wiederkehrende Wetterphänomene, darum geht es doch eigentlich, nicht darum, Tage runterzuzählen.
Ich bin mir nicht ganz sicher, was du mit Grafik meinst. Ich kann einen Kalender als Tabelle darstellen. Beschreib mal bitte, was du dir vorstellst..
Das ist dem Programm im Grunde Wumpe. Du kannst damit auch synodischen, siderischen und tropischen Monat für fünf Monde tracken, wenn das dein Ding ist. Oder das Erscheinen der Feenkönigen bei Hofe nebst dem Flackern des Artefakts von Schnackpuck und die Aussaatzeit von Yamok-Wurzeln. Muss ich nicht wissen, deshalb hab ich ja versucht eine recht eingängige Spezialsprache zu schreiben, damit man ohne viel Aufmachung ein System zur Zeitmessung schreiben kann.
Tage sind zwar sicherlich eine zu grobe Zeiteinheit für ein flexibles Kalendersystem, jedoch denke ich, dass in >95% aller klassichen RPG-Settings zumindest mal gilt, dass eine Stunde 60 Minuten und eine Minute 60 Sekunden hat. Selbst der 24-Stunden-Tag ist sehr weit verbreitet. Ich meine, dass diese Werte sich daher als sensible defaults anbieten - sie sind genau das, was in 95% zutrifft, von daher sollte es so einfach wie möglich sein, einen Kalender mit diesen Standardwerten zu erzeugen.
Events: Meist dürfte es sich hier um einen Tag - z.B. einen Feiertag handeln, insofern wäre es ein sensible default, von einer Dauer von einem Tag auszugehen. Ich denke auch, dass man standardmäßig annehmen kann, dass ein solcher Event sowohl vorwärts als auch rückwärts wiederholt werden sollte.
Ich frage mich gerade überhaupt ob sowas wie Ostern tatsächlich auf Kalender-API-Level verankert werden sollte. Im Grunde ist das ja ein Ereignis, das nur für einen bestimmten Kulturkreis gilt.
myCalendar = new Calendar based on system "Calendar of Harptos" with start date "13th Marpenoth 1485 DR"
advance myCalendar by 3 days
advance myCalendar by 4 hours and 30 minutes
rewind myCalendar by 1 year
myCalendar = {
system: 'Calendar of Harptos' ,
interntalValue: 189798297898L
}
textualRepresentation = myCalendar.format() // assuming standard format
assert that textualRepresentation equals "13th Marpenoth 1485 DR"
assert that myCalendar.getYear equals 1485
assert that myCalendar.getDayOfMonth equals 13 // start counting at 1, nobody wants to count days of month starting at 0
assert that myCalendar.getMonth equals 10 // start counting at 1, nobody wants to count month number starting at 0
assert that myCalendar.getMonthName equals "Marpenoth"
assert that myCalendar.getMonthAlias("poetic1") equals "Leafall"
assert that myCalendar.getYearName equals "Year of the Iron Dwarf's Vengeance"
myCalendar = {
system: 'Calendar of Harptos' ,
interntalValue: 189798297898L
}
year therein month 11 therein third monday
calendar = new Calendar("Calendar of Harptos")
myDate = calendar.getDate("13th Marpenoth 1485 DR")
// oder
myDate = calendar.getDate(1485, 10, 13)
myDate.[b]getYear[/b] equals 1485
november indexIn year // returns 10, weil Zählung fängt bei 0 an
myDate.getMonthName
myDate.getMonth.name ...
interval yearOfTheIronDwarfsVengeance = calendar getInterval for year 1485 // ==> [1485-01-01T00:00:00, 1486-01-01T00:00:00[
// oder aber:
interval yearOfTheIronDwarfsVengeance = myDate getInterval for unit year // ==> Same as above
Hm, ich hätte das Ganze von der astronomischen Seite aufgezogen. Solare und lunare Zyklen (vielleicht mit mehreren Monden), Jahreszeiten, Feiertage, Sonnwenden und Tagundnachtgleichen, Aussaat und Ernte, wiederkehrende Wetterphänomene, darum geht es doch eigentlich, nicht darum, Tage runterzuzählen.
Was ich mit Herausforderung bei den Kalendern meinte ist: Es sollte für Nicht-Programmierer möglich sein, auf relativ einfache Weise den Kalender ihrer Spielwelt so zu beschreiben, dass die App damit arbeiten kann.das ganze vermutlich etwas komplizieren könnte...
val month = (Days(6) as "week") *5 as "month"
val quartal= Days(1) as "extra day" + month *3
val standardYear = quartal *4 as "year"
val leapYear = (quartal * 4 + Days(1) as "leap day" ) as "year"
val fourYearCycle = standardYear * 3 + leapYear
val century = fourYearCycle * 24 + standardYear * 4
val millenium = century * 9 + fourYearCycle * 25
val meinKalender = Calendar ( millenium )
fehlt noch der Jahreswechsel. Also vielleicht so:Code: [Auswählen]val standardYear = quartal *4 as "year"
val leapYear = (quartal * 4 + Days(1) as "leap day" ) as "year"
val standardYear = quartal *4 as "year" + Days(1) as "turnOfTheYear"
val leapYear = (quartal * 4 + Days(1) as "leap day" ) as "year" + Days(1) as "turnOfTheYear"
val standardYear = (quartal *4 + Days(1) as "extra day") as "year"
val leapYear = (quartal * 4 + Days(1) as "extra day" + Days(1) as "leap day" ) as "year"
Das hat wenig mit Scala oder Java zu tun. Die Frage ist, wie die Funktionen geschrieben sind. ;)
date"""$number(day). $name(month) $number(year)"""
Dass man diesen Unterschied nicht sofort erkennt, ist eine Eigenschaft von DSL-freundlichen Sprachen: Keine Semikola, Funktionsaufrufe brauchen keine Klammern, Zugriff auf Eigenschaften und Methoden brauchen keine Punkte.
val leapYear = (quartal * 4 + Days(1) as "extra day" + Days(1) as "leap day" ) as "year"
könnte man z.B. leapYear = (quartal * 4 + Days(1) |as| "extra day" + Days(1) |as| "leap day" ) |as| "year"
machen und das wäre ein gültiger Python-Ausdruck den man durch den Python-Parser jagen könnte und bei passender Metaprogrammierung würde das Teil dann auch das gewünschte machen.var d = Date(2016, 10, 31)
d advanceBy (1, MONTH)
println(d.format) // 30.11.2016
var d = Date(2016, 2, 29)
d.advanceBy (1, YEAR)
println(d.format) // 28.02.2017
// Zeiteinheiten
var millis = Tick("millisecond") countFromZero
var seconds = Cycle from millis(1000) named "second" countFromZero
var minutes = Cycle from seconds(60) named "minute" countFromZero
var hours = Cycle from minutes(60) named "hour" countFromZero
var days= Cycle from hours(24) named "day"
var months= IrregularUnit("month")
var years = IrregularUnit("year")
// Ausprägungen unregelmäßiger Zeiteinheiten
val january = days(31) as month named "January" aka ("short","Jan")
val february = days(28) as month named "February" aka ("short","Feb")
val march = days(31) as month named "March" aka ("short","Mar")
val april = days(30) as month named "April" aka ("short","Apr")
val may = days(31) as month named "May" aka ("short","May")
val june = days(30) as month named "June" aka ("short","Jun")
val july = days(31) as month named "July" aka ("short","Jul")
val august = days(31) as month named "August" aka ("short","Aug")
val september = days(30) as month named "September" aka ("short","Sep")
val october = days(31) as month named "October" aka ("short","Oct")
val november = days(30) as month named "November" aka ("short","Nov")
val december = days(31) as month named "December" aka ("short","Dec")
val standardYear = january + february + march + april + may + june + july +
august + september + october + november + december as year
val leapFebruary = day(29) as month named "February"aka ("short","Feb")
val leapYear = january + leapFebruary + march + april + may + june + july +
august + september + october + november + december as year
val october1582 = october exclude (5 to 14)
val year1582 = january + february + march + april + may + june + july +
august + september + october1582 + november + december as year
// Wir bauen uns die Schaltregel und den Wechsel von Julianisch auf Gregorianisch
val westernEra =
Era given 1582 have year1582 // wenn eine Jahreszahl abgefragt wird, wir das 1. Passende geliefert
given (x => x > 1582 && x%100==0 && x%400!=0) have standardYear
given (_ % 4 == 0) have leapYear
rest standardYear
exclude 0 // Jahr 0 gibts nicht.
setEraNames ( x => Map( "default" -> (if (x>=0) x + " CE" else x + " BCE") ) ) // setzt die Jahresnamen
// Wir machen einen Kalender draus
val westernCalendar = Calendar(westernEra)
[1] Normaler heutiger benutzter Kalender
[2] Kalender, die verschiedene Zeitalter und darin unterschiedliche Zeitrechnungen berücksichtigen. z.B. im ersten Zeitalter 8-Tage Wochen, 50-70 Tage Monate), im zweiten Zeitalter 5-Tage Wochen etc.
[3]Lunare römische Kalender
[4]Maya-Kalender
[5]Weltenübergreifender Kalender (wie im oben geposteten Tool https://www.wizards.com/dnd/ec/index.htm) mit unterschiedlich schnell vergehender Zeit
[6]Nichtlinearer Zeitfluss Kalender (für Zeitreisenszenarios mit alternativen Realitäten)
[7]Nichtkonstanter Zeitfluss (Relativistischer Kalender für so etwas wie im Film "Interstellar")
Das man eine Übersicht des gebastelten Kalenders als JPG, BMP oder PNG exportieren kann um sie sich später auszudrucken, in Kampagnen-Handouts einzufügen oder in ein Wiki zu tun. So z.B.:
object MyFirstProgram extends App {
println("Hello World")
}
case class EberronCalendar(days : TimeUnit = CommonDays.days){
lazy val weeks = 'week of days withNames("Sul","Mol","Zol","Wir","Zor","Far","Sar")
lazy val months = 'month of (weeks, 4)
lazy val years = 'year of months withNames(
"Zarantyr","Olarune","Therendor",
"Eyre","Dravago","Nymm",
"Lharvion","Barrakas","Rhaan",
"Sypheros","Aryth","Vult"
)
lazy val solarCalendar = Calendar(years) setTimestampZero 998
// Name, Orbit, RemainsNotable, StartingPosition
private val planeData = Vector(
("Danvi",134400,33600,50400),
("Dolurrh",33600,336,8559),
("Fernia",1680,28,1344),
("Irian",1008,10,420),
("Lamannia",364,7,94),
("Mabar",1680,3,422),
("Risia",1680,28,505),
("Shavarath",12096,336,4032),
("Syrania",3360,1,841),
("Thelanis",75600,2352,20076),
("Xoriat",2352000,336,588168)
)
def planeNames = planeData.map(_._1)
lazy val planesFactory = Satellites.withPhases(("Remote","Waxing"),("Coterminous","Waning"))
/**
* The planes orbiting the world of Eberron as a CalendarSystem.
* Except for Dal Quor, which is always remote, and Kythri which is utterly random.
*/
lazy val planes : Vector[Calendar] = planeData.map{
case (name,orbit,remainsNotable,startingPosition) =>
{
val phase = if (startingPosition < orbit/2) 1 else 2
val day = startingPosition % (orbit/2)
planesFactory.make(name.toLowerCase,orbit,days,remainsNotable) setTimestampZero (0,phase,day)
}
}
lazy val eberronCalendar : Calendar = planes.foldLeft (CalendarSystem(Vector(solarCalendar )))
{ case (sys: Calendar,simple: Calendar) => sys synchronise simple}
}