9.7. Gra robotów

9.7.1. Pole gry

Spróbujemy teraz pokazać rozgrywkę z gry robotów. Zaczniemy od zbudowania areny wykorzystywanej w grze. W pliku mcpi-rg.py umieszczamy następujący kod:

Kod nr
 1#!/usr/bin/env python
 2# -*- coding: utf-8 -*-
 3
 4import os
 5import json
 6from time import sleep
 7import mcpi.minecraft as minecraft  # import modułu minecraft
 8import mcpi.block as block  # import modułu block
 9
10os.environ["USERNAME"] = "Steve"  # nazwa użytkownika
11os.environ["COMPUTERNAME"] = "mykomp"  # nazwa komputera
12
13mc = minecraft.Minecraft.create("192.168.1.10")  # połączenie z MCPi
14
15
16class GraRobotow(object):
17    """Główna klasa gry"""
18
19    obstacle = [(0,0),(1,0),(2,0),(3,0),(4,0),(5,0),(6,0),(7,0),(8,0),(9,0),
20        (10,0),(11,0),(12,0),(13,0),(14,0),(15,0),(16,0),(17,0),(18,0),(0,1),
21        (1,1),(2,1),(3,1),(4,1),(5,1),(6,1),(12,1),(13,1),(14,1),(15,1),
22        (16,1),(17,1),(18,1),(0,2),(1,2),(2,2),(3,2),(4,2),(14,2),(15,2),
23        (16,2),(17,2),(18,2),(0,3),(1,3),(2,3),(16,3),(17,3),(18,3),(0,4),
24        (1,4),(2,4),(16,4),(17,4),(18,4),(0,5),(1,5),(17,5),(18,5),(0,6),
25        (1,6),(17,6),(18,6),(0,7),(18,7),(0,8),(18,8),(0,9),(18,9),(0,10),
26        (18,10),(0,11),(18,11),(0,12),(1,12),(17,12),(18,12),(0,13),(1,13),
27        (17,13),(18,13),(0,14),(1,14),(2,14),(16,14),(17,14),(18,14),(0,15),
28        (1,15),(2,15),(16,15),(17,15),(18,15),(0,16),(1,16),(2,16),(3,16),
29        (4,16),(14,16),(15,16),(16,16),(17,16),(18,16),(0,17),(1,17),(2,17),
30        (3,17),(4,17),(5,17),(6,17),(12,17),(13,17),(14,17),(15,17),(16,17),
31        (17,17),(18,17),(0,18),(1,18),(2,18),(3,18),(4,18),(5,18),(6,18),
32        (7,18),(8,18),(9,18),(10,18),(11,18),(12,18),(13,18),(14,18),(15,18),
33        (16,18),(17,18),(18,18)]
34
35    plansza = []  # współrzędne dozwolonych pól gry
36
37    def __init__(self, mc):
38        """Konstruktor klasy"""
39        self.mc = mc
40        self.poleGry(0, 0, 0, 18)
41        # self.mc.player.setPos(19, 20, 19)
42
43    def poleGry(self, x, y, z, roz=10):
44        """Funkcja tworzy pole gry"""
45
46        podloga = block.STONE
47        wypelniacz = block.AIR
48
49        # podloga i czyszczenie
50        self.mc.setBlocks(x, y - 1, z, x + roz, y - 1, z + roz, podloga)
51        self.mc.setBlocks(x, y, z, x + roz, y + roz, z + roz, wypelniacz)
52        # granice pola
53        x = y = z = 0
54        for i in range(19):
55            for j in range(19):
56                if (i, j) in self.obstacle:
57                    self.mc.setBlock(x + i, y, z + j, block.GRASS)
58                else:  # tworzenie listy współrzędnych dozwolonych pól gry
59                    self.plansza.append((x + i, z + j))
60
61
62def main(args):
63    gra = GraRobotow(mc)  # instancja klasy GraRobotow
64    print gra.plansza  # pokaż w konsoli listę współrzędnych pól gry
65    return 0
66
67
68if __name__ == '__main__':
69    import sys
70    sys.exit(main(sys.argv))

Zaczynamy od definicji klasy GraRobotow, której instancję tworzymy w funkcji głównej main() i przypisujemy do zmiennej: gra = GraRobotow(mc). Konstruktor klasy wywołuje metodę poleGry(), która buduje pusty plac i arenę, na której walczą roboty.

Pole gry wpisane jest w kwadrat o boku 19 jednostek. Część pól kwadratu wyłączona jest z rozgrywki, ich współrzędne zawiera lista obstacle. Funkcja poleGry() wykorzystuje dwie zagnieżdżone pętle, w których zmienne iteracyjne i, j przyjmują wartości od 0 do 18, wyznaczając wszystkie pola kwadratu. Jeżeli dane pole zawarte jest w liście pól wyłączonych if (i, j) in obstacle, umieszczamy w nim blok trawy – wyznaczą one granice planszy. W przeciwnym wypadku dołączamy współrzędne pola w postaci tupli do listy pól dozwolonych: self.plansza.append((x + i, z + j)). Wykorzystamy tę listę później do „czyszczenia” pola gry.

