Wojna o rdzeń nano


21

Jest to adaptacja Core War , programu KOTH z 20 wieku. Mówiąc ściślej, używa niesamowicie uproszczonego zestawu instrukcji, opartego głównie na oryginalnej propozycji .

tło

W Core War istnieją dwa programy walczące o kontrolę nad komputerem. Celem każdego programu jest zwycięstwo poprzez zlokalizowanie i zakończenie programu przeciwnego.

Bitwa odbywa się w głównej pamięci komputera. Ta pamięć nazywa się Core i zawiera 8192 adresów. Na początku bitwy kod każdego zawodnika (zwanego wojownikiem) jest umieszczany w losowej części pamięci. Wykonanie programu przebiega na przemian między wojownikami, wykonując jedną instrukcję dla każdego z nich. Każda instrukcja może modyfikować część rdzenia, co prowadzi do możliwości samodzielnej modyfikacji programów.

Celem jest zakończenie programu przeciwnika. Program kończy się, gdy próbuje wykonać niepoprawną instrukcję, którą jest dowolna DATinstrukcja.

Zestaw instrukcji

Każdy program składa się z szeregu instrukcji niskiego poziomu, z których każde zajmuje dwa pola, zwane polami A i B.

Ten zestaw instrukcji czerpie głównie z oryginalnej specyfikacji. Główne zmiany to 1) wyjaśnienie dotyczące dodawania / odejmowania poleceń oraz 2) zmiana #trybu adresowania, aby można go było używać w dowolnym miejscu. Większość pełnych wersji Core Wars ma ponad 20 kodów, 8 trybów adresowania i zestaw „modyfikatorów instrukcji”.

Kody

Każda instrukcja musi mieć jeden z siedmiu różnych kodów.

  • DAT A B- (dane) - To po prostu zawiera liczby Ai B. Co ważne, proces umiera, gdy próbuje wykonać instrukcję DAT.
  • MOV A B- (przenieś) - Przenosi zawartość lokalizacji Apamięci do lokalizacji pamięci B. Oto demonstracja „przed i po”:

    MOV 2 1
    ADD @4 #5
    JMP #1 -1
    
    MOV 2 1
    JMP #1 -1
    JMP #1 -1
    
  • ADD A B- (dodaj) - Dodaje zawartość lokalizacji Apamięci do lokalizacji pamięci B. Dwa pierwsze pola obu zostaną dodane, a drugie pola zostaną dodane.

    ADD 2 1
    MOV @4 #5
    JMP #1 -1
    
    ADD 2 1
    MOV @5 #4
    JMP #1 -1
    
  • SUB A B- (odejmij) - Odejmuje zawartość miejsca w pamięci Aod (i zapisuje wynik w) miejscu w pamięci B.

    SUB 2 1
    MOV @4 #5
    JMP #1 -1
    
    SUB 2 1
    MOV @3 #6
    JMP #1 -1
    
  • JMP A B- (skok) - Przejdź do lokalizacji A, która zostanie wykonana w następnym cyklu. Bmusi być liczbą, ale nic nie robi (możesz jej użyć do przechowywania informacji).

    JMP 2 1337
    ADD 1 2
    ADD 2 3
    

    Skok oznacza, że ADD 2 3zostanie wykonany następny cykl.

  • JMZ A B- (skok, jeśli zero) - Jeśli oba pola linii Bmają wartość 0, program przeskakuje do lokalizacji A.

    JMZ 2 1
    SUB 0 @0
    DAT 23 45
    

    Ponieważ dwa pola instrukcji 1 mają wartość 0, polecenie DAT zostanie wykonane w następnej turze, co doprowadzi do nieuchronnej śmierci.

  • CMP A B- (porównaj i pomiń, jeśli nie są równe) - Jeśli pola w instrukcjach Ai Bnie są równe, pomiń następną instrukcję.

    CMP #1 2
    ADD 2 #3
    SUB @2 3
    

    Ponieważ dwa pola instrukcji 1 i 2 mają jednakową wartość, polecenie ADD nie jest pomijane i jest wykonywane w następnej turze.

Kiedy dwie instrukcje są dodawane / odejmowane, dwa pola (A i B) są dodawane / odejmowane parami. Tryb adresowania i kod operacji nie są zmieniane.

Tryby adresowania

Istnieją trzy rodzaje trybów adresowania. Każde z dwóch pól instrukcji ma jeden z tych trzech trybów adresowania.

  • Natychmiastowe#X - Xto linia, która ma być używana bezpośrednio w obliczeniach. Na przykład #0jest pierwszą linią programu. Linie ujemne odnoszą się do linii w rdzeniu przed rozpoczęciem programu.

    ... //just a space-filler
    ...
    ADD #3 #4
    DAT 0 1
    DAT 2 4
    

    Spowoduje to dodanie pierwszej z dwóch linii DAT do drugiej, ponieważ znajdują się one odpowiednio w wierszach 3 i 4. Nie chciałbyś jednak używać tego kodu, ponieważ DAT zabije twojego bota w następnym cyklu.

  • WzględneX - liczba Xreprezentuje lokalizację docelowego adresu pamięci w stosunku do bieżącego adresu. Liczba w tej lokalizacji jest używana do obliczeń. Jeśli linia #35jest wykonywana i zawiera -5, wówczas #30używana jest linia .

    ... //just a space-filler
    ...
    ADD 2 1
    DAT 0 1
    DAT 2 4
    

    Spowoduje to dodanie drugiej linii DAT do pierwszej.

  • Pośrednie@X - liczba Xreprezentuje adres względny. Zawartość w tym miejscu jest tymczasowo dodawana do liczby X w celu utworzenia nowego adresu względnego, z którego pobierana jest liczba. Jeśli linia #35jest wykonywana, a jej drugim polem jest @4, a drugie pole linii #39zawiera liczbę -7, wówczas #32używana jest linia .

    ... //just a space-filler
    ...
    ADD @1 @1
    DAT 0 1
    DAT 2 4
    

    To doda pierwszy DAT do drugiego, ale w bardziej skomplikowany sposób. Pierwsze pole to @ 1, które pobiera dane z tego adresu względnego, który jest pierwszym polem pierwszego DAT, a 0. To jest interpretowane jako drugi adres względny z tej lokalizacji, więc 1 + 0 = 1 daje sumę przesunięcie w stosunku do oryginalnej instrukcji. Dla drugiego pola @ 1 pobiera wartość z tego względnego adresu (1 w drugim polu pierwszego DAT) i dodaje ją do siebie w ten sam sposób. Całkowite przesunięcie wynosi wtedy 1 + 1 = 2. Tak więc ta instrukcja jest wykonywana podobnie do ADD 1 2.

