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

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()
:
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.

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

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

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

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:
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:
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ę:

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()
:
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:
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)


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 funkcjipokazRunde()
.

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


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

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:
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: