Как перенести эту функцию NetHack на Python?

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

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

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

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

Я бы ожидал, что моя функция и функция "control" будут давать хотя бы одну фазу луны, но в настоящее время они этого не делают, и я не уверен, почему!

Вот код 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()

Ответ 1

Указанный код в значительной степени не тестируемый - и вам нужно сделать его проверяемым. Итак, вам нужен код 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). Затем вы можете видеть, где вещи отклоняются.

Ответ 2

Изменить: Оказывается, что оба "проблемы", которые я заметил здесь, были основаны на непонимании структуры 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

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

Ответ 3

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

Ответ 4

Любопытно, что когда я компилирую и запускаю пример 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", но это также не привело к правильному ответу.

Ответ 5

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

# 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, чтобы получить текущее местное время. Heres, как я это сделал (вставьте приведенный ниже код в 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%

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

Ответ 6

Вот мое преобразование, и я протестировал это против кода 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

Ответ 7

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

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

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

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