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:
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ł:
jeżeli nie ma nic lepszego, broń się,
z punktu wejścia idź bezpiecznie do środka;
atakuj wrogów wokół siebie, o ile to bezpieczne, jeżeli nie, idź bezpiecznie do środka;
atakuj wrogów dwa pola obok;
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:
# 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()
:
# 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:
# 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.
# 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:
# 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.
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…
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:
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.
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: