3.5. RG – klocki 3B

3.5.1. Robot dotychczasowy

Na podstawie reguł i klocków z części pierwszej mogliśmy stworzyć następującego robota:

Kod nr
 1#! /usr/bin/env python
 2# -*- coding: utf-8 -*-
 3
 4import rg
 5
 6class Robot:
 7
 8    def act(self, game):
 9
10        wszystkie = {(x, y) for x in xrange(19) for y in xrange(19)}
11        wejscia = {poz for poz in wszystkie if 'spawn' in rg.loc_types(poz)}
12        zablokowane = {poz for poz in wszystkie if 'obstacle' in rg.loc_types(poz)}
13        przyjaciele = {poz for poz in game.robots if game.robots[poz].player_id == self.player_id}
14        wrogowie = set(game.robots) - przyjaciele
15
16        sasiednie = set(rg.locs_around(self.location)) - zablokowane
17        wrogowie_obok = sasiednie & wrogowie
18        wrogowie_obok2 = {poz for poz in sasiednie if (set(rg.locs_around(poz)) & wrogowie)} - przyjaciele
19        bezpieczne = sasiednie - wrogowie_obok - wrogowie_obok2 - wejscia - przyjaciele
20
21        def mindist(bots, poz):
22            return min(bots, key=lambda x: rg.dist(x, poz))
23
24        if wrogowie:
25            najblizszy_wrog = mindist(wrogowie,self.location)
26        else:
27            najblizszy_wrog = rg.CENTER_POINT
28
29        # działanie domyślne:
30        ruch = ['guard']
31
32        if self.location in wejscia:
33            if bezpieczne:
34                ruch = ['move', mindist(bezpieczne, rg.CENTER_POINT)]
35        elif wrogowie_obok:
36            if 9*len(wrogowie_obok) >= self.hp:
37                if bezpieczne:
38                    ruch = ['move', mindist(bezpieczne, rg.CENTER_POINT)]
39            else:
40                ruch = ['attack', wrogowie_obok.pop()]
41        elif wrogowie_obok2:
42            ruch = ['attack', wrogowie_obok2.pop()]
43        elif bezpieczne:
44            ruch = ['move', mindist(bezpieczne, najblizszy_wrog)]
45
46        return ruch

Jego działanie opiera się na wyznaczeniu zbiorów pól określonego typu zastosowaniu następujących reguł:

  1. jeżeli nie ma nic lepszego, broń się,

  2. z punktu wejścia idź bezpiecznie do środka;

  3. atakuj wrogów wokół siebie, o ile to bezpieczne, jeżeli nie, idź bezpiecznie do środka;

  4. atakuj wrogów dwa pola obok;

  5. idź bezpiecznie na najbliższego wroga.

Spróbujemy go ulepszyć dodając, ale i prezycując reguły.

3.5.2. Śledź wybrane miejsca

Aby unikać niepotrzebnych kolizji, nie należy wchodzić na wybrane wcześniej pola. Trzeba więc zapamiętywać pola wybrane w danej rundzie.

Przed klasą Robot definiujemy dwie zmienne globalne, następnie na początku metody .act() inicjujemy dane:

Kod nr
# zmienne globalne
runda_numer = 0 # numer rundy
wybrane_pola = set() # zbiór wybranych w rundzie pól

# inicjacja danych
# wyzeruj zbiór wybrane_pola przy pierwszym robocie w rundzie
global runda_numer, wybrane_pola
if game.turn != runda_numer:
    runda_numer = game.turn
    wybrane_pola = set()

Do zapamiętywania wybranych w rundzie pól posłużą funkcje ruszaj() i stoj():

Kod nr
# jeżeli się ruszamy, zapisujemy docelowe pole
def ruszaj(poz):
    wybrane_pola.add(poz)
    return ['move', poz]

# jeżeli stoimy, zapisujemy zajmowane miejsce
def stoj(act, poz=None):
    wybrane_pola.add(self.location)
    return [act, poz]

Ze zbioru bezpieczne wyłączamy wybrane pola i stosujemy nowe funkcje:

Kod nr
# ze zbioru bezpieczne wyłączamy wybrane_pola
bezpieczne = sasiednie - wrogowie_obok - wrogowie_obok2 - \
             wejscia - przyjaciele - wybrane_pola

# stosujemy nowy kod w regule "atakuj wroga dwa pola obok"
elif wrogowie_obok2 and self.location not in wybrane_pola:

# stosujemy funkcje "ruszaj()" i "stoj()"

# zamiast: ruch = ['move', mindist(bezpieczne, rg.CENTER_POINT)]
ruch = ruszaj(mindist(bezpieczne, rg.CENTER_POINT))

# zamiast: ruch = ['attack', wrogowie_obok.pop()]
ruch = stoj('attack', wrogowie_obok.pop())

# zamiast: ruch = ['move', mindist(bezpieczne, najblizszy_wrog)]
ruch = ruszaj(mindist(bezpieczne, najblizszy_wrog))

Wskazówka

Można zapamiętywać wszystkie wybrane ruchy lub tylko niektóre. Przetestuj, czy ma to wpływ na skuteczność AI.

3.5.3. Atakuj najsłabszego

Do tej pory atakowaliśmy przypadkowego robota wokół nas, lepiej wybrać najsłabszego.

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

elif wrogowie_obok:
    ...
    else:
        ruch = stoj('attack', minhp(wrogowie_obok))

