Как перенести эту функцию NetHack на Python? - PullRequest
10 голосов
/ 03 июня 2009

Я пытаюсь написать функцию Python, которая возвращает то же значение фазы луны, что и в игре NetHack. Это можно найти в hacklib.c .

Я пытался просто скопировать соответствующую функцию из кода NetHack, но я не верю, что получаю правильные результаты.

Функция, которую я написал - phase_of_the_moon().

Функции position() и phase() я обнаружил в сети, и я использую их как показатель успеха моей функции. Они очень точны и дают результаты, которые приблизительно соответствуют серверу nethack.alt.org (см. http://alt.org/nethack/moon/pom.txt). Однако, что мне нужно, это точная репликация исходной функции NetHack, идиосинкразии без изменений.

Я бы ожидал, что моя функция и функция «управления» дадут, по крайней мере, одну и ту же фазу луны, но в настоящее время это не так, и я не уверен, почему!

Вот код NetHack:

/*
 * moon period = 29.53058 days ~= 30, year = 365.2422 days
 * days moon phase advances on first day of year compared to preceding year
 *  = 365.2422 - 12*29.53058 ~= 11
 * years in Metonic cycle (time until same phases fall on the same days of
 *  the month) = 18.6 ~= 19
 * moon phase on first day of year (epact) ~= (11*(year%19) + 29) % 30
 *  (29 as initial condition)
 * current phase in days = first day phase + days elapsed in year
 * 6 moons ~= 177 days
 * 177 ~= 8 reported phases * 22
 * + 11/22 for rounding
 */
int
phase_of_the_moon()     /* 0-7, with 0: new, 4: full */
{
    register struct tm *lt = getlt();
    register int epact, diy, goldn;

    diy = lt->tm_yday;
    goldn = (lt->tm_year % 19) + 1;
    epact = (11 * goldn + 18) % 30;
    if ((epact == 25 && goldn > 11) || epact == 24)
        epact++;

    return( (((((diy + epact) * 6) + 11) % 177) / 22) & 7 );
}

Вот функция getlt() (также в hacklib.c):

static struct tm *
getlt()
{
    time_t date;

#if defined(BSD) && !defined(POSIX_TYPES)
    (void) time((long *)(&date));
#else
    (void) time(&date);
#endif
#if (defined(ULTRIX) && !(defined(ULTRIX_PROTO) || defined(NHSTDC))) || (defined(BSD) && !defined(POSIX_TYPES))
    return(localtime((long *)(&date)));
#else
    return(localtime(&date));
#endif
}

Вот мой код Python:

from datetime import date

def phase_of_the_moon():
   lt = date.today()

   diy = (lt - date(lt.year, 1, 1)).days
   goldn = ((lt.year - 1900) % 19) + 1
   epact = (11 * goldn + 18) % 30;
   if ((epact == 25 and goldn > 11) or epact == 24):
      epact += 1
   return ( (((((diy + epact) * 6) + 11) % 177) / 22) & 7 )

import math, decimal, datetime
dec = decimal.Decimal

def position(now=None): 
   if now is None: 
      now = datetime.datetime.now()

   diff = now - datetime.datetime(2001, 1, 1)
   days = dec(diff.days) + (dec(diff.seconds) / dec(86400))
   lunations = dec("0.20439731") + (days * dec("0.03386319269"))

   return lunations % dec(1)

def phase(pos): 
   index = (pos * dec(8)) + dec("0.5")
   index = math.floor(index)
   return {
      0: "New Moon", 
      1: "Waxing Crescent", 
      2: "First Quarter", 
      3: "Waxing Gibbous", 
      4: "Full Moon", 
      5: "Waning Gibbous", 
      6: "Last Quarter", 
      7: "Waning Crescent"
   }[int(index) & 7]

def phase2(pos): 
   return {
      0: "New Moon", 
      1: "Waxing Crescent", 
      2: "First Quarter", 
      3: "Waxing Gibbous", 
      4: "Full Moon", 
      5: "Waning Gibbous", 
      6: "Last Quarter", 
      7: "Waning Crescent"
   }[int(pos)]

def main():
   ## Correct output
   pos = position()
   phasename = phase(pos)
   roundedpos = round(float(pos), 3)
   print "%s (%s)" % (phasename, roundedpos)

   ## My output
   print "%s (%s)" % (phase2(phase_of_the_moon()), phase_of_the_moon())

if __name__=="__main__": 
   main()

Ответы [ 7 ]

4 голосов
/ 03 июня 2009

Написанный код в значительной степени непроверяем - и вам нужно сделать его тестируемым. Итак, вам нужен код C:

int
phase_of_the_moon()     /* 0-7, with 0: new, 4: full */
{
    register struct tm *lt = getlt();
    return testable_potm(lt);
}

static int
testable_potm(const struct tm *lt)
{
    register int epact, diy, goldn;

    diy = lt->tm_yday;
    goldn = (lt->tm_year % 19) + 1;
    epact = (11 * goldn + 18) % 30;
    if ((epact == 25 && goldn > 11) || epact == 24)
        epact++;

    return( (((((diy + epact) * 6) + 11) % 177) / 22) & 7 );
}

Теперь вы можете запускать тесты с несколькими значениями времени. Альтернативный способ сделать это - подделать getlt() вместо.

Затем вам понадобятся параллельные изменения в вашем коде Python. Затем вы создаете файл значений time_t, который может быть прочитан как Python, так и C, а затем преобразован в соответствующую структуру (через localtime() в C). Тогда вы можете увидеть, где вещи отклоняются.

3 голосов
/ 03 июня 2009

Редактировать: Оказалось, что обе "проблемы", которые я здесь обнаружил, были основаны на неправильном понимании структуры tm. Я оставлю ответ без изменений для обсуждения в комментариях, но оставлю ваши голоса за кого-то, кто действительно может быть прав. ; -)


