Monthly archive for February, 2011

Kulosztorm

2011-02-27 15:30

Normalnie nikt raczej nie kupuje książek czy gier z gatunków, za którymi nie przepada. Sam zresztą tego nie robię, więc potrzebne byłyby dość niecodzienne okoliczności, abym zainteresował się, dajmy na to, strzelanką typu FPP. Fakt, że Bulletstorm jest polską produkcją (na tyle, na ile to dzisiaj możliwe) nie byłby wystarczający. Potrzebny był do tego efektowny trailer, interesujący nie tyle ze względu na grafikę, co specyficzne i zastanawiające przedstawienie gameplayu w połączeniu ze stroną fabularną (tak, jest tam coś takiego ;]). Rodziło ono podejrzenie, że gra w rzeczywistości jest czymś zupełnie innym niż to widać na pierwszy rzut oka.
W każdym razie uznałem, iż warto się o tym przekonać i zobaczyć przy okazji, jak się obecnie miewa ten gatunek gier. W końcu od Quake’a 3 minęło już “trochę” czasu :) Zaopatrzyłem się więc w Bulletstorm z zamiarem zweryfikowania swoich podejrzeń. Okazały się one w stu procentach zgodne z rzeczywistością… a jednocześnie zupełnie nieprawdziwe.

Jak należy to wszystko rozumieć? Ano tak mianowicie, że Bulletstorm jest swoją własną parodią. Naśmiewając się z konwencji gatunku, jest zarazem im wierna w niemal każdym szczególe. Mamy tu bowiem wszystko, czego w porządnym FPS-ie zabraknąć nie może: mnóstwo zróżnicowanych lokacji, spory wybór broni przeznaczonych do siania zniszczenia i oczywiście hordy nieprzeliczonych wrogów, na których można nasz arsenał wypróbować. Efekty są rzecz jasna wyjątkowo krwiste i jestem przekonany, że do tego aspektu Bulletstorm już niedługo przyczepią się mainstreamowe media, jak to mają w zwyczaju (zresztą wygląda na to, że w Fox News już to zrobiono).
W tym samym czasie gra jest też wybitnie udanym pastiszem całego gatunku strzelanek. Zamiast mrocznych podziemi i krętych korytarzy, z których w każdej chwili może wyskoczyć wróg, prawie cały czas poruszamy się po otwartej przestrzeni i w pełnym słońcu, a przeciwnicy są widoczni jak na dłoni. Dialogi i przerywniki filmowe ukazują natomiast to, że ogromne giwery mają przede wszystkim reprezentować równie wielkie ego głównego bohatera, który sam w sobie jest karykaturą postaci z filmów akcji.
Najzabawniejsze jest jednak to, iż niewątpliwe innowacje, jakie Bulletstorm wprowadza do mechaniki gry FPS, są zarazem doskonałym żartem z całego kierunku rozwoju, jakim podąża ten gatunek. Jak bowiem wiadomo, jedną z głównych cech tej gry jest duża liczba interaktywnych elementów środowiska i intensywnie wykorzystywany silnik fizyczny. Na tej podstawie oparty jest system przyznający punkty za odpowiednio efektowne pozbywanie się wrogów, najlepiej z wykorzystaniem przedmiotów dostępnych wokoło. W ten sposób Bulletstorm posłusznie podąża za obowiązującym trendem (czerpiąc oczywiście z dokonań takich poprzedników jak choćby Crysis), pokazując jednocześnie, co jest jego prawdziwym celem. Bo realizm realizmem, ale wiadomo przecież, że chodzi tu o jak najlepiej wyglądającą jatkę ;)

Wydaje się, że to nie przypadek, że po dłuższej chwili rozgrywki w Bulletstorm nasuwają się nieodparte skojarzenia z filmem Kill Bill. W oświadczeniu prasowym przedstawiciel Electronic Arts użył zresztą takiego porównania i działa ono zdecydowanie na korzyść tej gry. W tym sensie moje podejrzenia się sprawdziły i dlatego mogę z czystym sumieniem polecić tę produkcję także tym, którzy na co dzień preferują nieco ambitniejsze pozycje.
Na koniec zaś wypada tylko pogratulować twórcom. Udało im się bowiem dokonać nie lada sztuki: stworzyli grę, przy której dobrze bawić będą się zarówno zwolennicy, jak i przeciwnicy jej gatunku – o ile oczywiście potrafią na nią spojrzeć z odpowiednim przymrużeniem oka. Okazuje się zatem, że dzisiaj nawet “głupie FPS-y” wykazują artystyczną głębię ;]

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

