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

import rg

class Robot:

    def act(self, game):

        wszystkie = {(x, y) for x in xrange(19) for y in xrange(19)}
        wejscia = {poz for poz in wszystkie if 'spawn' in rg.loc_types(poz)}
        zablokowane = {poz for poz in wszystkie if 'obstacle' in rg.loc_types(poz)}
        przyjaciele = {poz for poz in game.robots if game.robots[poz].player_id == self.player_id}
        wrogowie = set(game.robots) - przyjaciele

        sasiednie = set(rg.locs_around(self.location)) - zablokowane
        wrogowie_obok = sasiednie & wrogowie
        wrogowie_obok2 = {poz for poz in sasiednie if (set(rg.locs_around(poz)) & wrogowie)} - przyjaciele
        bezpieczne = sasiednie - wrogowie_obok - wrogowie_obok2 - wejscia - przyjaciele

        def mindist(bots, poz):
            return min(bots, key=lambda x: rg.dist(x, poz))

        if wrogowie:
            najblizszy_wrog = mindist(wrogowie,self.location)
        else:
            najblizszy_wrog = rg.CENTER_POINT

        # działanie domyślne:
        ruch = ['guard']

        if self.location in wejscia:
            if bezpieczne:
                ruch = ['move', mindist(bezpieczne, rg.CENTER_POINT)]
        elif wrogowie_obok:
            if 9*len(wrogowie_obok) >= self.hp:
                if bezpieczne:
                    ruch = ['move', mindist(bezpieczne, rg.CENTER_POINT)]
            else:
                ruch = ['attack', wrogowie_obok.pop()]
        elif wrogowie_obok2:
            ruch = ['attack', wrogowie_obok2.pop()]
        elif bezpieczne:
            ruch = ['move', mindist(bezpieczne, najblizszy_wrog)]

        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
 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
71
72
73
74
75
76
77
78
#! /usr/bin/env python
# -*- coding: utf-8 -*-

import rg

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

class Robot:

    def act(self, game):

        global runda_numer, wybrane_pola
        if game.turn != runda_numer:
            runda_numer = game.turn
            wybrane_pola = set()

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

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

        # 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 - wrogowie_obok2 - wejscia - przyjaciele - wybrane_pola

        # funkcja znajdująca najsłabszego wroga obok z podanego zbioru (bots)
        def mindist(bots, loc):
            return min(bots, key=lambda x: rg.dist(x, loc))

        if wrogowie:
            najblizszy_wrog = mindist(wrogowie,self.location)
        else:
            najblizszy_wrog = rg.CENTER_POINT

        # działanie domyślne:
        ruch = ['guard']

        # jeżeli jesteś w punkcie wejścia, opuść go
        if self.location in wejscia:
            ruch = ruszaj(mindist(bezpieczne, 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,
        # najsłabszego wroga
        if wrogowie_obok:
            if 9*len(wrogowie_obok) >= self.hp:
                if bezpieczne:
                    ruch = ruszaj(mindist(bezpieczne, rg.CENTER_POINT))
            else:
                ruch = ['attack', minhp(wrogowie_obok)]

        if wrogowie_obok2 and self.location not in wybrane_pola:
            ruch = ['attack', wrogowie_obok2.pop()]

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