9.5. Algorytmy

W tym scenariuszu spróbujemy pokazać w Minecrafcie Pi algorytm symulujący ruchy Browna oraz algorytm stosujący metodę Monte Carlo do wyliczenia przybliżonej wartości liczby Pi.

9.5.1. Ruchy Browna

Za pomocą wybranego edytora utwórz pusty plik, umieść w nim podany niżej kod i zapisz w katalogu mcpi-sim pod nazwą mcpi-rbrowna.py:

Kod nr
  1#!/usr/bin/env python
  2# -*- coding: utf-8 -*-
  3
  4import os
  5import numpy as np  # import biblioteki do obliczeń naukowych
  6import matplotlib.pyplot as plt  # import biblioteki do tworzenia wykresow
  7from random import randint
  8from time import sleep
  9import mcpi.minecraft as minecraft  # import modułu minecraft
 10import mcpi.block as block  # import modułu block
 11
 12os.environ["USERNAME"] = "Steve"  # wpisz dowolną nazwę użytkownika
 13os.environ["COMPUTERNAME"] = "mykomp"  # wpisz dowolną nazwę komputera
 14
 15mc = minecraft.Minecraft.create("192.168.1.10")  # połączenie z serwerem
 16
 17
 18def plac(x, y, z, roz=10, gracz=False):
 19    """
 20    Funkcja tworzy podłoże i wypełnia sześcienny obszar od podanej pozycji,
 21    opcjonalnie umieszcza gracza w środku.
 22    Parametry: x, y, z - współrzędne pozycji początkowej,
 23    roz - rozmiar wypełnianej przestrzeni,
 24    gracz - czy umieścić gracza w środku
 25    Wymaga: globalnych obiektów mc i block.
 26    """
 27
 28    podloga = block.SAND
 29    wypelniacz = block.AIR
 30
 31    # podloga i czyszczenie
 32    mc.setBlocks(x, y - 1, z, x + roz, y - 1, z + roz, podloga)
 33    mc.setBlocks(x, y, z, x + roz, y + roz, z + roz, wypelniacz)
 34    # umieść gracza w środku
 35    if gracz:
 36        mc.player.setPos(x + roz / 2, y + roz / 2, z + roz / 2)
 37
 38
 39def wykres(x, y, tytul="Wykres funkcji", *extra):
 40    """
 41    Funkcja wizualizuje wykres funkcji, której argumenty zawiera lista x
 42    a wartości lista y i ew. dodatkowe listy w parametrze *extra
 43    """
 44    if len(extra):
 45        plt.plot(x, y, extra[0], extra[1])  # dwa wykresy na raz
 46    else:
 47        plt.plot(x, y, "o:", color="blue", linewidth="3", alpha=0.8)
 48    plt.title(tytul)
 49    plt.grid(True)
 50    plt.show()
 51
 52
 53def rysuj(x, y, z, blok=block.IRON_BLOCK):
 54    """
 55    Funkcja wizualizuje wykres funkcji, umieszczając bloki w pionie/poziomie
 56    w punktach wyznaczonych przez pary elementów list x, y lub x, z
 57    """
 58    czylista = True if len(y) > 1 else False
 59    for i in range(len(x)):
 60        if czylista:
 61            print(x[i], y[i])
 62            mc.setBlock(x[i], y[i], z[0], blok)
 63        else:
 64            print(x[i], z[i])
 65            mc.setBlock(x[i], y[0], z[i], blok)
 66
 67
 68def ruchyBrowna():
 69
 70    n = int(raw_input("Ile ruchów? "))
 71    r = int(raw_input("Krok przesunięcia? "))
 72
 73    x = y = 0
 74    lx = [0]  # lista odciętych
 75    ly = [0]  # lista rzędnych
 76
 77    for i in range(0, n):
 78        # losujemy kąt i zamieniamy na radiany
 79        rad = float(randint(0, 360)) * np.pi / 180
 80        x = x + r * np.cos(rad)  # wylicz współrzędną x
 81        y = y + r * np.sin(rad)  # wylicz współrzędną y
 82        x = int(round(x, 2))  # zaokrągl
 83        y = int(round(y, 2))  # zaokrągl
 84        print(x, y)
 85        lx.append(x)
 86        ly.append(y)
 87
 88    # oblicz wektor końcowego przesunięcia
 89    s = np.fabs(np.sqrt(x**2 + y**2))
 90    print "Wektor przesunięcia: {:.2f}".format(s)
 91
 92    wykres(lx, ly, "Ruchy Browna")
 93    rysuj(lx, [1], ly, block.WOOL)
 94
 95
 96def main():
 97    mc.postToChat("Ruchy Browna")  # wysłanie komunikatu do mc
 98    plac(-80, -20, -80, 160)
 99    plac(-80, 0, -80, 160)
