2009-06-02 6 views
10

Estoy tratando de escribir una función de Python que devuelva el mismo valor de fase lunar que en el juego NetHack. Esto se encuentra en hacklib.c.¿Cómo portar esta función de NetHack a Python?

He intentado simplemente copiar la función correspondiente del código de NetHack, pero no creo que esté obteniendo los resultados correctos.

La función que he escrito es phase_of_the_moon().

Las funciones position() y phase(), las encontré en la red, y las estoy usando como una indicación del éxito de mi función. Son muy precisos y dan resultados que coinciden aproximadamente con el servidor nethack.alt.org (ver http://alt.org/nethack/moon/pom.txt). Sin embargo, lo que busco es una replicación exacta de la función original de NetHack, la idiosincrasia intacta.

Espero que mi función y la función de "control" den la misma fase lunar al menos, pero actualmente no lo hacen y no estoy seguro de por qué.

Este es el código 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); 
} 

Aquí es la función getlt() (también en 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 
} 

Aquí está mi código 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

Ah, el código de nethack ... ahora es un código complejo. – Craig

+0

Lo sé, pero seguro que puedo lidiar con una función teensy weety leetle !! – nakedfanatic

+0

Por un lado, la línea que define 'epact' termina con un punto y coma. – Zifre

Respuesta

4

El código tal como está escrito es en gran parte no comprobable, y debe hacerlo comprobable. Por lo tanto, necesita que el código C sea:

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); 
} 

Ahora puede ejecutar pruebas con valores de tiempo múltiples. La forma alternativa de hacerlo es falsificar getlt() en su lugar.

Luego necesita cambios paralelos en su código Python. A continuación, crea un archivo de time_t valores que pueden leer tanto Python como C, y luego convertirse en una estructura apropiada (a través de localtime() en C). Entonces puedes ver dónde se están desviando las cosas.

+1

Estoy de acuerdo; en este punto, lo que se necesita es más pruebas y basar esas pruebas en una copia local de la función original en lugar de confiar en alt.org, que parece que ni siquiera está ejecutando el código de Nethack. (No veo ninguna forma de obtener un porcentaje de phase_of_the_moon(), pero alt.org lo proporciona). –

3

Edit: Resulta que ambos "problemas" la vi e se basaron en un malentendido de la estructura tm. Dejaré la respuesta intacta por el bien de la discusión en los comentarios, pero guarde sus votos para alguien que pueda estar realmente correcto. ;-)


Advertencia: No estoy muy familiarizado con C time constructs; Principalmente estoy saliendo de la documentación del campo suministrada para strftime.

Veo dos "errores" en su puerto. En primer lugar, creo que tm_year está destinado a ser el año sin siglo, no el año menos 1900, por lo tanto, goldn debe ser ((lt.year % 100) % 19) + 1. En segundo lugar, su cálculo para diy es de base cero, mientras que tm_yday aparece (nuevamente, de los documentos) para ser basado en una sola. Sin embargo, no estoy seguro sobre este último, como se acaba de fijación de la línea goldn da un resultado correcto (al menos por hoy), en tanto que la fijación tanto da la respuesta equivocada:

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

Una vez más, esto es sobre todo conjeturas . Por favor se amable. :-)

+0

¡Gracias por su ayuda! Me refería a una tabla como http://www.cplusplus.com/reference/clibrary/ctime/tm/ Esto describe tm_year como "años desde 1900" y tm_yday como "días desde el 1 de enero de \t (0-365) " ¡Parece que estás obteniendo el resultado correcto! Si tuviera que adivinar diría que tu corrección "goldn" es correcta, pero me gustaría estar seguro ya que me gustaría una función duplicada exacta. Tal vez haga algunas pruebas de alguna manera. – nakedfanatic

+0

¿Quizás variaciones en stdc? La función getlt() de Nethack ciertamente parece saltar a través de muchos aros diferentes para obtener el valor en primer lugar. Anywho, aquí está la referencia que encontré que intenta mapear los códigos strftime para los miembros de tm: http://www.opengroup.org/onlinepubs/009695399/functions/strftime.html –

+0

¡Oh! Otra posibilidad: los resultados de las versiones originales y de Python de la función se verán afectados por la zona horaria. Es posible que el resultado correcto sea 2 en la zona horaria de su computadora, pero 3 en alt.org's. –

1

Curiosamente, cuando compilar y ejecutar el ejemplo nethack me sale "2" como la respuesta ("primer trimestre", que es el mismo que el puerto)

#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()); 
} 

de salida:

> a.out 
phase of the moon 2 

Pero eso no parece ser la respuesta correcta, ya que hoy, weatherunderground.com y alt.org informan la fase de la luna como "Waxing Gibbous" (también conocido como 3).

Intenté eliminar el "-1900" pero eso tampoco dio la respuesta correcta.

+0

Eso es curioso. Tal vez deba ajustar el reloj de mi sistema y ver qué resultados reporta NetHack en qué fechas. Tal vez esa función es simplemente inexacta. Quiero decir que la función puede haber sido escrita alrededor de 1987, todos esos valores redondeados (como se describe en el comentario del preámbulo de la función) pueden haber causado que los resultados se desvíen de donde deberían estar por un día más o menos. Solo un pensamiento. – nakedfanatic

0

Me gusta pensar que sé una o dos cosas sobre los calendarios, así que vamos a ver si puedo aclarar algunas cosas.

La Iglesia Católica define la fecha de Pascua en términos de fases lunares (esta es la razón por la cual la fecha salta de año en año). Debido a esto, debe ser capaz de calcular la fase lunar aproximada, y su algoritmo para hacerlo se explica here.

No he hecho una verificación muy detallada, pero parece que el algoritmo de NetHack se basa en gran medida en el algoritmo de la Iglesia. El algoritmo de NetHack parece, como el algoritmo de la Iglesia, prestar atención solo a la fecha del calendario, ignorando las zonas horarias y la hora del día.

El algoritmo de NetHack utiliza solo el año y el día del año. Puedo decir de examinar el código que, para ser compatible con el Y2K, que tm_year tiene que ser el año menos 1900.

1

siguiente código es borrowed from this site, pegar aquí para una fácil referencia (y en caso de que otro sitio se cae). Parece hacer lo que quieras.

# 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, '%') 

Se puede utilizar el módulo de time para obtener el hora local actual. He aquí cómo lo hice (pasta continuación Código publicado a 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, '%') 

Salida:

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

Luna cosas es divertido. :)

1

Aquí está mi conversión de la misma, y ​​he probado esto contra el código C al pasar los valores de xrange (0, 1288578760, 3601), y ambos devuelven los mismos valores. Tenga en cuenta que lo he cambiado para que pueda pasar los segundos desde época, para que pueda probarlo contra la versión C para un tercio de un millón de valores diferentes. El valor "segundo" es opcional

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

estoy larga tarde en este hilo, pero fwiw, la pantalla del servidor alt.org de pom través de la web sólo las actualizaciones sobre cron un par de veces por día así que si estás apagado por un poco de eso, esa podría ser la razón. El juego se ejecuta desde lo que está en el código de red en sí mismo, por lo que no sufre el mismo problema de almacenamiento en caché. -drew (propietario de alt.org)