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

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

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

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


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: | 2022-05-22 o 19:52 w Sphinx 1.5.3 |
---|---|
Autorzy: | Patrz plik “Autorzy” |