Funkcja minhp() poda nam położenie najsłabszego wroga. Argument parametru key, czyli wyrażenie lambda zwraca właściwość robotów, czyli punkty HP, wg której są porównywane.

3.5.4. Samobójstwo lepsze niż śmierć?

Jeżeli grozi nam śmierć, a nie ma bezpiecznego miejsca, aby uciec, lepiej popełnić samobójstwo:

Kod nr
# samobójstwo lepsze niż śmierć
elif wrogowie_obok:
    if bezpieczne:
        ...
    else:
        ruch = stoj('suicide')

3.5.5. Unikaj nierównych starć

Nie warto walczyć z przeważającą liczbą wrogów.

Kod nr
elif wrogowie_obok:
    if 9*len(wrogowie_obok) >= self.hp:
        ...
    elif len(wrogowie_obok) > 1:
        if bezpieczne:
            ruch = ruszaj(mindist(bezpieczne, rg.CENTER_POINT))
    else:
        ruch = stoj('attack', minhp(wrogowie_obok))

3.5.6. Goń najsłabszych

Zamiast atakować słabego uciekającego robota, lepiej go gonić, może trafi w gorsze miejsce…

Kod nr
elif wrogowie_obok:
    ...
    else:
        cel = minhp(wrogowie_obok)
        if game.robots[cel].hp <= 5:
            ruch = ruszaj(cel)
        else:
            ruch = stoj('attack', minhp(wrogowie_obok))

3.5.7. Robot zaawansowany

Po dodaniu/zmodyfikowaniu omwionych powyej reguł kod naszego robota może wyglądać tak:

Kod nr
 1#! /usr/bin/env python
 2# -*- coding: utf-8 -*-
 3
 4import rg
 5
 6runda_numer = 0 # numer rundy
 7wybrane_pola = set() # zbiór wybranych w rundzie pól
 8
 9class Robot:
10
11    def act(self, game):
12
13        global runda_numer, wybrane_pola
14        if game.turn != runda_numer:
15            runda_numer = game.turn
16            wybrane_pola = set()
17
18        # jeżeli się ruszamy, zapisujemy docelowe pole
19        def ruszaj(loc):
20            wybrane_pola.add(loc)
21            return ['move', loc]
22
23        # jeżeli stoimy, zapisujemy zajmowane miejsce
24        def stoj(act, loc=None):
25            wybrane_pola.add(self.location)
26            return [act, loc]
27
28        # wszystkie pola
29        wszystkie = {(x, y) for x in xrange(19) for y in xrange(19)}
30        # punkty wejścia
31        wejscia = {poz for poz in wszystkie if 'spawn' in rg.loc_types(poz)}
32        # pola zablokowane
33        zablokowane = {poz for poz in wszystkie if 'obstacle' in rg.loc_types(poz)}
34        # pola zajęte przez nasze roboty
35        przyjaciele = {poz for poz in game.robots if game.robots[poz].player_id == self.player_id}
36        # pola zajęte przez wrogów
37        wrogowie = set(game.robots) - przyjaciele
38        # pola sąsiednie
39        sasiednie = set(rg.locs_around(self.location)) - zablokowane
40        # pola sąsiednie zajęte przez wrogów
41        wrogowie_obok = sasiednie & wrogowie
42        wrogowie_obok2 = {poz for poz in sasiednie if (set(rg.locs_around(poz)) & wrogowie)} - przyjaciele
43        # pola bezpieczne
44        bezpieczne = sasiednie - wrogowie_obok - wrogowie_obok2 - wejscia - przyjaciele - wybrane_pola
45
46        # funkcja znajdująca najsłabszego wroga obok z podanego zbioru (bots)
47        def mindist(bots, loc):
48            return min(bots, key=lambda x: rg.dist(x, loc))
49
50        if wrogowie:
51            najblizszy_wrog = mindist(wrogowie,self.location)
52        else:
53            najblizszy_wrog = rg.CENTER_POINT
54
55        # działanie domyślne:
56        ruch = ['guard']
57
58        # jeżeli jesteś w punkcie wejścia, opuść go
59        if self.location in wejscia:
60            ruch = ruszaj(mindist(bezpieczne, rg.CENTER_POINT))
61
62        # jeżeli jesteś w środku, broń się
63        if self.location == rg.CENTER_POINT:
64            ruch = ['guard']
65
66        # jeżeli obok są przeciwnicy, atakuj, o ile to bezpieczne,
67        # najsłabszego wroga
68        if wrogowie_obok:
69            if 9*len(wrogowie_obok) >= self.hp:
70                if bezpieczne:
71                    ruch = ruszaj(mindist(bezpieczne, rg.CENTER_POINT))
72            else:
73                ruch = ['attack', minhp(wrogowie_obok)]
74
75        if wrogowie_obok2 and self.location not in wybrane_pola:
76            ruch = ['attack', wrogowie_obok2.pop()]
77
78        return ruch

Na koniec trzeba przetestować robota. Czy rzeczywiście jest lepszy od poprzednich wersji?

3.5.8. Podsumowanie

Nie myśl, że zastosowanie wszystkich powyższych reguł automatycznie ulepszy robota. Weź pod uwagę fakt, że roboty pojawiają się w losowych punktach, oraz to, że strategia przeciwnika może być inna od zakładanej. Zaproponowane połączenie klocków nie musi być optymalne. Przetestuj kolejne wersje robotów, ustal ich zalety i wady, eksperymentuj, aby znaleźć lepsze rozwiązania.


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”