Typy wyliczeniowe w Pythonie

2011-02-24 23:52

W Pythonie jest wiele konstrukcji językowych, które wydawać się mogą dziwne dla programistów przyzwyczajonych do innych języków. O kilku z nich już pisałem, a o paru innych pewnie zdarzy mi się jeszcze napomknąć. Dzisiaj jednak chcę wspomnieć o mechanizmie dobrze znanym z wielu innych języków, którego Python nie posiada w ogóle i jakoś sobie z tym brakiem radzi. Mam tu na myśli tytułowe typy wyliczeniowe, czyli enumy.

Jeśli zazwyczaj programujemy w językach kompilowanych ze ściśle kontrolowanymi typami, taki brak może się wydawać co najmniej irytujący. Wiemy oczywiście, że podobną funkcję może pełnić zestaw odpowiednich stałych, ale odpowiedniość nie jest zwykle dokładna – w Javie czy C# konstrukcja enum tworzy na przykład dodatkowy zasięg. Lecz nie jest to jedyna i prawdopodobnie też najważniejsza różnica.
Zdaje mi się raczej, że kluczową cechą typów wyliczeniowych jest to, że definiują one tylko pewien abstrakcyjny zbiór możliwości – bez konieczności ustalania, czym dokładnie jest każda z nich. Naturalnie wiadomo, że “pod spodem” są to po prostu liczby (aczkolwiek w Javie jest trochę inaczej), ale nie musimy się zastanawiać, skąd się one wzięły. Nie musimy nawet wiedzieć, do jakiego typu liczbowego one należą, choć niekiedy (np. w C#) możemy to doprecyzować.

Ta ostatnia cecha nie jest jednak niczym niezwykłym w języku o dynamicznym typowaniu, takim jak Python. Nieokreśloność typu dotyczy tu bowiem każdej zmiennej i dlatego nie za bardzo pasuje tu koncepcja ograniczania jej wartości do jakiegoś z góry ustalonego zbioru. Technicznie rzecz ujmując, nie bardzo też da się to zrobić.
Podobnie niezbyt pasującą do Pythona koncepcją jest sterowanie logiką za pomocą zbioru wariantów wziętych “znikąd”, czyli stałych wyliczeniowych o automatycznie generowanych wartościach. Brak w tym języku instrukcji switch jest pewnie również konsekwencją odejścia od tego rodzaju abstrakcji. Założenie jest raczej takie, aby w miarę możliwości operować na surowych danych i nie dokonywać na nich żadnych pojęciowych “wygładzeń”. Ma to sens, gdyż dwa podstawowe cele abstrahowania wartości na zbiór przypadków – upraszczanie API i zwiększanie efektywności – niespecjalnie aplikują się do Pythona.

Jak to jednak bywa w prawdziwym świecie, coś w rodzaju typów wyliczeniowych przydaje się czasami mimo wszystko. Odpowiedzią jest wtedy rzeczywiście zestaw stałych, zapewne zgrupowanych pod szyldem wspólnego zasięgu klasy. Ponieważ musimy nadać im wartości, możemy zadbać o to, by bezpośrednio odnosiły się do danych, które przetwarzamy. A jeśli w skrajnym przypadku trzeba faktycznie wziąć je z powietrza, wystarczy zastosować poniższy idiom z rozpakowywaniem range‘a:

  1. class SomeEnum(object):
  2.     FOO, BAR, LULZ, KEK = range(4)

Istnieją oczywiście bardziej wyrafinowane rozwiązania, pozwalające chociażby na iterowanie po wszystkich nazwach i wartościach naszego enuma. Sądzę jednak, że podobna funkcjonalność jest przydatna raczej rzadko.

Tags: , ,
Author: Xion, posted under Programming » 1 comment

Regulacja głośności jako porażka funkcjonalna

2011-02-21 19:36

Fajnie jest używać oprogramowania, które da się łatwo dopasować do swoich upodobań – zwłaszcza, jeśli da się to zrobić łatwo, szybko i intuicyjnie, bez konieczności czytania długich manuali. Niestety, konstruowanie interfejsu udostępniającego szeroką funkcjonalność oraz wcześniej wspomniane cechy nie jest łatwe. Dla mnie dobrym przykładem tego, co może pójść źle podczas próby uszczęśliwienia użytkownika mnogością opcją i elastycznością konfiguracji jest banalny na pierwszy rzut oka mechanizm: regulacja głośności dźwięku w komputerach PC.

Problem z nim związany polega – mówiąc ogólnie – na rozbiciu zagadnienia na mniejsze fragmenty (dzięki czemu w teorii uzyskujemy większą kontrolę nad każdym z nich) bez zastanowienia się nad niepożądanymi efektami wzajemnych relacji między tymi fragmentami. Regulacja głośności w komputerach PC występuje bowiem obecnie nie w jednym, a w kilku miejscach. W skrajnych przypadkach może być ich więcej, niż da się policzyć na palcach jednej dłoni, gdy w grę wchodzi:

  • sprzętowe pokrętło głośności, będące częścią słuchawek, zestawu głośników lub kontrolek na obudowie laptopa
  • globalne, systemowe ustawienie głośności dźwięku (w Windows dostępne po kliknięciu na ikonkę w zasobniku)
  • globalne ustawienie głośności regulowane w panelu kontrolnym sterownika karty dźwiękowej
  • systemowy poziom dźwięku ustawiany dla każdego z głośników (centralnego, subwoofera, itd.)
  • systemowy poziom dźwięku ustawiany dla poszczególnych aplikacji lub komponentów systemowych
  • właściwy dla aplikacji (np. gier) poziom głośności ustawiany w samym programie

Ałć. Sporo tego, prawda? W celu usprawiedliwienia tego bałaganu można argumentować, że za prawie każdy z elementów tej listy odpowiada ktoś, jako że znajdują się one w rożnych warstwach abstrakcji. Tak też w teorii powinniśmy je traktować i nawet czerpać korzyści z tego faktu, na przykład poprzez przyciszenie efektów dźwiękowych w grze na rzecz muzyki z działającego w tle odtwarzacza.
Teoria zaś, jak wiemy, niczym nie różni się od praktyki – ale tylko w teorii. W praktyce możemy mieć zupełnie inny przypadek użycia, kiedy na przykład próbujemy rozmowy przez komunikator i stwierdzamy, że nie słyszymy drugiej strony wystarczająco głośno. Będąc rozsądnym użytkownikiem (optymistyczne założenie ;]) udamy się wpierw do ustawień programu i tam wyregulujemy głośność. Ale jeśli to nie pomoże, zapewne w drugim kroku pokręcimy odpowiednim pokrętłem lub przesuniemy globalny systemowy suwak. W konsekwencji następny chord.wav czy inny dźwięk będący częścią UI systemu może nam dostarczyć, mówiąc oględnie, zaskakująco intensywnych wrażeń słuchowych ;)

To jest właśnie przykład niepożądanej interakcji pomiędzy zachodzącymi na siebie fragmentami zagadnienia. Ale w przypadku regulacji głośności nawet pożądane interakcje nie są oczywiste. Czy łatwo jest bowiem określić, w jaki sposób X% nadrzędnego i Y% podrzędnego poziomu dźwięku przełoży się na to, co ostatecznie usłyszymy w głośnikach? Wymaga to chwili zastanowienia, a przecież mówimy tu o czynności, którą powinno się wykonywać automatycznie, bezwiednie i niemal zupełnie nieświadomie! Nie spodziewam się też, aby statystyczny użytkownik miał jakiekolwiek pojęcie o istotnym tutaj prawie Webera-Fechnera, które dodatkowo wpływa na faktycznie słyszaną intensywność wynikowego dźwięku.

Z tych wszystkich narzekań wyłania się wniosek, że regulacja głośności w PC-tach to zagadnienie o sztucznie zawyżonym poziomie komplikacji. Mogłoby ono być znacznie prostsze, gdyby nie obciążono go balastem pozornej konfigurowalności. Jako dobry przykład może służyć analogiczny mechanizm w telefonach, opierający się na dokładnie jednej sprzętowej kontrolce poziomu dźwięku (np. przyciskach) i braku zależności między poszczególnymi ustawieniami (np. multimediów, dzwonka czy rozmowy).

Tags: , ,
Author: Xion, posted under Computer Science & IT, Thoughts » 9 comments

Więcej niż operatory logiczne

2011-02-11 20:44

Bardzo przydatną cechą operatorów logicznych w wielu językach programowania jest leniwa ewaluacja (lazy evaluation). Polega ona na pominięciu obliczania tych argumentów operatorów && (and) i || (or), które i tak nie mają szans wpłynąć na ostateczny wynik. To pozwala na tworzenie warunków podobnych do poniższego:

  1. if (obj != null && obj.Valid) { obj.DoSomething(); }

Drugi człon nie wykona się tutaj w ogóle, jeśli pierwszy okaże się fałszywy, więc zmienna obj ustawiona na null nie spowoduje błędu wykonania.
Oczywiście technika ta jest doskonale znana każdemu przynajmniej średnio zaawansowanemu programiście. Okazuje się jednak, że przynajmniej jeden język idzie dalej i uogólnia ją w sposób pozwalający na stosowanie operatorów and i or do argumentów niebędących wartościami logicznymi. Jaki to język?… Python, rzecz jasna :)

W Pythonie dwa standardowe operatory ‘logiczne’ działają w oparciu o możliwość określenia specyficznie pojmowanej prawdziwości wyrażenia, którego typem niekoniecznie jest bool. Mówiąc w skrócie, każde wyrażenie niepuste i różne od zera – łącznie z odwołaniami do obiektów, liczbami, tablicami, słownikami i innymi kolekcjami – jest uważane za prawdziwe, gdy wystąpi w kontekście wymagającym rozróżnienia prawdy i fałszu.
Takim kontekstem jest chociażby warunek instrukcji if lub while – ale nie tylko. Możliwość “rzutowania na bool” (fachowo nazywanego koercją) jest też wykorzystywana w definicji operatorów and i or, które są z grubsza następujące:

  • A and B jest równe:
    • A, jeśli A jest wyrażeniem fałszywym
    • B – w przeciwnym wypadku
  • A or B jest równe:
    • A, jeśli A jest wyrażeniem prawdziwym
    • B – w przeciwnym wypadku

W pierwszej chwili mogą one wydawać się dość skomplikowane, ale nietrudno jest zauważyć, że “działają” one zgodnie z oczekiwaniami wobec argumentów typu bool i mogą być obliczane leniwie. Ponieważ jednak dzięki nim rezultatem operatora nie jest po prostu True lub False, lecz jeden z argumentów, możliwe jest stosowanie and i or także wtedy, gdy wynikiem nie ma być wcale wartość logiczna.

Wbrew pozorom ma to czasem wielki sens. Oto bardzo typowy przykład kodu, który korzysta z tej sztuczki:

  1. s = get_a_string_from_somewhere() or "N/A"
  2. print s

Co tu się dzieje?… Jeśli wywołanie funkcji zwróci prawdziwą (czyli niepustą i niezerową, więc zapewne sensowną) wartość, jest ona wyświetlona. W przeciwnym razie korzysta się z napisu zastępczego. Dzięki elastycznemu operatorowi or łatwo więc można określić pewnego rodzaju wartość domyślną (czyli fallback) dla wyrażenia.
Z kolei operator and jest często wykorzystywany do warunkowego odwoływania się do “głęboko ukrytej” wartości, wymagającej przejścia przez ciąg kilku potencjalnie pustych odwołań:

  1. return obj and obj.wrapper and obj.wrapper.result

Jeśli któreś z nich jest równe None, to taki będzie rezultat całej konstrukcji. W przeciwnym razie wynikiem będzie ostatni argument.

Istnieje szansa, że przynajmniej jeden z powyższych mechanizmów wygląda znajomo, jeśli ma się doświadczenie w językach Java lub C#. Sztuczka z operatorem or odpowiada bowiem podwójnemu znakowi zapytania (??) z C#, zaś przykład z and wprowadzonemu w Javie 7 operatorowi ?. (znak zapytania i kropka).
Zapewne też w tym momencie wszyscy przypomną sobie o starym dobrym operatorze trójargumentowym, występującym we wspomnianych dwóch językach i jeszcze wielu innych. Okazuje się, że w Pythonie jego działanie też można symulować przy użyciu operatorów and i or:

  1. res = (condition and if_true) or if_false

Nie trzeba jednak tego robić, bowiem od wersji 2.5 istnieje nieco inny składniowo odpowiednik takiej konstrukcji:

  1. res = if_true if condition else if_false

Warto zwrócić uwagę na inną niż typowa kolejność wyrażeń w tej konstrukcji.

Tags: ,
Author: Xion, posted under Programming » 1 comment

Dwa podejścia do technologii

2011-02-05 1:36

Tematy związane z technologiami komputerowymi są zaskakująco często motywem wielu zażartych sporów między ich użytkownikami. Nie jest to w sumie nowość, bo tak zwane święte wojny są niemal tak stare, jak szeroko pojęta kultura hakerska – a więc liczą sobie dziesiątki lat. Teraz jednak wspomniane technologie są wszędzie i w związku tym dyskusje między zwolennikami poszczególnych rozwiązań zataczają o wiele szersze kręgi. Wydaje mi się też, że w przeważającej części inny jest też ich charakter. Zaryzykuję też twierdzenie, że wiele z nich – w tym te najbardziej prominentne – maja u swych podstaw jedną przyczynę, której uczestnicy sporów nie są najczęściej świadomi.

Uważam mianowicie, że istnieją dwa wyraźnie różne podejścia do rozwiązań technologicznych dowolnego rodzaju. Z braku lepszych terminów określę je mianem geeka i laika. To właśnie zasadnicze różnice w postrzeganiu nowoczesnych technologii między tymi dwoma podejściami są, według mnie, główną przyczyną zażartych, często jałowych i zawsze niekonkluzywnych pojedynków na słowa.
Doprecyzowując, chodzi o priorytety: skłonność do poświęcenia pewnego zbioru cech produktów, rozwiązań i technologii na rzecz innego zbioru właściwości. Nie muszą to być zresztą własności ze sobą sprzeczne, ale zwykle każdy ze zbiorów może być dość jednoznacznie przypisany do jednego konkretnego rozwiązania z danej kategorii.

W przypadku, gdy mówimy o laiku, pożądanymi własnościami będą przede wszystkim: atrakcyjne wykonanie, bezproblemowe działanie i prostota obsługi. To będą główne kryteria, jakimi kierować się będzie osoba reprezentująca to podejście i będą one wpływały na dokonywane przez nią wybory konkretnych opcji spośród wielu możliwości.
Przykłady oczywiście znaleźć nietrudno. Jeśli ktoś reprezentuje taką postawę, to będzie używał raczej systemu Windows (lub OS X-a) niż Linuksa; przeglądał sieć raczej za pomocą Chrome’a niż Firefoksa; posługiwał się raczej iPhonem niż innym rodzajem smartphone‘a; preferował raczej laptopy lub tablety niż komputery stacjonarne – i tak dalej, żeby wspomnieć tylko o tych najbardziej rozgrzewających Internet debatach.
Dla geeka cennymi wartościami będzie natomiast zupełnie inny zestaw: elastyczność, rozszerzalność i szerokie pole manewru w zakresie dostosowywania do specyficznych potrzeb. To zaś potencjalnie koreluje z opcjami, które w wymienionych wcześniej alternatywach były określone jako mniej prawdopodobne.

Te dwie postawy nie są w żadnym wypadku wykluczające się – zwłaszcza wtedy, gdy przejawiają się wyborami w osobnych, mało związanych ze sobą dziedzinach. Sądzę jednak, że przewaga częstości występowania jednej lub drugiej mogłaby sugerować związek z jakimiś bardziej fundamentalnymi cechami charakteru czy osobowości danej osoby. Przy okazji należałoby oczywiście oszacować wpływ takich czynników jak podatność na działania marketingowe czy łatwość zmiany przyzwyczajeń.
W sumie więc mógłby to być interesujący temat do badań porównawczych, potencjalnie skutkujący gazetowymi nagłówkami w stylu: Amerykańscy naukowcy odkryli, że użytkownicy Linuksa jedzą więcej fistaszków ;-)

 


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