11

我正在尝试编写一个 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()
4

7 回答 7

5

编写的代码在很大程度上是不可测试的——您需要使其可测试。因此,您需要 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_tPython 和 C 都可以读取的值文件,然后转换为适当的结构(通过localtime()C 语言)。然后你可以看到事情在哪里偏离。

于 2009-06-03T00:54:48.757 回答
3

编辑:原来我在这里发现的两个“问题”都是基于对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

同样,这主要是猜测。请善待。:-)

于 2009-06-02T23:44:57.557 回答
2

我在这个线程上已经很晚了,但是 fwiw,alt.org 服务器通过 web 显示的 pom 每天只在 cron 上更新几次,所以如果你离它有点远,那可能就是原因。游戏本身从 nethack 代码本身的任何内容运行,因此不会遇到相同的缓存问题。-drew(alt.org 所有者)

于 2011-03-01T22:58:43.530 回答
1

奇怪的是,当我编译并运行 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”(又名 3)。

我尝试删除“-1900”,但这也没有得到正确的答案。

于 2009-06-03T00:27:40.170 回答
1

以下代码是从该站点借用的,将其粘贴到此处以方便参考(以防其他站点出现故障)。似乎做你想做的事。

# 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%

月亮的东西很有趣。:)

于 2009-12-22T09:42:18.220 回答
1

这是我对它的转换,我已经通过从 xrange(0, 1288578760, 3601) 中传入值对 C 代码进行了测试,它们都返回相同的值。请注意,我已对其进行了更改,以便您可以通过自纪元以来的秒数,以便我可以针对 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
于 2010-11-01T02:56:56.820 回答
0

我喜欢认为我对日历了解一两件事,所以让我们看看我是否可以弄清楚一些事情。

天主教会根据月相来定义复活节的日期(这就是日期每年跳跃的原因)。正因为如此,它需要能够计算近似月相,这里解释了它的算法。

我没有做非常详细的检查,但似乎 NetHack 算法很大程度上基于 Church 的算法。NetHack 算法似乎和 Church 的算法一样,只关注日历日期,忽略时区和一天中的时间。

NetHack 算法仅使用年份和年份中的日期。通过检查代码,我可以看出,为了兼容 Y2K,tm_year 必须是减去 1900 的年份。

于 2009-12-22T09:30:24.207 回答