Как перенести эту функцию NetHack на Python?
Вопрос
Я пытаюсь написать функцию 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()
Решение
Написанный код по большей части не поддается тестированию, и вам необходимо сделать его тестируемым.Итак, вам нужен код 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()
в С).Тогда вы увидите, где что-то отклоняется.
Другие советы
Редактировать: Оказывается, обе «проблемы», которые я здесь заметил, были основаны на неправильном понимании 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
Опять же, это в основном догадки.Пожалуйста, будьте добры.:-)
Я сильно опоздал с этой веткой, но, кстати, отображение pom на сервере alt.org через Интернет обновляется в cron только пару раз в день, поэтому, если вы немного отклонились от этого, это может быть причиной.Сама игра запускается из всего, что есть в самом коде nethack, поэтому не имеет такой проблемы с кэшированием.-Дрю (владелец alt.org)
Любопытно, что когда я компилирую и запускаю пример 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 сообщают о фазе Луны как о «Растущей Луне» (также известной как 3).
Я попытался удалить «-1900», но это тоже не привело к правильному ответу.
Следующий код взято с этого сайта, вставив его сюда для удобства использования (и на случай, если другой сайт выйдет из строя).Кажется, делаешь то, что хочешь.
# 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
модуль, чтобы получить текущее местное время.Вот как я это сделал (вставьте приведенный ниже код для тестового запуска):
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%
Лунные штучки — это весело.:)
Вот мое преобразование, и я проверил его на коде 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
Мне нравится думать, что я кое-что знаю о календарях, так что давайте посмотрим, смогу ли я прояснить кое-что.
Католическая церковь определяет дату Пасхи по лунным фазам (вот почему дата меняется из года в год).По этой причине необходимо иметь возможность рассчитать приблизительную фазу Луны, а алгоритм этого расчета объясняется. здесь.
Я не проводил очень детальной проверки, но похоже, что алгоритм NetHack во многом основан на алгоритме Чёрча.Алгоритм NetHack, как и алгоритм Чёрча, похоже, обращает внимание только на календарную дату, игнорируя часовые пояса и время суток.
Алгоритм NetHack использует только год и день года.Изучая код, я могу сказать, что для совместимости с Y2K tm_year должен быть годом минус 1900.