Każdy program może zawierać do 64 instrukcji.

Po rozpoczęciu rundy oba programy są losowo umieszczane w banku pamięci z 8192 lokalizacjami. Wskaźnik instrukcji dla każdego programu rozpoczyna się na początku programu i jest zwiększany po każdym cyklu wykonywania. Program umiera, gdy wskaźnik instrukcji spróbuje wykonać DATinstrukcję.

Parametry rdzenia

Rozmiar rdzenia to 8192, a limit czasu to 8192 * 8 = 65536 tyknięć. Rdzeń ma charakter cykliczny, więc zapis na adres 8195 jest taki sam jak zapis na adres 3. Wszystkie nieużywane adresy są inicjowane DAT #0 #0.

Każdy zawodnik nie może być dłuższy niż 64 linie. Liczby całkowite będą przechowywane jako liczby całkowite ze znakiem 32-bitowym.

Rozbiór gramatyczny zdania

Aby ułatwić programowanie konkurentom, dodam funkcję parowania linii do parsera. Wszelkie słowa występujące w linii przed kodem operacji będą interpretowane jako etykiety linii. Na przykład tree mov 4 6ma etykietę linii tree. Jeśli gdziekolwiek w programie znajduje się pole zawierające tree #treelub @tree, liczba zostanie zastąpiona. Również wielkie litery są ignorowane.

Oto przykład zastępowania etykiet linii:

labelA add labelB @labelC
labelB add #labelC labelC
labelC sub labelA @labelB

Tutaj etykiety A, B i C znajdują się w wierszach 0, 1 i 2. Wystąpienia #labelzostaną zastąpione numerem wiersza etykiety. Wystąpienia labellub @labelsą zastąpione względną lokalizacją etykiety. Tryby adresowania są zachowane.

ADD 1 @2
ADD #2 1
SUB -2 @-1

Punktacja

Dla każdej pary uczestników odbywa się każda możliwa bitwa. Ponieważ wynik bitwy zależy od względnych przesunięć obu programów, wypróbowywane jest każde możliwe przesunięcie (około 8000 z nich). Co więcej, każdy program ma szansę na pierwsze przesunięcie w każdym przesunięciu. Program, który wygrywa większość tych przesunięć, jest zwycięzcą pary.

Za każdą parę wygraną przez wojownika przyznaje się 2 punkty. Za każdą remis wojownik otrzymuje 1 punkt.

Możesz przesłać więcej niż jednego wojownika. Obowiązują typowe zasady wielokrotnego przesyłania, takie jak brak łączenia tagów, brak współpracy, tworzenia króla itp. W Core War i tak nie ma miejsca na to, więc nie powinno to być wielkim problemem.

Kontroler

Kod kontrolera wraz z dwoma łatwymi przykładowymi botami znajduje się tutaj . Ponieważ ta konkurencja (uruchamiana przy użyciu oficjalnych ustawień) jest całkowicie deterministyczna, tworzona tabela wyników będzie dokładnie taka sama jak oficjalna tabela wyników.

Przykład Bot

Oto przykładowy bot pokazujący niektóre funkcje języka.

main mov bomb #-1
     add @main main
     jmp #main 0
bomb dat 0 -1

Ten bot działa, powoli usuwając całą pozostałą pamięć w rdzeniu, zastępując ją „bombą”. Ponieważ bomba jest DATinstrukcją, każdy program, który dotrze do bomby, zostanie zniszczony.

Istnieją dwie etykiety linii, „główna” i „bomba”, które służą do zastąpienia liczb. Po wstępnym przetwarzaniu program wygląda następująco:

MOV 3 #-1
ADD @-1 -1
JMP #0 0
DAT 0 -1

Pierwszy wiersz kopiuje bombę do linii bezpośrednio nad programem. Następny wiersz dodaje wartość bomby ( 0 -1) do polecenia ruchu, a także demonstruje użycie @trybu adresowania. To dodanie powoduje, że polecenie move wskazuje nowy cel. Następne polecenie bezwarunkowo przeskakuje z powrotem na początek programu.


Aktualny ranking

24 - Turbo
22 - DwarvenEngineer
20 - HanShotFirst
18 - Dwarf
14 - ScanBomber
10 - Paranoid
10 - FirstTimer
10 - Janitor
10 - Evolved
6 - EasterBunny
6 - CopyPasta
4 - Imp
2 - Slug

Wyniki parami:

Dwarf > Imp
CopyPasta > Imp
Evolved > Imp
FirstTimer > Imp
Imp > Janitor
Imp > ScanBomber
Slug > Imp
DwarvenEngineer > Imp
HanShotFirst > Imp
Turbo > Imp
EasterBunny > Imp
Paranoid > Imp
Dwarf > CopyPasta
Dwarf > Evolved
Dwarf > FirstTimer
Dwarf > Janitor
Dwarf > ScanBomber
Dwarf > Slug
DwarvenEngineer > Dwarf
HanShotFirst > Dwarf
Turbo > Dwarf
Dwarf > EasterBunny
Dwarf > Paranoid
Evolved > CopyPasta
FirstTimer > CopyPasta
Janitor > CopyPasta
ScanBomber > CopyPasta
CopyPasta > Slug
DwarvenEngineer > CopyPasta
HanShotFirst > CopyPasta
Turbo > CopyPasta
CopyPasta > EasterBunny
Paranoid > CopyPasta
Evolved > FirstTimer
Evolved > Janitor
ScanBomber > Evolved
Evolved > Slug
DwarvenEngineer > Evolved
HanShotFirst > Evolved
Turbo > Evolved
EasterBunny > Evolved
Paranoid > Evolved
Janitor > FirstTimer
ScanBomber > FirstTimer
FirstTimer > Slug
DwarvenEngineer > FirstTimer
HanShotFirst > FirstTimer
Turbo > FirstTimer
FirstTimer > EasterBunny
FirstTimer > Paranoid
ScanBomber > Janitor
Janitor > Slug
DwarvenEngineer > Janitor
HanShotFirst > Janitor
Turbo > Janitor
Janitor > EasterBunny
Janitor > Paranoid
ScanBomber > Slug
DwarvenEngineer > ScanBomber
HanShotFirst > ScanBomber
Turbo > ScanBomber
ScanBomber > EasterBunny
ScanBomber > Paranoid
DwarvenEngineer > Slug
HanShotFirst > Slug
Turbo > Slug
EasterBunny > Slug
Paranoid > Slug
DwarvenEngineer > HanShotFirst
Turbo > DwarvenEngineer
DwarvenEngineer > EasterBunny
DwarvenEngineer > Paranoid
Turbo > HanShotFirst
HanShotFirst > EasterBunny
HanShotFirst > Paranoid
Turbo > EasterBunny
Turbo > Paranoid
Paranoid > EasterBunny

