3.4. RG – klocki 2B

Wersja B oparta jest na zbiorach i operacjach na nich.

Wskazówka

  • Każdy “klocek” można testować osobno, a później w połączeniu z innymi. Warto i trzeba zmieniać kolejność stosowanych reguł!

3.4.1. Typy pól

Zobaczmy, w jaki sposób dowiedzieć się, w jakim miejscu się znajdujemy, gdzie wokół mamy wrogów lub pola, na które można wejść. Dysponując takimi informacjami, będziemy mogli podejmować bardziej przemyślane działania. Wykorzystamy wyrażenia zbiorów (ang. set comprehensions) (zob. wyrażenie listowe) i operacje na zbiorach (zob. zbiór).

3.4.1.1. Czy to wejście?

Kod nr
# wszystkie pola na planszy jako współrzędne (x, y)
wszystkie = {(x, y) for x in xrange(19) for y in xrange(19)}

# punkty wejścia (spawn)
wejscia = {poz for poz in wszystkie if 'spawn' in rg.loc_types(poz)}

# warunek sprawdzający, czy "poz" jest w punkcie wejścia
if poz in wejscia:
    pass

Metody i właściwości biblioteki rg:

  • gr.loc_types(poz) – zwraca typ pola wskazywanego przez poz:

    • invalid – poza granicami planszy(np. (-1, -5) lub (23, 66));
    • normal – w ramach planszy;
    • spawn – punkt wejścia robotów;
    • obstacle – pola zablokowane ograniczające arenę.

3.4.1.2. Czy obok jest wróg?

Wersja oparta na zbiorach wykorzystuje różnicę i cześć wspólną zbiorów.

Kod nr
# pola zablokowane
zablokowane = {poz for poz in wszystkie if 'obstacle' in rg.loc_types(poz)}

# pola zajęte przez nasze roboty
przyjaciele = {poz for poz in game.robots if game.robots[poz].player_id == self.player_id}

# pola zajęte przez wrogów
wrogowie = set(game.robots) - przyjaciele

# pola sąsiednie
sasiednie = set(rg.locs_around(self.location)) - zablokowane

# pola obok zajęte przez wrogów
wrogowie_obok = sasiednie & wrogowie

# warunek sprawdzający, czy obok są wrogowie
if wrogowie_obok:
    pass

Metody i właściwości biblioteki rg:

  • rg.locs_around(poz, filter_out=None) – zwraca listę położeń sąsiadujących z poz. Jako filter_out można podać typy położeń do wyeliminowania, np.: rg.locs_around(self.location, filter_out=('invalid', 'obstacle')).

Wskazówka

Definicje zbiorów należy wstawić na początku metody Robot.act() – przed pierwszym użyciem.

Wykorzystując powyższe “klocki” możemy napisać robota realizującego następujące reguły:

  1. Opuść jak najszybciej wejście;
  2. Atakuj wrogów obok;
  3. W środku broń się;
  4. W ostateczności idź do środka.

3.4.1.3. Implementacja

Przykładowa implementacja może wyglądać następująco:

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

import rg

class Robot:

    def act(self, game):
        
        # wszystkie pola
        wszystkie = {(x, y) for x in xrange(19) for y in xrange(19)}
        # punkty wejścia
        wejscia = {poz for poz in wszystkie if 'spawn' in rg.loc_types(poz)}
        # pola zablokowane
        zablokowane = {poz for poz in wszystkie if 'obstacle' in rg.loc_types(poz)}
        # pola zajęte przez nasze roboty
        przyjaciele = {poz for poz in game.robots if game.robots[poz].player_id == self.player_id}
        # pola zajęte przez wrogów
        wrogowie = set(game.robots) - przyjaciele
        # pola sąsiednie
        sasiednie = set(rg.locs_around(self.location)) - zablokowane
        # pola sąsiednie zajęte przez wrogów
        wrogowie_obok = sasiednie & wrogowie

        # działanie domyślne:
        ruch = ['move', rg.toward(self.location, rg.CENTER_POINT)]

        # jeżeli jesteś w punkcie wejścia, opuść go
        if self.location in wejscia:
            ruch = ['move', rg.toward(self.location, rg.CENTER_POINT)]

        # jeżeli jesteś w środku, broń się
        if self.location == rg.CENTER_POINT:
            ruch = ['guard']

        # jeżeli obok są przeciwnicy, atakuj
        if wrogowie_obok:
            ruch = ['attack', wrogowie_obok.pop()]

        return ruch

Metoda .pop() zastosowana do zbioru zwraca element losowy.

3.4.1.4. Ćwiczenie 1

Zapisz powyższą implementację w katalogu robot i przetestuj ją w symulatorze, a następnie wystaw ją do walki z robotem podstawowym. Poeksperymentuj z kolejnością reguł, która określa ich priorytety!

Wskazówka

Do kontrolowania logiki działania robota zamiast rozłącznych instrukcji warunkowych: if war1: ... if war2: ... itd. można użyć instrukcji złożonej: if war1: ... elif war2: ... [elif war3: ...] else: ....

3.4.2. Atakuj, jeśli nie umrzesz

Warto atakować, ale nie wtedy, gdy grozi nam śmierć. Można przyjąć zasadę, że atakujemy tylko wtedy, kiedy suma potencjalnych uszkodzeń będzie mniejsza niż zdrowie naszego robota. Zmień więc dotychczasowe reguły ataku wroga korzystając z poniższych “klocków”:

Kod nr
# WERSJA B
# jeżeli obok są przeciwnicy, atakuj
if wrogowie_obok:
    if 9*len(wrogowie_obok) >= self.hp:
        pass
    else:
        ruch = ['attack', wrogowie_obok.pop()]