100    ruchyBrowna()
101    return 0
102
103
104if __name__ == '__main__':
105    main()

Większość kodu powinna być już zrozumiała. Importy bibliotek, nawiązywanie połączenia z serwerem MC Pi, funkcje plac(), wykres() i rysuj() omówione zostały w poprzednim scenariuszu Funkcje w mcpi.

W funkcji ruchyBrowna() na początku pobieramy od użytkownika ilość ruchów cząsteczki do wygenerowania oraz ich długość, co ma znaczenie podczas ich odwzorowywania w świecie MC Pi. Następnie w pętli:

  • losujemy kąt wskazujący kierunek ruchu cząsteczki,

  • wyliczamy współrzędne kolejnego punktu korzystając z funkcji cos() i sin() (np. x = x + r * np.cos(rad)),

  • zaokrąglamy wyniki do 2 miejsc po przecinku (np. x = int(round(x, 2))) i drukujemy,

  • na koniec dodajemy obliczone współrzędne do list odciętych i rzędnych (np. lx.append(x)).

Po wyjściu z pętli obliczamy długość wektora przesunięcia, korzystając z twierdzenia Pitagorasa, i drukujemy wynik z dokładnością do dwóch miejsc po przecinku (wyrażenie formatujące: {:.2f}).

Po tych operacjach pozostaje wykreślenie ruchu cząsteczki w matplotlib i wyznaczenie go w Minecrafcie.

../../_images/rbrowna-matplot.png

Wskazówka

Przed uruchomieniem wizualizacji warto ustawić Steve’a w tryb lotu (dwukrotne naciśnięcie spacji).

9.5.1.1. (Nie)powtarzalność

Kilkukrotne uruchomienie dotychczasowego kodu pokazuje, że za każdym razem generowany jest inny tor ruchu cząsteczki. Z jednej strony to dobrze, bo to potwierdza przypadkowość symulowanych ruchów, z drugiej strony przydatna byłaby możliwość zapamiętania wyjątkowo malowniczych sekwencji.

Zmienimy więc funkcję ruchyBrowna() tak, aby zapisywała i ewentualnie odczytywała wygenerowany i zapisany ruch cząsteczki. Musimy też dodać dwie funkcje narzędziowe zapisujące i czytające dane.

Kod nr
 68def ruchyBrowna(dane=[]):
 69
 70    if len(dane):
 71        lx, ly = dane  # rozpakowanie listy
 72        x = lx[-1]  # ostatni element lx
 73        y = ly[-1]  # ostatni element ly
 74    else:
 75        n = int(raw_input("Ile ruchów? "))
 76        r = int(raw_input("Krok przesunięcia? "))
 77
 78        x = y = 0
 79        lx = [0]  # lista odciętych
 80        ly = [0]  # lista rzędnych
 81
 82        for i in range(0, n):
 83            # losujemy kąt i zamieniamy na radiany
 84            rad = float(randint(0, 360)) * np.pi / 180
 85            x = x + r * np.cos(rad)  # wylicz współrzędną x
 86            y = y + r * np.sin(rad)  # wylicz współrzędną y
 87            x = int(round(x, 2))  # zaokrągl
 88            y = int(round(y, 2))  # zaokrągl
 89            print(x, y)
 90            lx.append(x)
 91            ly.append(y)
 92
 93    # oblicz wektor końcowego przesunięcia
 94    s = np.fabs(np.sqrt(x**2 + y**2))
 95    print "Wektor przesunięcia: {:.2f}".format(s)
 96
 97    wykres(lx, ly, "Ruchy Browna")
 98    rysuj(lx, [1], ly, block.WOOL)
 99    if not len(dane):
100        zapisz_dane((lx, ly))
101
102
103def zapisz_dane(dane):
104    """Funkcja zapisuje dane w formacie json w pliku"""
105    import json
106    plik = open('rbrowna.log', 'w')
107    json.dump(dane, plik)
108    plik.close()
109
110
111def czytaj_dane():
112    """Funkcja odczytuje dane w formacie json z pliku"""
113    import json
114    dane = []
115    nazwapliku = raw_input("Podaj nazwę pliku z danymi lub naciśnij ENTER: ")
116    if os.path.isfile(nazwapliku):
117        with open(nazwapliku, "r") as plik:
118            dane = json.load(plik)
119    else:
120        print "Podany plik nie istnieje!"
121    return dane
122
123
124def main():
125    mc.postToChat("Ruchy Browna")  # wysłanie komunikatu do mc
126    plac(-80, -20, -80, 160)
127    plac(-80, 0, -80, 160)
128    ruchyBrowna(czytaj_dane())
129    return 0

Z powyższego kodu wynika, że jeżeli funkcja ruchyBrowna() otrzyma niepustą listę danych (if len(dane):), wczyta z niej dane współrzędnych x i y. W przeciwnym wypadku generowane będą nowe, które zostaną zapisane: zapisz_dane((lx, ly)).

Funkcja zapisz_dane(), pobiera tuplę zawierającą listę współrzędnych x i y, otwiera plik o podanej nazwie do zapisu (open('rbrowna.log', 'w')) i zapisuje w nim dane w formacie json.

Funkcja czytaj_dane() prosi o podanie nazwy pliku z danymi, jeśli istnieje, zwraca dane zapisane w formacie json, które w funkcji ruchyBrowna() rozpakowywane są jako listy wartości x i y: lx, ly = dane. Jeżeli podany plik z danymi nie istnieje, zwracana jest pusta lista, a w funkcji ruchyBrowna() generowane są nowe dane.

W funkcji głównej zmieniamy wywołanie funkcji na ruchyBrowna(czytaj_dane()) i testujemy zmieniony kod. Za pierwszym razem wciskamy Enter, generujemy i zapisujemy dane, za drugim razem podajemy nazwę pliku rbrowna.log.

../../_images/rbrowna0.png

9.5.1.2. Ruch cząsteczki

Do tej pory ruch cząsteczki wizualizowane był jako pojedyncze punkty. Możemy jednak pokazać pokonaną trasę liniowo, używając omawianej już biblioteki minecraftstaff. Pod funkcją rysuj() umieszczamy następującą funkcję:

Kod nr
 68def rysuj_linie(x, y, z, blok=block.IRON_BLOCK):
 69    """
 70    Funkcja wizualizuje wykres funkcji, umieszczając bloki w pionie/poziomie
 71    w punktach wyznaczonych przez pary elementów list x, y lub x, z
 72    przy użyciu metody drawLine()
 73    """
 74    import local.minecraftstuff as mcstuff
 75    mcfig = mcstuff.MinecraftDrawing(mc)
 76    czylista = True if len(y) > 1 else False
 77    for i in range(len(x) - 1):
 78        x1 = int(x[i])
 79        x2 = int(x[i + 1])
 80        if czylista:
 81            y1 = int(y[i])
 82            y2 = int(y[i + 1])
 83            mc.setBlock(x2, y2, z[0], block.GRASS)
 84            mc.setBlock(x1, y1, z[0], block.GRASS)
 85            mcfig.drawLine(x1, y1, z[0], x2, y2, z[0], blok)
 86            mc.setBlock(x2, y2, z[0], block.GRASS)
 87            mc.setBlock(x1, y1, z[0], block.GRASS)
 88            print (x1, y1, z[0], x2, y2, z[0])
 89        else:
 90            z1 = int(z[i])
 91            z2 = int(z[i + 1])
 92            mc.setBlock(x1, y[0], z1, block.GRASS)
 93            mc.setBlock(x2, y[0], z2, block.GRASS)
 94            mcfig.drawLine(x1, y[0], z1, x2, y[0], z2, blok)
 95            mc.setBlock(x1, y[0], z1, block.GRASS)
 96            mc.setBlock(x2, y[0], z2, block.GRASS)
 97            print (x1, y[0], z1, x2, y[0], z2)
 98        sleep(1)  # przerwa na reklamę :-)
 99    mc.setBlock(0, 1, 0, block.OBSIDIAN)
100    if czylista:
101        mc.setBlock(x2, y2, z[0], block.OBSIDIAN)
102    else:
103        mc.setBlock(x2, y[0], z2, block.OBSIDIAN)