Najnowsza aktualizacja (nowe wersje Turbo i Paranoid) działała na starym laptopie około 5 minut. Chciałbym podziękować Ilmari Karonenowi za jego ulepszenia w kontrolerze . Jeśli masz lokalną kopię kontrolera, powinieneś zaktualizować swoje pliki.


Co się stanie, jeśli dwa konkurujące boty spróbują użyć tej samej etykiety?
mbomb007

1
@ mbomb007 Etykiety są przetwarzaniem wstępnym i są obliczane podczas analizowania pliku źródłowego bota. Twoje etykiety nie będą oddziaływać z żadnymi etykietami konkurencji.
PhiNotPi

1
@ mbomb007 Aby programy się nie nakładały. Nie planuję też dodawać żadnych funkcji do tej wersji, poza tymi dla Micro Core War.
PhiNotPi

1
@ mbomb007 Adresowanie pośrednie odwołuje się do tego samego pola, które tworzy odniesienie (1. lub 2.). Nie ma modyfikatorów instrukcji. Nie opieram tego wyzwania na standardzie z 94.
PhiNotPi

2
@ Thrax Powiem nie, że nie jesteś ograniczony do jednego zgłoszenia. Obowiązują typowe zasady wielokrotnego przesyłania (bez łączenia tagów itp.), Chociaż i tak nie ma zbyt wiele miejsca na współpracę w wojnach głównych.
PhiNotPi,

Odpowiedzi:


9

Krasnoludzki inżynier

Nowy i ulepszony krasnolud. Wygrywa ze wszystkimi innymi zgłoszonymi do tej pory. Fantazyjny, zoptymalizowany rozmiar kroku kroku jest tutaj prawdopodobnie przesadą.

        MOV bomb    @aim
aim     MOV bomb    @-6326
        SUB step    aim
step    JMZ #0      6328
        MOV 0       1
bomb    DAT 0       3164

Godne uwagi funkcje obejmują szybką pętlę bombardowania, która rzuca dwie bomby w czterech cyklach, dla średniej prędkości bombardowania 0,5 c w starym żargonie Core War, a także użycie JMZdo wykrywania, kiedy bombardowanie jest zakończone i nadszedł czas, aby przejść do planu B ( tutaj Imp).