Metody i właściwości biblioteki rg:

  • self.hp – ilość punktów HP robota.

3.4.2.1. Ćwiczenie 2

Dodaj powyższą regułę do poprzedniej wersji robota.

3.4.3. Ruszaj się bezpiecznie

Zamiast iść na oślep lepiej wchodzić czy uciekać na bezpieczne pola. Za “bezpieczne” przyjmiemy na razie pole puste, niezablokowane i nie będące punktem wejścia.

Wersja B. Kod nr
# WERSJA B
# zbiór bezpiecznych pól
bezpieczne = sasiednie - wrogowie_obok - wejscia - przyjaciele

3.4.4. Atakuj 2 kroki obok

Jeżeli w odległości 2 kroków jest przeciwnik, zamiast iść w jego kierunku i narażać się na szkody, lepiej go zaatakuj, aby nie mógł bezkarnie się do nas zbliżyć.

Kod nr
# WERSJA B
wrogowie_obok2 = {poz for poz in sasiednie if (set(rg.locs_around(poz)) & wrogowie)} - przyjaciele

if wrogowie_obok2:
    ruch = ['attack', wrogowie_obok2.pop()]

3.4.5. Składamy reguły

3.4.5.1. Ćwiczenie 3

Jeżeli czujesz się na siłach, spróbuj dokładać do robota w wersji B (opartego na zbiorach) po jednej z przedstawionych reguł, czyli: 1) Atakuj, jeśli nie umrzesz; 2) Ruszaj się bezpiecznie; 3) Atakuj na 2 kroki. Przetestuj w symulatorze każdą zmianę.

Omówione reguły można poskładać w różny sposób, np. tak:

W wersji B opartej na zbiorach:

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

import rg

class Robot:

    def act(self, game):
        
        # wszystkie pola
        wszystkie = {(x, y) for x in xrange(19) for y in xrange(19)}
        # punkty wejścia
        wejscia = {poz for poz in wszystkie if 'spawn' in rg.loc_types(poz)}
        # pola zablokowane
        zablokowane = {poz for poz in wszystkie if 'obstacle' in rg.loc_types(poz)}
        # pola zajęte przez nasze roboty
        przyjaciele = {poz for poz in game.robots if game.robots[poz].player_id == self.player_id}
        # pola zajęte przez wrogów
        wrogowie = set(game.robots) - przyjaciele
        # pola sąsiednie
        sasiednie = set(rg.locs_around(self.location)) - zablokowane
        # pola sąsiednie zajęte przez wrogów
        wrogowie_obok = sasiednie & wrogowie
        wrogowie_obok2 = {poz for poz in sasiednie if (set(rg.locs_around(poz)) & wrogowie)} - przyjaciele
        # pola bezpieczne
        bezpieczne = sasiednie - wrogowie_obok - wejscia - przyjaciele

        # działanie domyślne:
        ruch = ['move', rg.toward(self.location, rg.CENTER_POINT)]

        # jeżeli jesteś w punkcie wejścia, opuść go
        if self.location in wejscia:
            ruch = ['move', rg.toward(self.location, rg.CENTER_POINT)]

        # jeżeli jesteś w środku, broń się
        if self.location == rg.CENTER_POINT:
            ruch = ['guard']

        # jeżeli obok są przeciwnicy, atakuj, o ile to bezpieczne
        if wrogowie_obok:
            if 9*len(wrogowie_obok) >= self.hp:
                if bezpieczne:
                    ruch = ['move', bezpieczne.pop()]
            else:
                ruch = ['attack', wrogowie_obok.pop()]

        if wrogowie_obok2:
            ruch = ['attack', wrogowie_obok2.pop()]

        return ruch

3.4.6. Możliwe ulepszenia

Poniżej pokazujemy “klocki”, których możesz użyć, aby zoptymalizować robota. Zamieszczamy również listę pytań do przemyślenia, aby zachęcić cię do samodzielnego konstruowania najlepszego robota :-)

3.4.6.1. Atakuj najsłabszego

Kod nr
# wersja B
# funkcja znajdująca najsłabszego wroga obok z podanego zbioru (bots)
def minhp(bots):
    return min(bots, key=lambda x: game.robots[x].hp)

if wrogowie_obok:
    ...
    else:
        ruch = ['attack', minhp(wrogowie_obok)]

3.4.6.2. Najkrócej do celu

Funkcji mindist() można użyć do znalezienia najbliższego wroga, aby iść w jego kierunku, kiedy opuścimy punkt wejścia:

Kod nr
# WERSJA B
# funkcja zwraca ze zbioru pól (bots) pole najbliższe podanego celu (poz)
def mindist(bots, poz):
    return min(bots, key=lambda x: rg.dist(x, poz))

najblizszy_wrog = mindist(wrogowie,self.location)

3.4.6.3. Inne

  • Czy warto atakować, jeśli obok jest więcej niż 1 wróg?
  • Czy warto atakować 1 wroga obok, ale mocniejszego od nas?
  • Jeżeli nie można bezpiecznie się ruszyć, może lepiej się bronić?
  • Jeśli jesteśmy otoczeni przez wrogów, może lepiej popełnić samobójstwo...
  • Spróbuj zmienić akcję domyślną.
  • Spróbuj użyć jednej złożonej instrukcji warunkowej!

Proponujemy, żebyś sam zaczął wprowadzać i testować zasugerowane ulepszenia. Możesz też zajrzeć do trzeciego zestawu klocków.


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-11-17 o 06:13 w Sphinx 1.5.3
Autorzy:Patrz plik “Autorzy”