Jak widać, jest to zmodyfikowana funkcja, której użyliśmy po raz pierwszy w scenariuszu Funkcje. Zmiany dotyczą dodatkowych instrukcji typu mc.setBlock(x2, y2, z[0], block.GRASS), których zadaniem jest zaznaczenie innymi blokami wylosowanych punktów reprezentujących ruch cząsteczki. Instrukcja sleep(1) wstrzymując budowanie na 1 sekundę wywołuje wrażenie animacji i pozwala śledzić na bieżąco budowany tor. Końcowe instrukcje służą zaznaczeniu początku i końca ruchu blokami obsydianu.

Na koniec trzeba w funkcji ruchyBrowna() zmienić wywołanie rysuj() na rysuj_linie().

Eksperymenty

Uruchamiamy kod i eksperymentujemy. Dla 100 ruchów z krokiem przesunięcia 5 możemy uzyskać np. takie rezultaty:

../../_images/rbrowna1.png
../../_images/rbrowna2.png

Nic nie stoina przeszkodzie, żeby cząsteczka „ruszała się” w pionie nad i… pod wodą:

../../_images/rbrowna3.png
../../_images/rbrowna4.png

9.5.2. Liczba Pi

Mamy koło o promieniu r, którego środek umieszczamy w początku układu współrzędnych (0, 0). Na kole opisany jest kwadrat o boku 2r. Spróbujmy to zbudować w MC Pi. W pliku mcpi-lpi.py umieszczamy kod:

Kod nr
 1#!/usr/bin/python
 2# -*- coding: utf-8 -*-
 3
 4import os
 5import random
 6from time import sleep
 7import mcpi.minecraft as minecraft  # import modułu minecraft
 8import mcpi.block as block  # import modułu block
 9import local.minecraftstuff as mcstuff
10
11os.environ["USERNAME"] = "Steve"  # nazwa użytkownika
12os.environ["COMPUTERNAME"] = "mykomp"  # nazwa komputera
13
14mc = minecraft.Minecraft.create("192.168.1.10")  # połączenie z serwerem
15
16
17def plac(x, y, z, roz=10, gracz=False):
18    """Funkcja wypełnia sześcienny obszar od podanej pozycji
19    powietrzem i opcjonalnie umieszcza gracza w środku.
20    Parametry: x, y, z - współrzędne pozycji początkowej,
21    roz - rozmiar wypełnianej przestrzeni,
22    gracz - czy umieścić gracza w środku
23    Wymaga: globalnych obiektów mc i block.
24    """
25
26    podloga = block.STONE
27    wypelniacz = block.AIR
28
29    # kamienna podłoże
30    mc.setBlocks(x, y - 1, z, x + roz, y - 1, z + roz, podloga)
31    # czyszczenie
32    mc.setBlocks(x, y, z, x + roz, y + roz, z + roz, wypelniacz)
33    # umieść gracza w środku
34    if gracz:
35        mc.player.setPos(x + roz / 2, y + roz / 2, z + roz / 2)
36
37
38def model(promien, x, y, z):
39    """
40    Fukcja buduje obrys kwadratu, którego środek to punkt x, y, z
41    oraz koło wpisane w ten kwadrat
42    """
43
44    mcfig = mcstuff.MinecraftDrawing(mc)
45    obrys = block.SANDSTONE
46    wypelniacz = block.AIR
47
48    mc.setBlocks(x - promien, y, z - promien, x +
49                 promien, y, z + promien, obrys)
50    mc.setBlocks(x - promien + 1, y, z - promien + 1, x +
51                 promien - 1, y, z + promien - 1, wypelniacz)
52    mcfig.drawHorizontalCircle(0, 0, 0, promien, block.GRASS)
53
54
55def liczbaPi():
56    r = float(raw_input("Podaj promień koła: "))
57    model(r, 0, 0, 0)
58
59
60def main():
61    mc.postToChat("LiczbaPi")  # wysłanie komunikatu do mc
62    plac(-50, 0, -50, 100)
63    mc.player.setPos(20, 20, 0)
64    liczbaPi()
65    return 0
66
67
68if __name__ == '__main__':
69    main()

Funkcja model() działa podobnie do funkcji plac(), czyli na początku budujemy wokół środka układu współrzędnych płytę z bloku, który będzie zarysem kwadratu. Później budujemy drugą płytę o blok mniejszą z powietrza. Na koniec rysujemy koło.

../../_images/mcpi-lpi1.png

9.5.2.1. Deszcz punktów

Teraz wyobraźmy sobie, że pada deszcz. Część kropel upada w obrębie kwadratu, ich ilość oznaczymy zmienną ileKw, a część również w obrębie koła – oznaczymy je zmienną ileKo. Ponieważ znamy promień koła, możemy ułożyć proporcję, zakładając, że stosunek pola koła do pola kwadratu równy będzie stosunkowi kropel w kole do kropel w kwadracie:

\frac{\Pi * r^2}{(2 * r)^2} = \frac{ileKo}{ileKw}

Z prostego przekształcenia tej równości możemy wyznaczyć liczbę Pi:

\Pi = \frac{4 * ileKo}{ileKw}

Uzupełniamy więc kod funkcji liczbaPi():

Kod nr
55def liczbaPi():
56    r = float(raw_input("Podaj promień koła: "))
57    model(r, 0, 0, 0)
58
59    # pobieramy ilość punktów w kwadracie
60    ileKw = int(raw_input("Podaj ilość losowanych punktów: "))
61    ileKo = 0  # ilość punktów w kole
62
63    blok = block.SAND
64    for i in range(ileKw):
65        x = round(random.uniform(-r, r))
66        y = round(random.uniform(-r, r))
67        print x, y
68        if abs(x)**2 + abs(y)**2 <= r**2:
69            ileKo += 1
70        # umieść blok w MC Pi
71        mc.setBlock(x, 10, y, blok)
72
73    mc.postToChat("W kole = " + str(ileKo) + " W Kwadracie = " + str(ileKw))
74    pi = 4 * ileKo / float(ileKw)
75    mc.postToChat("Pi w przyblizeniu: {:.10f}".format(pi))
76

Jak widać w nowym kodzie, na początku pobieramy od użytkownika ilość „kropel” deszczu, czyli punktów do wylosowania. Następnie w pętli losujemy ich współrzędne w przedziale <-r;r> w instrukcji typu: x = round(random.uniform(-r, r), 10). Funkcja uniform() zwraca wartości zmiennoprzecinkowe, które zaokrąglamy do 10 miejsca po przecinku.

Korzystając z twierdzenia Pitagorasa układamy warunek pozwalający sprawdzić, które punkty „wpadły” do koła: if abs(x)**2 + abs(y)**2 <= r**2: – i zliczamy je.

Instrukcja mc.setBlock(x, 10, y, blok) rysuje punkty w MC Pi za pomocą bloków piasku (SAND), dzięki czemu uzyskujemy efekt spadania.

Wyliczenie wartości Pi i wydrukowanie jej jest prostą formalnością.

Uruchomienie powyższego kodu dla promienia 30 i 1000 punktów dało następujący efekt:

../../_images/mcpi-lpi2.png

Jak widać, niektóre punkty po zaokrągleniu ich współrzędnych w MC Pi nakładają się na siebie.

9.5.2.2. Podkolorowanie

Punkty wpadające do koła mogłyby wyglądać inaczej niż poza nim. Można by to osiągnąć przez ustawienie różnych typów bloków w pętli for, ale tylko blok piaskowy daje efekt spadania. Zrobimy więc inaczej. Zmieniamy funkcję liczbaPi():

Kod nr
55def liczbaPi():
56    r = float(raw_input("Podaj promień koła: "))
57    model(r, 0, 0, 0)
58
59    # pobieramy ilość punktów w kwadracie
60    ileKw = int(raw_input("Podaj ilość losowanych punktów: "))
61    ileKo = 0  # ilość punktów w kole
62    wKwadrat = []  # pomocnicza lista punktów w kwadracie
63    wKolo = []  # pomocnicza lista punktów w kole
64
65    blok = block.SAND
66    for i in range(ileKw):
67        x = round(random.uniform(-r, r))
68        y = round(random.uniform(-r, r))
69        wKwadrat.append((x, y))
70        print x, y
71        if abs(x)**2 + abs(y)**2 <= r**2:
72            ileKo += 1
73            wKolo.append((x, y))
74
75        mc.setBlock(x, 10, y, blok)
76
77    sleep(5)
78    for pkt in set(wKwadrat) - set(wKolo):
79        x, y = pkt
80        mc.setBlock(x, i, y, block.OBSIDIAN)
81        for i in range(1, 3):
82            print x, i, y
83            if mc.getBlock(x, i, y) == 12:
84                mc.setBlock(x, i, y, block.OBSIDIAN)
85
86    mc.postToChat("W kole = " + str(ileKo) + " W Kwadracie = " + str(ileKw))
87    pi = 4 * ileKo / float(ileKw)
88    mc.postToChat("Pi w przyblizeniu: {:.10f}".format(pi))