Po uruchomieniu powinniśmy zobaczyć plac gry, a w konsoli listę pól, na których będą walczyć roboty.

../../_images/mcpi-rg01.png

9.7.2. Dane gry

Dane gry, czyli zapis 100 rund rozgrywki zawierający m. in. informacje o położeniu robotów oraz ich sile (punkty hp) musimy wygenerować uruchamiając walkę gotowych lub napisanych przez nas robotów.

W tym celu trzeba zmodyfikować bibliotekę game.py z pakietu rgkit. Jeżeli korzystałeś z naszego scenariusza i zainstalowałeś rgkit w wirtualnym środowisku ~/robot/env, plik ten znajdziesz w ścieżce ~/robot/env/lib/python2.7/site-packages/rgkit/game.py. Na końcu funkcji run_all_turns() po linii nr 386 wstawiamy podany niżej kod:

# BEGIN DODANE na potrzeby Kzk
import json
plik = open('lastgame.log', 'w')
json.dump(self.history, plik)
plik.close()
# END OF DODANE

Następnie po wywołaniu przykładowej walki: (env) root@kzk:~/robot$ rgrun bots/stupid26.py bots/Wall-E.py w katalogu ~/robot znajdziemy plik lastgame.log, który musimy umieścić w katalogu ze skryptem mcpi-rg.py.

Do definicji klasy GraRobotow w pliku mcpi-rg.py dodajemy metodę uruchom():

Kod nr
61    def uruchom(self, plik, ile=100):
62        """Funkcja odczytuje z pliku i wizualizuje rundy gry robotów."""
63
64        if not os.path.exists(plik):
65            print "Podany plik nie istnieje!"
66            return
67
68        plik = open(plik, "r")  # otwórz plik w trybie tylko do odczytu
69        runda_nr = 0
70        for runda in json.load(plik):
71            print "Runda ", runda_nr
72            print runda  # pokaż dane rundy w konsoli
73            runda_nr = runda_nr + 1
74            if runda_nr > ile:
75                break
76
77
78def main(args):
79    gra = GraRobotow(mc)  # instancja klasy GraRobotow
80    gra.uruchom("lastgame.log", 10)
81    return 0

Omawianą metodę wywołujemy w funkcji głównej main() przekazując jej jako parametry nazwę pliku z zapisem rozgrywki oraz ilość rund do pokazania: gra.uruchom("lastgame.log", 10).

W samej metodzie zaczynamy od sprawdzenia, czy podany plik istnieje w katalogu ze skryptem. Jeżeli nie istnieje (if not os.path.exists(plik):) drukujemy komunikat i wychodzimy z funkcji.

Jeżeli plik istnieje, otwieramy go w trybie tylko do odczytu. Dalej, ponieważ dane gry zapisane są w formacie json, w pętli for runda in json.load(plik): dekodujemy jego zawartość wykorzystując metodę load() modułu json. Instrukcja print runda pokaże nam w konsoli format danych kolejnych rund.

Po uruchomieniu kodu widzimy, że każda runda to lista zawierająca słowniki określające właściwości poszczególnych robotów.

../../_images/mcpi-rg02.png

Ćwiczenie 1

Skopiuj z konsoli dane jednej z rund, uruchom konsolę IPython Qt i wklej do niej.

../../_images/mcpi-rg-ip01.png

Następnie przećwicz wydobywanie słowników z listy:

../../_images/mcpi-rg-ip02.png

– oraz wydobywanie konkretnych danych ze słowników, a także rozpakowywanie tupli (robot['location']) określających położenie robota:

../../_images/mcpi-rg-ip03.png

9.7.3. Pokaż rundę

Słowniki opisujące roboty walczące w danej rundzie zawierają m.in. identyfikatory gracza, położenie robota oraz jego ilość punktów hp. Wykorzystamy te informacje w funkcji pokazRunde().

Klasę GraRobotow w pliku mcpi-rg.py uzupełniamy dwoma metodami:

Kod nr
77    def pokazRunde(self, runda):
78        """Funkcja buduje układ robotów na planszy w przekazanej rundzie."""
79        self.czyscPole()
80        for robot in runda:
81            blok = block.WOOL if robot['player_id'] else block.WOOD
82            x, z = robot['location']
83            print robot['player_id'], blok, x, z
84            self.mc.setBlock(x, 0, z, blok)
85        sleep(1)
86        print
87
88    def czyscPole(self):
89        """Funkcja wypelnia blokami powietrza pole gry."""
90        for xz in self.plansza:
91            x, z = xz
92            self.mc.setBlock(x, 0, z, block.AIR)

W metodzie pokazRunde() na początku czyścimy pole gry, czyli wypełniamy je blokami powietrza – to zadanie funkcji czyscPole(). Jak widać, wykorzystuje ona stworzoną wcześniej listę dozwolonych pól. Kolejne tuple współrzędnych odczytujemy w pętli for xz in self.plansza: i rozpakowujemy x, z = xz.

