Nic nie sprzyja kreatywnej pracy tak jak stan podgorączkowy 😀 . Ahh, uroki jesieni. Postanowiłem wykorzystać ten błogosławiony stan i zasiadłem do mojego dawno nie ruszanego projektu. Dzięki temu, gra tekstowa w jQuery uzyskała nową aktualizację. Poprzednie wpisy o tekstówce można znaleźć tutaj oraz tutaj.
Tym razem do gry dodałem możliwość używania przedmiotów. Dzięki temu może zajść interakcja pomiędzy graczem a światem gry. Na przykład znaleziony klucz może posłużyć do otworzenia zamkniętych drzwi.
W najnowszą wersje gry można zagrać klikając w ten link.
Jak zawsze kompletny kod jest widoczny w źródle strony. Przedstawię jedynie nowy kod. Zmiany z tej aktualizacji nie są zbyt obszerne. Największa zmiana to nowy obiekt, przechowujący dane przedmiotów. Do tego dodałem nową metodę obiektu Player – useItem. Zmieniłem też fragment kodu w metodzie showItemMenu głównego obiekty gry, odpowiadający za zdarzenie kliknięcia w opcję „użyj”.
Zacznę od kodu wywołującego funkcję użycia przedmiotu w zdarzeniu on click, metody showItemMenu.
1 2 3 4 5 6 7 8 9 |
this.playerInterface.find("#itemMenu li:nth-child(2)").on("click", function() { var itemInfo = that.player.useItem(item); that.view.printUpdate(itemInfo, "itemResult"); var itemsHere = that.rooms[that.player.currRoom].items; that.board.find("p.gameInfo").first().find(".itemSpan").remove(); if (!(itemsHere.length === 0)) { that.view.printItems(itemsHere); }; }); |
Na początku, tworzę zmienną itemInfo, do której przypisany będzie wynik metody obiektu Player – useItem. useItem przyjmuje jeden argument, nazwę klikniętego przedmiotu. Zwrócony zostanie tekst, który będzie opisem wyniku użycia przez gracza danego elementu ekwipunku. Tekst ten wyświetlany jest następnie przez metodę obiektu View – printUpdate. Dodałem nowy typ wiadomości w printUpdate – itemResult. Wiadomości tego typu będą wypisane na niebiesko i nie spowodują wyszarzenia opisu pomieszczenia. Na koniec aktualizuję listę przedmiotów wypisanych w opisie lokacji. Robię to na wypadek, jakby użycie przedmiotu miało wpływ na tę listę (np. czasem w związku z użyciem przedmiotu, postać go upuszcza).
Kolejny fragment to metoda useItem
1 2 3 |
this.useItem = function(item) { return (this.itemCollection[item].action(this.rooms, this.currRoom, this.items, inventory)); }; |
Jak widać metoda useItem nie jest specjalnie rozbudowana. Zwraca ona jedynie takiego ‚potworka’. this.itemCollection to pole obiektu Player, któremu przypisana została w argumencie kolekcja obiektów. Zawiera iba dane wszystkich przedmiotów (podobnie do kolekcji rooms, też przekazanej obiektowi Player). poprzez parametr item, odwołuję się do konkretnego (klikniętego przez gracza) ekwipunku i wywołuję jego metodę action. action to metoda która posiada każdy przedmiot i w najprostszym przypadku zwraca ona tylko tekst opisujący wynik użycia przedmiotu. W bardziej skomplikowanych przypadkach może ona też wpłynąć na inne rzeczy. Jako parametry przekazywane jej są, kolekcja pokoi, niesione w tej chwili przedmioty postaci oraz referencja do elementu html, w którym przedmioty te są wypisane.
Przejdę w końcu do kodu kolekcji przedmiotów, aby zilustrować metodę action przykładami.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
var itemCollection = { nóż : { usableInfo : "Chwytasz nóż i sięgasz do szafki, niestety nie znajdujesz niczego co można byłby użyć do zrobienia kanapki.", notUsableInfo : "Łapiesz nóż. Rękojeść jest chłodna.", action : function(rooms, room, playerItems, inventory) { if (room === "kuchnia") { return this.usableInfo; } else { return this.notUsableInfo; } } }, klucz : { usableInfo : "Otwierasz przejście używając klucza", notUsableInfo : "Nie widzisz niczego co możnabyłoby otworzyć tym kluczem", action : function(rooms, room, playerItems, inventory) { if (room === "sypialnia" && !(rooms.sypialnia.roomOpen)) { rooms[room].pd = "skrytka"; rooms[room].desc = "Jesteś w sypialni. Przez okno wpadają czerwone promienie zachodzącego słońca, przy sciane stoi wiekowe łóżko z baldachimem, zasłonięte pożółkłą firanką. Przez moment wydaje Ci sie że widzisz ruch za zwiewnym materiałem, ale to chyba tylko twoja wyobraźnia. Półka z książkami stojąca przy południowej jest odsunięta. Odsłania przejśce na południe. Możesz też iść na zachód, do kuchni."; var kluczIndex = playerItems.indexOf("klucz"); playerItems.splice(kluczIndex,1); console.log(playerItems); rooms.sypialnia.roomOpen = true; return this.usableInfo; } else { return this.notUsableInfo; } } }, lusterko : { usableInfo : "Przeglądasz sie w lusterku. Nagle w odbicu dostrzegasz upiorną twarz o przekrwionych ślepiach! Stoi zaraz za Tobą. Ze strachu upuszczasz lusterko. Rozlega się dźwiek tłuczonego szkła. Obraczasz się. W pomieszczeniu nie ma nikogo oprócz Ciebie", notUsableInfo : "Przeglądasz sie w lusterku. Przeczesujesz włosy placami. Pomimo okoliczności wyglądasz świetnie", action : function(rooms, room, playerItems, inventory) { if(room === "skrytka"){ var lusterkoIndex = playerItems.indexOf("lusterko"); playerItems.splice(lusterkoIndex,1); inventory.find("li:contains(lusterko)").remove(); rooms.skrytka.items.push("potłuczone lusterko"); return this.usableInfo; } else { return this.notUsableInfo; } } }, księga : { usableInfo : "Przeglądasz księgę. Jest napisana w obcym, wyglądającym jak arabski, języku. Grube tomiszcze posiada wiele dziwacznych ilustracji, które sprawiają wrażenie jakby były krzyżówką wykresów geometrycznych i obrazków z podręcznika anatomii. Od patrzenia na nie zaczyna kręcić Ci się w głowie.", notUsableInfo : "Kartkujesz księgę. Jest tu zbyt ciemno aby cokolwiek przeczytać", action : function(rooms, room, playerItems, inventory) { if(room === "kuchnia" && rooms.kuchnia.candleLit) { return this.usableInfo; } else { return this.notUsableInfo; } } }, "potłuczone lusterko" : { usableInfo : "", notUsableInfo : "to potłuczone lusterko do niczego już się nie nada", action : function(rooms, room, playerItems, inventory) { return this.notUsableInfo; } }, zapałki : { usableInfo : "Zapalasz świecę. Pomieszczenie rozjaśnia ciepły, żółtawy blask", notUsableInfo : "W pudełku jest tylko kilka zapałek. Po chwili zastanowienia dochodzisz do wniosku, że nie chcesz marnować ich na głupie zabawy.", action : function(rooms, room, playerItems, inventory) { if(room === "kuchnia" && rooms.kuchnia.candleLit === false){ rooms.kuchnia.candleLit = true; rooms.kuchnia.desc = "Jesteś w kuchni. Na środku pomieszczenia stoi stary drewniany stół, pokryty kurzem i pajęczynami. Na stole leżą naczynia i sztućce, jakby porzucone w trakcie posiłku. Na kredensie płonie duża biała świeca, oświetlając pomieszczenie. Migotające cienie kuchennych przedmiotów rzucają upiorne cienie na ściany i podłogę. Z kuchni są dwa wyjścia, jedno na południe i jedno wschód."; return this.usableInfo; } else { return this.notUsableInfo; } } } }; |
Jak widać, zmienna itemCollection to obiekt, którego pola, posiadają klucze równe nazwą występujących w grze przedmiotów. Wartości tych pól to kolejne obiekty. Każdy z tych obiektów posiada dwa pola oraz jedna metodą. Pola to usableInfo oraz notUsableInfo a metoda to action.
Pola zawierają łańcuchy znaków, są to opisy wyników użycia przez postać gracza danego przedmiotu. Jeden to opis zwrócony, gdy przedmiot zostanie użyty w pasującym kontekście a drugi, w pozostałych przypadkach. O metodzie action pisałem już wyżej. Widać na powyższych przykładach, że zależnie od przedmiotu może ona mieć różne efekty. Na przykład zmienić opis pomieszczenia, dodać nowe przejścia, usuwać przedmioty z inwentarza gracza itp.
Częścią wyniku działania metody action zawsze będzie zwrócenie tekstu, który poprzez obiekt Player oraz View, zostanie wyświetlony na planszy gracza.
Przemyślenia na tym etapie prac
Od czasu gdy ostatni raz pracowałem and tym projektem, minęły prawie dwa tygodnie. Po takim czasie, na własną twórczość można często spojrzeć krytycznym okiem. Dostrzegłem parę problemów, które wcześniej nie zwróciły mojej uwagi.
Pierwsza rzecz, to nazwy zmiennych. Myślałem, że dobierałem je tak, że trafnie opisywały swoją zawartość. Jednak kiedy zacząłem wgłębiać się w kod zauważyłem, że wiele zmiennych czy parametrów (np. items, inventory czy playerItems), niewiele mi mówi. Musiałem doczytać kontekst aby dojść do tego do czego dana zmienna służy. Kod tej gry, jeszcze nie jest zbyt duży, wręcz przeciwnie. Dlatego może nie wydało mi się to początkowo problemem. Jednak gdy kod zacznie się rozrastać, mało trafione nazwy mogą stać się kłopotem. Wniosek: rozsądniej nazywać zmienne.
Druga sprawa. Przestała mi się podobać architektura kodu. Pierwotnie uważałem, że wszystko znajduje się na odpowiednim miejscu. Teraz zauważam, że nie do końca tak jest. Na przykład obiekt View zajmuje się aktualizowaniem widoku gry, ale element zawierający listę przedmiotów niesionych przez gracza, edytowany jest w innym miejscu, co gorsza, nie tylko w jednym. Myślę, że logikę kodu można byłoby rozłożyć lepiej.
Obawiam się, że pomimo niewielkiej objętości, kod powoli zamienia się trochę w spagetti. Kiedy myślę o tym, jak dodać kolejne funkcję, widzę, że musiałbym wprowadzać zmiany też w wielu istniejących już elementach. Prawda jest taka, że zaczynając go pisać, nie miałem pojęcia jak będzie wyglądać kompletna gra. Do teraz nie wiem 🙂 To na pewno nie jest dobre podejście do pisania programów. Na szczęście ten projekt to ćwiczenie, jednym z głównych celi było wyłonienie słabych stron w moim kodzie. Udało się 🙂
Zauważenie tych błędów we wczesnej fazie produkcji, pozwala mi na podjęcie odpowiednich kroków. Możliwe, że zacznę pisać od początku, może zmienię obecny kod. W każdym razie na pewno będę musiał wrócić do fazy projektowania i ustalić jak ma wyglądać skończona gra.
Najlepszym sposobem na bycie na bieżąco z moimi projektami jest polubienie mojej strony na facebooku. Jest ona aktualizowana prawie codziennie 🙂