8.4. Funkcje w mcpi

O Minecrafcie w wersji na Raspberry Pi myśleć można jak o atrakcyjnej formie wizualizacji tego co można przedstawić w grafice dwu- lub trójwymiarowej. Zobaczmy zatem jakie budowle otrzymamy, wyliczając współrzędne bloków za pomocą funkcji matematycznych. Przy okazji niejako przypomnimy sobie użycie opisywanej już w naszych scenariuszach biblioteki matplotlib, która jest dedykowanym dla Pythona środowiskiem tworzenia wykresów 2D.

8.4.1. Funkcja liniowa

Za pomocą wybranego edytora utwórz pusty plik, umieść w nim podany niżej kod i zapisz w katalogu mcpi-sim pod nazwą mcpi-funkcje.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
#!/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 wykresów
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łaczenie z mc


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.STONE
    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)
    plt.title(tytul)
    # plt.xlabel(podpis)
    plt.grid(True)
    plt.show()


def fun1(blok=block.IRON_BLOCK):
    """
    Funkcja f(x) = a*x + b
    """
    a = int(raw_input('Podaj współczynnik a: '))
    b = int(raw_input('Podaj współczynnik b: '))
    x = range(-10, 11)  # lista argumentów x = <-10;10> z krokiem 1
    y = [a * i + b for i in x]  # wyrażenie listowe
    print x, "\n", y
    wykres(x, y, "f(x) = a*x + b")
    for i in range(len(x)):
        mc.setBlock(x[i], 1, y[i], blok)


def main():
    mc.postToChat("Funkcje w Minecrafcie")  # wysłanie komunikatu do mc
    plac(-80, 0, -80, 160)
    mc.player.setPos(22, 10, 10)
    fun1()
    return 0


if __name__ == '__main__':
    main()

Większość kodu powinna być już zrozumiała, czyli importy bibliotek, nawiązywania połączenia z serwerem MC Pi, czy funkcja plac() tworząca przestrzeń do testów. Podobnie funkcja wykres(), która pokazuje nam graficzną reprezentację funkcji za pomocą biblioteki matblotlib. Na uwagę zasługuje w niej tylko parametr *extra, który pozwala przekazać argumenty i wartości dodatkowej funkcji.

Funkcja fun1() pobiera od użytkownika dwa współczynniki i odwzorowuje argumenty z dziedziny <-10;10> na wartości wg liniowego równania: f(x) = a * x + b. Przeciwdziedzinę można byłoby uzyskać “na piechotę” za pomocą kodu:

y = []
for i in x:
    y.append(a * i + b)

– ale efektywniejsze jest wyrażenie listowe: y = [a * i + b for i in x]. Po zobrazowaniu wykresu za pomocą funkcji funkcji wykres() i biblioteki matplotlib “budujemy” ją w MC Pi w pętli odczytującej wyliczone pary argumentów i wartości funkcji, stanowiących współrzędne kolejnych bloków umieszczanych poziomo.

Uruchom i przetestuj omówiony kod podając współczynniki np. 4 i 6.

8.4.2. Układ współrzędnych

Spróbujmy pokazać w Mc Pi układ współrzędnych oraz ułatwić “budowanie” wykresów za pomocą osobnej funkcji. Po funkcji wykres() umieszczamy w pliku mcpi-funkcje.py nowy kod:

Kod nr
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
def uklad(blok=block.OBSIDIAN):
    """
    Funkcja rysuje układ współrzędnych
    """
    for i in range(-80, 81, 2):
        mc.setBlock(i, -1, 0, blok)
        mc.setBlock(0, -1, i, blok)
        mc.setBlock(0, i, 0, blok)


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)

– a pętlę tworzącą wykres w funkcji fun1() zastępujemy wywołaniem:

rysuj(x, y, [1], blok)

Funkcja rysuj() potrafi zbudować bloki zarówno w poziomie, jak i w pionie w zależności od tego, czy lista wartości funkcji przekazana zostanie jako parametr y czy też z. Do rozpoznania tego wykorzystujemy zmienną sterującą ustawianą w instrukcji: czylista = True if len(y) > 1 else False.

Zawartość funkcji main() zmieniamy na:

Kod nr
90
91
92
93
94
95
96
def main():
    mc.postToChat("Funkcje w Minecrafcie")  # wysłanie komunikatu do mc
    plac(-80, -40, -80, 160)
    mc.player.setPos(-4, 10, 20)
    uklad()
    fun1()
    return 0

Po uruchomieniu zmienionego kodu powinniśmy zobaczyć wykres naszej funkcji w pionie.

../../_images/mcpi-funkcje02.png

Kod “budujący” wykresy funkcji możemy urozmaicić wykorzystując poznaną wcześniej bibliotekę minecraftstuff. Poniżej funkcji rysuj() dodajemy:

Kod nr
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
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])
            print (x1, y1, z[0], x2, y2, z[0])
            mcfig.drawLine(x1, y1, z[0], x2, y2, z[0], blok)
        else:
            z1 = int(z[i])
            z2 = int(z[i + 1])
            print (x1, y[0], z1, x2, y[0], z2)
            mcfig.drawLine(x1, y[0], z1, x2, y[0], z2, blok)

