如何将此NetHack函数移植到Python?

11 投票
7 回答
1764 浏览
提问于 2025-04-15 12:00

我正在尝试写一个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 个回答

2

我在这个话题上来得有点晚,不过说一下,alt.org 服务器在网页上显示 pom 的更新频率是每天定时更新几次,所以如果你看到的数据和这个有点差距,那可能就是原因所在。游戏本身是直接从 nethack 的代码里运行的,所以不会遇到同样的缓存问题。 -drew(alt.org 的拥有者)

3

编辑:结果发现我之前提到的两个“问题”其实是对 tm 结构体的误解。我会保留这个回答,以便评论区的讨论,但请把投票留给那些可能真的正确的人。;-)


注意:我对C语言中的时间构造不是很熟悉,主要是根据 strftime 的文档来理解的。

我在你的代码中发现了两个“错误”。首先,我认为 tm_year 应该表示的是没有世纪的年份,而不是年份减去1900,所以 goldn 应该是 ((lt.year % 100) % 19) + 1。其次,你计算 diy 的方式是从零开始的,而根据文档, tm_yday 似乎是从1开始的。不过,我对后者不太确定,因为只修正 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

再次强调,这主要是猜测。请多包涵。:-)

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_t值的文件,这个文件可以被Python和C都读取,然后通过C语言中的localtime()函数把它转换成合适的结构。这样你就能看到哪里出现了问题。

撰写回答