Skocz do zawartości
  • 👋 Witaj na MPCForum!

    Przeglądasz forum jako gość, co oznacza, że wiele świetnych funkcji jest jeszcze przed Tobą! 😎

    • Pełny dostęp do działów i ukrytych treści
    • Możliwość pisania i odpowiadania w tematach
    • System prywatnych wiadomości
    • Zbieranie reputacji i rozwijanie swojego profilu
    • Członkostwo w jednej z największych społeczności graczy

    👉 Dołączenie zajmie Ci mniej niż minutę – a zyskasz znacznie więcej!

    Zarejestruj się teraz

Jak obiektowo pisać boty


Rekomendowane odpowiedzi

Opublikowano

Witam,

zdarza mi się pisać różne boty (głównie takie, które zajmują się automatyzacją czynności na stronach internetowych). Problemem jest to, że gdy trzeba coś zmienić w kodzie to zajmuje mi to za długo przez to, że kod jest mało elastyczny. Powiedzmy, że chcę napisać bota do gry plemiona.pl. Jest to przeglądarkowa gra, w której możemy robić wiele rzeczy: tworzyć wojsko, wysyłać ataki itd.

 

Jest wiele serwerów Plemion, ale różnią się one tylko stroną docelową. Wersja polska będzie dotyczyła strony plemiona.pl a wersja USA tribalwars.us. Dodatkowo, na każdym serwerze jest wiele światów, na których można grać (takie podserwery). Na każdym świecie mogę mieć jedno konto i na tym koncie wiele wiosek.

 

Czyli mogę grać np. na polskim serwerze, świat 55 i 70. Na świecie 55 mam 6 wiosek a na świecie 70 33 wioski. Jak to teraz odpowiednio okodować?

 

Mogę zrobić tak:

Klasa Serwer(LOCATION location) - dotyczy serwera na którym będziemy grać. Do konstruktora przekazuję typ wyliczeniowy lokacji, np. LOCATION.pl. Posiada referencję do obiektu klasy World.

Klasa World(int number) - dotyczy świata na którym będziemy grać. Do konstruktora przekazujemy numer świata. Metody to np. pobranie ustawień świata

Klasa Account(string login, string password) - główna klasa konta. Do konstruktora przekazujemy login i hasło. Klasa posiada referencje do obiektu List<Village> Villages.

Klasa Village(int id) - klasa wioski. Tutaj możemy tworzyć wojska, wysyłać surowce itd.

 

No właśnie, czy to jest dobrze zrobione? Która klasa powinna odpowiadać za zalogowanie się? Klasa Serwer, czy klasa Account? Jeśli klasa Account to potrzebuję jeszcze w jakiś sposób uzyskać dostęp do pola Serwer.Location, ponieważ potrzebuję wiedzieć, czy zalogować się na plemiona.pl czy na plemiona.it. W takim razie klasa Account powinna mieć referencję do obiektu klasy Serwer? Wydawało mi się, że powinno być odwrotnie:

 

lista serwerów posiada referencję do listy światów, która posiada referencję do listy kont. Każde konto posiada referencję do listy wiosek. Każda wioska posiada referencję do listy budynków...

 

Czy może powinienem zrobić klasę Bot, która będzie zarządzała wszystkim, np:

 

Account account = new Account("login","pass");
Server server = new Server(Location.PL, 114);
Bot bot = new Bot(account, server);

class Bot() {
	
	public Server Serwer {get; private set;}
	public Account Account {get; private set;}

	
	public Bot(Server server, Account account) {
		this.Server = server;
		this.Account = account;
	}
	
	public bool AttackPlayer(Village myVillage, Village enemyVillage, Army army) {
		return myVillage.Attack(enemyVillage, army);
	}
	
	public void UpdateAllVillages() {
		foreach (var village in Account.Villages)
			village.Update();
	}
	
}

Tutaj problem będzie taki, że klasa Bot może stać się za duża (w sumie to nie wykouje ona wszystkiego, bo cała treść metody Attack jest w klasie Village). No i też zanim użyję metody Attack klasy Village muszę jeszcze wiedzieć czy gram na serwerze polskim, czy niemieckim. W takim razie klasa Village musiałaby mieć referencję do klasy Server, a w moim przypadku tę rerefencję ma klasa Bot.

 

Wysyłanie wojska powinno być zajęciem klasy Account, Village czy Bot? Teoretycznie to wioska wysyła wojsko na przeciwną wioskę. Ale czy takie stosowanie klasy Bot jako samego zarządzania jest złe? Pamiętam gdy uczyłem się programować to miałem przykład gry, gdzie atakowanie przeciwnika to było wywołanie metody Attack klasy Game.

 

Zobaczcie na to:

class Server() {
	public LOCATION Location {get; private set;}
	public World World {get; private set;}
	
	public Server(LOCATION location, int worldNumber) {
		this.Location = location;
		this.World = new World(worldNumber);
	}
}

class World() {
	public int Number {get; private set;}

        //wlasciwosci ustawienia swiata
	
	public World(int worldNumber) {
		this.Number = worldNumber;
	}

        public void UpdateWorldSettings() {
                ...
        }
}

enum LOCATION() {
	pl,
	com,
	us,
	de
}

class Bot() {
	public Server Server {get; private set;}
	public Account Account {get; private set;}
	
	public Bot(Server server, Account account) {
		this.Server = server;
		this.Account = account;
	}

}

class Account() {
	public string Nick {get; private set;}
	public string Password {get; private set;}
	public CookieContainer Cookie {get; private set;}
	public RestClient Client {get; private set;}
	
	public Account(string nick, string password) {
		this.Nick = nick
		this.Password = password;
		this.CookieContainer = new CookieContainer();
		this.RestClient = new RestClient("https://plemiona.pl");
	}
}

I w klasie Account napotykam problem. W konstruktorzę  mam 

this.RestClient = new RestClient("https://plemiona.pl");

Czyli zakładam, że gram na polskim serwerze. No a ja chciałbym mieć możliwość wybrania na jakim mam mieć. Oczywiście do konstruktora mógłbym przekazać referencję do Serwer.Location, ale wydaje mi się to mało intuicyjne. Już bardziej logiczne wydaje mi się, że to klasa Serwer jest odpowiedzialna za wysyłanie wszystkich POSTów i GETów.

 

Czy może powinno być tak, że klasa Account powinna mieć metody np. logowania się. Klasa Village metody wysyłania ataków. Klasa World metody pobierania ustawień świata. Ale skoro wszystie klasy mają wysyłać żądania do plemiona.pl to skąd w każdej klasie będę wiedział, że mam wysyłać żądania do plemiona.pl a nie plemiona.com? 

 

Mógłbym skorzystać z statycznej klasy Settings, ale nie jestem do tego przekonany. Ja chciałbym mieć wszystko uporządkowane; w jednej logicznej całości. Może powinienem do każdej klasy przekazywać w konstruktorze referencję do klasy World i Server? Macie jakieś propozycje?

 

Z tego wszystkiego wydaje mi się, że najlepszą opcją byłoby, gdyby wszystkie obiekty posiadały referencję do obiektu Bot. Wtedy klasa Village wyglądałaby tak:

class Village() {
	public int Id {get; private set;}
	public int Wood {get; private set;}
	public int Iron {get; private set;}
	public int Points {get; private set;}
	public Point Location {get; private set;}
	public Bot Bot {get; private set;}
	public Army Army {get; private set;}
	
	public Village(int id, Bot bot) {
		this.Id = id;
		this.Bot = bot;
	}
	
	//gettery settery
	
