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
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import numpy as np  # import biblioteki do obliczeń naukowych
import matplotlib.pyplot as plt  # import biblioteki do tworzenia wykresow
from random import randint
from time import sleep
import mcpi.minecraft as minecraft  # import modułu minecraft
import mcpi.block as block  # import modułu block

os.environ["USERNAME"] = "Steve"  # wpisz dowolną nazwę użytkownika
os.environ["COMPUTERNAME"] = "mykomp"  # wpisz dowolną nazwę komputera

mc = minecraft.Minecraft.create("192.168.1.10")  # połączenie z serwerem


def plac(x, y, z, roz=10, gracz=False):
    """
    Funkcja tworzy podłoże i wypełnia sześcienny obszar od podanej pozycji,
    opcjonalnie umieszcza gracza w środku.
    Parametry: x, y, z - współrzędne pozycji początkowej,
    roz - rozmiar wypełnianej przestrzeni,
    gracz - czy umieścić gracza w środku
    Wymaga: globalnych obiektów mc i block.
    """

    podloga = block.SAND
    wypelniacz = block.AIR

    # podloga i czyszczenie
    mc.setBlocks(x, y - 1, z, x + roz, y - 1, z + roz, podloga)
    mc.setBlocks(x, y, z, x + roz, y + roz, z + roz, wypelniacz)
    # umieść gracza w środku
    if gracz:
        mc.player.setPos(x + roz / 2, y + roz / 2, z + roz / 2)


def wykres(x, y, tytul="Wykres funkcji", *extra):
    """
    Funkcja wizualizuje wykres funkcji, której argumenty zawiera lista x
    a wartości lista y i ew. dodatkowe listy w parametrze *extra
    """
    if len(extra):
        plt.plot(x, y, extra[0], extra[1])  # dwa wykresy na raz
    else:
        plt.plot(x, y, "o:", color="blue", linewidth="3", alpha=0.8)
    plt.title(tytul)
    plt.grid(True)
    plt.show()


def rysuj(x, y, z, blok=block.IRON_BLOCK):
    """
    Funkcja wizualizuje wykres funkcji, umieszczając bloki w pionie/poziomie
    w punktach wyznaczonych przez pary elementów list x, y lub x, z
    """
    czylista = True if len(y) > 1 else False
    for i in range(len(x)):
        if czylista:
            print(x[i], y[i])
            mc.setBlock(x[i], y[i], z[0], blok)
        else:
            print(x[i], z[i])
            mc.setBlock(x[i], y[0], z[i], blok)


def ruchyBrowna():

    n = int(raw_input("Ile ruchów? "))
    r = int(raw_input("Krok przesunięcia? "))

    x = y = 0
    lx = [0]  # lista odciętych
    ly = [0]  # lista rzędnych

    for i in range(0, n):
        # losujemy kąt i zamieniamy na radiany
        rad = float(randint(0, 360)) * np.pi / 180
        x = x + r * np.cos(rad)  # wylicz współrzędną x
        y = y + r * np.sin(rad)  # wylicz współrzędną y
        x = int(round(x, 2))  # zaokrągl
        y = int(round(y, 2))  # zaokrągl
        print(x, y)
        lx.append(x)
        ly.append(y)

    # oblicz wektor końcowego przesunięcia
    s = np.fabs(np.sqrt(x**2 + y**2))
    print "Wektor przesunięcia: {:.2f}".format(s)

    wykres(lx, ly, "Ruchy Browna")
    rysuj(lx, [1], ly, block.WOOL)


def main():
    mc.postToChat("Ruchy Browna")  # wysłanie komunikatu do mc
    plac(-80, -20, -80, 160)
    plac(-80, 0, -80, 160)
    ruchyBrowna()
    return 0


if __name__ == '__main__':
    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
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
def ruchyBrowna(dane=[]):

    if len(dane):
        lx, ly = dane  # rozpakowanie listy
        x = lx[-1]  # ostatni element lx
        y = ly[-1]  # ostatni element ly
    else:
        n = int(raw_input("Ile ruchów? "))
        r = int(raw_input("Krok przesunięcia? "))

        x = y = 0
        lx = [0]  # lista odciętych
        ly = [0]  # lista rzędnych

        for i in range(0, n):
            # losujemy kąt i zamieniamy na radiany
            rad = float(randint(0, 360)) * np.pi / 180
            x = x + r * np.cos(rad)  # wylicz współrzędną x
            y = y + r * np.sin(rad)  # wylicz współrzędną y
            x = int(round(x, 2))  # zaokrągl
            y = int(round(y, 2))  # zaokrągl
            print(x, y)
            lx.append(x)
            ly.append(y)

    # oblicz wektor końcowego przesunięcia
    s = np.fabs(np.sqrt(x**2 + y**2))
    print "Wektor przesunięcia: {:.2f}".format(s)

    wykres(lx, ly, "Ruchy Browna")
    rysuj(lx, [1], ly, block.WOOL)
    if not len(dane):
        zapisz_dane((lx, ly))


def zapisz_dane(dane):
    """Funkcja zapisuje dane w formacie json w pliku"""
    import json
    plik = open('rbrowna.log', 'w')
    json.dump(dane, plik)
    plik.close()


def czytaj_dane():
    """Funkcja odczytuje dane w formacie json z pliku"""
    import json
    dane = []
    nazwapliku = raw_input("Podaj nazwę pliku z danymi lub naciśnij ENTER: ")
    if os.path.isfile(nazwapliku):
        with open(nazwapliku, "r") as plik:
            dane = json.load(plik)
    else:
        print "Podany plik nie istnieje!"
    return dane


def main():
    mc.postToChat("Ruchy Browna")  # wysłanie komunikatu do mc
    plac(-80, -20, -80, 160)
    plac(-80, 0, -80, 160)
    ruchyBrowna(czytaj_dane())
    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
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
def rysuj_linie(x, y, z, blok=block.IRON_BLOCK):
    """
    Funkcja wizualizuje wykres funkcji, umieszczając bloki w pionie/poziomie
    w punktach wyznaczonych przez pary elementów list x, y lub x, z
    przy użyciu metody drawLine()
    """
    import local.minecraftstuff as mcstuff
    mcfig = mcstuff.MinecraftDrawing(mc)
    czylista = True if len(y) > 1 else False
    for i in range(len(x) - 1):
        x1 = int(x[i])
        x2 = int(x[i + 1])
        if czylista:
            y1 = int(y[i])
            y2 = int(y[i + 1])
            mc.setBlock(x2, y2, z[0], block.GRASS)
            mc.setBlock(x1, y1, z[0], block.GRASS)
            mcfig.drawLine(x1, y1, z[0], x2, y2, z[0], blok)
            mc.setBlock(x2, y2, z[0], block.GRASS)
            mc.setBlock(x1, y1, z[0], block.GRASS)
            print (x1, y1, z[0], x2, y2, z[0])
        else:
            z1 = int(z[i])
            z2 = int(z[i + 1])
            mc.setBlock(x1, y[0], z1, block.GRASS)
            mc.setBlock(x2, y[0], z2, block.GRASS)
            mcfig.drawLine(x1, y[0], z1, x2, y[0], z2, blok)
            mc.setBlock(x1, y[0], z1, block.GRASS)
            mc.setBlock(x2, y[0], z2, block.GRASS)
            print (x1, y[0], z1, x2, y[0], z2)
        sleep(1)  # przerwa na reklamę :-)
    mc.setBlock(0, 1, 0, block.OBSIDIAN)
    if czylista:
        mc.setBlock(x2, y2, z[0], block.OBSIDIAN)
    else:
        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
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#!/usr/bin/python
# -*- coding: utf-8 -*-

import os
import random
from time import sleep
import mcpi.minecraft as minecraft  # import modułu minecraft
import mcpi.block as block  # import modułu block
import local.minecraftstuff as mcstuff

os.environ["USERNAME"] = "Steve"  # nazwa użytkownika
os.environ["COMPUTERNAME"] = "mykomp"  # nazwa komputera

mc = minecraft.Minecraft.create("192.168.1.10")  # połączenie z serwerem