Kiedyś grałem w Core War w latach 90. (niektórzy z was mogli zobaczyć podstawowy przewodnik, który napisałem w '97), więc pomyślałem, że byłoby interesujące zobaczyć, które stare strategie ze świata RedCode '88 / '94 mogłyby przydać się w tym wariancie.

Moje pierwsze myśli to:

  • Nie ma SPL, więc nie ma replikatorów (i nie ma pierścieni imp / spirali). To powinno wzmocnić bombowce. (Poza tym wszystkie te wymyślne strategie bombardowania zaprojektowane z myślą o replikatorach i spiralach impów? Zupełnie niepotrzebne i bezużyteczne. Po prostu bombarduj dowolnymi DATs.)

  • Z drugiej strony CMPskanowanie jest nadal potencjalnie szybsze niż bombardowanie, więc szybki skaner może mieć szansę.

  • Brak in / dekrementacji powoduje, że czyszczenie rdzenia jest bardzo wolne. W rzeczywistości rdzeń czysty w tym wariancie jest właściwie tylko bombowcem z (nieoptymalnym) rozmiarem kroku ± 1. To znowu boli skanery; jednak skaner jednorazowy → strategia bombowca może działać.

  • Szybkie skanery / szybkie bombowce (strategia z wczesnej fazy gry wykorzystująca rozwiniętą pętlę skanowania / bombardowania, dla tych, którzy nie są tak znani z żargonu Core War) są nadal potencjalnie przydatne, ale tylko w przypadku długich programów (którymi oni są, więc istnieje pewien rodzaj informacji zwrotnej efekt tutaj). Trudno powiedzieć, czy naprawdę warto.

  • System punktacji jest interesujący. Remisy zdobywają o połowę mniej punktów niż wygrana (zamiast 1/3, jak w tradycyjnej Wojnie Podstawowej), co czyni je bardziej atrakcyjnymi. Z drugiej strony, jedynym programem, który może zdobyć wiele remisów na tych zasadach, jest imp. (Również brak de / krokach sprawia imp bramy ciężko, więc nawet proste chochliki faktycznie zrobić mają szansę strzelenia krawat jeśli dotrą do ich przeciwnik żywy).

  • Ponadto, ponieważ końcowe rankingi zależą tylko od tego, które programy pokonałeś, a nie od tego, o ile je pokonałeś, zazwyczaj faworyzuje wpisy ogólne. Lepiej jest po prostu ledwo pokonać wszystkich przeciwników, niż całkowicie zniszczyć ich połowę i po prostu ledwo przegrać z resztą.

  • Ponieważ kod jest publiczny, zawsze można znaleźć program, który pokona każde wcześniejsze zgłoszenie - być może nawet kilka z nich - bez względu na to, jak ogólnie są dobre. Takie sztuczki (takie jak dostosowanie wielkości kroku, aby uderzyć przeciwnika tuż przed nim) mogą łatwo wydawać się tanie. I oczywiście docelowy gracz zawsze może po prostu przesłać nową wersję z różnymi stałymi.

Tak czy inaczej, wynik tego wszystkiego jest taki, że zdecydowałem, że powinienem spróbować napisać albo szybki bombowiec, albo bardzo szybki skaner, a może przyczepić do niego szybki skaner / bombowiec. Spośród tych opcji szybki bombowiec wydawał się najprostszy i najprawdopodobniej zadziałał.

W tym momencie spędziłem zbyt wiele czasu na ulepszaniu i optymalizacji kodu interpretera PhiNotPi, ponieważ pomyślałem, że prawdopodobnie przeprowadzę wiele prób brutalnej siły, aby zoptymalizować stałe. Tak się składa, że ​​nigdy nie musiałem tego robić - powyższy kod jest właściwie pierwszą wersją, która faktycznie działała (po kilku nieudanych próbach, które popełniły samobójstwo z powodu głupich błędów).


Sztuczka, która sprawia, że ​​mój bombowiec jest szybki, wykorzystuje adresowanie pośrednie, aby rzucić dwie bomby na każdą ADD. Oto jak to działa:

  1. W pierwszym cyklu wykonujemy MOV bomb @aim. Spowoduje to skopiowanie bombinstrukcji do dowolnego miejsca w rdzeniu pola B aimpunktów (początkowo dokładnie 6326 instrukcji wcześniej aimlub 6328 instrukcji wcześniej step; zobaczysz, dlaczego te liczby mają znaczenie później).

  2. W następnym kroku wykonujemy aimsamą instrukcję! Na pierwszym przejeździe, to wygląda tak: MOV bomb @-6326. Tak więc kopiuje bombdo miejsca, na które wskazuje pole B instrukcji na 6326 liniach przed sobą.

    Co jest wcześniej w 6326 liniach aim? To kopia, bombktórą umieściliśmy tam jeden cykl wcześniej! I właśnie tak zorganizowaliśmy rzeczy, aby pole B bombmiało niezerową wartość, więc nowa bomba nie zostanie skopiowana na starą, ale w pewnej odległości (w rzeczywistości tutaj odległość wynosi 3164, co stanowi połowę naszego nominalnego rozmiaru kroku 6328; ale inne przesunięcia mogłyby działać, być może nawet lepiej).

  3. W następnym cyklu dostosowujemy nasz cel SUB step aim, który odejmuje wartości stepinstrukcji (która również jest skokiem, który wykonamy w następnej kolejności, chociaż może to być po prostu prosta DAT) aim.

    (Jeden szczegół, aby pamiętać, jest to, że my niby chcą wartości A stepna zero, tak że będziemy nadal rzucać te same bomby na następnej iteracji Mimo że nie jest to bezwzględnie konieczne, chociaż;. Tylko bomby rzucone przez pierwsze instrukcji konieczności posiadania ich B-field równą 3164, reszta może być cokolwiek).

  4. Następnie JMZsprawdź, czy instrukcja 6328 od niej odchodzi, jest wciąż równa zero, a jeśli tak, przeskakuje z powrotem na górę kodu. Teraz 6328 jest wielkością kroku naszego bombowca i dzieli się przez 8 (ale nie 16); tak więc, jeśli tylko będziemy rzucać bombami co 6328 kroków, w końcu wrócilibyśmy do miejsca, w którym zaczęliśmy, zbombardowując co ósme instrukcje w rdzeniu (i z dodatkowymi bombami przesuniętymi o 3163 = 6328/2 ≡ 4 (mod 8) , trafilibyśmy co czwartą instrukcję).

    Ale zaczęliśmy nasz bieg bombardowania w 6328 instrukcją przedJMZ i cofnęła przez -6328 przy każdej iteracji, więc mamy zamiar bombardować lokalizacja 6328 kroki po tym JMZtylko jednej iteracji, zanim będziemy uderzyć JMZsię. Kiedy więc JMZwykryje bombę po 6328 instrukcjach po niej, jest to znak, że pokryliśmy jak najwięcej jądra, jak możemy, nie uderzając się w siebie, i powinniśmy przejść na strategię tworzenia kopii zapasowych, zanim się zabijemy.

  5. Jeśli chodzi o strategię tworzenia kopii zapasowych, to zwykły stary MOV 0 1chochlik, ponieważ na razie nie mogłem wymyślić nic lepszego. Z mojego punktu widzenia, jeśli zbombardowaliśmy co czwarte miejsce rdzenia i nadal nie wygrywamy, prawdopodobnie walczymy z czymś bardzo małym lub bardzo defensywnym, i równie dobrze możemy spróbować przetrwać i zadowolić się remisem. Jest w porządku, ponieważ takie małe lub defensywne programy na ogół nie są zbyt dobre w zabijaniu czegokolwiek innego, więc nawet jeśli wygramy tylko kilka walk przypadkiem, prawdopodobnie nadal wyjdziemy na prowadzenie.


Ps. Jeśli ktoś tego chce, oto mój nieco ulepszony kod turniejowy PhiNotPi . Jest około dwa razy szybszy, zapisuje stare wyniki bitwy, dzięki czemu nie trzeba ich ponownie uruchamiać, i naprawia coś, co uważam za drobny błąd w obliczaniu wyników bitwy. Zmiany zostały scalone do głównej wersji przez PhiNotPi. Dzięki!


1
Właśnie dlatego, że punktacja testuje KAŻDĄ możliwą kombinację lokalizacji początkowych programu, a program, który zdobył najwięcej punktów, wygrywa. To sprawia, że ​​remisy są niemożliwe lub całkowicie niekorzystne, ponieważ dopóki program nigdy nie zabije się i zbombarduje przynajmniej jeden adres raz, pokona impa, mając jedną wygraną, a reszta remisów.
mbomb007

9

Widok wykresu

Może to być użyte jako narzędzie do debugowania. Wyświetla rdzeń i pokazuje lokalizację odtwarzacza. Aby go użyć, musisz wywołać go z kodu. Mam również zmodyfikowaliśmy pod warunkiem Game.java, że automatycznie wyświetla GraphView.

PhiNotPi i Ilmari Karonen niedawno zmienili kontroler. Ilmari Karonen był na tyle miły, że zapewnił zaktualizowaną wersję GameView w tej lokalizacji .

import javax.swing.*;
import java.awt.*;

public class GameView extends JComponent{

    final static Color[] commandColors = new Color[]{
            Color.black, //DAT
            Color.blue,  //MOV
            Color.blue,  //ADD
            Color.blue,  //SUB
            Color.blue,  //JMP
            Color.blue,  //JMZ
            Color.blue,  //CMP
    };

    final static Color[] specialColors = new Color[]{
            new Color(0,0,0),
            new Color(190, 255, 152),
            Color.yellow,
            new Color(0, 93, 14),
            new Color(96, 92, 4),
            new Color(0, 93, 14),
            new Color(96, 92, 4),
            new Color(0, 93, 14),
            new Color(96, 92, 4)
    };

    final static Color playerOneColor = Color.green;
    final static Color playerTwoColor = Color.white;

    final Game game;

    int playerOneLocation;
    int playerTwoLocation;

    final static int width = 128;
    final static int height = 64;

    public GameView(Game game) {
        this.game = game;
    }

    @Override
    public void paint(Graphics g) {
        int pixelWidth = getSize().width;
        int pixelHeight = getSize().height;
        if (width > pixelWidth){
            pixelWidth = width;
            setSize(width, pixelHeight);
        }
        if (height > pixelHeight){
            pixelHeight = height;
            setSize(pixelWidth, height);
        }
        int squareWidth = Math.min(pixelWidth / width, pixelHeight / height);
        for (int x = 0; x < squareWidth * width; x += squareWidth){
            for (int y = 0; y < squareWidth * height; y += squareWidth){
                int index = (y / squareWidth) * width + (x / squareWidth);
                Color color = commandColors[game.core[index][0]];
                if (game.coreData[index] != 0){
                    color = specialColors[game.coreData[index]];
                }
                if (index == playerOneLocation){
                    color = playerOneColor;
                }
                if (index == playerTwoLocation){
                    color = playerTwoColor;
                }
                g.setColor(color);
                g.fillRect(x, y, squareWidth, squareWidth);
            }
        }
    }

    public void setLocations(int p1loc, int p2loc){
        this.playerOneLocation = p1loc;
        this.playerTwoLocation = p2loc;
    }
}

Zmodyfikowana Game.java:

import javax.swing.*;
import java.util.Random;
import java.util.ArrayList;
import java.util.Arrays;
/**
 * This runs a game of Core Wars between two players.  It can be called mutiple times.
 * 
 * @author PhiNotPi 
 * @version 3/10/15
 */
public class Game
{
    final Player p1;
    final Player p2;
    final int coreSize;
    final int coreSizeM1;
    final int maxTime;
    final int debug;
    public int[][] core;
    public int[] coreData; //Used in debugging.
    int offset1;
    int offset2;
    Random rand;
    ArrayList<int[]> p1code;
    ArrayList<int[]> p2code;
    int p1size;
    int p2size;
    GameView gameView;
    int time = 1000000; //Time in nanoseconds between frames
    public Game(Player A, Player B, int coreSize, int maxTime, int debug)
    {
        p1 = A;
        p2 = B;

        coreSize--;
        coreSize |= coreSize >> 1;
        coreSize |= coreSize >> 2;
        coreSize |= coreSize >> 4;
        coreSize |= coreSize >> 8;
        coreSize |= coreSize >> 16;
        coreSize++;

        this.coreSize = coreSize;
        this.coreSizeM1 = coreSize - 1;
        this.maxTime = maxTime / 2;
        this.debug = debug;
        core = new int[coreSize][5];
        rand = new Random();
        p1code =  p1.getCode();
        p1size = p1code.size();
        p2code =  p2.getCode();
        p2size = p2code.size();
        if (debug == 1){
            gameView = new GameView(this);
            JFrame frame = new JFrame("Game");
            frame.add(gameView);
            frame.setVisible(true);
            frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
            frame.setSize(128, 64);
            coreData = new int[coreSize];
        }
    }

    public int runAll()
    {
        int sum = 0;
        for(int i = 0; i < coreSize - p1size - p2size; i++)
        {
            sum += run(i) - 1;
        }
        if(sum > 0)
        {
            return 1;
        }
        if(sum < 0)
        {
            return -1;
        }
        return 0;
    }

    public int run()
    {
        return run(rand.nextInt(coreSize - p1size - p2size + 1));
    }

    public int run(int deltaOffset)
    {
        core = new int[coreSize][5];
        //offset1 = rand.nextInt(coreSize);
        offset1 = 0;
        for(int i = 0; i != p1size; i++)
        {
            //System.arraycopy(p1.getCode().get(i), 0, core[(offset1 + i) % coreSize], 0, 5 );
            int[] line = p1code.get(i);
            int loc = (offset1 + i) & coreSizeM1;
            core[loc][0] = line[0];
            core[loc][1] = line[1];
            core[loc][2] = line[2];
            core[loc][3] = line[3];
            core[loc][4] = line[4];
            if (debug != 0){
                coreData[loc] = 1;
            }
        }
        offset2 = offset1 + p1size + deltaOffset;
        for(int i = 0; i != p2size; i++)
        {
            //System.arraycopy(p2.getCode().get(i), 0, core[(offset2 + i) % coreSize], 0, 5 );
            int[] line = p2code.get(i);
            int loc = (offset2 + i) & coreSizeM1;
            core[loc][0] = line[0];
            core[loc][1] = line[1];
            core[loc][2] = line[2];
            core[loc][3] = line[3];
            core[loc][4] = line[4];
            if (debug != 0){
                coreData[loc] = 2;
            }
        }

        int p1loc = offset1 & coreSizeM1;
        int p2loc = offset2 & coreSizeM1;
        for(int time = 0; time != maxTime; time++)
        {
            if(debug != 0)
            {
                //printCore(p1loc,p2loc);
                //System.out.println("p1loc " + p1loc);
                //System.out.println("offset " + offset1);
                gameView.setLocations(p1loc, p2loc);
                gameView.repaint();
                try {
                    Thread.sleep(time / 1000000, time % 1000000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            if(core[p1loc][0] == 0)
            {
                return 0;
            }
            p1loc = execute(p1loc, offset1, 1);

            if(debug != 0)
            {
                //printCore(p1loc,p2loc);
                //System.out.println("p2loc " + p2loc);
                //System.out.println("offset " + offset2);
                gameView.setLocations(p1loc, p2loc);
                gameView.repaint();
                /*try {
                    Thread.sleep(time);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }*/
            }
            if(core[p2loc][0] == 0)
            {
                return 2;
            }
            p2loc = execute(p2loc, offset2, 2);

        }
        return 1;
    }
    public int execute(int ploc, int offset, int player)
    {
        int line1 = offset + core[ploc][3];
        if(core[ploc][1] != 0)
        {
            line1 += ploc - offset;
        }
        if(core[ploc][1] == 2)
        {
            line1 += core[line1 & coreSizeM1][3];
        }
        int line2 = offset + core[ploc][4];
        if(core[ploc][2] != 0)
        {
            line2 += ploc - offset;
        }
        if(core[ploc][2] == 2)
        {
            line2 += core[line2 & coreSizeM1][4];
        }
        line1 = line1 & coreSizeM1;
        line2 = line2 & coreSizeM1;
        int opcode = core[ploc][0];
        ploc = (ploc + 1) & coreSizeM1;
        //String opDescription = "";
        if(opcode == 1)
        {
            core[line2][0] = core[line1][0];
            core[line2][1] = core[line1][1];
            core[line2][2] = core[line1][2];
            core[line2][3] = core[line1][3];
            core[line2][4] = core[line1][4];
            if (debug != 0) {
                coreData[line2] = player + 2;
            }
            return ploc;
            //opDescription = "Moved from " + line1 + " to " + line2;
        }
        if(opcode == 2)
        {
            core[line2][3] += core[line1][3];
            core[line2][4] += core[line1][4];
            if (debug != 0) {
                coreData[line2] = player + 4;
            }
            return ploc;
            //opDescription = "Added " + line1 + " to " + line2;
        }
        if(opcode == 3)
        {
            core[line2][3] -= core[line1][3];
            core[line2][4] -= core[line1][4];
            if (debug != 0) {
                coreData[line2] = player + 6;
            }
            return ploc;
                //opDescription = "Subtracted " + line1 + " to " + line2;
        }
        if(opcode == 4)
        {
            ploc = line1;
            return ploc;
                //opDescription = "Jumped to " + line1;
        }
        if(opcode == 5)
        {
                if(core[line2][3] == 0 && core[line2][4] == 0)
                {
                    ploc = line1;
                    //opDescription = "Jumped to " + line1;
                }
                else
                {
                    //opDescription = "Did not jump to " + line1;
                }
                return ploc;
        }
        if(opcode == 6)
        {
            if(core[line1][3] == core[line2][3] && core[line1][4] == core[line2][4])
            {
                //opDescription = "Did not skip because " + line1 + " and " + line2 + " were equal.";
            }
            else
            {
                ploc = (ploc + 1) & coreSizeM1;
                //opDescription = "Skipped because " + line1 + " and " + line2 + " were not equal.";
            }
            return ploc;
        }
        if(debug != 0)
        {
            //System.out.println(opDescription);
        }
        return ploc;
    }
    /*public void printCore(int p1loc, int p2loc)
    {
        int dupCount = 0;
        int[] dupLine = new int[]{0,0,0,0,0};
        for(int i = 0; i < core.length; i++)
        {
            int[] line = core[i];
            if(Arrays.equals(line, dupLine) && i != p1loc && i != p2loc)
            {
                if(dupCount == 0)
                {
                    System.out.println(Player.toString(line));
                }
                dupCount++;
            }
            else
            {
                if(dupCount == 2)
                {
                    System.out.println(Player.toString(dupLine));
                }
                else if(dupCount > 2)
                {
                    System.out.println("    " + (dupCount - 1) + " lines skipped.");
                }
                System.out.println(Player.toString(line));
                if(i == p1loc)
                {
                    System.out.print(" <- 1");
                }
                if(i == p2loc)
                {
                    System.out.print(" <- 2");
                }
                dupLine = line;
                dupCount = 1;
            }
        }
        if(dupCount == 2)
        {
            System.out.println(Player.toString(dupLine));
        }
        else if(dupCount > 2)
        {
            System.out.println("    " + (dupCount - 1) + " lines skipped.");
        }
    }*/
}

Wygląda na to, że zmodyfikowałeś także Player. Dostaję./Game.java:275: error: method toString in class Object cannot be applied to given types; System.out.println(Player.toString(line)); ^ required: no arguments found: int[]
AShelly

@AShelly Przepraszamy za to. Powinienem był skomentować tę printCore()metodę.
TheNumberOne

9

Turbo

main   add three target
test   jmz -1 @target
bomb   mov three @target
       sub j1 target 
       mov jump @target
       sub j1 target 
       mov copy @target
       sub j1 target
two    mov decr @target
j1     jmp @target 1
target dat -8 -8   
decr   sub #two 3
copy   mov 2 @2
jump   jmp -2 0
three dat -9 -9

Moja druga próba CoreWar. Zaprojektowany, aby pokonać Dwarf. Skanuje dane o 3, a następnie umieszcza bombę co 2. Każdy etap przebiega tylko w 3 instrukcjach, mając nadzieję, że bomby krasnoluda tęsknią.

NOWOŚĆ Turbo ++ : teraz ulepszona. Skanuje wstecz, aż znajdzie dane, a następnie się tam przenosi, a następnie bombarduje wstecz. Istnieje nadzieja, że ​​ruch ten albo zapycha przeciwnika, albo znajduje się w miejscu zbombardowanym, a zatem bezpiecznym (ish).

... A edycja, która sprawia, że ​​skanowanie jest rzadsze, sprawia, że ​​bije wszystkich!


Wydaje się, że pokonuje znacznie więcej niż tylko krasnoluda. Gratulacje! Myślę, że mógłbyś osiągnąć trzecie miejsce, gdybyś tylko mógł pokonać Imp.
Ilmari Karonen,

Zaktualizowałem ten, ale w rzeczywistości jest to dość duża ewolucja od poprzedniej. Czy powinienem zamiast tego zrobić nowy wpis?
AShelly,

Nie zamierzam mówić w imieniu PhiNotPi, ale zgaduję, że to zależy od ciebie. Wykonanie aktualizacji w miejscu oznacza po prostu wycofanie starego wpisu. W każdym razie jeszcze więcej gratulacji za unikanie bomb na twojej drodze do trzeciego miejsca! Myślę, że twój jest jak dotąd jedynym wpisem do pokonania DwarvenEngineer parami.
Ilmari Karonen,

Dobra robota ;). jesteś tym, który pokonałeś teraz!
Hit

8

Krasnolud

Wspólny i prosty program przedstawiający karła rzucającego kamieniami. Umieszcza DATinstrukcję co cztery adresy.

add 2 3
mov 2 @2
jmp -2 #4
dat #0 #4

EDYCJA: Naprawiono adresowanie. Najwyraźniej tryby adresowania różnią się od specyfikacji, z którą powiązany jest PO.


Myślę, że to „dodaj nr 3 3” do pierwszego wiersza, prawda?
Hit

@Hit Nope. Chcę trafić co 4 adres. Mógłbym użyć add 3 3, ale wtedy podwoiłoby każdą pętlę zamiast dodawać, a to nie byłoby przydatne. #4jest natychmiastowy, więc dodaje liczbę 4do drugiej wartości w adresie następującym 3po bieżącym adresie.
mbomb007

Myślę, że źle interpretujesz #tryb adresowania w wyzwaniu. Jak podano w specyfikacji, zmieniłem #tryb adresowania.
PhiNotPi

Powinieneś postąpić tak: „dodaj 2 3 mov 2 @ 2 jmp -2 4 dat 0 4”
Hit

Przy prawidłowym zachowaniu nawet pokonał ewolucję
Hit

7

Ewoluował

Naprawdę nie rozumiem, jak to działa. Wydaje się, że konstruuje swój kod źródłowy przed zrobieniem czegokolwiek. Chciałbym, żeby ktoś wyjaśnił mi, jak to działa.

Po przestudiowaniu go odkryłem, że jest to po prostu zmodyfikowany karzeł ze strażnikiem chochlików. Zamiast bombardować wrogów DATinstrukcjami, tasuje kod przeciwnika. Bomba również co dwa rejestry zamiast co cztery rejestry. Mając wystarczająco dużo czasu, bez wątpienia sam się zniszczy.

MOV -2 #-1
MOV #4 -9
SUB -5 #6
MOV #1 1
MOV #-6 #4
SUB @8 @7
JMP -3 @4
DAT #-4 8
JMP -1 9
JMP 5 #-10
CMP @-1 #0
SUB 3 #-10
JMP @10 #-9
JMZ #1 10
MOV #3 2
ADD @9 @-3
CMP #-3 @7
DAT @0 @-2
JMP @-7 #6
DAT @-8 -6
MOV @0 #9
MOV #2 1
DAT @6882 #-10
JMP @3 4
CMP @8 2
ADD -7 @11
ADD @1 #-9
JMZ @-5 7
CMP 11 5526
MOV @8 6
SUB -6 @0
JMP 1 11
ADD @-3 #-8
JMZ @-14 @-5
ADD 0 @-8
SUB #3 @9
JMP #-1 5
JMP #9 @1
CMP -9 @0
SUB #4 #-2
JMP #-8 5
DAT -1 @-10
MOV 6 #2
CMP @-11 #-14
ADD @4 @-3
MOV @5 #-6
SUB -3 -2
DAT @-10 #-1
MOV #-13 #-6
MOV #1 5
ADD 5 #-5
MOV -8 @-1
DAT 0 10
DAT #5 #7
JMZ 6 -5
JMZ -12 -11
JMP 5 @-7
MOV #7 -3
SUB #-7 @-3
JMP -4 @-11
CMP @-5 #-2
JMZ @-1 #0
ADD #3 #2
MOV #5 @-6

1
Skąd to masz?
PyRulez

4
@PyRulez Jest generowany komputerowo za pomocą algorytmu genetycznego.
TheNumberOne

1
Wygląda na to, że wykonanie nie przebiega dalej niż linia nr 6, ponieważ tam przeskakuje z powrotem do programu. Uważam, że powodem sukcesu jest większa liczba ruchów / pętli niż u konkurencji.
PhiNotPi,

6

Nowicjusz

Jeśli to działa, powinien spróbować zająć pozycję na początku rdzenia i stworzyć obronę

main MOV 5 #0
     ADD #data #main
     CMP #main #max
     JMP #0 0
     JMP #main 0
     MOV #data #100
     ADD #data -1
     JMP -2 0
data DAT 1 1
max  DAT 8 3

Nie działa tak, jak myślałem, że tak będzie: #0odnosi się do początku twojego programu (tj. Tego samego, co #main), a nie do początku rdzenia (który zresztą nie jest tak naprawdę sensowną koncepcją - rdzeń jest okólnik, Twój kod nie może określić, gdzie zaczyna się lub kończy). Co się dzieje, to twoja pierwsza instrukcja (main ) zastępuje się MOV #data #100ciągiem, po którym kod skutecznie zmienia się w 0,25 c (= jedna instrukcja na cztery cykle) do przodu, usuwając rdzeń.
Ilmari Karonen,

@IlmariKaronen Och, dziękuję za wyjaśnienie. Pomyliłem się #0na początku rdzenia. 5 pierwszych instrukcji jest wtedy całkowicie bezużytecznych.
Thrax,

6

CopyPasta

Ten prosty program nigdy nie uczestniczył w CoreWar, tylko próbuje samodzielnie skopiować i wkleić, a następnie wykonać kopię. Może nie mieć poprawnego zachowania, proszę powiedz mi, czy tak jest.

Jest zbyt pacyfistyczny i tak naprawdę nie może wygrać.

MOV 6 0
MOV @-1 @-1
CMP @-2 3
JMP 4242 0
SUB -3 -4
JMP -4 0
DAT 0 4244

Ta bieżąca edycja prawdopodobnie nie będzie w kolejnej aktualizacji tabeli liderów (teraz prowadzę turniej). Stara wersja zdobywała jednak wstępne wyniki (mały rozmiar rdzenia).
PhiNotPi

Okay :) Starsza wersja nie wychodzi z pętli1, to nie jest tak naprawdę pożądane zachowanie, staram się to naprawić.
Hit

Obecna wersja wydaje się zepsuta. Nie wiem jeszcze dlaczego.
PhiNotPi

1
Zmieniłem narzędzia do debugowania, więc teraz mogę zdiagnozować problem. To, co się dzieje, polega na tym, że program kopiuje tylko drugą połowę samego siebie (począwszy od JMP loop 0). Następnie, gdy przeskakuje do miejsca, w którym powinien być początek kopii, jest to po prostu puste miejsce i traci.
PhiNotPi

2
Proszę zignorować mój wcześniejszy (teraz usunięty) komentarz; Przetestowałem niepoprawną wersję twojego kodu (jak na ironię, z powodu błędu kopiowania i wklejenia), dlatego tak źle to dla mnie działało.
Ilmari Karonen

6

Dozorca

Powinien sprawdzić, czy poniższe adresy są puste, a jeśli nie, czyści je (tym samym, mam nadzieję, usunięcie bota przeciwnika).

Edycja: Ta nowa wersja powinna być szybsza (teraz, gdy poprawnie zrozumiałem JMZpolecenie i @odniesienie).

JMZ 2 6
MOV 4 @-1
ADD 2 -2
JMP -3 0
DAT 0 1
DAT 0 0

Czy woźny popełnia samobójstwo w pierwszej JMZ? Powinno to być co najmniej JMZ 2 8. Nawiasem mówiąc, używając @ możesz zmniejszyć dwa dodawać do tylko jednego. Coś w stylu: „JMZ 2 @ 5 MOV 5 @ 4 ADD 2 3 JMP -3 0 DAT 0 1 DAT 0 2 DAT 0 0” (niesprawdzone)
Hit

@ Hit Nie przeskakuje, bo tam jest adres 2 ADD 3 -2, ale masz rację, że powinien go zmienić, tak myślę.
mbomb007

Tak, źle odczytałem instrukcję JMZi pomyślałem, że JMZ A Bsprawdzam Ai skaczę do Bif 0, kiedy najwyraźniej jest odwrotnie. Dzięki, że zauważyłem, bo tego nie zrobiłem :)
plannapus

5

ScanBomber

Usuń moje komentarze przed kompilacją. Skanuje przez chwilę, a następnie bombarduje, gdy znajdzie program. Jednak pewnie nadal przegra z moim Krasnoludem.

scan add #eight #range  ; scan
jmz #scan @range
sub #six #range
fire mov #zero @range   ; bombs away! (-6)
add #two #range
mov #zero @range
add #two #range
mov #zero @range
add #two #range
mov #zero @range        ; (+0)
add #two #range
mov #zero @range
add #two #range
mov #zero @range
add #two #range
mov #zero @range
add #two #range
mov #zero @range        ; (+8)
range jmp #scan 6
two dat 0 2
six dat 0 6
zero dat 0 0
eight dat 0 8

OP zdefiniował #zupełnie inaczej niż specyfikację (przeczytaj link, do którego linkował), muszę jeszcze naprawić ten program.
mbomb007

@TheBestOne Myślę, że to naprawiłem. Czy teraz wygląda na to, że ma to sens? Czy też muszę umieszczać #przed każdym odniesieniem zero? Tak, myślę, że muszę ...
mbomb007

Teraz działa dobrze. Pokonuje każdego bota z wyjątkiem krasnoluda i impa.
TheNumberOne

@ TheBestOne Dwarf jest zbyt mały i zostałby wykryty tylko w 50% możliwego umiejscowienia programu. Prawdopodobnie przegrywa z Impem, ponieważ bombarduje się po obejrzeniu całej pamięci.
mbomb007

5

Han Shot First (v2)

Uznałem, że konkurencja mogłaby wykorzystać więcej różnorodności, więc oto mój drugi wpis: CMPskaner jednorazowy .

To jest wersja 2 , z ulepszoną obroną przeciw Imp - może teraz pokonać Imp, choćby tylko o jeden punkt. Nadal przegrywa z Krasnoludzkim Inżynierem, ale jak dotąd bije wszystko inne, stawiając go obecnie na pierwszym miejscu.

scan    ADD bomb    aim
aim     CMP 17      12
        JMZ scan    #-3
loop    MOV bomb    @aim
        ADD step    aim
step    JMP loop    #2
bomb    DAT 10      10

Działa poprzez porównywanie sąsiednich lokalizacji rdzenia w odległości 5 kroków od siebie, w odstępach co 10 kroków, aż do znalezienia różnicy. Kiedy to robi, zaczyna rzucać bomby w 2-etapowych odstępach, aż zabije przeciwnika lub zapętli się wokół rdzenia, aby dotrzeć do siebie.

Jeśli skanowanie nie znajdzie niczego innego, ostatecznie zapętli się, znajdzie swój kod i go zaatakuje. Byłoby to samobójcze, gdyby nie przypadek, że pierwsza bomba wylądowała dokładnie naaim linii, powodując zrzucenie następnej bomby o 12 pozycji (zamiast zwykłych 2) w dół rdzenia, wygodnie pomijając kod. (Dzieje się tak również z 50% prawdopodobieństwem, jeśli skan znajdzie coś, ale nie zabije przeciwnika.) Ponieważ rozmiar rdzenia jest wielokrotnością dwóch, tak się stanie, jeśli bombardowanie zapętli się, eliminując potrzebę dalsza strategia tworzenia kopii zapasowych.

(Ta sztuczka polegająca na samobombardowaniu była początkowo czystym zbiegiem okoliczności - zaplanowałem zupełnie inny sposób przejścia ze skanowania do trybu bombardowania, jeśli nic nie zostanie znalezione, ale kiedy po raz pierwszy przetestowałem kod, stały się po prostu słuszne działaj w ten sposób i postanowiłem się z tym trzymać).



4

Ślimak

     mov    ones    @-1024
     mov    from    -3
     mov    here    -3
loop mov    @-5 @-4
     add    ones  -5
     jmz    -17 -6
     add    ones  -8    
     jmp    loop    42
ones dat    1   1
from dat    2   2
here dat    -11 -11

Przeszukuje przestrzeń pamięci do tyłu. Czasami rzuca bombę daleko.


3

Zajączek wielkanocny

Lubi skakać do tyłu :)

loop mov 0 -10
     add data loop
     cmp -7 data
     jmp -13 0
     jmp loop 0
data dat 1 1

3

Paranoidalny

Niby kopia makaronu, ale sprawdzi, czy kod został zmodyfikowany przez bombardowanie. Jeśli tak, skopiuj obok krasnoluda i go wykonaj. Jeśli uda mi się ponownie utworzyć GameView, spróbuję zmienić niektóre stałe.

copy    MOV data copy
loop    MOV @-1 @-1
    CMP @copy end
out JMP check 0
    SUB loop copy
    JMP loop 0
data    DAT 0 4109
check   MOV data copy
loop2   CMP @copy @copy
    JMP ok 0
    MOV aah 2
ok  CMP @copy end
    JMP 4098 0
    SUB loop copy
    JMP loop2 0
panic   MOV end copy
    MOV jump out
    JMP loop 0
jump    JMP 4124 0
dwarf   ADD 2 bomb
    MOV bomb @bomb
    JMP dwarf 4
bomb    DAT 0 4
aah JMP 3 0
end DAT 19 4127

Okej, właściwie to działa, a ja po prostu popsułem, dzięki za nowy bieg;)
Hit
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.