Po wyczyszczeniu pola gry, z danych rundy przekazanych do metody pokazRunde() odczytujemy w pętli for robot in runda: słowniki opisujące kolejne roboty.

W skróconej instrukcji warunkowej sprawdzamy identyfikator gracza: if robot['player_id']. Jeżeli wynosi 1 (jeden), roboty będą oznaczane blokami bawełny, jeżeli 0 (zero) – blokami drewna.

Następnie z każdego słownika rozpakowujemy tuplę określającą położenie robota: x, z = robot['location']. W uzyskanych współrzędnych umieszczamy ustalony dla gracza typ bloku.

Dodatkowo drukujemy kolejne dane w konsoli print robot['player_id'], blok, x, z.

Zanim uruchomimy kod, musimy jeszcze zamienić instrukcję print runda w metodzie uruchom() na wywołanie omówionej funkcji:

Kod nr
70        for runda in json.load(plik):
71            print "Runda ", runda_nr
72            self.pokazRunde(runda)
73            runda_nr = runda_nr + 1
74            if runda_nr > ile:
75                break

Po uruchomieniu kodu powinniśmy zobaczyć już rozgrywkę:

../../_images/mcpi-rg03.png

9.7.4. Kolory

Takie same bloki wykorzystywane do pokazywania ruchów robotów obydwu graczy nie wyglądają zbyt dobrze. Spróbujemy odróżnić od siebie obydwie drużyny i pokazać, że roboty w starciach tracą siłę, czyli punkty życia hp.

Do definicji klasy GraRobotow dodajemy jeszcze jedną metodę o nazwie wybierzBlok():

Kod nr
 94    def wybierzBlok(self, player_id, hp):
 95        """Funkcja dobiera kolor bloku w zależności od gracza i hp robota."""
 96        player1_bloki = (block.GRAVEL, block.SANDSTONE, block.BRICK_BLOCK,
 97                         block.FARMLAND, block.OBSIDIAN, block.OBSIDIAN)
 98        player2_bloki = (block.WOOL, block.LEAVES, block.CACTUS,
 99                         block.MELON, block.WOOD, block.WOOD)
100        return player1_bloki[hp / 10] if player_id else player2_bloki[hp / 10]

Metoda definiuje dwie tuple, po jednej dla każdego gracza, zawierające zestawy bloków używane do wyświetlenia robotów danej drużyny. Dobór typów w tuplach jest oczywiście czysto umowny.

Siła robotów (hp) przyjmuje wartości od 0 do 50, dzieląc tę wartość całkowicie przez 10, otrzymujemy liczby od 0 do 5, które wykorzystamy jako indeksy wskazujące typ bloku przeznaczony do wyświetlenia robota danego zawodnika.

Skrócona instrukcja warunkowa player1_bloki[hp / 10] if player_id else player2_bloki[hp / 10] bada wartość identyfikatora gracza if player_id i zwraca player1_bloki[hp / 10], jeżeli wynosi on 1 (jeden) oraz player2_bloki[hp / 10] jeżeli równa się 0 (zero).

Pozostaje jeszcze zastąpienie instrukcji blok = block.WOOL if robot['player_id'] else block.WOOD w metodzie pokazRunde() wywołaniem omówionej funkcji, czyli:

Kod nr
80        for robot in runda:
81            blok = self.wybierzBlok(robot['player_id'], robot['hp'])
82            x, z = robot['location']
83            print robot['player_id'], blok, x, z
84            self.mc.setBlock(x, 0, z, blok)
../../_images/mcpi-rg04.png
../../_images/mcpi-rg05.png

9.7.5. Trzeci wymiar

Ćwiczenia

Warto poeksperymentować z wizualizacją gry wykorzystując trójwymiarowość Minecrafta. Można uzyskać spektakularne rezulaty. Poniżej kilka sugestii.

  • Stosunkowo łatwo urozmaicić wizualizację gry używając wartości hp (siła robota) jako współrzędnej określającej położenie bloku w pionie. Wystarczy zmienić instrukcję self.mc.setBlock(x, 0, z, blok) w funkcji pokazRunde().

../../_images/mcpi-rg06.png
  • Jeżeli udało ci się wprowadzić powyższą poprawkę i bloki umieszczame są na różnej wysokości, można zmienić typ umieszczanych bloków na piasek (SAND).

../../_images/mcpi-rg07.png
../../_images/mcpi-rg08.png
  • Można spróbować wykorzystać omawianą w scenariuszu Figury 2D i 3D bibliotekę minecraftstuff. Wykorzystując funkcję drawLine() oraz wartość siły robotów robot['hp'] jako współrzędną określającą położenie bloku w pionie, można rysować kolejne rundy w postaci słupków.

../../_images/mcpi-rg09.png

Informacja

Dziękujemy uczestnikom szkolenia przeprowadzonego w ramach programu „Koduj z Klasą” w Krakowie (03.12.2016 r.), którzy zgłosili powyższe pomysły i sugestie.

Ź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”