– a wywołanie rysuj() w funkcji fun1() zmieniamy na rysuj_linie(). Sprawdź rezultat.

8.4.3. Kolejne funkcje

W pliku mcpi-funkcje.py tuż nad funkcją główną main() umieszczamy kod wyliczający dziedziny i przeciwdziedziny dwóch kolejnych funkcji:

Kod nr
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
def fun2(blok=block.REDSTONE_ORE):
    """
    Wykres funkcji f(x), gdzie x = <-1;2> z krokiem 0.15, przy czym
    f(x) = x/(x+2) dla x >= 1
    f(x) = x*x/3 dla x < 1 i x > 0
    f(x) = x/(-3) dla x <= 0
    """
    x = np.arange(-1, 2.15, 0.15)  # lista argumentów x
    y = []  # lista wartości f(x)

    for i in x:
        if i <= 0:
            y.append(i / -3)
        elif i < 1:
            y.append(i ** 2 / 3)
        else:
            y.append(i / (i + 2))
    wykres(x, y, "Funkcja mieszana")
    x = [round(i * 20, 2) for i in x]
    y = [round(i * 20, 2) for i in y]
    print x, "\n", y
    rysuj(x, y, [1], blok)


def fun3(blok=block.LAPIS_LAZULI_BLOCK):
    """
    Funkcja f(x) = log2(x)
    """
    x = np.arange(0.1, 41, 1)  # lista argumentów x
    y = [np.log2(i) for i in x]
    y = [round(i, 2) * 2 for i in y]
    print x, "\n", y
    wykres(x, y, "Funkcja logarytmiczna")
    rysuj(x, y, [1], blok)


def main():
    mc.postToChat("Funkcje w Minecrafcie")  # wysłanie komunikatu do mc
    plac(-80, -20, -80, 160)
    mc.player.setPos(-8, 10, 26)
    uklad(block.DIAMOND_BLOCK)
    fun1()
    fun2()
    fun3()
    return 0

W funkcji fun2() wartości dziedziny uzyskujemy dzięki metodzie arange(start, stop, step) z biblioteki numpy. Potrafi ona generować listę wartości zmiennopozycyjnych w podanym zakresie <start;stop) z określonym krokiem step.

Przeciwdziedzinę wyliczamy w pętli w zależności od przedziałów, w których znajdują się argumenty, za pomocą złożonej instrukcji warunkowej. Następnie wartości zarówno dziedziny, jak i przeciwdziedziny przeskalowujemy w wyrażeniach listowych, mnożąc przez stały współczynnik, aby wykres w MC Pi był większy i wyraźniejszy. Przy okazji współrzędne zaokrąglamy do dwóch miejsc po przecinku, np.: x = [round(i * 20, 2) for i in x].

W funkcji fun3() w podobny jak powyżej sposób obliczamy argumenty i wartości funkcji logarytmicznej.

Na koniec zmieniamy też nieco wywołania w funkcji głównej. Przetestuj podany kod.

../../_images/mcpi-funkcje04.png

8.4.4. Funkcja kwadratowa

Przygotujemy wykres funkcji kwadratowej. Przed funkcją główną umieszczamy następujący kod:

Kod nr
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
def fkw(x, a=0.3, b=0.1, c=0):
    return a * x**2 + b * x + c


def fkwadratowa():
    """
    Funkcja przygotowuje dziedzinę funkcji kwadratowej
    oraz dwie przeciwdziedziny, druga z odwróconym znakiem. Następnie
    buduje ich wykresy w poziomie i w pionie.
    """
    while True:
        lewy = float(raw_input("Podaj lewy kraniec przedziału: "))
        prawy = float(raw_input("Podaj prawy kraniec przedziału: "))
        if lewy * prawy < 1 and lewy <= prawy:
            break
    print lewy, prawy

    # x = np.arange(lewy, prawy, 0.2)
    x = np.linspace(lewy, prawy, 60, True)
    x = [round(i, 2) for i in x]
    y1 = [fkw(i) for i in x]
    y1 = [round(i, 2) for i in y1]
    y2 = [-fkw(i) for i in x]
    y2 = [round(i, 2) for i in y2]
    print x, "\n", y1, "\n", y2
    wykres(x, y1, "Funkcja kwadratowa", x, y2)
    rysuj_linie(x, [1], y1, block.GRASS)
    rysuj(x, [1], y2, block.SAND)
    rysuj(x, y1, [1], block.WOOL)
    rysuj_linie(x, y2, [1], block.IRON_BLOCK)


def main():
    mc.postToChat("Funkcje w Minecrafcie")  # wysłanie komunikatu do mc
    plac(-80, -20, -80, 160)
    mc.player.setPos(-15, 10, -15)
    uklad(block.OBSIDIAN)
    fkwadratowa()
    return 0