Предостережение: я не очень знаком с конструкциями времени C; Я в основном ухожу с полевой документации, предоставленной для strftime.

Я вижу две "ошибки" в вашем порту. Во-первых, я считаю, что tm_year должен быть годом без столетия, а не годом минус 1900, поэтому goldn должно быть ((lt.year % 100) % 19) + 1. Во-вторых, ваши расчеты для diy начинаются с нуля, тогда как tm_yday представляется (опять же из документов) основанным на единицах. Однако я не уверен насчет последнего, поскольку исправление только строки goldn дает правильный результат (по крайней мере, на сегодняшний день), где исправление обоих дает неправильный ответ:

>>> def phase_of_the_moon():
    lt = date.today()

    diy = (lt - date(lt.year, 1, 1)).days
    goldn = ((lt.year % 100) % 19) + 1
    epact = (11 * goldn + 18) % 30
    if ((epact == 25 and goldn > 11) or epact == 24):
        epact += 1
    return ( (((((diy + epact) * 6) + 11) % 177) / 22) & 7 )

>>> phase_of_the_moon():
3

Опять же, это в основном догадки. Пожалуйста, будь добр. : -)

2 голосов
/ 02 марта 2011

Я давно опаздываю в этой теме, но, fwiw, отображение pom на веб-сайте alt.org обновляется только на cron пару раз в день, так что, если вы немного отключитесь, это может быть причина. Сама игра запускается из того, что есть в самом коде nethack, поэтому не страдает той же проблемой кеширования. -дрю (владелец alt.org)

1 голос
/ 01 ноября 2010

Вот мое преобразование, и я проверил это с кодом C, передавая значения из xrange (0, 1288578760, 3601), и они оба возвращают одинаковые значения. Обратите внимание, что я изменил его, чтобы вы могли передавать секунды с начала эпохи, чтобы я мог проверить его на версии C на треть от миллиона различных значений. Значение «секунд» является необязательным

def phase_of_the_moon(seconds = None):
   '0-7, with 0: new, 4: full'
   import time

   if seconds == None: seconds = time.time()
   lt = time.localtime(seconds)

   tm_year = lt.tm_year - 1900
   diy = lt.tm_yday - 1
   goldn = (tm_year % 19) + 1
   epact = (11 * goldn + 18) % 30

   if (epact == 25 and goldn > 11) or epact == 24: epact += 1

   return (((((diy + epact) * 6) + 11) % 177) / 22) & 7
1 голос
/ 22 декабря 2009

Следующий код заимствован с этого сайта , вставляя его сюда для удобства (и в случае, если другой сайт не работает). Кажется, делать то, что вы хотите.

# Determine the moon phase of a date given
# Python code by HAB