def plac(x, y, z, roz=10, gracz=False):
    """Funkcja wypełnia sześcienny obszar od podanej pozycji
    powietrzem i opcjonalnie umieszcza gracza w środku.
    Parametry: x, y, z - współrzędne pozycji początkowej,
    roz - rozmiar wypełnianej przestrzeni,
    gracz - czy umieścić gracza w środku
    Wymaga: globalnych obiektów mc i block.
    """

    podloga = block.STONE
    wypelniacz = block.AIR

    # kamienna podłoże
    mc.setBlocks(x, y - 1, z, x + roz, y - 1, z + roz, podloga)
    # czyszczenie
    mc.setBlocks(x, y, z, x + roz, y + roz, z + roz, wypelniacz)
    # umieść gracza w środku
    if gracz:
        mc.player.setPos(x + roz / 2, y + roz / 2, z + roz / 2)


def model(promien, x, y, z):
    """
    Fukcja buduje obrys kwadratu, którego środek to punkt x, y, z
    oraz koło wpisane w ten kwadrat
    """

    mcfig = mcstuff.MinecraftDrawing(mc)
    obrys = block.SANDSTONE
    wypelniacz = block.AIR

    mc.setBlocks(x - promien, y, z - promien, x +
                 promien, y, z + promien, obrys)
    mc.setBlocks(x - promien + 1, y, z - promien + 1, x +
                 promien - 1, y, z + promien - 1, wypelniacz)
    mcfig.drawHorizontalCircle(0, 0, 0, promien, block.GRASS)


def liczbaPi():
    r = float(raw_input("Podaj promień koła: "))
    model(r, 0, 0, 0)


def main():
    mc.postToChat("LiczbaPi")  # wysłanie komunikatu do mc
    plac(-50, 0, -50, 100)
    mc.player.setPos(20, 20, 0)
    liczbaPi()
    return 0


if __name__ == '__main__':
    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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
