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

如何将此NetHack函数移植到Python?,python,c,time,porting,nethack,Python,C,Time,Porting,Nethack,我正在尝试编写一个Python函数,它返回与游戏NetHack中相同的月亮相位值。这可以在中找到 我试图简单地从NetHack代码中复制相应的函数,但我不相信我得到了正确的结果 我所写的函数是月亮的相位() 我在网上找到了函数position()和phase(),并将它们用作我函数成功的标志。它们非常准确,给出的结果与nethack.alt.org服务器大致匹配(请参阅)。然而,我所追求的是对原始NetHack功能的精确复制,特性完好无损 我希望我的函数和“控制”函数至少能给出相同的月相,但目前

我正在尝试编写一个Python函数,它返回与游戏NetHack中相同的月亮相位值。这可以在中找到

我试图简单地从NetHack代码中复制相应的函数,但我不相信我得到了正确的结果

我所写的函数是月亮的相位()

我在网上找到了函数
position()
phase()
,并将它们用作我函数成功的标志。它们非常准确,给出的结果与nethack.alt.org服务器大致匹配(请参阅)。然而,我所追求的是对原始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中):

以下是我的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()

编辑:我发现这里的两个“问题”都是基于对
tm
struct的误解。为了便于评论中的讨论,我将保留完整的答案,但请将您的投票留给可能真正正确的人。;-)


警告:我不太熟悉C时间结构;我主要是从
strftime
提供的现场文档开始

我在你的端口上看到两个“bug”。首先,我相信
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

同样,这主要是猜测。请客气点。:-)

奇怪的是,当我编译并运行nethack示例时,我得到的答案是“2”(“第一季度”,与您的端口相同)

但这似乎不是正确的答案,因为今天,weatherunderground.com和alt.org将月球的相位报告为“上蜡凸出”(也称为3)


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

编写的代码基本上是不稳定的,您需要使其可测试。因此,您需要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()
)。然后你可以看到事情偏离了什么方向。

我喜欢认为我对日历有一两点了解,所以让我们看看我是否可以澄清一些事情

天主教会根据月相来定义复活节的日期(这就是为什么复活节的日期一年比一年跳跃的原因)。因此,它需要能够计算近似的月球相位,并解释了其算法

我还没有做过非常详细的检查,但NetHack算法似乎主要基于Church的算法。NetHack算法似乎和Church算法一样,只关注日历日期,而忽略了时区和一天中的时间

NetHack算法只使用年份和日期。通过检查代码,我可以看出,要与Y2K兼容,tm_year必须是减去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, '%')
输出:

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

月亮的东西很有趣

这是我对它的转换,我通过传入xrange(0122885787603601)中的值对C代码进行了测试,它们都返回相同的值。请注意,我已经更改了它,这样您就可以通过epoch之后的秒数,这样我就可以针对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

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

啊,nethack代码……这是一些复杂的代码。我知道,但我肯定能处理一个小小的leetle函数!!首先,定义epact的行以分号结尾。哇!是的;它不应该在那里,但也不会造成任何伤害。谢谢你的帮助!我指的是这样一个表格,它将tm_year描述为“1900年以来的年份”,tm_yday描述为“1月1日以来的天数(0-365)”,但你似乎得到了正确的结果!如果我猜的话,我会说你的“goldn”修复是正确的——但我想确定一下,因为我想要一个精确的复制函数。也许我会做一些测试,也许是stdc的变体?Nethack的getlt()函数似乎首先要通过许多不同的步骤来获取值。总之,我找到了一个参考资料,它试图将strftime代码映射到tm成员:哦!另一种可能性——函数的原始版本和Python版本的结果都会受到时区的影响。正确的结果可能是计算机时区中的2,但alt.org中的3。我认为我的时区(+12小时)将报告更高级的阶段,而不是不太高级的阶段。确实,我还没有检查NetHack代码的其余部分,所以我可能遗漏了一些东西
# 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, '%')
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%
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