9.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.

9.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#!/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 wykresów
 7import mcpi.minecraft as minecraft  # import modułu minecraft
 8import mcpi.block as block  # import modułu block
 9
10os.environ["USERNAME"] = "Steve"  # wpisz dowolną nazwę użytkownika
11os.environ["COMPUTERNAME"] = "mykomp"  # wpisz dowolną nazwę komputera
12
13mc = minecraft.Minecraft.create("192.168.1.10")  # połaczenie z mc
14
15
16def plac(x, y, z, roz=10, gracz=False):
17    """
18    Funkcja tworzy podłoże i wypełnia sześcienny obszar od podanej pozycji,
19    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    # podloga i czyszczenie
30    mc.setBlocks(x, y - 1, z, x + roz, y - 1, z + roz, podloga)
31    mc.setBlocks(x, y, z, x + roz, y + roz, z + roz, wypelniacz)
32    # umieść gracza w środku
33    if gracz:
34        mc.player.setPos(x + roz / 2, y + roz / 2, z + roz / 2)
35
36
37def wykres(x, y, tytul="Wykres funkcji", *extra):
38    """
39    Funkcja wizualizuje wykres funkcji, której argumenty zawiera lista x
40    a wartości lista y i ew. dodatkowe listy w parametrze *extra
41    """
42    if len(extra):
43        plt.plot(x, y, extra[0], extra[1])  # dwa wykresy na raz
44    else:
45        plt.plot(x, y)
46    plt.title(tytul)
47    # plt.xlabel(podpis)
48    plt.grid(True)
49    plt.show()
50
51
52def fun1(blok=block.IRON_BLOCK):
53    """
54    Funkcja f(x) = a*x + b
55    """
56    a = int(raw_input('Podaj współczynnik a: '))
57    b = int(raw_input('Podaj współczynnik b: '))
58    x = range(-10, 11)  # lista argumentów x = <-10;10> z krokiem 1
59    y = [a * i + b for i in x]  # wyrażenie listowe
60    print x, "\n", y
61    wykres(x, y, "f(x) = a*x + b")
62    for i in range(len(x)):
63        mc.setBlock(x[i], 1, y[i], blok)
64
65
66def main():
67    mc.postToChat("Funkcje w Minecrafcie")  # wysłanie komunikatu do mc
68    plac(-80, 0, -80, 160)
69    mc.player.setPos(22, 10, 10)
70    fun1()
71    return 0
72
73
74if __name__ == '__main__':
75    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.

9.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
52def uklad(blok=block.OBSIDIAN):
53    """
54    Funkcja rysuje układ współrzędnych
55    """
56    for i in range(-80, 81, 2):
57        mc.setBlock(i, -1, 0, blok)
58        mc.setBlock(0, -1, i, blok)
59        mc.setBlock(0, i, 0, blok)
60
61
62def rysuj(x, y, z, blok=block.IRON_BLOCK):
63    """
64    Funkcja wizualizuje wykres funkcji, umieszczając bloki w pionie/poziomie
65    w punktach wyznaczonych przez pary elementów list x, y lub x, z
66    """
67    czylista = True if len(y) > 1 else False
68    for i in range(len(x)):
69        if czylista:
70            print(x[i], y[i])
71            mc.setBlock(x[i], y[i], z[0], blok)
72        else:
73            print(x[i], z[i])
74            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
90def main():
91    mc.postToChat("Funkcje w Minecrafcie")  # wysłanie komunikatu do mc
92    plac(-80, -40, -80, 160)
93    mc.player.setPos(-4, 10, 20)
94    uklad()
95    fun1()
96    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
77def rysuj_linie(x, y, z, blok=block.IRON_BLOCK):
78    """
79    Funkcja wizualizuje wykres funkcji, umieszczając bloki w pionie/poziomie
80    w punktach wyznaczonych przez pary elementów list x, y lub x, z
81    przy użyciu metody drawLine()
82    """
83    import local.minecraftstuff as mcstuff
84    mcfig = mcstuff.MinecraftDrawing(mc)
85    czylista = True if len(y) > 1 else False
86    for i in range(len(x) - 1):
87        x1 = int(x[i])
88        x2 = int(x[i + 1])
89        if czylista:
90            y1 = int(y[i])
91            y2 = int(y[i + 1])
92            print (x1, y1, z[0], x2, y2, z[0])
93            mcfig.drawLine(x1, y1, z[0], x2, y2, z[0], blok)
94        else:
95            z1 = int(z[i])
96            z2 = int(z[i + 1])
97            print (x1, y[0], z1, x2, y[0], z2)
98            mcfig.drawLine(x1, y[0], z1, x2, y[0], z2, blok)

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

9.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
114def fun2(blok=block.REDSTONE_ORE):
115    """
116    Wykres funkcji f(x), gdzie x = <-1;2> z krokiem 0.15, przy czym
117    f(x) = x/(x+2) dla x >= 1
118    f(x) = x*x/3 dla x < 1 i x > 0
119    f(x) = x/(-3) dla x <= 0
120    """
121    x = np.arange(-1, 2.15, 0.15)  # lista argumentów x
122    y = []  # lista wartości f(x)
123
124    for i in x:
125        if i <= 0:
126            y.append(i / -3)
127        elif i < 1:
128            y.append(i ** 2 / 3)
129        else:
130            y.append(i / (i + 2))
131    wykres(x, y, "Funkcja mieszana")
132    x = [round(i * 20, 2) for i in x]
133    y = [round(i * 20, 2) for i in y]
134    print x, "\n", y
135    rysuj(x, y, [1], blok)
136
137
138def fun3(blok=block.LAPIS_LAZULI_BLOCK):
139    """
140    Funkcja f(x) = log2(x)
141    """
142    x = np.arange(0.1, 41, 1)  # lista argumentów x
143    y = [np.log2(i) for i in x]
144    y = [round(i, 2) * 2 for i in y]
145    print x, "\n", y
146    wykres(x, y, "Funkcja logarytmiczna")
147    rysuj(x, y, [1], blok)
148
149
150def main():
151    mc.postToChat("Funkcje w Minecrafcie")  # wysłanie komunikatu do mc
152    plac(-80, -20, -80, 160)
153    mc.player.setPos(-8, 10, 26)
154    uklad(block.DIAMOND_BLOCK)
155    fun1()
156    fun2()
157    fun3()
158    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

9.4.4. Funkcja kwadratowa

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

Kod nr
150def fkw(x, a=0.3, b=0.1, c=0):
151    return a * x**2 + b * x + c
152
153
154def fkwadratowa():
155    """
156    Funkcja przygotowuje dziedzinę funkcji kwadratowej
157    oraz dwie przeciwdziedziny, druga z odwróconym znakiem. Następnie
158    buduje ich wykresy w poziomie i w pionie.
159    """
160    while True:
161        lewy = float(raw_input("Podaj lewy kraniec przedziału: "))
162        prawy = float(raw_input("Podaj prawy kraniec przedziału: "))
163        if lewy * prawy < 1 and lewy <= prawy:
164            break
165    print lewy, prawy
166
167    # x = np.arange(lewy, prawy, 0.2)
168    x = np.linspace(lewy, prawy, 60, True)
169    x = [round(i, 2) for i in x]
170    y1 = [fkw(i) for i in x]
171    y1 = [round(i, 2) for i in y1]
172    y2 = [-fkw(i) for i in x]
173    y2 = [round(i, 2) for i in y2]
174    print x, "\n", y1, "\n", y2
175    wykres(x, y1, "Funkcja kwadratowa", x, y2)
176    rysuj_linie(x, [1], y1, block.GRASS)
177    rysuj(x, [1], y2, block.SAND)
178    rysuj(x, y1, [1], block.WOOL)
179    rysuj_linie(x, y2, [1], block.IRON_BLOCK)
180
181
182def main():
183    mc.postToChat("Funkcje w Minecrafcie")  # wysłanie komunikatu do mc
184    plac(-80, -20, -80, 160)
185    mc.player.setPos(-15, 10, -15)
186    uklad(block.OBSIDIAN)
187    fkwadratowa()
188    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?

9.4.5. Funkcje trygonometryczne

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

Kod nr
182def trygon():
183    x1 = np.arange(-50.0, 50.0, 1)
184    y1 = 5 * np.sin(0.1 * np.pi * x1)
185    y1 = [round(i, 2) for i in y1]
186    print x1, "\n", y1
187
188    x2 = range(0, 361, 10)  # lista argumentów x
189    y2 = [None if i == 90 or i == 270 else np.tan(i * np.pi / 180) for i in x2]
190    x2 = [i // 10 for i in x2]
191    y2 = [round(i * 3, 2) if i is not None else None for i in y2]
192    print x2, "\n", y2
193    wykres(x1, y1, "Funkcje sinus i tangens", x2, y2)
194
195    del x2[9]  # usuń 10 element listy
196    del y2[9]  # usuń 10 element listy
197    del x2[x2.index(27)]  # usuń element o wartości 27
198    del y2[y2.index(None)]  # usuń element None
199    print x2, "\n", y2
200    rysuj(x1, [1], y1, block.GOLD_BLOCK)
201    rysuj(x2, y2, [1], block.OBSIDIAN)
202
203
204def main():
205    mc.postToChat("Funkcje w Minecrafcie")  # wysłanie komunikatu do mc
206    plac(-80, -20, -80, 160)
207    mc.player.setPos(17, 17, 24)
208    uklad(block.DIAMOND_BLOCK)
209    trygon()
210    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:

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

Autorzy:

Patrz plik „Autorzy”