8.7. Gra robotów

8.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
 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
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import json
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"  # nazwa użytkownika
os.environ["COMPUTERNAME"] = "mykomp"  # nazwa komputera

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


class GraRobotow(object):
    """Główna klasa gry"""

    obstacle = [(0,0),(1,0),(2,0),(3,0),(4,0),(5,0),(6,0),(7,0),(8,0),(9,0),
        (10,0),(11,0),(12,0),(13,0),(14,0),(15,0),(16,0),(17,0),(18,0),(0,1),
        (1,1),(2,1),(3,1),(4,1),(5,1),(6,1),(12,1),(13,1),(14,1),(15,1),
        (16,1),(17,1),(18,1),(0,2),(1,2),(2,2),(3,2),(4,2),(14,2),(15,2),
        (16,2),(17,2),(18,2),(0,3),(1,3),(2,3),(16,3),(17,3),(18,3),(0,4),
        (1,4),(2,4),(16,4),(17,4),(18,4),(0,5),(1,5),(17,5),(18,5),(0,6),
        (1,6),(17,6),(18,6),(0,7),(18,7),(0,8),(18,8),(0,9),(18,9),(0,10),
        (18,10),(0,11),(18,11),(0,12),(1,12),(17,12),(18,12),(0,13),(1,13),
        (17,13),(18,13),(0,14),(1,14),(2,14),(16,14),(17,14),(18,14),(0,15),
        (1,15),(2,15),(16,15),(17,15),(18,15),(0,16),(1,16),(2,16),(3,16),
        (4,16),(14,16),(15,16),(16,16),(17,16),(18,16),(0,17),(1,17),(2,17),
        (3,17),(4,17),(5,17),(6,17),(12,17),(13,17),(14,17),(15,17),(16,17),
        (17,17),(18,17),(0,18),(1,18),(2,18),(3,18),(4,18),(5,18),(6,18),
        (7,18),(8,18),(9,18),(10,18),(11,18),(12,18),(13,18),(14,18),(15,18),
        (16,18),(17,18),(18,18)]

    plansza = []  # współrzędne dozwolonych pól gry

    def __init__(self, mc):
        """Konstruktor klasy"""
        self.mc = mc
        self.poleGry(0, 0, 0, 18)
        # self.mc.player.setPos(19, 20, 19)

    def poleGry(self, x, y, z, roz=10):
        """Funkcja tworzy pole gry"""

        podloga = block.STONE
        wypelniacz = block.AIR

        # podloga i czyszczenie
        self.mc.setBlocks(x, y - 1, z, x + roz, y - 1, z + roz, podloga)
        self.mc.setBlocks(x, y, z, x + roz, y + roz, z + roz, wypelniacz)
        # granice pola
        x = y = z = 0
        for i in range(19):
            for j in range(19):
                if (i, j) in self.obstacle:
                    self.mc.setBlock(x + i, y, z + j, block.GRASS)
                else:  # tworzenie listy współrzędnych dozwolonych pól gry
                    self.plansza.append((x + i, z + j))


def main(args):
    gra = GraRobotow(mc)  # instancja klasy GraRobotow
    print gra.plansza  # pokaż w konsoli listę współrzędnych pól gry
    return 0


if __name__ == '__main__':
    import sys
    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

8.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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
    def uruchom(self, plik, ile=100):
        """Funkcja odczytuje z pliku i wizualizuje rundy gry robotów."""

        if not os.path.exists(plik):
            print "Podany plik nie istnieje!"
            return

        plik = open(plik, "r")  # otwórz plik w trybie tylko do odczytu
        runda_nr = 0
        for runda in json.load(plik):
            print "Runda ", runda_nr
            print runda  # pokaż dane rundy w konsoli
            runda_nr = runda_nr + 1
            if runda_nr > ile:
                break


def main(args):
    gra = GraRobotow(mc)  # instancja klasy GraRobotow
    gra.uruchom("lastgame.log", 10)
    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

8.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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
    def pokazRunde(self, runda):
        """Funkcja buduje układ robotów na planszy w przekazanej rundzie."""
        self.czyscPole()
        for robot in runda:
            blok = block.WOOL if robot['player_id'] else block.WOOD
            x, z = robot['location']
            print robot['player_id'], blok, x, z
            self.mc.setBlock(x, 0, z, blok)
        sleep(1)
        print

    def czyscPole(self):
        """Funkcja wypelnia blokami powietrza pole gry."""
        for xz in self.plansza:
            x, z = xz
            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
71
72
73
74
75
        for runda in json.load(plik):
            print "Runda ", runda_nr
            self.pokazRunde(runda)
            runda_nr = runda_nr + 1
            if runda_nr > ile:
                break

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

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

8.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
 95
 96
 97
 98
 99
100
    def wybierzBlok(self, player_id, hp):
        """Funkcja dobiera kolor bloku w zależności od gracza i hp robota."""
        player1_bloki = (block.GRAVEL, block.SANDSTONE, block.BRICK_BLOCK,
                         block.FARMLAND, block.OBSIDIAN, block.OBSIDIAN)
        player2_bloki = (block.WOOL, block.LEAVES, block.CACTUS,
                         block.MELON, block.WOOD, block.WOOD)
        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
81
82
83
84
        for robot in runda:
            blok = self.wybierzBlok(robot['player_id'], robot['hp'])
            x, z = robot['location']
            print robot['player_id'], blok, x, z
            self.mc.setBlock(x, 0, z, blok)
../../_images/mcpi-rg04.png
../../_images/mcpi-rg05.png

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

Note

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