Posts from 2008

using nieco inaczej

2008-07-14 18:42

W C++ istnieje słowo kluczowe using, z którym zetknął się chyba każdy. Zwykle dotyczy to nieśmiertelnej linijki:

  1. using namespace std;

Dlatego też słowo to kojarzy się przede wszystkim z przestrzeniami nazw (namespaces), a najczęściej tylko i wyłącznie z nimi.

Jednak using ma też swoje zastosowanie – i to zapewne znacznie ciekawsze – przy definiowaniu klas. Formalnie rzecz ujmując, słówko to pozwala wprowadzić składową pochodzącą z klasy bazowej (metodę lub pole) do zasięgu klasy pochodnej. Niezbyt to ekscytujące na pierwszy rzut oka, ale faktem jest, że dzięki skorzystaniu z using możemy dokonać przynajmniej jednej koniecznej czasami operacji. Możliwa jest mianowicie zmiana kontroli dostępu do danego składnika klasy, czyli określenie, czy ma on być prywatny czy może publiczny.
Wyobraźmy sobie na przykład, że w klasie bazowej mamy jakieś funkcje niepubliczne, które chcemy udostępnić na zewnątrz w klasie pochodnej:

  1. class Base { protected: void Fun(); };
  2.  
  3. class Foo : public Base
  4. {
  5.     public:
  6.         using Base::Fun; // teraz metoda Fun() jest publiczna
  7. };

Sytuacja nie jest wcale taka hipotetyczna. Sam miałem ostatnio taki przypadek, gdy najwygodniej było po prostu wbudować w klasę bazową pewną ukrytą funkcjonalność, która była używana przez wiele z jej klas pochodnych do ich wewnętrznych celów. Jednak niektóre z tych klas musiały tę “bazową” funkcjonalność udostępnić na zewnątrz jako publiczną. I tu przydało się użycie using w sposób zaprezentowany wyżej. (Dla zainteresowanych wspomnę, że opisywana sytuacja praktycznie wystąpiła u mnie w kodzie systemu GUI :]).

Tags: ,
Author: Xion, posted under Programming » 9 comments

Triki z PowerShellem #7 – Zliczanie wierszy

2008-07-12 10:10

Gdy zawzięcie i wytrwale tworzymy jakiś koderski projekt, chciałoby się od czasu do czasu zmierzyć, ile to już pracy zdołaliśmy w nim wykonać. Pewnym sposobem na to jest policzenie ilości wszystkich linii kodu, które udało nam się już napisać.
Istnieją do tego nawet oddzielne aplikacje, niekiedy z rozbudowanym interfejsem graficznym. Jednak takie proste zadanie można by przecież zrealizować przy pomocy równie prostego narzędzia. Jakiego? Oczywiście – niewielkiego skryptu PowerShella :) Chociażby takiego jak ten:

  1. # lines.ps1
  2. # Skrypt liczący wiersze kodu w plikach podanego katalogu
  3.  
  4. # Parametr: ścieżka do katalogu z plikami
  5. param ([string]$path = ".")
  6.  
  7. # Stałe
  8. $EXTENSIONS = @("cpp", "h", "hpp")  # Rozszerzenia interesujących plików
  9.  
  10. # Zmienne
  11. $filesCount = 0
  12. $totalLinesCount = 0
  13. $emptyLinesCount = 0
  14.  
  15. # Przeszukujemy podany katalog rekurencyjnie (wszerz)
  16. $dirQueue = New-Object Collections.Queue
  17. $dirQueue.Enqueue($path)
  18. while ($dirQueue.Count -gt 0)
  19. {
  20.     # Dodajemy podkatalogi do przeszukania
  21.     $dir = [string]$dirQueue.Dequeue()
  22.     foreach ($subDir in [IO.Directory]::GetDirectories($dir))
  23.         { $dirQueue.Enqueue($subDir) }
  24.        
  25.     # Pobieramy wszystkie pliki w aktualnym katalogu pasujące do filtra
  26.     $files = New-Object Collections.ArrayList
  27.     foreach ($ext in $EXTENSIONS)
  28.         { $files.AddRange([IO.Directory]::GetFiles($dir, "*." + $ext)) }
  29.    
  30.     # Liczymy w nich wiersze
  31.     foreach ($file in $files)
  32.     {
  33.         ++$filesCount
  34.         foreach ($line in [IO.File]::ReadAllLines($file))
  35.         {
  36.             ++$totalLinesCount
  37.             if ($line.Trim().Length -eq 0) { ++$emptyLinesCount }
  38.         }
  39.     }
  40. }
  41.  
  42. # Wyświetlamy statystyki
  43. "Files: " + $filesCount | Out-Host
  44. "Total lines: " + $totalLinesCount | Out-Host
  45. "Non-empty lines: " + ($totalLinesCount - $emptyLinesCount) | Out-Host