	public bool Attack(Village enemyVillage, Army army) {
		if (!this.HaveArmy(army)) return false;
		
		try {
			RestRequest attackRequest = new RestRequest("/attack", METHOD.POST);
			attackRequest.AddParameters("warriors", army.Warriors.Amount);
			attackRequest.AddParameters("archers", army.Archers.Amount);
			attackRequest.AddParameters("slaves", army.Slaves.Amount);
			attackRequest.AddParameters("InputId", myVillage.Id);
			attackRequest.AddParameters("TargetId", enemyVillage.Id);
			
			string response = this.Bot.Client.Execute(attackRequest).content;
			var json = JsonConvert.DeserializeObject<dynamic>(response);
			return json.success == "true";
		} catch {
			return false;
		}	
		
	}

	public bool HaveArmy(Army army) {
		try {
			if (!this.Army.Warriors.Amount >= army.Warriors.Amount) return false;
			if (!this.Army.Archers.Amount >= army.Archers.Amount) return false;
			if (!this.Army.Slaves.Amount >= army.Slaves.Amount) return false;
			return true;
		} catch {
			return false;
		}	
	} 
}

class Army {
	public Warrior Warriors = new Warrior();
	public Archer Archers = new Archer();
	public Slave Slaves = new Slave();
}

class Unit {
	public string ArmyName {get; private set;}
	public int MoveSpeed {get; private set;}
	public int Amount {get; private set;}
	//getters setters
}

class Warrior :Unit {
	MoveSpeed = this.Bot.Server.WarriorSpeed;
}

Tylko no właśnie. Warrior by musiało mieć referencję do Bot, by znać prędkość poruszania się. W takim razie może lepiej by jednak było użyć statycznej klasy i wtedy:

class Warrior :Unit {
        MoveSpeed = ServerSettings.WarriorSpeed;
}
Gość 67d64b62d9
Opublikowano

Abstrahując od odpowiedniego podziału na klasy to należy zwrócić uwagę na złą praktykę, którą popełniasz:
 

class Server() {
	public LOCATION Location {get; private set;}
	public World World {get; private set;}
	
	public Server(LOCATION location, int worldNumber) {
		this.Location = location;
		this.World = new World(worldNumber);
	}
}

class World() {
	public int Number {get; private set;}

        //wlasciwosci ustawienia swiata
	
	public World(int worldNumber) {
		this.Number = worldNumber;
	}

        public void UpdateWorldSettings() {
                ...
        }
}

enum LOCATION() {
	pl,
	com,
	us,
	de
}

class Bot() {
	public Server Server {get; private set;}
	public Account Account {get; private set;}
	
	public Bot(Server server, Account account) {
		this.Server = server;
		this.Account = account;
	}

}

Mianowicie listę serwerów i wersji językowych powinieneś pobierać proceduralnie