Deklarujemy dwie pomocnicze listy, do których zapisujemy w pętli współrzędne punktów należących do kwadratu i koła, np. wKwadrat.append((x, y)). Następnie wstrzymujemy wykonanie kodu na 5 sekund, aby bloki piasku zdążyły opaść. W wyrażeniu set(wKwadrat) - set(wKolo) każda lista zostaje przekształcona na zbiór, a następnie zostaje obliczona ich różnica. W efekcie otrzymujemy współrzędne punktów należących do kwadratu, ale nie do koła. Ponieważ niektóre bloki piasku układają się jeden na drugim, wychwytujemy je w pętli wewnętrznej if mc.getBlock(x, i, y) == 12: – i zmieniamy na obsydian.

9.5.2.3. Trzeci wymiar

Siła MC Pi tkwi w 3 wymiarze. Możemy bez większych problemów go wykorzystać. Na początku warto zauważyć, że w algorytmie wyliczania wartości liczby Pi nic się nie zmieni. Stosunek pola koła do pola kwadratu zastępujemy bowiem stosunkiem objętości walca, którego podstawa ma promień r, do objętości sześcianu o boku 2r. Otrzymamy zatem:

\frac{\Pi * r^2 * 2 * r}{(2 * r)^3} = \frac{ileKo}{ileKw}

Po przekształceniu skończymy na takim samym jak wcześniej wzorze, czyli:

\Pi = \frac{4 * ileKo}{ileKw}

Aby to wykreślić, zmienimy funkcje model(), liczbaPi() i main(). Sugerujemy, żeby dotychczasowy plik zapisać pod inną nazwą, np. mcpi-lpi3D.py, i wprowadzić następujące zmiany:

Kod nr
 38def model(r, x, y, z, klatka=False):
 39    """
 40    Fukcja buduje obrys kwadratu, którego środek to punkt x, y, z
 41    oraz koło wpisane w ten kwadrat
 42    """
 43
 44    mcfig = mcstuff.MinecraftDrawing(mc)
 45    obrys = block.OBSIDIAN
 46    wypelniacz = block.AIR
 47
 48    mc.setBlocks(x - r - 10, y - r, z - r - 10, x +
 49                 r + 10, y + r, z + r + 10, wypelniacz)
 50    mcfig.drawLine(x + r, y + r, z + r, x - r, y + r, z + r, obrys)
 51    mcfig.drawLine(x - r, y + r, z + r, x - r, y + r, z - r, obrys)
 52    mcfig.drawLine(x - r, y + r, z - r, x + r, y + r, z - r, obrys)
 53    mcfig.drawLine(x + r, y + r, z - r, x + r, y + r, z + r, obrys)
 54
 55    mcfig.drawLine(x + r, y - r, z + r, x - r, y - r, z + r, obrys)
 56    mcfig.drawLine(x - r, y - r, z + r, x - r, y - r, z - r, obrys)
 57    mcfig.drawLine(x - r, y - r, z - r, x + r, y - r, z - r, obrys)
 58    mcfig.drawLine(x + r, y - r, z - r, x + r, y - r, z + r, obrys)
 59
 60    mcfig.drawLine(x + r, y + r, z + r, x + r, y - r, z + r, obrys)
 61    mcfig.drawLine(x - r, y + r, z + r, x - r, y - r, z + r, obrys)
 62    mcfig.drawLine(x - r, y + r, z - r, x - r, y - r, z - r, obrys)
 63    mcfig.drawLine(x + r, y + r, z - r, x + r, y - r, z - r, obrys)
 64
 65    mc.player.setPos(x + r, y + r + 1, z + r)
 66
 67    if klatka:
 68        mc.setBlocks(x - r, y - r, z - r, x + r, y + r, z + r, block.GLASS)
 69        mc.setBlocks(x - r + 1, y - r + 1, z - r + 1, x +
 70                     r - 1, y + r - 1, z + r - 1, wypelniacz)
 71        mc.player.setPos(0, 0, 0)
 72
 73    for i in range(-r, r + 1, 5):
 74        mcfig.drawHorizontalCircle(0, i, 0, r, block.GRASS)
 75
 76
 77def liczbaPi(klatka=False):
 78    r = int(raw_input("Podaj promień koła: "))
 79    model(r, 0, 0, 0, klatka)
 80
 81    # pobieramy ilość punktów w kwadracie
 82    ileKw = int(raw_input("Podaj ilość losowanych punktów: "))
 83    ileKo = 0  # ilość punktów w kole
 84    wKwadrat = []  # pomocnicza lista punktów w kwadracie
 85    wKolo = []  # pomocnicza lista punktów w kole
 86
 87    for i in range(ileKw):
 88        blok = block.OBSIDIAN
 89        x = round(random.uniform(-r, r))
 90        y = round(random.uniform(-r, r))
 91        z = round(random.uniform(-r, r))
 92        wKwadrat.append((x, y, z))
 93        print x, y, z
 94        if abs(x)**2 + abs(z)**2 <= r**2:
 95            blok = block.DIAMOND_BLOCK
 96            ileKo += 1
 97            wKolo.append((x, y, z))
 98
 99        mc.setBlock(x, y, z, blok)