Podajemy mu tylko ścieżkę do katalogu z naszym kodem, a on przeglądnie wszystkie pliki z ustalonymi rozszerzeniami i wyświetli statystykę z liczbą linii: wszystkich oraz niepustych. Cała ta czynność ta nie zajmuje w kodzie specjalnie dużo miejsca, gdyż najważniejsze jej elementy: pobranie podkatalogów, plików w katalogu i wierszy tekstu w pliku dają się zrobić pojedynczymi wywołaniami funkcji (odpowiednio: GetDirectories i GetFiles z System.IO.Directory oraz System.IO.File.ReadAllLines). To aczkolwiek zasługa nie samego PowerShella, tylko niezawodnej platformy .NET ;)

Skrypt ten można rzecz jasna znacznie usprawnić, pozwalając chociażby na definiowanie filtra nazw plików z kodem w jego wywołaniu czy też umożliwiając wykluczanie niektórych katalogów (np. bin, Debug, Release) w celu przyspieszenia zliczania. Jeśli ktoś miałby na to ochotę, może się dodawaniem takich feature‘ów pobawić :)

Tags:
Author: Xion, posted under Applications, Programming » Comments Off on Triki z PowerShellem #7 – Zliczanie wierszy

Podwójna okazja

2008-07-10 23:35

Dawno, dawno temu… A właściwie to prawie dokładnie rok temu z bliżej niewyjaśnionych do dzisiaj przyczyn naszła mnie ochota, aby reaktywować swoją stronę domową w postaci tego oto (dev)bloga. Od tego czasu zdążyło zmienić się sporo, ale wbrew przeciwnościom losu (uosabianym głównie przez lenistwo, które to, jak wiadomo, ma wśród wszystkich zadań zawsze najwyższy priorytet :]) blog ów nadal istnieje i nie wydaje się, żeby przynajmniej w najbliższej przyszłości miało się coś w tej kwestii zmienić. Bowiem podobno rok właśnie to taka magiczna granica, której przekroczenie udaje się tylko w mniej więcej połowie przypadków. Oczywiście to tylko statystyka (a według niej ja i mój pies mamy po trzy nogi), ale daje ona chociaż powody do umiarkowanego optymizmu – a ich nigdy dosyć.

Ten rok był też czasem “ucierania się” stylu oraz tematyki tego, co w regularnych odstępach czasu staram się tutaj publikować. Po drodze życie zweryfikowało rzecz jasna zbyt ambitne założenia i z devloga zrobiło się… no właśnie, powiedzmy, że ‘blog IT’ – żeby nie powiedzieć “nie-wiadomo-co” :) W zasadzie nie było to zresztą trudne do przewidzenia: aby systematycznie zdawać relacje z koderskiej pracy w sposób chociaż śladowo interesujący, trzeba by chyba dysponować dobą o długości co najmniej 50 godzin. Nie da się też ukryć, że formuła devloga jest też dość sztywna i zapewne dla niewielu czytelników byłaby ona na swój sposób ciekawa.
Z tych (i pewnie nie tylko z tych) powodów założona forma ewoluowała w kierunku czegoś nieco odmiennego, albo raczej trochę rozszerzonego względem pierwotnych planów. Zamiast opisywać tylko to, co ostatnio udało mi się zakodować, zacząłem też włączać różnego rodzaju programistyczne ciekawostki, dziwnostki, sugestie, porównania, krótkie omówienia, wskazówki, przemyślenia i inne tego typu “kawałki informacji”, opatrzone zwykle moją subiektywną opinią lub komentarzem. Trudno powiedzieć, co tak naprawdę je łączy – naturalnie poza technikaliami w rodzaju często przewijających się języków lub platform programistycznych. Publikując je spodziewam się jednak, że ktoś może na nie przypadkiem lub celowo natrafić i stwierdzić: “O, to całkiem interesujące…” albo “Hej, tego wcześniej nie wiedziałem!”. Być może w ten sposób zainteresuje się też jakimś tematem i zapragnie rozszerzyć swoją wiedzę w danym kierunku, co rzecz jasna przyniesie pożytek nie tylko jemu (lub jej).

