// Gomoku - Eryk Kopczynski // Idea strategii: // --------------- // Kazde pole na planszy jest elementem (co najwyzej) 20 linii (piatek): // w kazdym z czterech dostepnych kierunkow mamy 5 mozliwych piatek. // Przykladowo, na ponizszym rysunku: // .......................... // .......................... // .......................... // .......................... // .......XOOOd.......O.O.X.. // ....................XXO... // ..XXc..............OXeOX.. // ....X...............XOO... // ....X........ba....O.X.X.. // ............O............. // .......................... // .......................... // .......................... // .......................... // (pola a,b,c,d,e sa puste) // - Pole "a" nalezy do 20 calkowicie pustych linii; // - Pole "b" nalezy do 16 calkowicie pustych linii i 4 linii z // pojedynczym kolkiem; // - Pole "c" nalezy do 12 pustych linii, 2 linii z pojedynczym X, i // 6 linii z podwojnym X; // - Pole "d" nalezy do 15 pustych linii, po jednej linii z pojedynczym, // podwojnym i potrojnym O, 1 linii z pojedynczym X, i 1 linii // z trzema O i jednym X; // - Pole "e" nalezy do 20 linii, na kazdej z nich sa i X, i O. // Idea jest taka: stawiamy nasz pionek na puste pole majace najwieksza // wartosc; wartoscia pola jest suma wartosci wszystkich 20 linii // przechodzacych przez to pole. Wartosc linii zalezy od jej zawartosci. // Linie zawierajace pionki obu graczy sa bezwartosciowe. Nie ma szans, by // ktoremus z graczy udalo sie tam utworzyc linie. // Linie calkowicie puste maja bardzo niska wartosc. // Linie, na ktorych sa juz 4 nasze pionki, maja dominujaca wartosc. // Zawsze bedziemy wstawiac tam pionek, jesli jest taka linia. // Linie, na ktorych sa juz 4 pionki przeciwnika, maja mniejsza wartosc, // ale rowniez dominujaca w stosunku do pozostalych. Zawsze bedziemy tam // stawiac pionek, chyba, ze zachodzi poprzednia sytuacja. // Pozostale pola maja tym wieksza wartosc, im wiecej pionkow tam sie // znajduje (i zalezna od tego, czyje sa to pionki). // W naszym przykladzie: // Pole "e" ma wartosc zerowa, nie ma po co cos tam stawiac. // Ruch na pole "b" ma wartosc dosyc niska, ale wyzsza niz ruch na pole // "a". Oznacza to, ze na poczatku gry gracze beda woleli sie ruszac // tam, gdzie cos juz sie dzieje w bezposrednim sasiedztwie. // Ruch na pole "d" ma wprawdzie wysoka wartosc ze wzgledu na obecnosc // linii z trzema kolkami, ale za to ruch na pole "c" ma az szesc linii // z dwoma krzyzykami. Lepszym ruchem dla X w tej sytuacji jest ruch na // pole "c" (jak wiadomo doswiadczonym graczom); wartosc linii XX // w stosunku do OOO powinna odzwierciedlac ten wybor --- byc wprawdzie // nizsza, ale na tyle duza, by 5 XX bylo lepsze niz jedno OOO. Gdyby w // okolicy pola "d" byly natomiast jeszcze jakies inne kolka, byc moze // ruch wlasnie tam mialby wieksza wartosc. (Jesli by tam byly krzyzyki, // to tez... ale przypuszczalnie gracz O musialby wowczas zareagowac na // nowo wytworzone zagrozenia, wiec pole "c" nie zostaloby zepsute). // Jak dobrac najlepsze wartosci linii? Nie wiadomo... Tutaj zostalo to // zrobione przy uzyciu algorytmu genetycznego, czyli symulacji doboru // naturalnego. W skrocie oznacza to, ze programy majace rozne wartosci // tych parametrow bija sie ze soba, i najlepsze przezywaja do nastepnej // rundy, przy okazji krzyzujac sie ze soba i mutujac. #include #include #include #include using namespace std; typedef long double ld; // Pole o wspolrzednych (x,y) jest reprezentowane jako board[(3+x)+23*(3+y)]. // Mozliwe wartosci board[pos] sa nastepujace: // 0 = puste pole // 1 = pionek nasz (zazwyczaj) // 5 = pionek przeciwnika // 25 = straznik // Straznicy to elementy tablicy nie odpowiadajace zadnemu polu od (1,1) // do (15,15); linie wychodzace poza plansze rowniez liczymy, ale maja one // wartosc 0 --- upraszcza to implementacje. // Dlaczego nie 0,1,2,3? Zeby z sumy wartosci pol na linii (i z faktu, ze // co najmniej jedno pole na linii jest puste) mozna bylo jednoznacznie // wywnioskowac, jakie wartosci sumujemy. int board[529]; struct player { // Parametry okreslajace strategie gracza (genom). ld q[7]; // Wartosc ruchu dla kazdej sumy linii. ld delta[125]; // [Algorytm genetyczny] Liczba punktow zdobyta w turnieju. int score; // Ustalanie tablicy delta. void setDelta() { for(int k=0; k<125; k++) delta[k] = 0; delta[4] = 10000; delta[20] = 1; delta[3] = q[0]; delta[15] = q[4]; delta[2] = delta[3] * q[1]; delta[10] = delta[15] * q[5]; delta[1] = delta[2] * q[2]; delta[5] = delta[10] * q[6]; delta[0] = (delta[1]+delta[5]) * q[3]; // Dla wszystkich pozostalych 0 } // Odwrocenie roli, czyli szukanie najlepszego ruchu dla gracza "5", nie // dla gracza "1". void revRole() { swap(delta[1], delta[5]); swap(delta[2], delta[10]); swap(delta[3], delta[15]); swap(delta[4], delta[20]); } // Suma wartosci delta dla wszystkich linii przechodzacych przez dane pole. ld deltaAt(int pos) { ld r = 0; // Kierunki: 1 = poziomo, 23 = pionowo, 22, 24 = ukosnie // (roznica numerow pola na sasiednich polach linii) int p[4] = {1,22,23,24}; // w kazdym kierunku ogladamy 5 linii... for(int k=0; k<4; k++) { int P = p[k]; int v = board[pos-4*P] + board[pos-3*P] + board[pos-2*P] + board[pos-P]; r += delta[v]; v += board[pos+ P] - board[pos-4*P]; r += delta[v]; v += board[pos+2*P] - board[pos-3*P]; r += delta[v]; v += board[pos+3*P] - board[pos-2*P]; r += delta[v]; v += board[pos+4*P] - board[pos-1*P]; r += delta[v]; } return r; } int bm; ld max() { // Wybieramy puste pole z najwieksza wartoscia deltaAt. // Jesli kilka pol ma ta sama wartosc, to wybieramy losowo. int qbm = 0; ld res = -1; for(int k=0; k<529; k++) if(board[k] == 0) { ld d = deltaAt(k); if(d >= res) { if(d > res) {qbm = 0; res = d;} qbm++; if(rand() % qbm == 0) bm = k; } } return res; } }; // Poczatkowe czyszczenie planszy. void clearBoard() { for(int y=1; y<=15; y++) for(int x=1; x<=15; x++) board[(3+x)+23*(3+y)] = 0; } // "Optymalne" wartosci parametrow gracza (znalezione przy uzyciu algorytmu // genetycznego). ld bestq[7] = {0.3355044,0.1991849,0.3135847,0.1029193,0.1890494,0.1967362,0.1022812}; void genetic(); int main() { srand(time(NULL)); // straznicy... for(int pos=0; pos<529; pos++) board[pos] = 25; clearBoard(); // odkomentuj, jesli chcesz uruchomic AG // genetic(); return 0; int first; int left; player P; for(int k=0; k<7; k++) P.q[k] = bestq[k]; P.setDelta(); scanf("%d%d", &first, &left); if(first) goto move; // petla gry... while(true) { int ex, ey; scanf("%d%d%d", &ex, &ey, &left); board[(3+ex)+23*(3+ey)] = 5; move: P.max(); board[P.bm] = 1; printf("%d %d\n", (P.bm%23)-3, (P.bm/23)-3); fflush(stdout); } } // ALGORYTM GENETYCZNY // (Implementacja i parametry AG uzyte do znalezienia tablicy bestq, // ktora zostala uzyta na konkursie, zmieniala sie w trakcie jej // poszukiwania. W rzeczywistosci bylo wiecej pokolen i wielkosc populacji // byla wieksza. Trudno wiec to nazwac oryginalnym AG z konkursu, // ale idea jest wlasnie taka.) #define POOL 10 // wielkosc populacji #define SURV 5 // ile przezywa, a ile zostaje zastapionych #define TURNS 100 // liczba pokolen #define ROUNDS 1 // tyle razy dana para (uporzadkowana) graczy ze soba gra #define TABELA 0 // czy wypisywac turniej #define PLANSZA 0 // czy pokazac plansze #define WYNIKI 1 // czy pisac wyniki kazdego pokolenia #define WYPPAR 1 // czy wypisywac parametry najlepszego gracza // populacja player ppool[POOL]; // przeprowadzenie meczu miedzy ppool[p] a ppool[q] int play(int p, int q) { clearBoard(); ppool[q].revRole(); // q gra piatkami, nie jedynkami int moves = 0; int res = 0; while(moves < 225) { ld sc; sc = ppool[p].max(); board[ppool[p].bm] = 1; if(sc >= 5000) { res = 1; break; } sc = ppool[q].max(); board[ppool[q].bm] = 5; if(sc >= 5000) { res = 2; break; } moves++; } // dajemy punkty w zaleznosci od wyniku meczu... if(res == 0) { ppool[p].score++; ppool[q].score++; } if(res == 1) { ppool[p].score+=4; } if(res == 2) { ppool[q].score+=4; } ppool[q].revRole(); // odwracamy z powrotem return res; } // przeprowadzenie turnieju (wyniki => ppool[p].score) void turniej() { for(int r=0; r