<div class="pb-lang-sec-options" style="display: none;">
    <ul>
        <li><a href="https://www.die-staemme.de"><span id="pb-flag-de" class="pb-flag pb-flag-de">die-staemme.de</span></a></li>
        <li><a href="https://www.staemme.ch"><span id="pb-flag-ch" class="pb-flag pb-flag-ch">staemme.ch</span></a></li>
        <li><a href="https://www.tribalwars.net"><span id="pb-flag-en" class="pb-flag pb-flag-en">tribalwars.net</span></a></li>
        <li><a href="https://www.tribalwars.nl"><span id="pb-flag-nl" class="pb-flag pb-flag-nl">tribalwars.nl</span></a></li>
        <li><a href="https://www.plemiona.pl"><span id="pb-flag-pl" class="pb-flag pb-flag-pl">plemiona.pl</span></a></li>
        <li><a href="https://www.tribalwars.se"><span id="pb-flag-se" class="pb-flag pb-flag-se">tribalwars.se</span></a></li>
        <li><a href="https://www.tribalwars.com.br"><span id="pb-flag-br" class="pb-flag pb-flag-br">tribalwars.com.br</span></a></li>
        <li><a href="https://www.tribalwars.com.pt"><span id="pb-flag-pt" class="pb-flag pb-flag-pt">tribalwars.com.pt</span></a></li>
        <li><a href="https://www.divokekmeny.cz"><span id="pb-flag-cz" class="pb-flag pb-flag-cz">divokekmeny.cz</span></a></li>
        <li><a href="https://www.triburile.ro"><span id="pb-flag-ro" class="pb-flag pb-flag-ro">triburile.ro</span></a></li>
        <li><a href="https://www.voyna-plemyon.ru"><span id="pb-flag-ru" class="pb-flag pb-flag-ru">voyna-plemyon.ru</span></a></li>
        <li><a href="https://www.fyletikesmaxes.gr"><span id="pb-flag-gr" class="pb-flag pb-flag-gr">fyletikesmaxes.gr</span></a></li>
        <li><a href="https://www.tribalwars.no.com"><span id="pb-flag-no" class="pb-flag pb-flag-no">tribalwars.no.com</span></a></li>
        <li><a href="https://www.divoke-kmene.sk"><span id="pb-flag-sk" class="pb-flag pb-flag-sk">divoke-kmene.sk</span></a></li>
        <li><a href="https://www.klanhaboru.hu"><span id="pb-flag-hu" class="pb-flag pb-flag-hu">klanhaboru.hu</span></a></li>
        <li><a href="https://www.tribalwars.dk"><span id="pb-flag-dk" class="pb-flag pb-flag-dk">tribalwars.dk</span></a></li>
        <li><a href="https://www.tribals.it"><span id="pb-flag-it" class="pb-flag pb-flag-it">tribals.it</span></a></li>
        <li><a href="https://www.klanlar.org"><span id="pb-flag-tr" class="pb-flag pb-flag-tr">klanlar.org</span></a></li>
        <li><a href="https://www.guerretribale.fr"><span id="pb-flag-fr" class="pb-flag pb-flag-fr">guerretribale.fr</span></a></li>
        <li><a href="https://www.guerrastribales.es"><span id="pb-flag-es" class="pb-flag pb-flag-es">guerrastribales.es</span></a></li>
        <li><a href="https://www.tribalwars.ae"><span id="pb-flag-ae" class="pb-flag pb-flag-ae">tribalwars.ae</span></a></li>
        <li><a href="https://www.tribalwars.co.uk"><span id="pb-flag-uk" class="pb-flag pb-flag-uk">tribalwars.co.uk</span></a></li>
        <li><a href="https://www.vojnaplemen.si"><span id="pb-flag-si" class="pb-flag pb-flag-si">vojnaplemen.si</span></a></li>
        <li><a href="https://www.plemena.com"><span id="pb-flag-hr" class="pb-flag pb-flag-hr">plemena.com</span></a></li>
        <li><a href="https://www.tribalwars.asia"><span id="pb-flag-th" class="pb-flag pb-flag-th">tribalwars.asia</span></a></li>
        <li><a href="https://www.tribalwars.works"><span id="pb-flag-zz" class="pb-flag pb-flag-zz">www.tribalwars.works</span></a></li>
        <li><a href="https://www.tribalwars.us"><span id="pb-flag-us" class="pb-flag pb-flag-us">tribalwars.us</span></a></li>
        <li><a href="https://www.tribalwarsmasters.net"><span id="pb-flag-ts" class="pb-flag pb-flag-ts">tribalwarsmasters.net</span></a></li>
    </ul>
</div>

ja zrobiłbym tak, że na wejściu pobierałbym listę wersji językowych i serwerów, uzupełniałbym nimi interfejs a po udanym zalogowaniu do gry przechowywałbym adres docelowy serwera gry w zmiennej globalnej i każde przyszłe żądania realizował na jego podstawie. Jeśli już to naprawisz to powinno być conajmniej prościej iść dalej.

Zarchiwizowany

Ten temat przebywa obecnie w archiwum. Dodawanie nowych odpowiedzi zostało zablokowane.

×
×
  • Dodaj nową pozycję...