Na początku w funkcji fkwadratowa() pobieramy od użytkownika przedział, w którym budować będziemy funkcję. Wymuszamy przy tym w pętli while, aby lewa i prawa granica miały inne znaki. Dalej używamy funkcji linspace(start, stop, num, endpoint), która generuje listę num wartości od punktu początkowego do końcowego, który uwzględniany jest, jeżeli argument endpoint ma wartość True. Kolejne wyrażenia listowe wyliczają przeciwdziedziny i zaokrąglają wartości do 2 miejsc po przecinku.

Sama funkcja kwadratowa a*x^2 + b*x + c zdefiniowana jest w funkcji fkw(), do której przekazujemy kolejne argumenty dziedziny i opcjonalnie współczynniki.

Instrukcje rysuj() i rysuj_linie() dzięki przekazywaniu przeciwdziedziny jako 2. lub 3. argumentu budują wykresy w poziomie lub w pionie za pomocą pojedynczych lub połączonych bloków.

Po przygotowaniu w funkcji głównej miejsca, ustawieniu gracza, narysowaniu układu i podaniu przedziału <-20, 20> otrzymamy konstrukcję podobną do poniższej.

../../_images/fkwadratowa1.png

Po zmianie funkcji na x**2 / 3 można otrzymać:

../../_images/fkwadratowa2.png

Zwróciłeś uwagę na to, że jeden z wykresów opada?

8.4.5. Funkcje trygonometryczne

Na koniec zobrazujemy funkcje trygonometryczne. Przed funkcją główną dopisujemy kod:

Kod nr
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
def trygon():
    x1 = np.arange(-50.0, 50.0, 1)
    y1 = 5 * np.sin(0.1 * np.pi * x1)
    y1 = [round(i, 2) for i in y1]
    print x1, "\n", y1

    x2 = range(0, 361, 10)  # lista argumentów x
    y2 = [None if i == 90 or i == 270 else np.tan(i * np.pi / 180) for i in x2]
    x2 = [i // 10 for i in x2]
    y2 = [round(i * 3, 2) if i is not None else None for i in y2]
    print x2, "\n", y2
    wykres(x1, y1, "Funkcje sinus i tangens", x2, y2)

    del x2[9]  # usuń 10 element listy
    del y2[9]  # usuń 10 element listy
    del x2[x2.index(27)]  # usuń element o wartości 27
    del y2[y2.index(None)]  # usuń element None
    print x2, "\n", y2
    rysuj(x1, [1], y1, block.GOLD_BLOCK)
    rysuj(x2, y2, [1], block.OBSIDIAN)


def main():
    mc.postToChat("Funkcje w Minecrafcie")  # wysłanie komunikatu do mc
    plac(-80, -20, -80, 160)
    mc.player.setPos(17, 17, 24)
    uklad(block.DIAMOND_BLOCK)
    trygon()
    return 0

W funkcji trygon() na początku wyliczamy dziedzinę i przeciwdziedzinę funkcji 5 * sin(0.1 * Pi * x), przy czym wartości y zaokrąglamy.

Dalej generujemy argumenty x dla funkcji tangens w przedziale od 0 do 360 co 10 stopni. Obliczając wartości y za pomocą wyrażenia listowego y2 = [None if i == 90 or i == 270 else np.tan(i * np.pi / 180) for i in x2] dla argumentów 90 i 270 wstawiamy None (czyli nic), ponieważ dla tych argumentów funkcja nie przyjmuje wartości. Dzięki temu uzyskamy poprawny wykres w matplotlib.

Aby wykresy obydwu funkcji nałożyły się na siebie, używając wyrażenia listowego, skalujemy argumenty i wartości funkcji tangens. Pierwsze dzielimy przez 10, drugie mnożymy przez 3 (i przy okazji zaokrąglamy). Konstrukcja if i is not None else None zapobiega wykonywaniu operacji dla wartości None, co generowałoby błędy.

Przygotowanie danych do zwizualizowania w Minecrafcie wymaga usunięcia 2 argumentów z listy x2 oraz odpowiadających im wartości None z listy y2, ponieważ nie tworzą one poprawnych współrzędnych. Pierwszą parę usuwamy podając wprost odpowiedni indeks w instrukcjach del x2[9] i del y2[9]. Indeksy elementów drugiej pary najpierw wyszukujemy x2.index(27) i y2.index(None), a później przekazujemy do instrukcji usuwającej del().

Po wywołaniu z ustawieniami w funkcji głównej takimi jak w powyższym kodzie powinniśmy zobaczyć obraz podobny do poniższego.

../../_images/trygon.png

Ćwiczenia

Warto poeksperymentować z wzorami funkcji, ich współczynnikami, wartościami przedziałów i ilością argumentów, aby zbadać jak te zmiany wpływają na ich reprezentację graficzną.

Można też rysować mieszać metody rysujące wykresy (rysuj(), rysuj_linie()), kolejność przekazywania im parametrów, rodzaje bloków itp. Spróbuj np. budować wykresy z piasku (block.STONE) ponad powierzchnią.

Ź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:2017-09-08 o 19:38 w Sphinx 1.4.5
Autorzy:Patrz plik “Autorzy”