def moon_phase(month, day, year):
    ages = [18, 0, 11, 22, 3, 14, 25, 6, 17, 28, 9, 20, 1, 12, 23, 4, 15, 26, 7]
    offsets = [-1, 1, 0, 1, 2, 3, 4, 5, 7, 7, 9, 9]
    description = ["new (totally dark)",
      "waxing crescent (increasing to full)",
      "in its first quarter (increasing to full)",
      "waxing gibbous (increasing to full)",
      "full (full light)",
      "waning gibbous (decreasing from full)",
      "in its last quarter (decreasing from full)",
      "waning crescent (decreasing from full)"]
    months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]

    if day == 31:
        day = 1
    days_into_phase = ((ages[(year + 1) % 19] + ((day + offsets[month-1]) % 30) + (year < 1900)) % 30)
    index = int((days_into_phase + 2) * 16/59.0)
    if index > 7:
        index = 7
    status = description[index]

    # light should be 100% 15 days into phase
    light = int(2 * days_into_phase * 100/29)
    if light > 100:
        light = abs(light - 200);
    date = "%d%s%d" % (day, months[month-1], year)

    return date, status, light

# put in a date you want ...
month = 5
day = 14
year = 2006  # use yyyy format

date, status, light = moon_phase(month, day, year)
print "moon phase on %s is %s, light = %d%s" % (date, status, light, '%')

Вы можете использовать модуль time для получения текущего местного времени . Вот как я это сделал (вставьте приведенный ниже код в testrun):

import time
tm = time.localtime()
month = tm.tm_mon
day = tm.tm_mday
year = tm.tm_year
date, status, light = moon_phase(month, day, year)
print "moon phase on %s is %s, light = %d%s" % (date, status, light, '%')

Выход:

moon phase on 22Dec2009 is waxing crescent (increasing to full), light = 34%

Луна - это весело. :)

1 голос
/ 03 июня 2009

Любопытно, что когда я компилирую и запускаю пример nethack, я получаю «2» в качестве ответа («Первый квартал», который совпадает с вашим портом)

#include <time.h>

static struct tm *
getlt()
{
        time_t date;
        (void) time(&date);
        return(localtime(&date));
}
/*
 * moon period = 29.53058 days ~= 30, year = 365.2422 days
 * days moon phase advances on first day of year compared to preceding year
 *  = 365.2422 - 12*29.53058 ~= 11
 * years in Metonic cycle (time until same phases fall on the same days of
 *  the month) = 18.6 ~= 19
 * moon phase on first day of year (epact) ~= (11*(year%19) + 29) % 30
 *  (29 as initial condition)
 * current phase in days = first day phase + days elapsed in year
 * 6 moons ~= 177 days
 * 177 ~= 8 reported phases * 22
 * + 11/22 for rounding
 */
int
phase_of_the_moon()     /* 0-7, with 0: new, 4: full */
{
    register struct tm *lt = getlt();
    register int epact, diy, goldn;

    diy = lt->tm_yday;
    goldn = (lt->tm_year % 19) + 1;
    epact = (11 * goldn + 18) % 30;
    if ((epact == 25 && goldn > 11) || epact == 24)
        epact++;

    return( (((((diy + epact) * 6) + 11) % 177) / 22) & 7 );
}

int main(int argc, char * argv[]) {
    printf ("phase of the moon %d\n\n", phase_of_the_moon());
}

выход:

> a.out
phase of the moon 2

Но это не похоже на правильный ответ, так как сегодня weatherunderground.com и alt.org сообщают о фазе луны как "Waxing Gibbous" (a.k.a 3).

Я пытался удалить "-1900", но это также не привело к правильному ответу.

0 голосов
/ 22 декабря 2009

Мне нравится думать, что я знаю кое-что о календарях, поэтому давайте посмотрим, смогу ли я кое-что прояснить.

Католическая церковь определяет дату Пасхи в терминах лунных фаз (именно поэтому дата прыгает из года в год). Из-за этого ему нужно уметь рассчитывать приблизительную фазу луны, и его алгоритм для этого объясняется здесь .

Я не провел очень детальную проверку, но похоже, что алгоритм NetHack в значительной степени основан на алгоритме Церкви. Алгоритм NetHack, как и алгоритм Чёрча, похоже, обращает внимание только на календарную дату, игнорируя часовые пояса и время суток.

Алгоритм NetHack использует только год и день года. Из проверки кода я могу сказать, что, чтобы быть совместимым с Y2K, tm_year должен быть годом минус 1900.

...