Wychodzi więc na to, że mamy do czynienia z wysoce subiektywnym, nieuporządkowanym, a także stronniczym, wybiórczym i w gruncie rzeczy nie do końca godnym zaufania źródłem informacji na tematy okołokoderskie i okołoinformatyczne. Cóż to więc za dziwny twór?… Zapewne z braku lepszych określeń pozostaje posługiwanie się bardzo ogólnym słowem-wytrychem: blog.
Muszę tutaj zaznaczyć, że przyznanie się do prowadzenia czegoś, co tak właśnie można nazwać, to dla mnie coś wciąż jeszcze lekko kłopotliwego. Dlaczego? Ano dlatego, że termin ‘blog’ nadal kojarzy mi się trochę ze złotą erą różowiasto-pokemoniastych wynurzeń znudzonych dwunastolatek :) Wiem, wiem – teraz to już przeszłość lub margines, a blogowanie stało się sprawą poważną, powszechną i różnorodną. Przepastna blogosfera (paskudny neologizm, swoją drogą) powinna zatem bez problemu pomieścić także i to, czym w regularnych odstępach czasu raczę wszystkich, którzy pojawiają się na tej stronie.

A zatem minął pierwszy rok istnienia tego bloga, zaś jednocześnie liczba zamieszczonych na nim notek wraz z niniejszą osiągnęła dokładnie 256 – co jest przecież porządną i bardzo okrągłą liczbą :) Zbieżności obu tych zdarzeń komentować raczej nie będę (;P), a zamiast tego pozwolę sobie wyrazić nieśmiałe życzenie – od siebie dla siebie i wszystkich czytających – abyśmy doczekali się kolejnych rocznic i jeszcze co najmniej kilku podobnych okazji. Ze swej strony mogę zapewnić, że dołożę wszelkich możliwych starań, aby tak właśnie się stało.

Tags:
Author: Xion, posted under Thoughts, Website » 15 comments

Samowskaźnik i usuwanie siebie

2008-07-10 17:35

Bez używania mechanizmu odśmiecania istnieje zawsze ryzyko, że skończymy z odwołaniem do obiektu, który został już zniszczony. (Przy używaniu GC też istnieje taka możliwość, ale musielibyśmy niejako sami się postarać, aby wystąpiła). Dlatego zaleca się na przykład, by każdemu wywołaniu delete towarzyszyło zerowanie wskaźnika:

  1. delete p; p = NULL;

dzięki czemu można ochronić się przez problemem wiszących wskaźników (dangling pointers).

Można? No cóż, nie do końca. Istnieje jeszcze możliwość, że obiekt zniszczy się sam, używając po prostu instrukcji delete this (jest to jak najbardziej legalne). A wtedy wszelkie odnoszące się do niego odwołania będą już nieważne.
Chyba że… obiekt sam je wyzeruje. Istnieje mianowicie śmieszny trik, polegający na przekazaniu do niego w konstruktorze wskaźnika na siebie (self-pointer):

  1. Foo* pFoo = new Foo(/* parametry konstruktora */, &pFoo);

A mówiąc dokładniej: wskaźnika na ów wskaźnik (zaczyna się robić ciekawie, prawda? ;]). Musi być on oczywiście zapamiętany, a cała sztuczka polega na tym, że w destruktorze obiektu następuje jego zerowanie:

  1. class Foo
  2. {
  3.     public:
  4.         Foo(/* ... */, Foo** ppSelf = NULL) : m_ppSelf(ppSelf) { /* ... */ }
  5.         ~Foo() { if (ppSelf) *ppSelf = NULL; }
  6.     private:
  7.         Foo* m_ppSelf;
  8. };

I teraz obiekt może radośnie się usuwać kiedy tylko zechce. A tak swoją drogą, jeśli rzeczywiście może on zniknąć w każdej chwili, to może wypadałoby też, aby sprawdzał, czy przypadkiem… już nie istnieje:

  1. void Foo::SomeMethod(/* ... */)
  2. {
  3.     if (!this) return;
  4.     // ...
  5. }

Pokręcone i przekombinowane? Powiedziałbym wręcz, że szalone :) Ale do takich sztuczek trzeba się uciekać, jeśli nie korzystamy z mechanizmów odśmiecania pamięci i nie potrafimy jednoznacznie określić czasu życia obiektów i ich wzajemnej przynależności.
Albo po prostu: gdy nie mamy lepszych pomysłów. Bo przecież w ostateczności lepiej chyba sprawdzi się zwyczajna flaga logiczna z metodą typu IsAlive, jeśli obiekt rzeczywiście może popełnić nagłe samobójstwo. Najlepiej jednak, żeby takich emo-obiektów było jak najmniej ;)

Tags:
Author: Xion, posted under Programming » 2 comments

Wskaźniki i referencje jako parametry

2008-07-09 19:07

Kiedy w C++ chcemy przekazać do funkcji odwołanie do obiektu (zezwalające na jego modyfikację wewnątrz funkcji), mamy do wyboru dwie metody. Ta alternatywa to posłużenie się wskaźnikiem albo referencją:

  1. void Function(Foo* pFoo);
  2. void Function(Foo& foo)

Czy istnieje uniwersalna odpowiedź na to, którą wybrać? Chyba nie. Jeśli chodzi o wskaźnik, to za jego użyciem może przemawiać:

  • Możliwość przekazania odwołania pustego, jeśli parametr nie jest obowiązkowy. Obejmuje to oczywiście zdefiniowanie NULL jako domyślnej wartości dla tego parametru w deklaracji funkcji. Nie jest to możliwe dla referencji (w C++).
  • Fakt, że w wywołaniu funkcji bardziej widoczne jest to, iż przekazany do niej za pośrednictwem wskaźnika obiekt może się zmienić. Jeśli na przykład obiekt ten jest zmienną lokalną, to konieczne jest posłużenie się operatorem &, który daje o tym jakąś widoczną wskazówkę (nie tak jasną jak ref/out w C#, ale zawsze). Nie jestem też wielkim fanem notacji węgierskiej, lecz w przypadku wskaźników stosowanie przedrostka p wydaje mi się akurat wskazane i w tym kontekście też zwiększa czytelność wywołania funkcji, wskazując, że przekazywany obiekt (alokowany na stercie) też może się zmienić.
    1. Foo foo; Foo* pFoo = new Foo();
    2. Function (&Foo); Function (pFoo); // funkcja może zmienić obiekt

Z kolei referencje mogą się popisać innymi zaletami:

  • Nie można do nich przekazać odwołania pustego. To może być zaletą, jeśli taka sytuacja jest niepożądana. Ponadto brak konieczności sprawdzania tego, czy referencja jest “pusta”, może lekko poprawić wydajność kodu generowanego przez kompilator.
  • Składnia użycia obiektu przekazywanego przez referencję zwykle bywa bardziej przejrzysta. Jest tak zwłaszcza wtedy, gdy używamy względem niego operatorów. Na przykład kolekcja dostępna przez wskaźnik musiałaby być indeksowana przez (*pArray)[i], zaś przez referencję po prostu jako array[i].

Widać więc, że jeśli kwestia odwołania pustego nie jest dla nas istotna, to decyzja może być trudna. Ale naturalnie jest tak tylko wtedy, gdy zechcemy się nad takimi sprawami zastanawiać ;]

Tags: ,
Author: Xion, posted under Programming » 6 comments

Kolorowanie semantyczne

2008-07-07 19:04

W dzisiejszych czasach niemal każdy edytor plików tekstowych nieco bardziej zaawansowany od windowsowego Notatnika oferuje funkcję kolorowania składni (syntax highlighting) przynajmniej kilku najpopularniejszych języków programowania. W przypadku całych IDE jest to oczywiście funkcjonalność absolutnie oczywista i niezbędna, jednak coraz więcej środowisk oferuje też coś więcej. Coś, co dla odróżnienia nazywam kolorowaniem semantycznym; nie jestem bowiem pewien, czy ten mechanizm ma jakąś ogólnie przyjętą nazwę.


Kodowanie semantyczne w Visual Studio z pluginem AssistX

