1 Spis treści 1. Podstawowe operacje w programie MATLAB ................................................................................ 2 2. M-pliki ............................................................................................................................................. 4 3. Macierze ........................................................................................................................................... 6 4. Wektory i funkcje ........................................................................................................................... 11 5. Instrukcje sterujące operacjami ...................................................................................................... 13 6. Grafika 2D...................................................................................................................................... 16 7. O teorii gier .................................................................................................................................... 21 8. Gry o sumie zero ............................................................................................................................ 21 9. Programowanie liniowe ................................................................................................................. 27 10. Gra o sumie zero jako problem programowania liniowego ......................................................... 33 11. Macierzowe gry niekooperacyjne i punkty siodłowe................................................................... 35 12. Rozwiązywanie gier niekooperacyjnych o małych wymiarach ................................................... 38 13. Dynamiczna (ewolucyjna) teoria gier .......................................................................................... 40 14. Okienka dialogowe ...................................................................................................................... 43 15. Podstawy animacji ....................................................................................................................... 45 16. Grafy w teorii gier – komponenty spójne .................................................................................... 47 17. Interaktywna współpraca z grafiką .............................................................................................. 50 18. Gry kombinatoryczne ................................................................................................................... 52 19. Gra Nim........................................................................................................................................ 54 2 1. Podstawowe operacje w programie MATLAB MATLAB jest językiem programowania wysokiego poziomu wraz z interaktywnym środowiskiem do wykonywania obliczeń i do tworzenia symulacji komputerowych. Nazwa programu pochodzi od słów MATrix LABoratory, ponieważ pierwsza wersja MATLABa została stworzona po to by wspomóc numeryczne obliczenia macierzowe. Obecnie program ten potrafi znacznie więcej, posiada dużą liczbę bibliotek i możliwości rozbudowy przez programowanie własnych funkcji użytkownika. Jedna cecha pozostała jednakże niezmienna. Typem danych jakim operuje MATLAB jest macierz. Nawet liczba jest reprezentowana jako tablica o wymiarach 1x1. Dostępne operatory działań macierzowych: dodawanie + odejmowanie mnożenie * dzielenie prawostronne / (dzielenie lewostronne \) potęgowanie ^ transpozycja ' Jeśli zamierzamy wykonać działania mnożenia, dzielenia lub potęgowania skalarnie to symbol działania musimy poprzedzić kropką ! Przykładowo: dla x = 1 3 5 polecenie: x.^2 daje wynik 1 9 25, natomiast polecenie x^2 skutkuje wyłącznie wyświetleniem komunikatu o błędzie, ponieważ niemożliwe jest podniesienie do kwadratu macierzy o wymiarach 1x3. Dostępne operatory logiczne: równe == różne ~= większe > mniejsze < niewiększe <= niemniejsze >= i& lub | 3 Przydatne polecenia: clc - czyści okno terminala clear – usuwa zmienne z obszaru roboczego help komenda – wyświetla podstawowe informacje o komendzie Dostępne stałe: pi - π = 3.1416 i, j - 1 - jednostka urojona Inf - nieskończoność NaN - brak liczby (symbol nieoznaczony) Zmienne w MATLAB-ie nie wymagają wcześniejszego deklarowania. Domyślnym typem jest typ double. MATLAB używa zapisu liczb z kropką jako separatorem dziesiętnym np. 5.872. Zakres wartości bezwzględnej liczb wynosi od 2.2251 × 10−308 do 1.7977 × 10308 . Sposób wyświetlania zależy od funkcji format. Przykładowe formaty liczb: short – cztery miejsca po przecinku long – 15 miejsc po przecinku bank – dwa miejsca po przecinku rat – ułamki Przykładowe użycie: >> a = 0.12555; >> format bank >> a a= 0.13 Ćwiczenie 1.1. Wypróbuj działanie różnych odmian funkcji format dla 1 zmiennej a = oraz zmiennej b = π. 3 Ćwiczenie 1.2. Wykonaj działanie dzielenia dwóch wybranych liczb zespolonych. Zwróć uwagę na kolejność działań (użyj nawiasów). 4 2. M-pliki Pliki programów MATLAB-a mają rozszerzenie .m (stąd nazwa M pliki). Domyślny folder do przechowywania plików użytkownika można odczytać poleceniem pwd. Tworząc własne programy w katalogu innym niż domyślny, trzeba programowi wskazać ścieżkę dostępu. Można ją dodać komendą addpath(nazwa_folderu) lub skorzystać z menu MATLAB-a. Żeby utworzyć nowy plik wybieramy new z file menu lub open w celu edycji istniejących plików. Nazwy plików powinny zaczynać się od litery, program rozróżnia małe i duże litery. Wprowadzenie pliku o nazwie identycznej z nazwą już istniejącego obiektu spowoduje jego przesłonięcie tzn. z nazwą zostanie skojarzony ostatnio aktualizowany plik. Należy się zatem wystrzegać nadawania plikom nazw tożsamych z nazwami poleceń MATLAB-a. M-pliki służą do tworzenia własnych poleceń i programów. Wyróżniamy dwa typy m-plików: funkcje i skrypty. Typowa konstrukcja funkcji (w naszym przypadku zależnej od dwóch zmiennych) wygląda następująco: function co_ma_być_wyświetlane_jako_wynik = nazwa_funkcji(a,b) % znak po którym można umieścić komentarz ciąg poleceń Funkcja zawiera zestaw poleceń MATLAB-a i powinna zaczynać się od słowa kluczowego function. Nazwa funkcji i pliku muszą być takie same. Powyżej prezentowany przykład powinien zatem zostać zapisany na dysku jako plik moja_funkcja.m. Zmienne a,b są parametrami wejściowymi, a zmienna c jest parametrem wyjściowym. Parametry te nie muszą być liczbami, ale np. macierzami czy ciągami znaków. Liczba parametrów wejściowych i wyjściowych można być dowolna. Funkcja przechowuje zmienne poza przestrzenią roboczą, można zatem dublować nazwy zmiennych używanych przez inne pliki. Funkcję wywołujemy wpisując jej nazwę wraz z danymi wejściowymi w edytorze komend np. moja(2,5). Polecenia w funkcji mają swoje echo. Jeśli nie chcemy oglądać wyniku danej operacji należy każde takie polecenie zakończyć średnikiem. 5 Skrypt ma konstrukcję podobną do funkcji z tą różnicą, że nie zaczyna się od słowa function. Skrypt w odróżnieniu od funkcji używa zmiennych z przestrzeni roboczej. Poniższy skrypt po wywołaniu wyświetla napis WITAM ! disp('WITAM !') Skrypt w odróżnieniu od funkcji może być zapisany z dowolnie wybraną nazwą. Ćwiczenie 2.1. Zapisz skrypt wyświetlający liczbę pi w formacie long i uruchom go. Wzbogać swój skrypt o instrukcję taką by po wywołaniu wyświetlał się napis informujący użytkownika co robi dany skrypt. Ćwiczenie 2.2. Napisz i wywołaj funkcję obliczającą sumę dwóch liczb. 6 3. Macierze Najprostszym sposobem na utworzenie macierzy jest wypisanie jej elementów w nawiasach kwadratowych. Elementy wprowadzamy wpisując kolejne liczby we wierszach, a kolejne wiersze oddzielając średnikami. Za separatory wyrazów we wierszach mogą służyć przecinki lub odstępy. Przykładowa macierz: >> a = [1 2 5; 3 4 3] a= 1 2 5 3 4 3 Macierz można wywołać wpisując jej nazwę: >> a a= 1 2 5 3 4 3 Element macierzy w k-tym wierszu i n-tej kolumnie wywołujemy poleceniem a(k,n). Program wyświetli wynik jako ans (od słowa answer). >> a(1,3) ans = 5 To samo, ale zapisane pod zmienną b: >> b = a(1,3) b=5 W podobny sposób można wywołać podmacierz danej macierzy np. >> c = a(2,1:3) % lub c = a(2,1:end) c=3 4 3 Wybraliśmy w ten sposób drugi wiersz i kolumny od 1 do 3. 7 Do generowania sekwencji używamy dwukropka np. >> 2:6 ans = 2 3 4 5 6 Rozmiary macierzy mogą być powiększane przez łączenie np. >> d = [a;[5 5 5]] d= 1 2 5 3 4 3 5 5 5 >> d = [a [6;6]] d= 1 2 5 6 3 4 3 6 przez wstawianie jako podmacierz np. >> e = [0 0 0 0 0;0 0 0 0 0;0 0 0 0 0;0 0 0 0 0]; >> e(2:3,2:4) = a e= 0 0 0 0 0 0 1 2 5 0 0 3 4 3 0 0 0 0 0 0 lub dodawanie nowego elementu spoza zakresu indeksów np. >> a(3,3) = 6 a= 1 2 5 0 3 4 3 0 0 0 0 6 Skasować kolumnę lub wiersz można poprzez wpisanie w jego miejsce pustej kolumny. 8 Usuwanie 3-ciej kolumny wygląda następująco: >> a(:,3) = [] a= 1 2 0 3 4 0 0 0 6 Uwaga: pod a jest zapisana ostatnia macierz z tą nazwą ! Do poszukiwania elementów o żądnej własności służy komenda find. Macierz jest przeszukiwana w sposób liniowy (przeszukiwane są kolejne wyrazy w kolumnach od pierwszej do ostatniej). >> find(a==4) ans = 5 Jeśli zadeklarujemy chęć zobaczenia wyniku w postaci numerów wierszy i kolumn wystarczy wpisać >>[i,j]=find(a==4) i=2 j=2 Przeszukiwanie zakończyło się znalezieniem liczby 4 na pozycji (2,2). Do zamiany indeksu liniowego na numery wiersza i kolumn służy polecenie ind2sub([liczba_wierszy liczba_kolumn],index_liniowy) >> [i,j] = ind2sub([3 3],6) % lub [i,j] = ind2sub(size(a),6) i=3 j=2 Jest to przydatne np. w celu znalezienia w macierzy pierwszego elementu o zadanych własnościach (np. = 0). Do znalezienie minimum z indeksów potrzebne jest indeksowanie liniowe, które później warto przetłumaczyć na numery wiersza i kolumny. Jest to przydatne np. do konstruowania solvera do sudoku. 9 Polecenie size(a) generuje wektor o długości 2 z liczbą wierszy i liczbą kolumn macierzy a, size(a,1) daje liczbę wierszy, size(a,2) liczbę kolumn. Polecenie (macierz warunek) daje w wyniku macierz o wartościach 1 w miejscu, gdzie warunek jest spełniony i 0 jeśli nie jest spełniony. Wartości 1 i 0 są w tym wypadku traktowane jak wartości logiczne prawdy i fałszu. Wywołajmy macierz a a= 1 2 0 3 4 0 0 0 6 Wskażmy w których miejscach znajdują się liczby większe lub równe 3: >> (a>=3) ans = 0 0 0 1 1 0 0 0 1 Do zamiany macierzy na macierz wartości logicznych służy polecenie logical. W wyniku otrzymujemy prawdę wszędzie tam, gdzie były niezerowe elementy macierzy. >> M = logical(a) M= 1 1 0 1 1 0 0 0 1 Taka macierz może być używana jako maska: >> b = [1 2 3;4 5 6;7 8 9] b= 1 2 3 4 5 6 7 8 9 10 >> W = M.*b % mnożenie skalarne - wyraz po wyrazie W= 1 2 0 4 5 0 0 0 9 Czasami konieczna jest zamiana macierzy na wektor i na odwrót: >> h = [1 2;3 4]; >>f = h(:) f= 1 3 2 4 >> g = reshape(f,3,3) g= 1 2 3 4 MATLAB oferuje szereg funkcji do tworzenia macierzy specjalnych: zeros(n,m) – macierz wymiaru nxm składajaca się z samych zer ones(n,m) – macierz wymiaru nxm składajaca się z samych jedynek diag(wektor) – macierz diagonalna z elmentami wektora na przekątnej eye(n) – macierz wymiaru nxn z jedynkami na przekątnej magic(n) – tzw. kwadrat magiczny o zadanym wymiarze rand(n,m) – macierz wymiaru nxm wypełniona wyrazami losowymi z przedziału (0,1) (rozkład jednostajny) Macierze są dwuwymiarowe. Pozycja każdego elementu w macierzy opisana jest dwoma parametrami - numer wiersza i numer kolumny. Tablice wielowymiarowe używają większej liczby indeksów. W poniższym przykładzie jest zapisana macierz trójwymiarowa: >>b(:,:,1) = [1 3; 2 4]; >>b(:,:,2) = [5 6; 7 1]; 11 4. Wektory i funkcje Wektory są szczególnym przypadkiem macierzy i wszystko o czym napisano w poprzednim rozdziale ich dotyczy. Wektory generujemy za pomocą wypisania ich elementów np. >> x = [1 1.5 2 2.5 3]; lub za pomocą dwukropków: >> x = 1:0.2:2 x = 1 1.2 1.4 1.6 1.8 2 Można też użyć polecenia linspace(s,t,n) by podzielić odcinek [s,t] na n-1 równych przedziałów i wypisać ich granice. >> linspace(1,2,6) x= 1 1.2 1.4 1.6 1.8 2 Dwie ostatnie metody są pomocne przy tworzeniu danych do rysowania wykresów. Dostęp do elementów wektora jest możliwy po podaniu numeru współrzędnej: >> x(3) ans = 1.4 Współrzędne wektorów można sumować i mnożyć poleceniami sum i prod, można też szukać elementów największych i najmniejszych poleceniami max i min np. >> max([1 4 2 5 2]) ans = 5 >> min([1 4 2 5 2]) ans = 1 12 Polecenia te stosowane do macierzy wykonują działania na kolejnych kolumnach np. >> a=[2 3 ;4 3] a= 2 3 4 3 >> prod(a) ans = 8 9 Na wektory nakładamy różne funkcje matematyczne. Podstawowe funkcje zostały wypisane poniżej: abs – wartość bezwzględna sin, cos, tan, cot – funkcje trygonometryczne floor – zaokraglenie w dół (całość) ceil – zaokraglenie w górę exp – e do potęgi argumentu log – logarytm naturalny log10 – logarytm dziesiętny (analogicznie log2 – logarytm o podstawie 2 itp.) sign – znak funkcji sqrt - pierwiastek kwadratowy Ćwiczenie 4.1. Dla zadanej macierzy A=[1 3 6 3; 0 6 2 4; 1 7 0 8] napisz polecenia: obliczające sumę wszystkich elementów macierzy, obliczające minimum z każdego wiersza, wyświetlające tylko te elementy macierzy, które są większe od 5 wraz z ich numerami wiersza i kolumny, generujące podmacierz złożoną tylko z dwóch ostatnich kolumn macierzy A. 13 5. Instrukcje sterujące operacjami Matlab umożliwia tworzenie własnych programów czyli plików o rozszerzeniu .m (patrz rozdział o m-plikach). W takich funkcjach lub skryptach można używać następujących instrukcji sterujących operacjami: if wyrażenie logiczne instrukcje elseif instrukcje else instrukcje end switch wyrażenie case wyrażenie1 instrukcje case wyrażenie2 instrukcje otherwise instrukcje end for zmienna = początek:koniec instrukcje end while wyrażenie logiczne instrukcje end 14 Przykładowe m-pliki: function b=znak(a) % funkcja przyjmuje wartość 1 jeśli a>0, zero gdy a=0 i -1 dla a<0. if a>0 b=1 elseif a==0 b=0 else b=-1 end function dzialania %funkcja pozwala na wybranie działania (dodawanie lub odejmowanie) i %wykonanie wybranej operacji na wpisanych liczbach a = input('Podaj a: '); b = input('Podaj b: '); x = input('Wybierz dzialanie (+, -): ','s'); switch x case '+' a+b case '-' a–b otherwise disp('Nie wybrano dzialania lub liczb'); end function s=sumowanie(n) %funkcja wypisuje sumę liczb od 1 do n s=0; for k=1:n s=s+k; end s 15 function ujemne %funkcja wyświetla prosbe o wpisanie liczby do momentu wpisania liczby %mniejszej lub równej zero. w=1; while w>0 w=input('Wpisz liczbe : '); end Ćwiczenie 5.1. Za pomocą poleceń MATLAB-a utwórz macierz o wymiarach 3x4 taką, że wyraz o indeksie (i,j) tej nowej macierzy jest sumą sąsiadów (we wszystkich możliwych kierunkach) wyrazu (i,j) macierzy A = [1 3 2 3; 0 1 2 4; 1 1 0 2]. Ćwiczenie 5.2 Oblicz stałą e korzystając ze wzoru e = ∞ 1 𝑛=0 n ! Wskazówka: Dla dużych n liczba 1/n! jest tak mała, że MATLAB traktuje ją jako równą zero. Ilu wyrazów szeregu należy użyć ? Ćwiczenie 5.3 Napisz funkcję, która wyświetla przybliżoną wartość x, spełniającego równanie 𝑥 = x 21+1 iterując formułę 𝑥𝑛+1 = 21 , x n +1 rozpoczynając od 𝑥1 = 1 i wykonując tyle kroków by dwa ostatnie wyrazy spełniały warunek 𝑥𝑘+1 − 𝑥𝑘 ≤ 0.0001. 16 6. Grafika 2D Najczęściej spotykanym sposobem graficznej prezentacji danych w języku MATLAB jest wykres funkcji jednej zmiennej. Służy do tego funkcja plot(x,y). Jeśli y jest wektorem, plot(y) utworzy wykres funkcji liniowej, gdzie na jednej osi będą numery współrzędnych, a na drugiej elementy wektora y. Jeżeli jako argumenty funkcji plot podamy dwa wektory x i y, komenda plot(x , y) utworzy wykres wektora y względem wektora x. Na przykład, aby wykreślić wartość funkcji sinus od napisać x = 0:pi/100:2*pi; y = sin(x); plot(x,y) Otrzymujemy następujący obrazek: 0 do 2π, możemy 17 Podanie wielu par x-y umożliwia utworzenie wielu wykresów przy jednym wywołaniu funkcji plot. MATLAB automatycznie dobiera kolor dla każdego zestawu danych. Na przykład, poniższy zapis kreśli trzy funkcje, każdą w innym, wyróżniającym się kolorze. x = 0:pi/100:2*pi; y1= sin(x); y2 = sin(2*x); y3 = cos(x); plot(t,y,t,y2,t,y3) Poszczególnym liniom na wykresie można nadać różne dodatkowe właściwości np: rodzaj linii – parametr 'LineStyle' np. '-', '--', ':' kolor linii – parametr 'Color' np 'y' – żółty, 'm' – fioletowy, 'c' – turkusowy, 'r' – czerwony, 'g' – zielony, 'b' – niebieski, 'w' – biały, 'k' – czarny. grubość linii – parametr 'LineWidth' – grubość linii w punktach rodzaj znaczników – parametr 'Marker' np. '+', 'o', 'p', 'd' , 'x', '<' itp. kolor krawędzi znacznika – parametr 'MarkerEdgeColor' kolor wypełnienia znacznika – parametr 'MarkerFaceColor' wielkość znaczników – parametr 'MarkerSize', wartość jest podawana w punktach. Skala wykresu jest dobierana automatycznie. Chcąc ją zmienić trzeba wywołać funkcję axis([xmin xmax ymin ymax]) i jako argument podać wektor określający nowe parametry osi. Do opisu wykresu służą polecenia: - title(„tekst‟) – dodaje tytuł rysunku; - xlabel(„tekst‟) – dodaje opis osi x; - ylabel(„tekst‟) – dodaje opis osi y; - text(x,y,„tekst‟) - umieszcza „tekst‟ w punkcie o współrzędnych (x,y); - legend('tekst1','tekst2',....) - tworzy legendę. Polecenie grid on/off – włącza/wyłącza siatkę, hold on/off włącza/wyłącza tryb zachowania zawartości okna (przydatne gdy chcemy narysować nieznaną z góry liczbę wykresów w jednym oknie). 18 W poniższym przykładzie \leq oznacza <=, \pi oznacza symbol liczby pi. t = -pi:pi/100:pi; y = sin(t); plot(t,y) axis([-pi pi -1 1]) xlabel('-\pi \leq \pi') ylabel('sin(t)') title('wykres funkcji sinus') text(1,-1/3,'\it{Funkcja nieparzysta.}') Przydatne polecenia: • Okno graficzne można wyczyścić wywołując funkcję clf; • Nowe okna można otworzyć przy pomocy funkcji figure; • W celu podzielenia okna graficznego na mniejsze okienka należy wykorzystać funkcję subplot(m,n,p), gdzie: m - liczba okienek w pionie; n - liczba okienek w poziomie; p - kolejny numer wykresu. • Otworzyć okno można podając jego numer jako argument; 19 W Matlabie istnieje system specyfikacji kolorów. Jest on oparty na kombinacji czerwonego, zielonego i niebieskiego, każdy kolor jest rozpatrywany jako kombinacja tych trzech z odpowiednią intensywnością. W tym systemie kolor jest identyfikowany w wektorem trójwymiarowym, gdzie każda współrzędna odpowiada za intensywność kolejno koloru czerwonego, zielonego i niebieskiego. Intensywności mierzymy liczbami z zakresu od 0 do 1. Wektor [0 0 0] koresponduje z kolorem czarnym, a wektor [1 1 1] z kolorem białym. Mapę kolorów można zadać poleceniem jet(liczba kolorów) lub hsv(liczba). Są także dostępne palety kolorów (np. gray, summer, autumn, spring, winter, hot, cool itp.). Aktualną paletę kolorów zmienia polecenie colormap(nazwa_palety) lub colormap(macierz_palety). Poniższa funkcja na jednym układzie współrzędnych rysuje wykresy funkcji 𝑦𝑘 = sin(kx) dla k z podanego zakresu. function wykres(n), x=[0:0.01:4]; kolor = jet(n); for k = [1:n], plot(x,sin(k*x),'Color', kolor(k,:)), hold on end Poniższy skrypt generuje „kolorowy kwadrat”: a=[0 100;30 50]; image(a) colormap(hsv(16)) Oprócz wykresów MATLAB rysuje też wielokąty, a co za tym idzie potrafi też zaznaczać obszary między wykresami. Służy do tego komenda fill np. x=[0:0.01:pi]; X=[x,fliplr(x)]; % fliplr(x) odwraca kolejność współrzędnych w wektorze Y=[sin(x), fliplr(cos(x))]; fill(X,Y,'r') 20 Ćwiczenie 6.1 Napisz program, który po otrzymaniu liczby n rysuje w różnych kolorach n parabol o współczynniku kierunkowym zmieniającym się w zakresie 1 do n. Przetestuj różne palety kolorów. Ćwiczenie 6.2 Narysuj pierwszą literę swojego nazwiska. Następnie pomniejsz ją, przesuń oraz obróć. Umieść na jednym rysunku obie litery, każdą w odmiennym kolorze. Zrób „szlaczek” powtarzając wielokrotnie podobne procedury. Wskazówka: Utwórz wektor W=[x;y;ones(length(x))]. Do zmiany rozmiaru służy przekształcenie skala=[s 0 0; 0 s 0; 0 0 1], gdzie s jest stałą jednokładności. Obrót jest zadany przez macierz: obrot=[cos(a) -sin(a) 0; sin(a) cos(a) 0; 0 0 1], gdzie a jest kątem o jaki chcemy obrócić literę. Natomiast do przesuwania służy przekształcenie przes=[1 0 t1; 0 1 t2; 0 0 1]. Ćwiczenie 6.3 Narysuj liść paproci wg algorytmu: 1. Ustal liczbę punktów i liczbę iteracji. 2. Utwórz macierz zerową przygotowaną na wpisanie w pierwszym wierszu pierwszej współrzędnej punktów, a w drugim drugiej współrzędnej. 3. Wylosuj współrzędne punktów z przedziału (0,1) 4. W pętli losuj wartość z przedziału (0,1). W zależności od tej wartości wybierz przekształcenie 1, 2, 3 lub 4 i wylicz nowe współrzędne punktów (do następnej iteracji). 5. Narysuj liść paproci z punktów o współrzędnych wyliczonych po ostatniej iteracji. Przekształcenia: Nr 1 : x=0, y=0.16y, częstotliwość 1% Nr 2 : x=0.2x-0.26y, y=0.23x + 0.22y+1.6, częstotliwość 7 % Nr 3 : x=-0.15x +0.28y, y=0.26x+0.24y+0.44, częstotliwość 7 % Nr 4 : x= 0.85x+0.04y, y=-0.04x+0.85y+1.6, częstotliwość 85 %. Pokombinuj z liczbą punktów na starcie, liczbą iteracji, odcieniami zieleni paprotki i proporcjami częstotliwości różnych przekształceń. 21 7. O teorii gier Teoria gier modeluje sytuacje konfliktowe, w których występuje wielu uczestników. Każdy z uczestników ma zestaw dostępnych decyzji do podjęcia oraz reguł wedle jakich te decyzje ma podejmować. W zależności od podjętych decyzji każdy z graczy otrzymuje wypłatę. Zakłada się, że wszyscy uczestnicy gry zachowują się racjonalnie, to znaczy, że każdy stara się maksymalnie zwiększyć swoją wypłatę. Teoria gier dostarcza narzędzi do doradzania graczom jak wybierać optymalne strategie w grze wedle określonych preferencji. Działa ona również w sytuacji kiedy mamy do czynienia z jednym graczem, działającym w warunkach niepewności lub przy niepełnych danych. Mówiąc w skrócie, teoria gier jest teorią podejmowania decyzji. Teoria gier znalazła zastosowanie w ekonomii, biologii ewolucyjnej, socjologii, naukach politycznych i informatyce. Gry dzielimy na jedno i wieloosobowe, równoczesne i naprzemienne, pojedyncze i iterowane, z pełną i niepełną informacją, kooperacyjne i niekooperacyjne oraz kombinatoryczne. Omawianie gier zaczniemy od najprostszego modelu gier niekooperacyjnych o sumie zero. 8. Gry o sumie zero W tym rozdziale opiszemy gry macierzowe o sumie zero. Nazwa gry bierze się faktu, że wypłaty graczy sumują się do zera (jeden gracz płaci drugiemu). Gra taka z konstrukcji jest zatem dwuosobowa i zdecydowanie nie zachęca do współpracy konkurujących graczy. Jest to zatem gra niekooperacyjna. Gra tego typu zawiera zazwyczaj następujące elementy: gracze - I i II; strategie - dla gracza I-szego X={1,2,3...,n}, dla gracza II-go Y={1,2,3...,m}; macierz wypłat A wymiaru n x m. Wyraz a(i,j) oznacza wypłatę pierwszego gracza, w sytuacji gdy gracz pierwszy podjął decyzję 'i', a gracz drugi podjął decyzję 'j'. Decyzje gracza I odpowiadają zatem wyborowi wierszy, a gracza II kolumn w macierzy A. 22 Dopuszcza się stosowanie tzw. strategii mieszanych, które są rozkładami prawdopodobieństwa kolejno na X i na Y. Odpowiadają one zachowaniu dużych grup graczy w miejsce pojedynczego gracza. Prawdopodobieństwa wyboru poszczególnych decyzji są określone przez procent graczy w grupie podejmujących daną decyzję. Gra polega na tym, że każdy osobnik z grupy pierwszej rozgrywa grę z każdym osobnikiem z grupy drugiej. Jeśli p = (𝑝1 , ⋯ , 𝑝𝑛 ) jest wektorem prawdopodobieństwa gracza I, a q = (𝑞1 , ⋯ , 𝑞𝑚 ) jest wektorem prawdopodobieństwa gracza II to wypłatę dla gracza I liczymy ze wzoru: 𝑢1 𝑝, 𝑞 = 𝑝𝐴𝑞 𝑇 , a gracza II-go 𝑢2 𝑝, 𝑞 = −𝑝𝐴𝑞 𝑇 . Strategia gracza pierwszego oznaczona jako wybór 'i' odpowiada w tym ujęciu wektorowi bazowemu 𝑒𝑖 = (0,0, ⋯ ,1, ⋯ ,0), gdzie jedynka stoi na i-tym miejscu . Taką strategię nazywamy czystą i piszemy 𝑝 = 𝑒𝑖 . Definicja (dolna i górna wartość gry) Dla gry o macierzy wypłat A=(a(i,j)) liczba 𝑣 = min𝑗 ( max𝑖 𝑎(𝑖, 𝑗)) jest nazywana górną, a liczba 𝑣 = max𝑖 ( min𝑗 𝑎(𝑖, 𝑗)) dolną wartością gry. Definicja (rozwiązanie gry) Rozwiązaniem gry nazywamy wektory prawdopodobieństwa 𝑝∗ = (𝑝1 , ⋯ , 𝑝𝑛 ) i 𝑞 ∗ = (𝑞1 , ⋯ , 𝑞𝑚 ) oraz liczbę rzeczywistą v takie, że spełnione są warunki 𝑢1 (𝑝∗ , 𝑞 ∗ ) = 𝑣 i 𝑢2 𝑝∗ , 𝑞 ∗ = −𝑣 . Strategie 𝑝∗ , 𝑞 ∗ nazywamy optymalnymi, a liczbę v nazywamy wartością gry. Twierdzenie (von Neumann, Morgenstern) (twierdzenie minimaksowe, zasadnicze twierdzenie teorii gier o sumie zero) Każda gra macierzowa o sumie zero ma przynajmniej jedno rozwiązanie. Każdemu z rozwiązań odpowiada ta sama wartości gry v. Jest ona równa 𝑣 = max( min 𝑢1 (𝑝, 𝑞)) = min( max 𝑢1 (𝑝, 𝑞)) 𝑝 𝑞 𝑞 𝑝 Jeśli 𝑣 = 𝑣 to taką grę nazywamy grą zamkniętą, strategie optymalne są wtedy zadane przez strategie czyste, a wartość gry jest równa 𝑣 = 𝑣 = 𝑣. 23 Definicja (punkt siodłowy) Punkt siodłowy to ten z elementów macierzy A który wyznacza dolną i górną wartość gry w przypadku gry zamkniętej. 6 3 5 punkt siodłowy jest 4 1 3 jeden i jest to wyraz w pierwszym wierszu i w drugiej kolumnie, zaznaczony kolorem czerwonym. Wartość gry wynosi 3. Słowo siodłowy bierze się stąd, że takiej liczbie we wierszu towarzyszą same wyrazy większe lub równe, a w kolumnie mniejsze lub równe. Żadnemu z graczy nie opłaca się zatem zrezygnować z tak obranej strategii. Dla gry o macierzy wypłat 𝐴 = Bardziej problematyczne jest rozwiązywanie gier otwartych (takich dla których dolna i górna wartość gry nie są równe). Jeśli macierz wypłat na dwa wiersze lub dwie kolumny z pomocą przychodzi nam algorytm rozwiązania graficznego. 2 3 4 . Strategię gracza pierwszego 5 2 1 można zadać przez liczbę x z przedziału [0,1] oznaczającą prawdopodobieństwo wyboru pierwszego wiersza. Wtedy wypłaty gracza I w zależności od kolumny wybranej przez gracza II są funkcjami liniowymi od x. Można je nanieść na układ współrzędnych. Gracz II wybierze tzw. obwiednię dolną zadanych prostych, bo zgodnie z swoimi preferencjami w ten sposób będzie minimalizować swoją stratę (przypomnienie: wypłata gracza I = strata gracza II). Gracz I wybierze taka wartość x, która na obwiedni odpowiada największej wartości. Prosta 𝑘1 jest postaci 𝑘1 : y=2x+5(1-x)=5-3x (2 i 5 to elementy w pierszej kolumnie), prosta 𝑘2 : y=3x+2(1-x), a 𝑘3 : y=4x+(1-x). Wartość gry v odpowiada maksimum obwiedni dolnej, a strategia optymalna gracza I wektorowi (xopt,1-xopt) dla xopt realizujacego to maksimum. Rozważmy macierz 𝐴 = 24 Można to zobrazować graficznie: Funkcja realizująca powyższe zadanie: function gra2_n(a), %funkcja od macierzy z dwoma wierszami m=size(a,2); % liczba kolumn x=[0:0.01:1]; % zakres x kol=jet(m); % paleta kolorów for k=[1:m], % powtarzaj od k=1 do k=m plot(x,a(1,k)*x+a(2,k)*(1-x),'Color', kol(k,:),'Linewidth',2) xlabel('strategia gracza I', „fontsize‟, 12), % podpis osi x ylabel('wartosc gry', „fontsize‟,12), %podpis osi y hold on, end f=a(1,1)*x+a(2,1)*(1-x); for k=[2:m], f=min(a(1,k)*x+a(2,k)*(1-x),f); end 25 plot(x,f,'b*') Żeby obliczyć strategię gracza II trzeba wybrać dwie kolumny, których wykresy przecinają się w punkcie (xopt,v), czyli w tym przypadku kolumna nr 1 i 2 i rozwiązać układ równań 2y+3(1-y)=5y+2(1-y). Używane elementy zostały wyróżnione w macierzy A 2 5 3 2 4 1 Sytuacja komplikuje się nieco jeśli przez punkt (xopt, v) przechodzą wykresy więcej niż dwóch prostych odpowiadających kolumnom. Wtedy trzeba rozpatrzeć każdą parę takich kolumn z osobna i wyliczyć odpowiadającą jej strategię gracza II. Optymalna dla gry będzie każda strategia, która jest kombinacją wypukłą w ten sposób wyliczonych strategii. Analogicznie postępujemy z macierzami o dwóch kolumnach. Różnica polega na rysowaniu wykresów zależności od y, gdzie y jest prawdopodobieństwem wyboru pierwszej kolumny i szukaniu minimum obwiedni górnej (zamiast maksimum obwiedni dolnej). Ćwiczenie 8.1 Wzbogać zaprezentowany program o obliczanie strategii optymalnych i wartość gry. Przydatne będzie polecenie fzero. Przykładowe użycie: >> fzero(@(x) sin(x) , [-1,1]), % szuka miejsca zerowego funkcji >> ans = 0 Ćwiczenie 8.2 Napisz program rozwiązujący gry z macierzą wypłat o dwóch kolumnach. Wybierz inne znaczniki do wyeksponowania obwiedni górnej niż zastosowane w prezentowanym programie. 26 Ćwiczenie 8.3 Wypróbuj ciąg poleceń: repmat('<',1,10) sprintf('f%i',1:10) s=6; disp(sprintf('liczba %d',s)) disp('liczba s') k=1.5; disp(sprintf('wynik wynosi %f',k)) disp('wynik wynosi k') i=12; fprintf('wykonano %d krokow', i); Opisz funkcje poleceń sprintf, fprintf i repmat. Ćwiczenie 8.4 Napisz program do rysowania macierzy. Wynik działania dla macierzy A=[2 3 7 0; 1 5 2 5] może wyglądać następująco: 2 3 1 5 7 2 0 5 Udoskonal program tak, by zaznaczał punkty siodłowe, jeśli takie istnieją. Do zmiany wielkości czcionki służy parametr 'Fontsize', którego wartość podaje się w punktach np. >> k = 45 >> text(-1,9, sprintf('%d',k), 'Fontsize',22)) 27 9. Programowanie liniowe Ogólne sformułowanie problemu programowania liniowego jest następujące: zmaksymalizować (lub zminimalizować) 𝑛 𝑧 = 𝑓 𝑥1 , ⋯ , 𝑥𝑛 = 𝑐𝑗 𝑥𝑗 𝑗 =1 przy ograniczeniach 𝑛 𝑎𝑖𝑗 𝑥𝑗 ≤ 𝑏𝑖 , 𝑖 = 1, … , 𝑚. 𝑗 =1 Współczynniki 𝑎𝑖𝑗 , 𝑏𝑖 , 𝑐𝑗 są zadanymi parametrami, a 𝑥𝑗 są zmiennymi decyzyjnymi. Zakłada się, że wszystkie zmienne decyzyjne są nieujemne. Istnieje kilka metod rozwiązywania tego typu zadań. Najprostszą jest tzw. metoda graficzna, ale jej zastosowanie ogranicza się do przykładów z dwoma zmiennymi decyzyjnymi. drugą jest metoda simplex. Nazwa tej metody pochodzi od symplexu czyli wypukłej figury wielowymiarowej, która zadaje zbiór dopuszczalnych punktów 𝑥 = (𝑥1 , ⋯ , 𝑥𝑛 ) spełniających ograniczenia problemu. Jest to metoda, która w większości przypadków znajduje rozwiązanie w stosunkowo krótkim czasie. Do chwili obecnej zaproponowano kilka innych, asymptotycznie wydajniejszych algorytmów np. metoda elipsoidalna czy metoda punktu wewnętrznego, ale ich przewaga nad metodą simplex ujawnia się dopiero przy zmiennych i ograniczeniach liczonych w tysiącach. W roku 2000 uznano algorytm simplex za jeden z 10 czołowych algorytmów ubiegłego wieku. Do rozwiązywania problemów programowania liniowego służy wbudowana funkcja MATLAB-a linprog. Można także użyć funkcji simplex z poniżej zapisanych plików (wykorzystywane podfunkcje + plik funkcji głównej). 28 function e = wr(m,i) % i-ty wektor bazowy w przestrzeni R^m e = zeros(m,1); e(i) = 1; function [row, mi] = MRT(a, b) % Test minimalnego ilorazu (minimum ratio test) na wektorach a i b. % row – numer wiersza (pivot row) % mi – wartość najmniejszego ilorazu (minimum) m = length(a); c = 1:m; a = a(:); b = b(:); l = c(b > 0); [mi, row] = min(a(l)./b(l)); row = l(row); function [m, j] = Br(d) % Reguła Branda zastosowana do macierzy d. % Wynik: % m - pierwsza ujemna liczba w macierzy d % j - index (indexowanie liniowe) liczby m. ind = find(d < 0); if ~isempty(ind) j = ind(1); m = d(j); else m = []; j = []; end function [subs, A, z]= petla_simplex(A, subs, mm, k) %Pętla do algorytmu simplex % A macierz problemu %mm=0 jeśli min, a mm=1 jeśli max. [m, n] = size(A); [mi, col] = Br(A(m,1:n-1)); 29 while ~isempty(mi) & mi<0 & abs(mi)>eps [row, small] = MRT(A(1:m-k,n),A(1:m-k,col)); if ~isempty(row) if abs(small) <= 100*eps & k == 1 [s,col] = Br(A(m,1:n-1)); end A(row,:)= A(row,:)/A(row,col); subs(row) = col; for i = 1:m if i ~= row A(i,:)= A(i,:)-A(i,col)*A(row,:); end end [mi, col] = Br(A(m,1:n-1)); end end z = A(m,n); function z=simplex(typ, c, A, rel, b) % typ - min(or max) z = c*x % Pod warunkami Ax rel b, x >= 0 !!! % Parametr typ: 'min' lub 'max'. % Parametr rel jest znakiem określającym znaki nierowności: % np rel = '<=>' oznacza nierówność <, rowność i jedną nierówność > % A jest macierzą % b jest wektorem kolumnowym % c jest wektorem if (typ == 'min') mm = 0; else mm = 1; c = -c; end b = b(:); c = c(:)'; [m, n] = size(A); n1 = n; 30 les = 0; neq = 0; red = 0; if length(c) < n c = [c zeros(1,n-length(c))]; end for i=1:m if (rel(i) == '<') A = [A wr(m,i)]; les = les + 1; elseif (rel(i) == '>') A = [A -wr(m,i)]; else neq = neq + 1; end end ncol = length(A); if les == m c = [c zeros(1,ncol-length(c))]; A = [A;c]; A = [A [b;0]]; [subs, A, z] = petla_simplex(A, n1+1:ncol, mm, 1); else A = [A eye(m) b]; if m > 1 w = -sum(A(1:m,1:ncol)); else w = -A(1,1:ncol); end c = [c zeros(1,length(A)-length(c))]; A = [A;c]; A = [A;[w zeros(1,m) -sum(b)]]; subs = ncol+1:ncol+m; av = subs; [subs, A, z] = petla_simplex(A, subs, mm, 2); nc = ncol + m + 1; 31 x = zeros(nc-1,1); x(subs) = A(1:m,nc); xa = x(av); com = intersect(subs,av); if (any(xa) ~= 0) disp(sprintf(' Pusty obszar, zignoruj wynik funkcji')) return else if ~isempty(com) red = 1; end end A = A(1:m+1,1:nc); A =[A(1:m+1,1:ncol) A(1:m+1,nc)]; [subs, A, z] = petla_simplex(A, subs, mm, 1); end if (z == inf | z == -inf) return end [m, n] = size(A); x = zeros(n,1); x(subs) = A(1:m-1,n); x = x(1:n1); if mm == 0 z = -A(m,n); else z = A(m,n); end %disp(sprintf('Problem ma rozwiazanie ')) %disp(sprintf(' Wartosci zmiennych:')) %for i=1:n1 %disp(sprintf(' x(%d)= %f ',i,x(i))) %end %disp(sprintf(' Optymalna wartosc funkcji celu:')) %disp(sprintf(' z= %f',z)) 32 Przykład użycia: Zmaksymalizuj funkcję z = F(x,y) = 30x+20y przy ograniczeniach 2x+y ≤ 1000, 3x+3y ≤ 2400, 1.5 x ≤ 600, x, y ≥ 0. >> type = 'max'; >> c = [30 20]; >> A = [2 1;3 3;1.5 0]; >> b = [1000;2400;600]; >> rel = '<<<'; >> z = simplex(type, c, A, rel, b) >> z=18000 ← to jest wynik Ćwiczenie 9.1 Wykonaj ilustrację graficzną do przykładu prezentowanego powyżej. Zaprojektuj program rysujący obszar punktów (x,y) spełniających zadane ograniczenia, na ten obszar nanieś wykres funkcji 30x+20y = a, gdzie a jest wyliczonym przez Matlab maximum funkcji F(x,y) = 30x+20y na narysowanym obszarze. 33 10. Gra o sumie zero jako problem programowania liniowego Programowanie liniowe jest metodą na rozwiązywanie gier dowolnej wielkości. Rozważmy grę o sumie zero z następującą macierzą wypłat: 0 𝐴 = −1 2 1 0 −3 −2 3 0 Szukamy minimalnej wartości v, że 𝑣 ≥ 0 ∙ 𝑦1 + 1 ∙ 𝑦2 −2 ∙ 𝑦3 , 𝑣 ≥ −1 ∙ 𝑦1 + 0 ∙ 𝑦2 + 3 ∙ 𝑦3 , 𝑣 ≥ 2 ∙ 𝑦1 − 3 ∙ 𝑦2 + 0 ∙ 𝑦3 , pod warunkami 𝑦𝑖 ≥ 0 i 𝑦1 + 𝑦2 + 𝑦3 = 1. Przenosząc wartość 𝑣 na prawą stronę powyższych równań otrzymujemy problem programowania liniowego polegający na zminimalizowaniu wartości v (𝑏𝑖 = 0 dla każdego 𝑖). W problemie programowania liniowego występuje założenie o nieujemności zmiennych. Wartość gry czasami bywa jednak ujemna. Tą przeszkodę można ominąć dodając do każdego wyrazu macierzy A stałą liczbę p, tak aby wszystkie wyrazy nowej macierzy wypłat były nieujemne. Wartość tej nowej gry wynosi według teorii gier p + v, gdzie v jest wartością wyjściowej gry i jest to liczba nieujemna. Program liczący wartość naszej przekładowej gry wygląda zatem następująco: type = 'min'; a = [0 1 -2; -1 0 3; 2 -3 0]; mma = min(min(a)); a1 = a-mma*ones(3,3); % tak żeby wartość gry była dodatnia c1 = zeros(1,3); c = [c1 1]; A1 = [a1 -ones(3,1)]; 34 A = vertcat(A1,[ones(1,3) 0]); r = repmat('<',1,3); rel = '<<<='; % lub rel=strcat(r,'=') % strcat – łączenie znaków; Pr = vertcat(zeros(3,1),[1]); z = simplex(type, c, A, rel, pr); v = z+mma Wywołanie programu daje wynik v=0. Ćwiczenie 10.1 Prześledź krok po kroku i wyjaśnij jakie operacje wykonuje powyżej prezentowany program. Ćwiczenie 10.2 Przerób podany skrypt na funkcję liczącą wartość gry dla dowolnej gry o sumie zero. Ćwiczenie 10.3 Niech G będzie grafem skierowanym z wierzchołkami ze zbioru W. Gracz pierwszy wybiera wierzchołki, a drugi krawędzie. Jeśli wybrana krawędź nie zaczyna się ani nie kończy w wybranym wierzchołku, to wypłata wynosi 0, jeśli zaczyna się w wybranym wierzchołku, to wypłata wynosi 1, a jeśli kończy to -1. Napisz macierz gry dla wybranego przez siebie grafu (przynajmniej trzy wierzchołki i przynajmniej trzy krawędzie). Oblicz wartość takiej gry. Pokaż, że w każdym przypadku wartość gry jest nieujemna. Wskazówka: jaką strategię gwarantująca brak straty (zerową wypłatę) może przyjąć gracz I ? 35 11. Macierzowe gry niekooperacyjne i punkty siodłowe W poprzednich rozdziałach zakładaliśmy, że suma wypłat graczy wynosi zero. W ogólnym modelu macierzowych gier niekooperacyjnych wygrana jednego z graczy nie musi być automatycznie przegraną drugiego, a suma wypłat graczy może zależeć od ich strategii. Interesy graczy nie są zatem całkowicie przeciwstawne. Gracze mogą maksymalizować jednocześnie różne wielkości. Możliwe jest też wprowadzenie większej liczby graczy. Konstrukcja takiej gry jest podobna do konstrukcji gry o sumie zero z ta różnicą, że każdy z graczy będzie korzystał ze swojej macierzy wypłat. Podstawowe elementy gry to: gracze, strategie graczy, wielowymiarowe macierze wypłat. Dla trzech graczy dane są w tym przypadku trzy trójwymiarowe macierze wypłat takich samych rozmiarów a,b i c. Wyraz a(i,j,k) zadaje wypłatę pierwszego gracza, w sytuacji gdy gracz pierwszy podjął decyzję 'i', a gracz drugi podjął decyzję 'j', a trzeci decyzję 'k'. Analogicznie w macierzy b są zapisane wypłaty gracza II, a w macierzy c, wypłaty gracza III. Decyzje gracza I odpowiadają wyborowi wierszy, gracza II kolumn, a gracza III warstw w odpowiednich macierzach. Podobnie jak w grach o sumie zero rozpatrujemy strategie mieszane, bo tylko takie ujęcie zbioru strategii gwarantuje znalezienie rozwiązania gry. Na początku będziemy rozważać gry dwuosobowe, ale wszystkie wprowadzone pojęcia bez trudu uogólniają się na gry wieloosobowe. Definicja (funkcje wypłat) Jeśli A jest macierzą wypłat gracza I, a B macierzą wypłat gracza II oraz 𝑝 = (𝑝1 , ⋯ , 𝑝𝑛 ) jest wektorem prawdopodobieństwa gracza I, a 𝑞 = (𝑞1 , ⋯ , 𝑞𝑛 ) jest wektorem prawdopodobieństwa gracza II to wypłatę dla gracza I liczymy ze wzoru 𝑢1 𝑝, 𝑞 = 𝑝 ∙ 𝐴 ∙ 𝑞 𝑇 , a gracza II 𝑢2 𝑝, 𝑞 = 𝑝 ∙ 𝐵 ∙ 𝑞 𝑇 . 36 Definicja (Rozwiązanie gry, punkt Nasha) Rozwiązaniem gry (punktem Nasha) nazywamy każdą parę strategii (p*, q*) taką, że 𝑢1 𝑝∗ , 𝑞 ∗ ≥ 𝑢1 (𝑝, 𝑞 ∗ ) oraz 𝑢2 𝑝∗ , 𝑞 ∗ ≥ 𝑢2 (𝑝∗ , 𝑞) dla dowolnych strategii p i q. Oznacza to, że dowolna zmiana strategii jednego z graczy, (bez zmiany strategii drugiego) nie spowoduje wzrostu jego wygranej (czyli krótko mówiąc jest nieopłacalna). Twierdzenie (Nash) Każda gra macierzowa ma przynajmniej jeden punkt równowagi. Punktów Nasha może być wiele, co gorsza każdemu z nich może odpowiadać inna para wypłat (czyli nie ma w tak skonstruowanych grach pojęcia wartości gry). W jednym z rozwiązań wypłaty mogą być korzystniejsze dla jednego z graczy, a w innym dla drugiego. Kluczowym problemem staje się zatem nie tylko wyliczenie punktów Nasha, ale ich odpowiednia selekcja. Sztandarowym przykładem gry dwuosobowej nie o sumie zero jest dylemat więźnia. Dwóch osaczonych w areszcie jest przesłuchiwanych na okoliczność wspólnie dokonanego przestępstwa. Obaj mężczyźni są przesłuchiwani oddzielnie i mają do dyspozycji następujące możliwości: przyznać się do winy (zdradzić), nie przyznawać się (milczeć). Jeśli obaj osaczeni przyznają się do winy zostają skazani na 5 lat. Jeśli jeden z nich przyzna się, a drugi milczy to ten co się przyznał wychodzi na wolność, a drugi dostaje karę 10 lat więzienia. Jeśli obaj milczą wyjdą na wolność po odsiedzeniu roku więzienia. Jest to tzw. gra symetryczna czy sytuacja obu graczy jest z ich punktu widzenia identyczna. Ustalmy, że przez A będziemy oznaczać macierz wypłat gracza I, a przez B gracza II. W tym wypadku 𝐴= −1 0 −10 −5 i 𝐵 = 𝐴𝑇 , gdzie strategia wyboru pierwszego wiersza odpowiada za milczenie, a drugiego za zdradę (analogicznie dla kolumn w przypadku gracza II). Z łatwością można zauważyć, że w przypadku braku komunikacji między graczami preferują oni zdradę, choć najlepszym rozwiązaniem byłoby zachowanie przez obu milczenia. Stąd nazwa gry: dylemat więźnia. 37 Para strategii (zdradź, zdradź) jest jednym punktem Nasha w grze. Można powiedzieć, że jest to taki uogólniony punkt siodłowy. Procedura szukania punktów siodłowych w ogólnej grze niekooperacyjnej jest inna niż w przypadku gier o sumie zero ze względu na to, że mamy do dyspozycji dwie macierze. Najlepiej wyjaśnić ją na przykładzie. Przykład 7 6 3 2 7 6 i 𝐵= . 2 3 4 7 2 5 Gracz pierwszy stara się zmaksymalizować swoją wypłatę w związku z czym szuka wyrazów maksymalnych w każdej kolumnie macierzy A, a gracz drugi szuka wyrazów maksymalnych, ale we wierszach macierzy B. Odpowiednie wyrazy zostały pogrubione poniżej: Weźmy grę o macierzach wypłat 𝐴 = 𝐴= 𝟕 2 𝟔 3 3 𝟒 𝐵= 2 𝟕 𝟕 6 2 5 W pierwszym wierszu i drugiej kolumnie widoczna jest zgodność intencji graczy. Jest to nasz szukany uogólniony punkt siodłowy. Do szukania pojedynczych punktów Nasha w grach niekooperacyjnych (ale nie o sumie zero) służy algorytm Lemke – Howson. Jest to algorytm o strukturze podobnej do programowania liniowego. Szukanie wszystkich punktów Nasha jest pracochłonne. Jak wcześniej wspomnieliśmy kluczowym problemem jest wybór odpowiednich punktów równowagi. W związku z tym w naszym skrypcie zajmiemy się szczególnymi typami gier w przypadku których znalezienie punktów Nasha jest łatwe lub szukamy punktów o z góry określonych właściwościach. Ćwiczenie 11.1 Skonstruuj program do rysowania dwóch macierzy i zaznaczania punktów siodłowych. 38 12. Rozwiązywanie gier niekooperacyjnych o małych wymiarach Definicja Zbiorem racjonalnych reakcji gracza I, który oznaczamy przez 𝑅1 nazywamy zbiór wszystkich par strategii (p, q) takich, że p jest najlepszą odpowiedzią na q, gdzie q przebiega zbiór wszystkich strategii gracza II tzn. 𝑝, 𝑞 ∈ 𝑅1 dokładnie wtedy gdy 𝑢1 𝑝, 𝑞 = max𝑟 𝑢1 (𝑟, 𝑞). Analogicznie definiujemy zbiór 𝑅2 . Fakt Zbiór punktów Nasha jest częścią wspólną zbiorów racjonalnych reakcji. W przypadku gier 2x2 strategie graczy można zadać poprzez podanie dwóch liczb: x z przedziału [0,1] – prawdopodobieństwo użycia pierwszego wiersza przez gracza I i y z przedziału [0,1] – prawdopodobieństwo użycia pierwszej kolumny przez gracza II. W związku z tym zbiory racjonalnych reakcji można przedstawić jako punkty na płaszczyźnie i w ten sposób bez trudu znaleźć wszystkie punkty Nasha. Takie rozwiązanie nazywamy graficznym. Przykładowy rysunek dla macierzy A=[2 3;4 1] i B=[1 5;4 1] wygląda następująco: 39 Punkty Nasha zapisane jako pary (x;y) to (1;0), (0;1) oraz (3/7; 1/2). Oznacza to w pierwszym przypadku wybranie pierwszego wiersza i drugiej kolumny, w drugim drugiego wiersza i pierwszej kolumny, a w trzecim wybór pierwszego wiersza z prawdopodobieństwem 3/7 a drugiego z prawdopodobieństwem 2/7, ponadto wybór pierwszej i drugiej kolumny z prawdopodobieństwami 1/2. Ćwiczenie 12.1 Wykonaj graficzną reprezentację zbiorów racjonalnych reakcji dla gry o macierzach wypłat: A=[6 3;3 4] i B=[2 7; 7 2]. 40 13. Dynamiczna (ewolucyjna) teoria gier W ewolucyjnej teorii gier rozważamy gry symetryczne dotyczące populacji różnego typu osobników. Zakłada się, że zdolność reprodukcyjna jest proporcjonalna do średniej wypłaty dla danego typu osobników. Proces zmiany liczebności w kolejnych pokoleniach (powtórzeniach gry) nazywamy dynamiką replikacyjną. Odbywa się ona według wzoru: 𝑝𝑡+1 𝑠 = 𝑝𝑡 (𝑠) 𝐴𝑠 𝑝 𝑇 𝑝𝐴𝑝 𝑇 , gdzie s jest numerem typu osobnika 𝐴𝑠 oznacza s-ty wiersz w macierzy wypłat symetrycznej gry macierzowej. Przypomnienie: W symetrycznej grze macierzowej macierz wypłat drugiego gracza jest macierzą transponowaną do macierzy wypłat gracza pierwszego. W opisie gry wystarczy zatem podać macierz wypłat pierwszego gracza, zazwyczaj oznaczoną przez A. W zależności od stanu początkowego dynamika replikacyjna prowadzi do konstrukcji tzw. punktów ewolucyjnie stabilnych. Maynard Smith jako pierwszy wprowadził to pojęcie jako propozycję równowagi w dynamicznej teorii gier. Definicja Mówimy, że strategia p (proporcje określonych osobników w liczbach sumujących się do jedności) jest strategią ewolucyjnie stabilną (ESS) jeśli (p,p) jest punktem Nasha oraz „żaden mutant korzystający z innej strategii nie ma szans dokonania inwazji na populację”. Warunek drugi w języku matematyki oznacza, że dla strategii r „niewiele różniącej się” od p mamy 𝑢1 𝑝, 𝑟 ≥ 𝑢1 (𝑟, 𝑟) (populacja w starych proporcjach ma przynajmniej taki sam potencjał jak nowych w zmienionym przez inwazję środowisku). Przykład Rozważmy populację złożoną z trzech typów osobników: uczciwych, którzy zawsze chcą połowę zasobów, zachłannych, którzy zawsze chcą więcej niż połowę np. 2/3 i skromnych, którzy zadowalają się 1/3. Kiedy jeden zachłanny spotyka drugiego zachłannego to obydwaj nic nie dostają ze względu na walkę o zasoby. Jeśli zachłanny spotyka uczciwego obaj też 41 nic nie otrzymują. Tylko spotkanie: zachłanny – skromny skutkuje podziałem zasobów w proporcji 2/3 i 1/3. Dwaj uczciwi podzielą się zasobami po połowie, dwaj skromni wezmą po 1/3, a spotkanie skromnego z uczciwym da rezultat 1/3 dla skromnego i 1/2 dla uczciwego. Przyjmując, że typ uczciwy oznaczymy jako typ 1, skromny jako typ 2, a zachłanny jako typ 3, wypłaty w naszej symetrycznej grze można przedstawić za pomocą macierzy (w celu uniknięcia wpisywania ułamków wypłaty zostały pomnożone przez 6, nie ma to wpływu na strategie optymalne): 3 𝐴= 2 0 3 2 4 0 2 0 Istnieją dwa ewolucyjnie stabilne punkty w takiej grze. Jednym z nich jest strategia wyboru pierwszego wiersza czyli stan w którym populacja składa się wyłącznie z osobników uczciwych. Nawet bez zastosowania wzorów matematycznych łatwo zauważyć, że gdyby w tej populacji pojawił się osobnik zachłanny to jego wypłata byłaby równa zero, co nie daje mu szans na przeżycie (zachłanni do przeżycia potrzebują pewnego odsetka skromnych), a jeśli pojawi się osobnik skromny, dostanie wypłatę mniejszą niż uczciwy i w związku z tym także nie ma dużych szans na propagowanie swojej strategii. Znalezienie drugiego punktu ewolucyjnie stabilnego pozostawiam jako ćwiczenie. Ćwiczenie 13.1 Napisz program, który po podaniu początkowej liczby osobników każdego typu przeprowadzi populację z podanego przykładu zgodnie z dynamiką replikacyjną przez 50 pokoleń. Ćwiczenie 13.2 Funkcja Matlaba [X , Y] = meshgrid(x, y) przekształca wektory x i y w parę macierzy X i Y. Wiersze wynikowej macierzy X są kopiami wektora x, kolumny wynikowej macierzy Y są kopiami wektora y. Taka „chmura” punktów może być użyta do znajdowania wartości lub testowania własności funkcji dwóch zmiennych. Wypróbuj ciąg poleceń: >> x = 1:0.5:3 ; >> y = 21:25; >> [X,Y] = meshgrid(x,y) 42 Ćwiczenie 13.3 Utwórz “chmurę” punktów będących wierzchołkami podziału kwadratu o boku 1 na kwadraty o boku 1/10. Narysuj punkty poleceniem scatter(x,y). Jaki wynik da zastosowanie polecenia plot(x,y) ? Wyjaśnij dlaczego tak to wygląda ? Ćwiczenie 13.4 Określ obszary przyciągania dla każdego z punktów ewolucyjnie stabilnych z poprzedniego ćwiczenia według następującego algorytmu: 1. Zadaj „chmurę” punktów dzieląc zakres x i y na przedziały długości 1/100. 2. Jeśli x+y<=1 to przyjmij x za procent osobników uczciwych, a y skromnych. 3. Ustal do jakiego punktu zbliżają się strategie zadane dynamiką ewolucyjną po np.100 iteracjach (ustal zakres błędu). 4. Za pomocą polecenia find znajdź, które punkty zbliżyły się dostatecznie blisko strategii „sami uczciwi” i używając polecenia plot zaznacz je na wykresie na czerwono. Pozostałe punkty z „chmury” spełniające warunek x+y<=1 zaznacz na niebiesko. 5. Uzupełnij rysunek odpowiednią legendą. Ćwiczenie 13.5 Znajdź w internecie opis gry jastrząb - gołąb i oblicz strategie ewolucyjnie stabilne. Odnajdź informacje na czym polegają strategie mściciela i pozera. Przeprowadź symulacje dla tak rozbudowanej gry 4x4. 43 14. Okienka dialogowe Niedogodnością w prowadzeniu symulacji jest konieczność każdorazowego wpisywania parametrów w programie i wywoływania nazwy funkcji w głównym oknie programu MATLAB. Dużym ułatwieniem jest wyświetlanie przez program zachęty do wpisania danych lub okienka dialogowe. Pierwsza opcja jest dostępna na Octave (darmowym odpowiedniku Matlaba), druga jest dostępna tylko dla użytkowników MATLAB-a. Wysłać na ekran zachętę do wpisania danych można poleceniem input Jeśli chcemy by program zapisał dane w postaci ciągu znaków trzeba to zaznaczyć. Polecenie w = input('Wpisz liczbe: ') (uwaga na polskie znaki !) po wpisaniu 4 nada zmiennej „w” wartość 4. Polecenie w = input('Wpisz macierz: ' ,'s') zapisze zmienną w jako ciąg znaków (nawiasów i symboli liczbowych np. w=[2 3;4 1]). Ciąg znaków na polecenia MATLAB-a (w podanym przykładzie na macierz) zamienia komenda eval. Ciągi znaków można porównać poleceniem strcmp. Ćwiczenie 14.1 Napisz i wywołaj skrypt. Wyjaśnij jego działanie krok po kroku. Czego nie zobaczymy na ekranie po wstawieniu średnika w trzeciej i przedostatniej linijce skryptu ? zapyt='tak'; while strcmp(zapyt, 'tak') wzor=input('podaj macierz: ','s') s=eval(wzor) if size(s,2)>1 disp('duzo kolumn') else disp('malo kolumn') end zapyt=input('czy chcesz wykonac program po raz kolejny ? Wpisz tak lub nie: ','s') end 44 Okienka dialogowe to proste interfejsy do wprowadzania danych. Korzystamy w tym wypadku z polecenia inputdlg Przykład tworzenia okna dialogowego do programu rysującego minimum lub maksimum z dwóch funkcji na przedziale [-2,2] function maxmin % napisy przy polach do wprowadzania zmiennych prompt={'y1:', 'y2:','Max czy min ? Napisz max lub min'}; tutul='Wzory funkcji'; % nazwa okna linie=1; %liczba linii do wpisania def={'cos(x)','2*x','max'}; % wartości domyślne answer=inputdlg(prompt,tutul,linie,def); x=[-2:0.01:2]; y1=eval(answer{1}); y2=eval(answer{2}); if strcmp{answer{3},'min'} % jeśli minimum y=min(y1,y2); elseif strcmp{answer{3},'max'} % jeśli maksimum y=max(y1,y2); end plot(x,y1,x,y2,x,y) Ćwiczenie 14.2 Napisz program wyświetlający okienko dialogowe z obrazka oraz wyświetlające kolorowy pasek z kolorami z palety hsv(n) lub jet(n), gdzie n jest wybrana liczbą. Ćwiczenie 14.3 Wzbogać program do symulacji z poprzedniego rozdziału (ćwiczenie 15.1) o okienko dialogowe do wprowadzania liczby graczy poszczególnych typów. 45 15. Podstawy animacji Obserwację przebiegu symulacji można przeprowadzać w czasie rzeczywistym w postaci animacji. W tym rozdziale omówimy potrzebne do tego celu narzędzia. Matlab umożliwia bardzo łatwe tworzenie animacji wykresów. Zawartość okna graficznego można zapisać przy pomocy polecenia getframe zapisać np.: w tablicy. Utworzoną tablicę odtworzymy poleceniem movie. Poniższy krótki skrypt ukazuje działanie poleceń: x=0:0.01:5; for k = 1:16 plot(x,sin(k*x)) M(k) = getframe; end movie(M,20) % liczba powtórzeń %movie(M,20,4) % ostatni argument to liczba obrazków na sekundę, %domyślna wartość to 12. Ćwiczenie 15.1 Zrób animację w postaci obracającej się litery (patrz rozdział 8) . Innym sposobem na tworzenie animacji jak również zrobienia pokazu slajdów jest zastosowanie polecenia pause. Zatrzymuje ono wykonywanie instrukcji do chwili naciśnięcia przycisku z klawiatury. Polecenie pause(n) zatrzymuje program na n sekund (n może być mniejsze od 1 np. 0.1). Poniższy skrypt pokazuje jak zrobić znikający napis tempo = 0.1 : 0.02: 1; for k = tempo text(0,0,'Napis na srodku', 'Fontsize', 22,'Color',[k k k]); axis([-1 2 -1 1]) pause(0.05); end; 46 Trzecią metodą na tworzenie animacji jest obserwacja procesów w czasie rzeczywistym tzn. program aktualizuje obiekty graficzne pobierając kolejny gotowy rysunek. Służy temu polecenie drawnow. Polecenie to, podobnie jak poprzednie, stosowane jest głównie do rysowania wykresów w pętli. Poniższy skrypt „rysuje” sinosoidę w przedziale [0,10] x=linspace(0:10,1000); for k=1:1000 x1=x(1:k); plot(x1,sin(x1)) % bardzo szybko axis([0,10,0,2]) drawnow end Ćwiczenie 15.2 Przerób program tak by na pierwszym wykresie widniała gotowa sinusoida, a następne znikała kawałek po kawałku z upływem czasu. 47 16. Grafy w teorii gier – komponenty spójne W tym rozdziale wyjaśnię jak szukać komponent spójnych grafu oraz jak sąsiedztwo punktów przetłumaczyć na język teorii grafów. Graf jest strukturą składająca się z wierzchołków V i krawędzi E. Elementami zbioru E są uporządkowane pary punktów ze zbioru wierzchołków. Na potrzeby tego skryptu będziemy zajmować się grafami nieskierowanymi. Grafy można opisywać za pomocą podania zbioru liczb, który numeruje wierzchołki i zbioru par tych liczb dla których odpowiadające im wierzchołki są połączone. Dane na temat grafu można także zapisać za pomocą macierzy sąsiedztwa A. Jest to macierz kwadratowa wymiaru n, gdzie n jest liczbą wierzchołków. Elementami tej macierzy są 0 i 1. Jeśli wierzchołki 'i' i 'j' są połączone to wyraz A(i,j)=1. W pozostałych przypadkach A(i,j)=0. Dla macierzy sąsiedztwa A A=[0 1 1 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 1 0 0 1 1 0 0 0 0 0 0 1 1 0 0 1 0 0 0 0 0 0 1 0 0 1 0;... 0;... 0;... 0;... 0;... 0;... 0;... 1;... 1;... 0] graf wygląda następująco: 48 Rysunek uzyskano posługując się komendą gplot(A,xy), gdzie xy=[0 0;0 1;1 3;2 3;4 2;4 1;3 -1;3 -2;2 -2;1 -1] jest spisem współrzędnych kolejnych wierzchołków. Komponenta spójna składa się z tych wszystkich wierzchołków, które są bezpośrednio lub pośrednio ze sobą połączone tzn. istnieje droga (ciąg krawędzi) w grafie, która je łączy. Nasz przykładowy graf ma dwie komponenty spójne. Do wyliczania komponent spójnych służy polecenie [p,q,r,s]=dmperm(B), gdzie B=A+Id (Id macierz z jedynkami na głównej przekątnej, macierz identycznościowa). Interesują nas zmienne p i r. Wektor p jest permutacją numerów wierzchołków, a wektor r podaje numer początku kolejnej komponenty spójnej po permutacji p. Dokładniej p(r(1):r(2)-1) daje spis numerów wierzchołków z pierwszej komponenty spójnej, p(r(2): r(3)-1) drugiej komponenty spójnej itd. Liczba komponent spójnych to o jeden mniej niż liczba współrzędnych wektora r . 49 Ćwiczenie 16.1 Wypróbuj i zinterpretuj: function graf A=[0 1 1 0 0 0 0 0 0 0;1 0 0 1 0 0 0 0 0 0;... 1 0 0 1 0 0 0 0 0 0;0 1 1 0 0 0 0 0 0 0;... 0 0 0 0 0 1 1 0 0 0;0 0 0 0 1 0 0 1 0 0;... 0 0 0 0 1 0 0 1 1 0;0 0 0 0 0 1 1 0 0 1;... 0 0 0 0 0 0 1 0 0 1;0 0 0 0 0 0 0 1 1 0]; xy=[0 0;0 1;1 3;2 3;4 2;4 1;3 -1;3 -2;2 -2;1 -1]; gplot(A,xy) [p,q,r,s]=dmperm(A+eye(10)); p r Ćwiczenie 16.2 Narysuj dwa rozłączne trójkąty. Ponumeruj wierzchołki tak aby kolene numery zostały rozmieszczone na dwóch różnych trójkątach. Napisz macierz sąsiedztwa tak skonstruowanego grafu. Zastosuj i zinterpretuj polecenia z poprzedniego ćwiczenia. Ćwiczenie 16.3 Napisz program, który po podaniu wektora liczb naturalnych z przedziału [1,10] wybiera z grafu z ćwiczenia 23.1 tylko wierzchołki o podanych numerach, sporządza rysunek takiego grafu, a następnie podaje czy graf jest spójny czy nie. Ćwiczenie 16.4 Stwórz procedurę rysująca grafy w 3d dla podanej macierzy (na początek może by dla ustalonego wymiaru). 50 17. Interaktywna współpraca z grafiką Do współpracy z grafiką potrzebny jest dostęp do jej parametrów. Każdy obiekt jest identyfikowany przez liczbę. Tę liczbę przyporządkowuje się zmiennej w momencie tworzenia grafiki np. h=plot(x,sin(x)). Do każdego obiektu można się odwołać podając jej identyfikator (tzw. uchwyt), w naszym przykładzie jest to litera h, lub bezpośrednio podając liczbę. Odczytać własności obiektu można używając funkcji get np. get(h) (obiekt w otwartym oknie), wartość żądanej własności można pobrać używając polecenia get(id_obiektu,'nazwa własności'), zmienić parametry można za pomocą funkcji set(id_obiektu,'nazwa własności', nowa wartość). Ważne uchwyty: gca (get current axes) – zwraca uchwyt do aktywnego układu współrzędnych gcbo (get callback object) – zwraca uchwyt do obiektu, z którego wywołano Callback gcf (get current figure) - zwraca uchwyt aktywnego okna gcbf (get callback figure) – zwraca uchwyt do okna z którego wywołano Callback Współpracę z grafiką umożliwia własność ButtonDownFcn. Poniższa funkcja prezentuje przykład obsługi tej własności. function wspolpraca x=[0:0.01:8]; y=sin(x); h=plot(x,y,'linewidth', 2) set(gca, 'ButtonDownFcn', 'obsluga_gca') set(h, 'ButtonDownFcn', 'obsluga_wykres') function obsluga_gca text(1,0.5,'klik') % t=text(...), pause(3), set(t,'Color',[1 1 1]) lub set(t,'Visible', 'off') % pobieranie współrzędnych myszki (dwie współrzędne) punkt=get(gca,'CurrrentPoint') 51 function obsluga_wykres grubosc=get(gcbo,'linewidth'); set(gcbo,'linewidth', 6 - grubosc) Ćwiczenie 17.1 Utwórz program wyświetlający napis 'klik' w miejscu kliknięcia. Ćwiczenie 17.2 Utwórz program, który na kliknięcie w okno graficzne rysuje w tym miejscu punkt o zadanych właściwościach. Do rysowania punktu można użyć polecenia line(współrzędna1, współrzędna2,'marker', 'rodzaj markera', 'markersize', liczba). Jak wysłać współrzędne położenia myszki w określone miejsce ? Służy do tego polecenie assignin. Wysłać informacje można albo do innego programu albo do głównej przestrzeni roboczej. Wypróbuj funkcję function zapisz x=5; % eksmisja do głównej przestrzeni roboczej assignin('base', 'nowyx', x); % eksmisja do programu który wywołuje funkcję zapisz assignin('caller', 'nowyx', x); Ćwiczenie 17.3 Udoskonal grę „15” tak by ruchy wykonywać za pomocą myszki na rysunku tablicy gry. 52 18. Gry kombinatoryczne Definicja Gry kombinatoryczne to gry dwuosobowe z pełną informacją, bez czynnika losowego, z zero-jedynkowymi wypłatami. Gracze wykonują ruchy naprzemiennie aż do zakończenia gry. Gry kombinatoryczne dzielimy na bezstronne (zbiór dostępnych ruchów dla gracza zależy wyłącznie od pozycji w grze) i wielostronne (dostępność ruchów zależy np. od koloru pionka gracza). Elementy skończonej gry bezstronnej: dwóch graczy, skończony zbiór pozycji w grze, skończony zestaw ruchów dostępnych w każdej pozycji. Zasady skończonej gry bezstronnej: gracze wykonują ruchy naprzemiennie gra kończy się gdy nie ma już możliwości ruchu gracz, który wykona ostatni ruch wygrywa. Założenie dodatkowe: gra kończy się zawsze po skończonej liczbie ruchów. Przykład (gra odejmowania) Pozycją początkowa w grze jest liczba 21. Ruch w grze polega na odjęciu 1,2 lub 3 od rezultatu poprzedniego gracza. W każdej bezstronnej, skończonej grze kombinatorycznej pozycje możemy podzielić na wygrywające (oznaczone przez W) i przegrywające (oznaczone przez P). Każda pozycja otrzymuje etykietkę W albo P. Pozycja przegrywająca to taka z której każdy ruch prowadzi do pozycji wygrywającej, a wygrywająca to taka z której istnieje co najmniej jeden ruch prowadzący do pozycji przegrywającej. Dla naszej przykładowej gry odejmowania pozycje przegrywające to wielokrotności liczby 4. Jedną z metod nadawania odpowiednich etykietek pozycjom w grze jest metoda indukcji wstecznej. Polega ona na przeprowadzenia analizy zaczynając od końcowych etapów gry. 53 Ćwiczenie 18.1 Znajdź pozycje przegrywające w grze odejmowania, w której od wyniku przeciwnika można odjąć co najwyżej połowę. Jakie są pozycje końcowe w tej grze ? Czy gracz rozpoczynający grę może ją wygrać, jeśli pozycją początkową jest liczba 100 ? Przeprowadź metodą indukcji wstecznej. Inne przykłady gier 1. Grę zaczynamy od ustawienia dwóch stosów (n i m krążków odpowiednio na każdym ze stosów). Ruch w grze polega na usunięciu jednego ze stosów i podzieleniu drugiego na dwa w dowolnej proporcji. 2. Zaczynamy grę od wybranej liczby (np. 44). Gracz rozpoczynający może od niej odjąć dowolną liczbę od niej mniejszą. Kolejne ruchy graczy polegają na odjęciu od wyniku uzyskanego przez przeciwnika nie więcej niż odjął przeciwnik w poprzednim ruchu. 3. Gra SOS – grę zaczynamy mając do dyspozycji 7 pustych kwadratów ustawionych sąsiadująco w jednym rzędzie. Gracze naprzemiennie wpisują po jednej literze w dowolnie wybrany pusty kwadrat (sami wybierają kwadrat i literę S lub O). Wygrywa gracz, któremu jako pierwszemu uda się napisać słowo SOS. Jeśli ta sztuka nie uda się żadnemu z graczy przyjmujemy to za remis. 54 19. Gra Nim Najbardziej znaną grą odejmowania jest gra Nim. Wybieramy liczbę naturalną k (k>1) i wektor liczb naturalnych 𝑥 = (𝑥1 , ⋯ , 𝑥𝑘 ). Rozważamy k stosów, j-ty stos składa się z 𝑥𝑗 elementów. Ruch gracza polega na wybraniu jednego ze stosów i zabraniu z niego dowolnej liczby elementów. Wygrywa ten gracz, który jako ostatni ma możliwość ruchu. Każdą pozycję w tej grze można zakodować jako k elementowy wektor. Jedyną pozycją końcową jest pozycja samych zer. Zgodnie z nomenklaturą wprowadzoną w poprzednim rozdziale pozycje w grze dzielimy na wygrywające (oznaczone przez W) i przegrywające (oznaczone przez P). W przypadku k=2 pozycje przegrywające to wektory o dwóch, równych współrzędnych. Jeśli mamy do czynienia z trzema niepustymi stosami, rozstrzygnięcie, które pozycje są wygrywające (i co więcej jak wykonać optymalne posunięcie z takiej pozycji) jest bardziej skomplikowane. Można skorzystać z indukcji wstecznej, ale ta metoda jest bardzo nieefektywna jeśli chodzi o złożoność obliczeniową. Definicja Sumą nim dwóch liczb naturalnych a i b oznaczoną przez a NIM b nazywamy liczbę, której kolejne cyfry w zapisie dwójkowym są sumą cyfr w zapisie dwójkowym a i b modulo 2. Jak to działa ? Każdą liczbę można zapisać jako 𝑎 = 𝑎𝑚 2𝑚 + 𝑎𝑚 −1 2𝑚 −1 +. . . +𝑎0 dla pewnego m, gdzie każde 𝑎𝑗 jest zerem lub jedynką. Będziemy w takim wypadku używać notacji 𝑎 = 𝑎𝑚 , 𝑎𝑚 −1 , . . . , 𝑎0 2 . Chcąc dodać liczby a i b każdą z nich trzeba zapisać w notacji dwójkowej tej samej długości. Definicja Suma nim liczb a i b dla 𝑎 = 𝑎𝑚 , 𝑎𝑚 −1 , . . . , 𝑎0 2 i 𝑏 = 𝑏𝑚 , 𝑏𝑚 −1 , . . . , 𝑏0 2 jest liczbą 𝑐 = 𝑐𝑚 , 𝑐𝑚 −1 , . . . , 𝑐0 2 taką, że 𝑐𝑗 = 𝑎𝑗 + 𝑏𝑗 𝑚𝑜𝑑2 czyli 𝑐𝑗 = 0 dokładnie wtedy gdy 𝑎𝑗 = 𝑏𝑗 . Przykład: Obliczmy 20 NIM 5. Zaczynamy od zapisania obu składowych liczb w systemie dwójkowym czyli 20= 10100 2 , a 5= 00101 2 . Liczby 20 i 5 mają obie jedynkę na trzecim miejscu od końca zatem ich suma nim będzie miała na tym miejscu (w swoim zapisie) cyfrę zero. 55 𝑎 𝑏 𝑐 1 0 1 0 0 0 1 1 0 0 0 0 0 1 czyli 𝑐= 10001 2 =17 . 1 Ostatecznie 20 NIM 5 = 17. Twierdzenie Pozycja 𝑥 = 𝑥1, 𝑥2,... , 𝑥𝑘 jest przegrywająca wtedy i tylko wtedy gdy suma nim współrzędnych wektora x wynosi 0. Dowód polega na wskazaniu metody konstrukcji ruchu z pozycji wygrywającej do przegrywającej i pokazaniu, że każdy ruch z pozycji przegrywającej prowadzi do pozycji wygrywającej. Ćwiczenie 19.1 Ustal czy w grze nim z trzema stosami o liczbie elementów równej kolejno 12,15 i 20. Uzasadnij, że jest to pozycja wygrywająca i wskaż optymalny ruch w gracza rozpoczynającego grę.