2009-06-02 65 views
10

我想写一个Python函数,它返回与游戏NetHack中相同的月相值。这在hacklib.c中找到。如何将这个NetHack函数移植到Python?

我试图简单地从NetHack代码复制相应的功能,但我不相信我会得到正确的结果。

我写的功能是phase_of_the_moon()

函数position()phase(),我在网上找到了,我用它们来表示我的函数成功。它们非常准确,结果与nethack.alt.org服务器大致相同(请参阅http://alt.org/nethack/moon/pom.txt)。然而,我之后是原始NetHack功能的完全复制,idiosyncrasies完好无损。

我期待我的功能和'控制'功能至少给出相同的月相,但目前他们没有,我不知道为什么!

这里是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() 
+0

啊nethack代码...现在,这是一些复杂的代码。 – Craig 2009-06-02 23:04:47

+0

我知道,但我当然可以处理一个teensy weensy甲虫功能! – nakedfanatic 2009-06-02 23:12:08

+0

首先,定义'epact'的行以分号结尾。 – Zifre 2009-06-02 23:30:20

回答

4

所写的代码在很大程度上是无法测试的 - 您需要使其可测试。所以,你需要的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())。然后你可以看到事情发生了什么变化。

+1

我同意;在这一点上,需要进行更多的测试,并将测试结果基于原始函数的本地副本,而不是依赖于alt.org,而开始看起来它甚至没有运行Nethack代码。 (我没有办法从phase_of_the_moon()中获得一个百分比,但是alt.org提供了它。) – 2009-06-03 13:34:22

3

编辑:原来我发现了她的两个“问题” e是基于对tm结构的误解。为了评论中的讨论,我将保留完整的答案,但请保留您的投票给实际上可能是正确的人。 ;-)


警告:我对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 

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

+0

感谢您的帮助!我指的是一台如http://www.cplusplus.com/reference/clibrary/ctime/tm/ 这说明tm_year为“自1900年”而tm_yday为“天自1月1日\t(0-365 )“ 虽然你似乎得到了正确的结果!如果我猜测我会说你的“金牌”修复是正确的 - 但我想确定,因为我想要一个确切的重复功能。也许我会以某种方式做一些测试。 – nakedfanatic 2009-06-02 23:56:44

+0

也许stdc的变化? Nethack的getlt()函数当然似乎跳过了许多不同的箍环,以获得首要的价值。任何人,这里是我发现的试图将strftime代码映射到tm成员的参考:http://www.opengroup.org/onlinepubs/009695399/functions/strftime.html – 2009-06-03 00:19:52

+0

哦!另一种可能性 - 函数的原始版本和Python版本的结果将受到时区的影响。计算机时区中的正确结果可能是2,但在alt.org中有3。 – 2009-06-03 00:31:54

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”(aka 3)。

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

+0

这很好奇。也许我需要调整我的系统时钟,并查看NetHack报告哪些日期的结果。也许这个功能简直是不准确的。我的意思是这个函数可能是在1987年左右写成的,所有这些四舍五入的值(如函数的序言部分所述)可能导致结果从大约一天左右的时间偏离了原来的位置。只是一个想法。 – nakedfanatic 2009-06-03 01:15:03

0

我喜欢认为我知道一些关于日历的事情,所以让我们看看我能否清除一些事情。

天主教会根据月相定义复活节的日期(这就是为什么日期每年都会跳来跳去)。因此,它需要能够计算近似的月相,并且它的算法解释为here

我没有做过非常详细的检查,但看起来NetHack算法很大程度上依赖于Church的算法。 NetHack算法似乎像教会的算法一样,只关注日历日期,忽略时区和时间。

NetHack算法只使用年份和年份的一天。我可以检查代码,是Y2K兼容告诉,这tm_year必须是年减去1900

1

下面的代码是borrowed from this site,将其粘贴在这里,以供参考(并且如果其他站点关闭)。似乎做你想做的事。

# 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模块得到当前本地时间。继承人我是如何做到的(贴贴在下面的代码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% 

月亮的东西很有趣。 :)

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
2

我长晚于这个线程,但FWIW,通过网络POM的alt.org服务器的显示器只在cron的更新几次,每天所以如果你是只是从它一点点,这可能是原因。游戏本身从nethack代码本身运行,所以不会遇到相同的缓存问题。 -drew(alt.org所有者)