O co w nim chodzi? Standardowe kolorowanie potrafi między innymi odróżnić słowa kluczowe od identyfikatorów, wyróżniając zwykle te pierwsze innym stylem czcionki, zaś te drugie pozostawiając bez zmian. Wszystkie identyfikatory: nazwy zmiennych, funkcji, typów, stałych, szablonów, itp., są więc formatowane tak samo i wyglądają identycznie.
Trik kolorowania semantycznego polega właśnie na rozróżnieniu tych wszystkich kategorii identyfikatorów. Oczywiście jest to możliwe tylko wtedy, gdy IDE dysponuje bazą danych o całej strukturze projektu. Ale taka baza jest przecież coraz częściej tworzona, tyle że w innym celu: zapewnienia automatycznego uzupełniania poprzez mechanizmy w rodzaju IntelliSense w Visual Studio. Na szczęście ktoś kreatywny wpadł na pomysł, że można ją wykorzystać także w inny sposób – i chwała mu za to :)

Kolorowanie semantyczne ma przynajmniej dwie zalety. Ułatwia ono zorientowanie się w przeglądanym kodzie, zwłaszcza takim który widzimy po raz pierwszy. Ponadto zaś pozwala ono na wczesne wykrycie prostych acz uciążliwych błędów w rodzaju literówek. Sprawiają one bowiem, że dany identyfikator – jako nieznany – jest kolorowany inaczej, co pozwala na łatwiejsze dostrzeżenie pomyłki.
A wady? Jak każdy mechanizm działający w tle, kolorowanie semantyczne zjada trochę zasobów systemowych – m.in. dla niego opłaca się mieć w procesorze więcej niż jeden rdzeń. A poza tym można zwyczajnie nie lubić pstrokatego kodu i preferować bardziej jednolitą kolorystykę… O gustach się przecież nie dyskutuje, a teoretycznie kodować można i w Notatniku :)

Tags:
Author: Xion, posted under Applications, Programming » 8 comments

Co tam, panie, w WoW-ie słychać?

2008-07-06 0:27

WoWScrnShot_101807_213347.jpgJakiś tydzień temu naszła mnie ochota, by zagrać sobie znowu w “jedynie słusznego MMORPG-a”, czyli sławetny World of Warcraft. Ciężko powiedzieć, dlaczego, skoro przez prawie 10 miesięcy nie chciało mi się tykać tej gry nawet końcem małego palca… Pewnie dobrą wymówką będą wakacje – krótkie wprawdzie, ale jednak :)
Wnioski z ponownego odwiedzenia wirtualnego świata WoW są, ogólnie mówiąc, różne i ciekawe zarazem. Najważniejszą obserwacją jest chyba to, co ktoś nazwał coraz większym “zcasualowaniem się” tej gry, a ja określam mianem “syndromu zbliżającego się dodatku”. (Drugi expansion pack do tej gry wejdzie właśnie niedługo w fazę beta-testów, na które można się już zapisywać). W skrócie polega to na tym, iż wszystkie elementy gry określane zgrabnie jako endgame – czyli te najtrudniejsze i najbardziej wymagające – są teraz znacznie bardziej dostępne nie tylko dla 5 czy 10 procent hardcore‘owych graczy. Lokacje, które kiedyś wymagały dobrze skoordynowanej drużyny 10 lub 25 osób (raidy), najlepiej z jednej gildii, są teraz w zasięgu grup formowanych spontanicznie z graczy w gruncie rzeczy mniej lub bardziej przypadkowych.

WoWScrnShot_063008_101143.jpgJest to w sumie naturalna kolej rzeczy, że dodawany na bieżąco przez twórców gry content z czasem się dewaluuje, co skutkuje wprowadzeniem nowych lokacji, bossów, itp. – i tak to się zasadniczo kręci :) Wielkim problemem chyba wszystkich MMORPG-ów jest jednak schematyczność, bowiem po pewnym czasie (czasem dłuższym – zwłaszcza w przypadku tak dopracowanej gry jak WoW) stwierdzić można, że to wszystko tak naprawdę już było. Trochę inaczej, ale duże analogie są jednak widoczne.
WoWScrnShot_062908_233755.jpgRecykling pomysłów jest zauważalny zwłaszcza w przypadku walk z bossami, co jest esencją każdego raidu. Dla niewtajemniczonych wyjaśniam, że podstawowy schemat przebiegu takiej walki w klasycznym MMORPG-u typu fantasy wyróżnia trzy elementy, czyli role pełnione przez poszczególnych graczy:

  • Rola tanka, pełniona przez postać (postaci) w ciężkiej zbroi z dużą ilością życia, czyli klasy wojownika, paladyna lub podobnych. Jej (ich) zadaniem jest skupić na sobie uwagę bossa i przyjąć większość zadawanych przez niego obrażeń.
  • Rola healera, czyli jednej lub kilku osób utrzymujących przy życiu cała grupę, ze szczególnym uwzględnieniem tanków. Zwykle jest to zadanie dla postaci posługujących się jakąś formą magii.
  • Rola damage dealera (“zadawacza obrażeń”), czyli postaci mających za zadanie zrobić złemu bossowi odpowiednio dużą krzywdę :) Wachlarz sposobów jest tutaj szeroki i obejmuje zarówno magię, jak i walkę fizyczną wręcz czy strzelanie z dystansu.