def liczbaPi():
    r = float(raw_input("Podaj promień koła: "))
    model(r, 0, 0, 0)

    # pobieramy ilość punktów w kwadracie
    ileKw = int(raw_input("Podaj ilość losowanych punktów: "))
    ileKo = 0  # ilość punktów w kole

    blok = block.SAND
    for i in range(ileKw):
        x = round(random.uniform(-r, r))
        y = round(random.uniform(-r, r))
        print x, y
        if abs(x)**2 + abs(y)**2 <= r**2:
            ileKo += 1
        # umieść blok w MC Pi
        mc.setBlock(x, 10, y, blok)

    mc.postToChat("W kole = " + str(ileKo) + " W Kwadracie = " + str(ileKw))
    pi = 4 * ileKo / float(ileKw)
    mc.postToChat("Pi w przyblizeniu: {:.10f}".format(pi))

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
def liczbaPi():
    r = float(raw_input("Podaj promień koła: "))
    model(r, 0, 0, 0)

    # pobieramy ilość punktów w kwadracie
    ileKw = int(raw_input("Podaj ilość losowanych punktów: "))
    ileKo = 0  # ilość punktów w kole
    wKwadrat = []  # pomocnicza lista punktów w kwadracie
    wKolo = []  # pomocnicza lista punktów w kole

    blok = block.SAND
    for i in range(ileKw):
        x = round(random.uniform(-r, r))
        y = round(random.uniform(-r, r))
        wKwadrat.append((x, y))
        print x, y
        if abs(x)**2 + abs(y)**2 <= r**2:
            ileKo += 1
            wKolo.append((x, y))

        mc.setBlock(x, 10, y, blok)

    sleep(5)
    for pkt in set(wKwadrat) - set(wKolo):
        x, y = pkt
        mc.setBlock(x, i, y, block.OBSIDIAN)
        for i in range(1, 3):
            print x, i, y
            if mc.getBlock(x, i, y) == 12:
                mc.setBlock(x, i, y, block.OBSIDIAN)

    mc.postToChat("W kole = " + str(ileKo) + " W Kwadracie = " + str(ileKw))
    pi = 4 * ileKo / float(ileKw)
    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
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
def model(r, x, y, z, klatka=False):
    """
    Fukcja buduje obrys kwadratu, którego środek to punkt x, y, z
    oraz koło wpisane w ten kwadrat
    """

    mcfig = mcstuff.MinecraftDrawing(mc)
    obrys = block.OBSIDIAN
    wypelniacz = block.AIR

    mc.setBlocks(x - r - 10, y - r, z - r - 10, x +
                 r + 10, y + r, z + r + 10, wypelniacz)
    mcfig.drawLine(x + r, y + r, z + r, x - r, y + r, z + r, obrys)
    mcfig.drawLine(x - r, y + r, z + r, x - r, y + r, z - r, obrys)
    mcfig.drawLine(x - r, y + r, z - r, x + r, y + r, z - r, obrys)
    mcfig.drawLine(x + r, y + r, z - r, x + r, y + r, z + r, obrys)

    mcfig.drawLine(x + r, y - r, z + r, x - r, y - r, z + r, obrys)
    mcfig.drawLine(x - r, y - r, z + r, x - r, y - r, z - r, obrys)
    mcfig.drawLine(x - r, y - r, z - r, x + r, y - r, z - r, obrys)
    mcfig.drawLine(x + r, y - r, z - r, x + r, y - r, z + r, obrys)

    mcfig.drawLine(x + r, y + r, z + r, x + r, y - r, z + r, obrys)
    mcfig.drawLine(x - r, y + r, z + r, x - r, y - r, z + r, obrys)
    mcfig.drawLine(x - r, y + r, z - r, x - r, y - r, z - r, obrys)
    mcfig.drawLine(x + r, y + r, z - r, x + r, y - r, z - r, obrys)

    mc.player.setPos(x + r, y + r + 1, z + r)

    if klatka:
        mc.setBlocks(x - r, y - r, z - r, x + r, y + r, z + r, block.GLASS)
        mc.setBlocks(x - r + 1, y - r + 1, z - r + 1, x +
                     r - 1, y + r - 1, z + r - 1, wypelniacz)
        mc.player.setPos(0, 0, 0)

    for i in range(-r, r + 1, 5):
        mcfig.drawHorizontalCircle(0, i, 0, r, block.GRASS)


def liczbaPi(klatka=False):
    r = int(raw_input("Podaj promień koła: "))
    model(r, 0, 0, 0, klatka)

    # pobieramy ilość punktów w kwadracie
    ileKw = int(raw_input("Podaj ilość losowanych punktów: "))
    ileKo = 0  # ilość punktów w kole
    wKwadrat = []  # pomocnicza lista punktów w kwadracie
    wKolo = []  # pomocnicza lista punktów w kole

    for i in range(ileKw):
        blok = block.OBSIDIAN
        x = round(random.uniform(-r, r))
        y = round(random.uniform(-r, r))
        z = round(random.uniform(-r, r))
        wKwadrat.append((x, y, z))
        print x, y, z
        if abs(x)**2 + abs(z)**2 <= r**2:
            blok = block.DIAMOND_BLOCK
            ileKo += 1
            wKolo.append((x, y, z))

        mc.setBlock(x, y, z, blok)

    mc.postToChat("W kole = " + str(ileKo) + " W Kwadracie = " + str(ileKw))
    pi = 4 * ileKo / float(ileKw)
    mc.postToChat("Pi w przyblizeniu: {:.10f}".format(pi))
    mc.postToChat("Stan na kamieniu!")

    while True:
        poz = mc.player.getPos()
        x, y, z = poz
        if mc.getBlock(x, y - 1, z) == block.STONE.id:
            for pkt in wKolo:
                x, y, z = pkt
                mc.setBlock(x, y, z, block.SAND)
            sleep(3)
            mc.player.setPos(0, r - 1, 0)
            break


def main():
    mc.postToChat("LiczbaPi")  # wysłanie komunikatu do mc
    plac(-50, 0, -50, 100)
    liczbaPi(False)
    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:2022-05-22 o 19:52 w Sphinx 1.5.3
Autorzy:Patrz plik “Autorzy”