2009-06-02 15 views
10

Próbuję napisać funkcję Pythona, która zwraca tę samą wartość fazy księżyca, co w grze NetHack. Można go znaleźć w hacklib.c.Jak przenieść tę funkcję NetHack do Pythona?

Próbowałem po prostu skopiować odpowiednią funkcję z kodu NetHack, ale nie sądzę, że otrzymuję poprawne wyniki.

Funkcja, którą napisałem to phase_of_the_moon().

Funkcje dostępne w sieci i używam ich jako wskazania sukcesu mojej funkcji. Są bardzo dokładne i dają wyniki, które w przybliżeniu pasują do serwera nethack.alt.org (patrz http://alt.org/nethack/moon/pom.txt). To, za czym jestem, jest jednak dokładną replikacją oryginalnej funkcji NetHack, nienaruszonych cech osobliwości.

Spodziewam się, że moja funkcja i funkcja "sterowania" dadzą co najmniej taką samą fazę księżyca, ale obecnie tak się nie dzieje i nie jestem pewien dlaczego!

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

Oto funkcja getlt() (również w 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 
} 

Oto mój kod 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 kod nethack ... teraz to jakiś skomplikowany kod. – Craig

+0

Wiem, ale na pewno mogę poradzić sobie z jedną z tych nastolatków, która jest bardzo zabawna! – nakedfanatic

+0

Po pierwsze, linia definiująca 'epact' kończy się średnikiem. – Zifre

Odpowiedz

4

Kod w formie pisemnej jest w dużej mierze niesprawdzalny - i trzeba go przetestować. Potrzebujesz więc kodu 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); 
} 

Teraz możesz uruchomić testy z wieloma wartościami czasu. Alternatywnym sposobem na zrobienie tego jest sfałszowanie getlt().

Następnie potrzebne są równoległe zmiany w kodzie Pythona. Następnie tworzysz zbiór wartości time_t, które można odczytać zarówno w Pythonie, jak iw C, a następnie przekonwertować na odpowiednią strukturę (poprzez localtime() w C). Wtedy możesz zobaczyć, gdzie rzeczy się odbiegają.

+1

Zgadzam się; w tym momencie potrzebne jest więcej testów i oparcie tego testu na lokalnej kopii oryginalnej funkcji zamiast polegania na alt.org, który zaczyna wyglądać tak, jakby nie uruchamiał nawet kodu Nethack. (Nie widzę żadnego sposobu na wyjście z fazy (phase_of_the_moon()), ale zapewnia go alt.org.) –

3

Edytuj: Wykazuje oba "problemy", że ją zauważyłem e zostały oparte na niezrozumieniu struktury tm. Pozostawię odpowiedź nietkniętą na potrzeby dyskusji w komentarzach, ale zachowaj głosy dla kogoś, kto może być poprawny. ;-)


Uwaga: Nie znam się dobrze na konstrukcjach czasu C; Przeważnie wychodzę z dokumentacji terenowej dostarczonej dla strftime.

Widzę dwa "błędy" w twoim porcie. Po pierwsze, uważam, że tm_year ma być rokiem bez wieku, a nie rokiem minus 1900, więc goldn powinien być ((lt.year % 100) % 19) + 1. Po drugie, twoje obliczenia dla diy są oparte na zera, podczas gdy tm_yday pojawia się (ponownie, z dokumentów) na podstawie jednej. Jednak nie jestem pewien o te ostatnie, jak ustalanie tylko linia goldn daje poprawny wynik (przynajmniej na dziś), gdzie jako mocowania zarówno daje złą odpowiedź:

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

Ponownie, jest to głównie domysły . Proszę bądź uprzejmy. :-)

+0

Dzięki za pomoc! Miałem na myśli tabelę taką jak http://www.cplusplus.com/reference/clibrary/ctime/tm/ Opisano tm_year jako "lata od 1900", a tm_yday jako "dni od 1 stycznia \t (0-365) " Wygląda na to, że masz odpowiedni wynik! Gdybym miał zgadywać, powiedziałbym, że twoja poprawka "goldn" jest poprawna - ale chciałbym być pewny, ponieważ chciałbym dokładnie zduplikować funkcję. Może jakoś spróbuję. – nakedfanatic