Mało skomplikowane, prawda? W czystej wersji (zwanej wyjątkowo obrazowo tank & spank) byłaby to w istocie wielka nuda. Dlatego zawsze występują liczne dodatkowe atrakcje, których może być niewiele lub całkiem sporo, ale według mnie wpadają zawsze w jedną z czterech poniższych grup:

  • Niebezpieczeństwa wymagające od graczy poruszania się albo przynajmniej odpowiedniego ustawienia. Zazwyczaj oznacza to konieczność stania w odpowiednim odstępie od innych, gdyż inaczej może wydarzyć się coś niedobrego. W bardziej skomplikowanym przypadku może to być konieczna okresowa zmiana pozycji, oznaczająca czasem długi bieg, wymagająca szybkiego refleksu, uwzględnienia kwestii widoczności lub poruszania się w trzech wymiarach. Niekiedy pomyłka kończy się bardzo nieprzyjemnie :)
  • Dodatkowi wrogowie, towarzyszący głównemu bossowi, z którymi trzeba sobie jakoś poradzić. Bywa, że problemem jest głównie ich ilość; niekiedy też są znacznie większym kłopotem od samego bossa. I wreszcie: wrogowie ci mogą mieć również całkiem dużo wspólnego z samymi graczami…
  • Innowacje w dziedzinie tankowania. Niestety (a może raczej -stety) bossowie generalnie nie wykazują się inteligencją: jako byty całkowicie oskryptowane nie koncentrują się na szybkim wyeliminowaniu kluczowych postaci, czyli healerów, lecz z uporem biją w ciągle leczone postaci ciężkozbrojne. Cóż, takie życie ;) Tym niemniej bywa, że czasami i tutaj zdarzają się jakieś innowacje, co objawia się na przykład koniecznością rotacji tanków, pełnienia tej roli przez klasy zupełnie nietypowe lub nawet przez postacie nie będące graczami (non-player characters – NPCs). Wyjątkowo boss może w ogóle nie potrzebować tankowania lub zwyczajnie być na nie odporny.
  • Łamanie schematów dotyczących ról poszczególnych typów postaci w całej walce. Healerzy mogą na przykład niemal całkowicie stracić możliwość leczenia na ten czas, co zmusza ich do zadawania obrażeń. W innym przypadku część graczy zwykle zajęta tym właśnie zadaniem musi dla odmiany zająć uwagę dodatkowych przeciwników biorących udział w walce. A bywa i tak, że tradycyjne klasy postaci zupełnie przestają mieć znaczenie, choć taka oryginalność jest wielce rzadka.

WoWScrnShot_070308_223122.jpgJaki jest wniosek z tej nieco przydługiej wyliczanki? Ano taki, że w WoW-ie nowatorskie pomysły pojawiają się niestety coraz rzadziej. Spośród 17 przykładów, które mimochodem wymieniłem (tak, chodzi o te dziwne linki ;D), tylko cztery pochodzą z wydanego półtora roku temu pierwszego dodatku, The Burning Crusade. Wszystkie inne koncepty pojawiły się już w podstawowej wersji gry i są tylko użytkowane ponownie z niewielkimi zmianami.
Czy to oznacza niedaleki zmierzch jej popularności? To niewykluczone. Ale przecież zawsze pozostaje jeszcze rozgrywka typu Player vs Player. Tam tendencje są zgoła odwrotne – upraszczające, a najlepszych ich wyrazem są areny. Weźmy po prostu dwie drużyny składające się z 2, 3 lub 5 graczy i niech wygra lepsza. Mało oryginalne, ale zadziwiająco popularne :D

Tags:
Author: Xion, posted under Games, Thoughts » Comments Off on Co tam, panie, w WoW-ie słychać?
 


© 2023 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.