Co tam, panie, w WoW-ie słychać?
Jakiś 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.
Jest 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.
Recykling 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:
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:
Jaki 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
Triki z PowerShellem #6 – Mail z załącznikamiKiedy mamy komuś przesłać plik, możemy niekiedy użyć do tego zwykłej poczty e-mail. Nie jest to oczywiście możliwe zawsze: plik nie powinien być zbyt duży i zwykle nie może też należeć do żadnego z "niebezpiecznych" typów (np. aplikacji EXE), by serwery pocztowe mogły go przepchnąć bez narażania lub zatykania sieci.
Jeśli w naszym przypadku tak jest, to możemy wykonać całą operację na przykład przy pomocy poniższego skryptu PowerShella:
# Stałe
$SERVER = "moj.server.pl"
$LOGIN = "loginSmtp"
$PASSWORD = "hasłoSmtp"
$FROM_ADDRESS = "ja@server.pl"
# Odczytanie adresu docelowego
$recipentAddress = Read-Host -Prompt "Recipent e-mail address"
$recipent = New-Object Net.Mail.MailAddress @($recipentAddress)
# Złożenie maila
$mail = New-Object Net.Mail.MailMessage
$mail.From = New-Object Net.Mail.MailAddress @($FROM_ADDRESS)
$mail.To.Add($recipent)
$mail.Subject = (New-Object IO.FileInfo @($file)).Name
# Dodanie załącznika
$attachment = New-Object Net.Mail.Attachment @($file,
[Net.Mime.MediaTypeNames+Application]::Octet)
$cd = $attachment.ContentDisposition
$cd.CreationDate = [IO.File]::GetCreationTime($file)
$cd.ModificationDate = [IO.File]::GetLastWriteTime($file)
$cd.ReadDate = [IO.File]::GetLastAccessTime($file)
$mail.Attachments.Add($attachment)
# Łączenie z serwerem SMTP i wysłanie maila
$smtp = New-Object Net.Mail.SmtpClient @($SERVER)
$smtp.Credentials = New-Object Net.NetworkCredential @($LOGIN, $PASSWORD)
# $smtp.EnableSsl = $true # Odkomentowujemy, jeśli serwer wymaga SSL
$smtp.Send($mail)
Jak widać, nie jest to nic skomplikowanego. A jak wykorzystać go w praktyce?... Podobnie jak prezentowany wcześniej skrypt do uploadu na serwer FTP, można go wywołać komendą w rodzaju:
którą podpinamy do menu kontekstowego plików lub podmenu Wyślij do.
W ten prosty sposób możemy oszczędzić sobie uruchamiania całego klienta poczty, względnie przeglądarki z webmailem. Naturalnie sporo rzeczy - jak chociażby temat wysyłanej wiadomości czy jej treść - możemy w zaprezentowanym kawałku kodu zmienić lub uzupełnić.
Wyliczenia kwalifikowaneW C++ typy wyliczeniowe deklaruje się zwykle poprzez coś podobnego do poniższego kawałka kodu:
Jego skutkiem jest jednak to, że nazwy stałych typu (tutaj: Left, Middle i Right) będą widoczne w całej przestrzeni nazw zawierającej daną deklarację enum. Jeśli więc przypadkiem jest ona globalna, to całkiem łatwo może ona spowodować konflikt chociażby z innym typem w rodzaju:
Aby zapobiegać takim sytuacjom, w Javie i C# stałe wyliczeniowe muszą być kwalifikowane nazwą odpowiedniego typu - używa się więc Sides.Left i Keys.Left. W C++ jest jednak inaczej, gdyż blok enum sam w sobie nie tworzy zasięgu (w przeciwieństwie np. do bloków class).
Można temu częściowo zaradzić w następujący sposób:
dzięki czemu możemy z naszego enuma korzystać tak:
Różnica względem wspomnianych dwóch języków polega na tym, że nazwa typu wyliczeniowego (Side) oraz kwalifikator stałych (Sides) nie są takie same. Wydaje się jednak (przynajmniej mi się tak wydaje :]), że w tym przypadku takie rozróżnienie jest logicznie poprawne i wygląda nawet czytelniej niż gdyby obie nazwy były identyczne.
Trik ten można naturalnie opakować w makro, które umożliwi łatwe tworzenie typów wyliczeniowych z kwalifikowanymi nazwami stałych. Nie poprawi to oczywiście funkcjonalności enumów w C++, ale przynajmniej sprawi, że będą ładniej wyglądały :D
Konstruktory bezparametroweW C++ konstruktor domyślny jest generowany automatycznie, jeśli w klasie nie zostanie zdefiniowany żaden inny. W przeciwnym wypadku nie jst on tworzony, co może prowadzić do sytuacji, gdy klasa posiada jedynie takie konstruktory, które wymagają podania parametrów. Jest ona niekorzystna przynajmniej z dwóch powodów:
Kiedy jednak zmieni się postać konstruktora klasy bazowej, wówczas modyfikacja będzie musiała dotyczyć wszystkich bezpośrednich potomków tej klasy. W nowej wersji standardu C++ ma być aczkolwiek wprowadzone nowe zadanie dla słowa kluczowego using, które pozwoli na automatyczne utworzenie konstruktorów "przekaźnikowych" - takich, których jedyną rolą jest wywołanie konstuktorów bazowych z takimi samymi parametrami jak te otrzymane w konstruktorze pochodnym. Jeśli jednak przy okazji chcemy coś zmienić czy pominąć, to nie ma rady: trzeba całą tę "sztafetę" zakodować ręcznie.
Dlatego też klasy będące przede wszystkim klasami bazowymi w dziedziczeniu dobrze jest wyposażać w konstruktory bezparametrowe. Może to aczkolwiek wymagać wyróżnienia w obiekcie stanów Zainicjowany-Niezainicjowany, które powinny być sprawdzane w jego metodach i w razie potrzeby odpowiednio sygnalizowane.
Bo to zła instrukcja była…Ostatnio na forum Warsztatu wyrosła dyskusja wokół niepozornej instrukcji break. Zaczęło się od zasłyszanej opinii, że korzystanie z niej jest objawem złego stylu programowania i że jest generalnie niezalecane. Argumentem na rzecz takiego twierdzenia jest to, że break jest podobny w swoim działaniu do etykiet i "wyklętej" instrukcji goto:
W sumie wyszła z tego całkiem długa polemika, która ujawniła kilka ciekawych faktów. Po pierwsze: przejrzystość kodu jest w całkiem dużym stopniu kwestią subiektywną i o ile jednemu może przeszkadzać brak (lub niewystarczająca ilość) komentarzy, to dla drugiego o wiele ważniejsze może być używanie lub nieużywanie określonych konstrukcji językowych. Po drugie: oprócz wspomnianego już goto czy wywołanego we wspomnianym wątku breaka, do instrukcji potencjalnie niepożądanych swobodnie można zaliczyć bardzo wiele innych rozwiązań - zależnie od upodobań, poczucia 'słuszności', bieżącej fazy księżyca i pewnie mnóstwa jeszcze innych kryteriów. I tak dostało się chociażby pętlom "nieskończonym" (typu while(true) lub for(;;)), wspomniano coś o wyjątkach, a sam napomknąłem o instrukcji continue lub "przedwczesnym" return:
Teoretycznie można by wszystkie te konstrukcje usunąć w imię ideologicznej czystości programowania strukturalnego, w którym każdy blok ma dokładnie jedno wejście i wyjście. Ale tu na szczęście wkracza wniosek trzeci: języki programowania używane w praktyce nie są żadnymi doskonałymi modelami, nad którymi należy się zachwycać. To narzędzia i jako takie powinny być przede wszystkim wygodne w użytkowaniu.
Więc nie warto drzeć kotów o sens stosowania break czy nawet goto (która to instrukcja też ma swoje zastosowania) i wydawać ogólne sądy na temat ich 'nieprofesjonalności'. Bo nawet jeśli ta czy inna instrukcja rzekomo jest zła, to korzystający z niej kod nadal może być dobry, czytelny i łatwy do modyfikacji... Łącznie z refaktoringiem, który tego czy innego breaka może przecież usunąć ;P
Znaki białe i bielszeW językach programowania i opisu (np. HTML lub XML) ważną rolę odgrywają tzw. białe znaki (whitespaces). Nie są one nigdy wypisywane ani drukowane jako symbole, ale zajmują miejsce na ekranie i czasem powodują dodatkowe efekty (jak na przykład przejście do nowego wiersza w przypadku znaku końca linii - line feed). W wielu przypadkach parsery tekstowe ignorują większą ilość tego typu znaków, pozwalając dzięki temu na dowolne formatowanie kodu. Ale czasami jest wręcz odwrotnie: istnieje mianowicie ezoteryczny język programowania - zwany oryginalnie Whitespace - w którym to właśnie znaki niedrukowane (a dokładniej spacja, tabulator i znak końca wiersza) są jedynymi, które są interpretowane jako kod.
Jakie jednak znaki należy uważać za niedrukowane?... Sprawa nie jest taka prosta, gdyż, podobnie jak rozróżnienie liter wielkich i małych, zależy ona od ustawień lokalnych języka. Większość języków programowania dysponuje aczkolwiek odpowiednią metodą sprawdzenia. W C++ jest to na przykład standardowa funkcja isspace. Ponieważ jednak w grę wchodzą ustawienia kulturowe, rezultaty zależą od języka systemu operacyjnego (przynajmniej teoretycznie).
W Unicode mamy na szczęście standardowo zdefiniowane, które znaki są traktowane jako białe. Te właśnie są sprawdzane przez metody w rodzaju java.lang.Character.isWhitepace w Javie czy System.Char.IsWhiteSpace w .NET. Działają one tak samo niezależnie od kontekstu kulturowego.
A co mamy zrobić, jeśli optujemy za ANSI? Wówczas możemy ustalić sobie swój własny zbiór znaków interpretowanych jako białe. Według mnie najlepszym zestawem jest następujący:
Te znaki to kolejno: tabulator (poziomy) (\t), line feed (\n), znak powrotu karetki (carriage return, \r) i zwykła spacja. Za takim wyborem przemawia chociażby fakt, że właśnie te znaki są uważane w XML za białe i że obejmują różne standardy kodowania końca wiersza na różnych platformach. Dwa inne znaki: tabulator pionowy (0x0B) i form feed (0x0C) są jeszcze uważane za białe w C, ale stosuje się je chyba zbyt rzadko, aby trzeba się było nimi przejmować :)
Triki z PowerShellem #5 – SkrótyPisząc skrypty w PowerShellu mamy dostęp do niemal całej platformy .NET, zawierającej grubo ponad dziesięć tysięcy klas, dlatego nieczęsto będziemy potrzebowali czegoś spoza tego wielkiego bogactwa. Ale takie przypadki też istnieją. Na szczęście PSh umożliwia też dostęp do innych narzędzi niż .NET - jak choćby do starych (nie)dobrych obiektów COM.
Przy ich pomocy można zrobić całkiem sporo, zwłaszcza jeśli chodzi o sterowanie aplikacjami, które udostępniają na zewnątrz odpowiednie interfejsy (a robi tak wiele większych programów, nie tylko Microsoftu). Ciekawsze jest chyba jednak łączenie technologii: "starego" COM-a i nowego .NET-a, aby razem zrobić coś użytecznego. Oto prosty przykład:
# Parametr: ścieżka do przeszukiwanego katalogu
param([string]$path = ".")
# Szukamy plików skrótów
$allLinks = [IO.Directory]::GetFiles($path, "*.lnk", [IO.SearchOption]::AllDirectories)
# Przeglądamy listę i szukamy nieistniejących
$wsh = New-Object -ComObject WScript.Shell
$invalidLinks = New-Object Collections.ArrayList
foreach ($linkFile in $allLinks)
{
$lnk = $wsh.CreateShortcut($linkFile)
if (-not ([IO.File]::Exists($lnk.TargetPath) -or [IO.Directory]::Exists($lnk.TargetPath)))
{ $invalidLinks.Add($linkFile) | Out-Null }
}
return [string[]]$invalidLinks.ToArray()
Dziwnym trafem najprostsze przykłady użycia COM są zwykle związane z tworzeniem skrótów (plików .lnk) przy pomocy metody CreateShortcut obiektu COM o identyfikatorze WScript.Shell. Obiekt ten był dawniej używany w skryptach hosta systemu Windows (Windows Scripting Host) i, jak widać, nadal zdarza mu się być użytecznym ;-)
Co jednak robi powyższy skrypt? Otóż nie tworzy żadnych nowych skrótów, a wręcz przeciwnie: wskazuje kandydatów do usunięcia, czyli skróty pokazujące na nieistniejące pliki lub katalogi. Na wyjściu dostajemy ich zgrabną listę, którą możemy przekierować na przykład do polecenia Remove-Item.
A tak możemy sobie wyczyścić Menu Start:
I pomyśleć, że kiedyś napisałem do tego cały osobny program... :)