+0

Być może wariacje na stdc? Funkcja getlt() Nethacka z pewnością wydaje się przeskakiwać przez wiele różnych obręczy, aby uzyskać wartość w pierwszej kolejności. Anywho, oto odnośnik, który znalazłem, który próbuje mapować kody strftime do tm członków: http://www.opengroup.org/onlinepubs/009695399/functions/strftime.html –

+0

Oh! Inna możliwość - na wyniki zarówno wersji oryginalnej, jak i wersji Pythona wpłynie strefa czasowa. Możliwe, że poprawny wynik to 2 w strefie czasowej komputera, ale 3 w alt.org. –

1

Co ciekawe, kiedy mogę skompilować i uruchomić nethack przykład dostaję "2" jako odpowiedź ("kwartał", która jest taka sama jak portu)

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

wyjściowa:

> a.out 
phase of the moon 2 

Ale to nie wydaje się właściwą odpowiedzią, ponieważ weatherunderground.com i alt.org podają fazę księżyca jako "Woskowanie Gibbusa" (alias 3).

Próbowałem usunąć "-1900", ale to też nie doprowadziło do prawidłowej odpowiedzi.

+0

To ciekawe. Być może muszę dostosować mój zegar systemowy i zobaczyć, jakie wyniki raportuje NetHack na temat dat. Być może ta funkcja jest po prostu niedokładna. Chodzi mi o to, że funkcja mogła zostać napisana około 1987 roku, wszystkie te zaokrąglone wartości (jak opisano w komentarzu do preambuły funkcji) mogły spowodować, że wyniki przesuną się z miejsca, w którym powinny znajdować się około jednego dnia. Tylko myśl. – nakedfanatic

0

Lubię myśleć, że znam coś na temat kalendarzy, więc zobaczmy, czy mogę wyjaśnić kilka spraw.

Kościół katolicki określa datę Wielkanocy pod względem faz księżyca (dlatego data skacze z roku na rok). Z tego powodu musi być w stanie obliczyć przybliżoną fazę księżyca, a jej algorytm do tego celu jest wyjaśniony here.

Nie przeprowadziłem bardzo szczegółowego sprawdzenia, ale wydaje się, że algorytm NetHacka opiera się w dużej mierze na algorytmie Kościoła. Wydaje się, że algorytm NetHack, podobnie jak algorytm Kościoła, zwraca uwagę tylko na datę kalendarzową, ignorując strefy czasowe i porę dnia.

Algorytm NetHack wykorzystuje tylko rok i dzień w roku. Mogę powiedzieć od sprawdzenia, że ​​kod, aby być kompatybilnym z Y2K, że tm_year musi być rokiem minus 1900.

1

Następujący kod jest borrowed from this site, wklejając go tutaj w celu łatwego odniesienia (i na wypadek, gdyby druga strona została wyłączona). Wydaje się robić to, co chcesz.

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

można użyć modułu time aby uzyskać bieżący czas lokalny. Herezje jak to zrobiłem (wklej poniższy kod pisał do 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, '%') 

wyjściowa:

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

Księżyc rzeczy jest zabawa. :)

1

Oto moje przekształcenie tego i przetestowałem to na kodzie C, przekazując wartości z xrange (0, 1288578760, 3601), a oba zwracają te same wartości. Zauważ, że zmieniłem to tak, że możesz przekazać sekundy od epoki, tak żebym mógł przetestować to przeciwko wersji C dla jednej trzeciej z miliona różnych wartości. W „sekunda” wartość jest opcjonalna

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

jestem długo późno na tego wątku, ale FWIW, wyświetli serwer alt.org za POM za pośrednictwem Internetu informacje będą dotyczyć jedynie cron kilka razy dziennie, więc jeśli jesteś tylko odrobinę od niego, to może być powód. Sama gra jest uruchamiana ze wszystkiego, co jest w kodzie nethack, więc nie cierpi na ten sam problem z buforowaniem. -drew (właściciel alt.org)

Powiązane problemy