100
101    mc.postToChat("W kole = " + str(ileKo) + " W Kwadracie = " + str(ileKw))
102    pi = 4 * ileKo / float(ileKw)
103    mc.postToChat("Pi w przyblizeniu: {:.10f}".format(pi))
104    mc.postToChat("Stan na kamieniu!")
105
106    while True:
107        poz = mc.player.getPos()
108        x, y, z = poz
109        if mc.getBlock(x, y - 1, z) == block.STONE.id:
110            for pkt in wKolo:
111                x, y, z = pkt
112                mc.setBlock(x, y, z, block.SAND)
113            sleep(3)
114            mc.player.setPos(0, r - 1, 0)
115            break
116
117
118def main():
119    mc.postToChat("LiczbaPi")  # wysłanie komunikatu do mc
120    plac(-50, 0, -50, 100)
121    liczbaPi(False)
122    return 0

Zadaniem funkcji model() jest stworzenie przestrzeni dla obrysu sześcianu i jego szkieletu. Opcjonalnie, jeżeli przekażemy do funkcji parametr klatka równy True, ściany mogą zostać wypełnione szkłem. Walec wizualizujemy w pętli for rysując kilka okręgów blokami trawy.

W funkcji liczbaPi() najważniejszą zmianą jest dodanie trzeciej zmiennej. Wartości wszystkich trzech współrzędnych losowane są w takim samym zakresie, ponieważ za środek całego układu przyjmujemy początek układu współrzędnych. Ważna zmiana zachodzi w funkcji warunkowej: if abs(x)**2 + abs(z)**2 <= r**2:. Do sprawdzenia, czy punkt należy do koła wykorzystujemy zmienne x i z, uwzględniając fakt, że w MC Pi wyznaczają one położenie w poziomie.

Bloki należące do sześcianu rysujemy za pomocą obsydianu, te w walcu – za pomocą diamentów.

Na końcu funkcji dodajemy nieskończoną pętlę (while True:), której zadaniem jest sprawdzanie, na jakim bloku znajduje się gracz: if mc.getBlock(x, y - 1, z) == block.STONE.id:. Jeżeli stanie on na kamieniu, wszystkie bloki należące do walca zamieniamy w pętli for pkt in wKolo: w piasek, a gracza teleportujemy do środka sześcianu.

Dla promienia o wielkości 20 i 1000 bloków uzyskać można poniższe budowle:

../../_images/mcpi-lpi4_1.png
../../_images/mcpi-lpi4_2.png
../../_images/mcpi-lpi4_3.png

Pozostaje eksperymentować z rozmiarami, typami bloków czy parametrem klatka określanym w wywołaniu funkcji liczbaPi() w funkcji głównej.

Źródła:


Licencja Creative Commons Materiały Python 101 udostępniane przez Centrum Edukacji Obywatelskiej na licencji Creative Commons Uznanie autorstwa-Na tych samych warunkach 4.0 Międzynarodowa.

Utworzony:

2025-04-12 o 10:21 w Sphinx 7.3.7

Autorzy:

Patrz plik „Autorzy”