From b622b9393226b5da3c4a0c8c16356c3caa1ec0ec Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Tue, 13 Dec 2022 23:21:51 +0100 Subject: [PATCH 01/34] Ready for takeoff --- lib/RPGEngine.hs | 39 +++++++++++++++++++++++++++++++++++++++ lib/VoorbeeldModule.hs | 10 ---------- lib/data/Game.hs | 22 ++++++++++++++++++++++ lib/render/RenderGame.hs | 19 +++++++++++++++++++ rpg-engine.cabal | 15 ++++++++++----- src/Main.hs | 16 ++++++++++++++-- stack.yaml | 3 ++- test/RPG-Engine-Test.hs | 0 test/VoorbeeldTest.hs | 11 ----------- verslag.pdf | 0 10 files changed, 106 insertions(+), 29 deletions(-) create mode 100644 lib/RPGEngine.hs delete mode 100644 lib/VoorbeeldModule.hs create mode 100644 lib/data/Game.hs create mode 100644 lib/render/RenderGame.hs create mode 100644 test/RPG-Engine-Test.hs delete mode 100644 test/VoorbeeldTest.hs create mode 100644 verslag.pdf diff --git a/lib/RPGEngine.hs b/lib/RPGEngine.hs new file mode 100644 index 0000000..3c4812a --- /dev/null +++ b/lib/RPGEngine.hs @@ -0,0 +1,39 @@ +-- Allows to play a game using RPGEngine. +-- Includes all logic and rendering. + +module RPGEngine +( playRPGEngine +) where + +import Game +import RenderGame +import Graphics.Gloss ( + Color(..) + , black + , play + ) + +----------------------------- Constants ------------------------------ + +-- Dimensions for main window +winDimensions :: (Int, Int) +winDimensions = (1280, 720) + +-- Offsets for main window +winOffsets :: (Int, Int) +winOffsets = (0, 0) + +-- Game background color +bgColor :: Color +bgColor = black + +---------------------------------------------------------------------- + +-- This is the gameloop. +-- It can receive input and update itself. It is rendered by a renderer. +playRPGEngine :: String -> Int -> IO() +playRPGEngine title fps = do + play window bgColor fps initGame render handleInputs step + where window = initWindow title winDimensions winOffsets + step _ g = g -- TODO Do something with step? + handleInputs _ g = g -- TODO Implement inputHanlders diff --git a/lib/VoorbeeldModule.hs b/lib/VoorbeeldModule.hs deleted file mode 100644 index 63dfdf3..0000000 --- a/lib/VoorbeeldModule.hs +++ /dev/null @@ -1,10 +0,0 @@ -module VoorbeeldModule - ( hoi -- oplijsting van de publieke functies - als je deze lijst en de haakjes weglaat, wordt alles publiek - , hallo - ) where - -hoi :: String -hoi = "Hoi" - -hallo :: String -hallo = "Hallo" diff --git a/lib/data/Game.hs b/lib/data/Game.hs new file mode 100644 index 0000000..350b386 --- /dev/null +++ b/lib/data/Game.hs @@ -0,0 +1,22 @@ +-- Representation of all the game's data + +module Game +( Game(..) +, initGame -- Initialize the game +) where + +----------------------------- Constants ------------------------------ + +data Game = Game { + -- TODO Add more + playerName :: String +} + +---------------------------------------------------------------------- + +-- Initialize the game +-- TODO Expand +initGame :: Game +initGame = Game { + playerName = "Tibo" +} diff --git a/lib/render/RenderGame.hs b/lib/render/RenderGame.hs new file mode 100644 index 0000000..aac0ed2 --- /dev/null +++ b/lib/render/RenderGame.hs @@ -0,0 +1,19 @@ +-- Allows to render the played game + +module RenderGame +( initWindow -- Initialize a window to play in +, render -- Rener the game +) where + +import Game +import Graphics.Gloss + +---------------------------------------------------------------------- + +-- Render the game +render :: Game -> Picture +render _ = Blank + +-- Initialize a window to play in +initWindow :: String -> (Int, Int) -> (Int, Int) -> Display +initWindow title dims offs = InWindow title dims offs diff --git a/rpg-engine.cabal b/rpg-engine.cabal index 7d6f4ad..b068317 100644 --- a/rpg-engine.cabal +++ b/rpg-engine.cabal @@ -1,13 +1,18 @@ name: rpg-engine version: 1.0.0 -author: Author name here +author: Tibo De Peuter cabal-version: 1.12 build-type: Simple library - hs-source-dirs: lib - build-depends: base >= 4.7 && <5 - exposed-modules: VoorbeeldModule + hs-source-dirs: lib, lib/control, lib/data, lib/render + build-depends: + base >= 4.7 && <5, + gloss >= 1.11 && < 1.14, gloss-juicy >= 0.2.3 + exposed-modules: + RPGEngine, + Game, + RenderGame executable rpg-engine main-is: Main.hs @@ -17,7 +22,7 @@ executable rpg-engine test-suite rpg-engine-test type: exitcode-stdio-1.0 - main-is: VoorbeeldTest.hs + main-is: RPG-Engine-Test.hs hs-source-dirs: test default-language: Haskell2010 build-depends: base >=4.7 && <5, hspec <= 2.10.6, rpg-engine diff --git a/src/Main.hs b/src/Main.hs index 55d35ac..bb69131 100644 --- a/src/Main.hs +++ b/src/Main.hs @@ -1,4 +1,16 @@ -import VoorbeeldModule (hoi) +import RPGEngine + +----------------------------- Constants ------------------------------ + +-- Title of the game +title :: String +title = "RPG Engine" + +-- Framerate of the game +fps :: Int +fps = 60 + +---------------------------------------------------------------------- main :: IO () -main = putStrLn hoi +main = playRPGEngine title fps diff --git a/stack.yaml b/stack.yaml index 2c311ed..2539f5a 100644 --- a/stack.yaml +++ b/stack.yaml @@ -35,12 +35,13 @@ packages: # These entries can reference officially published versions as well as # forks / in-progress versions pinned to a git hash. For example: # -# extra-deps: +extra-deps: # - acme-missiles-0.3 # - git: https://github.com/commercialhaskell/stack.git # commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a # # extra-deps: [] +- gloss-juicy-0.2.3@sha256:0c3bca95237cbf91f8b3b1936a0661f1e0457acd80502276d54d6c5210f88b25,1618 # Override default flag values for local packages and extra-deps # flags: {} diff --git a/test/RPG-Engine-Test.hs b/test/RPG-Engine-Test.hs new file mode 100644 index 0000000..e69de29 diff --git a/test/VoorbeeldTest.hs b/test/VoorbeeldTest.hs deleted file mode 100644 index 2b94edb..0000000 --- a/test/VoorbeeldTest.hs +++ /dev/null @@ -1,11 +0,0 @@ -import Test.Hspec - -import VoorbeeldModule (hoi, hallo) - -main :: IO () -main = hspec $ do - it "Returns correct string for hoi" $ do - hoi `shouldBe` "Hoi" - - it "Returns correct string for hallo" $ do - hallo `shouldBe` "Hallo" diff --git a/verslag.pdf b/verslag.pdf new file mode 100644 index 0000000..e69de29 From f348a47281552082a607e45145eda69bb06154a5 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Wed, 14 Dec 2022 15:12:28 +0100 Subject: [PATCH 02/34] Added basic test --- test/RPG-Engine-Test.hs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/RPG-Engine-Test.hs b/test/RPG-Engine-Test.hs index e69de29..39131fd 100644 --- a/test/RPG-Engine-Test.hs +++ b/test/RPG-Engine-Test.hs @@ -0,0 +1,7 @@ +import Test.Hspec + +main :: IO() +main = hspec $ do + describe "Dummy category" $ do + it "Dummy test" $ do + 0 `shouldBe` 0 \ No newline at end of file From 9e5f22458c509caf5ececc45950af09833675ac9 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Wed, 14 Dec 2022 15:13:43 +0100 Subject: [PATCH 03/34] Added state #6, #7, #8 --- lib/RPGEngine.hs | 6 ++--- lib/data/Game.hs | 15 ++++++++----- lib/data/State.hs | 32 +++++++++++++++++++++++++++ lib/render/Render.hs | 47 ++++++++++++++++++++++++++++++++++++++++ lib/render/RenderGame.hs | 19 ---------------- rpg-engine.cabal | 4 ++-- 6 files changed, 93 insertions(+), 30 deletions(-) create mode 100644 lib/data/State.hs create mode 100644 lib/render/Render.hs delete mode 100644 lib/render/RenderGame.hs diff --git a/lib/RPGEngine.hs b/lib/RPGEngine.hs index 3c4812a..44047fb 100644 --- a/lib/RPGEngine.hs +++ b/lib/RPGEngine.hs @@ -6,10 +6,10 @@ module RPGEngine ) where import Game -import RenderGame +import Render import Graphics.Gloss ( Color(..) - , black + , white , play ) @@ -25,7 +25,7 @@ winOffsets = (0, 0) -- Game background color bgColor :: Color -bgColor = black +bgColor = white ---------------------------------------------------------------------- diff --git a/lib/data/Game.hs b/lib/data/Game.hs index 350b386..3a07903 100644 --- a/lib/data/Game.hs +++ b/lib/data/Game.hs @@ -2,21 +2,24 @@ module Game ( Game(..) -, initGame -- Initialize the game + +-- Initialize the game +, initGame ) where +import State + ----------------------------- Constants ------------------------------ +-- TODO Add more data Game = Game { - -- TODO Add more - playerName :: String + -- Current state of the game + state :: State } ---------------------------------------------------------------------- --- Initialize the game --- TODO Expand initGame :: Game initGame = Game { - playerName = "Tibo" + state = defaultState } diff --git a/lib/data/State.hs b/lib/data/State.hs new file mode 100644 index 0000000..1ae7a29 --- /dev/null +++ b/lib/data/State.hs @@ -0,0 +1,32 @@ +-- Describes the current state of the game, +-- e.g. Main menu, game, pause, win or lose +-- Allows to easily go to a next state and change rendering accordingly + +module State +( State(..) +-- Default state of the game, Menu +, defaultState + +-- Get the next state based on the current state +, nextState +) where + +----------------------------- Constants ------------------------------ + +-- Current state of the game. +data State = Menu + | Playing + | Pause + | Win + | Lose + +---------------------------------------------------------------------- + +defaultState :: State +defaultState = Menu + +nextState :: State -> State +nextState Menu = Playing +nextState Playing = Pause +nextState Pause = Playing +nextState _ = Menu \ No newline at end of file diff --git a/lib/render/Render.hs b/lib/render/Render.hs new file mode 100644 index 0000000..a94fed5 --- /dev/null +++ b/lib/render/Render.hs @@ -0,0 +1,47 @@ +-- Allows to render the played game + +module Render +( +-- Initialize a window to play in +initWindow + +-- Render the game +, render +) where + +import Game(Game(..)) +import State(State(..)) +import Graphics.Gloss + +---------------------------------------------------------------------- + +initWindow :: String -> (Int, Int) -> (Int, Int) -> Display +initWindow title dims offs = InWindow title dims offs + +render :: Game -> Picture +render g@Game{ state = Menu } = renderMenu g +render g@Game{ state = Playing } = renderPlaying g +render g@Game{ state = Pause } = renderPause g +render g@Game{ state = Win } = renderWin g +render g@Game{ state = Lose } = renderLose g + + +-- TODO +renderMenu :: Game -> Picture +renderMenu _ = text "Menu" + +-- TODO +renderPlaying :: Game -> Picture +renderPlaying _ = text "Playing" + +-- TODO +renderPause :: Game -> Picture +renderPause _ = text "Pause" + +-- TODO +renderWin :: Game -> Picture +renderWin _ = text "Win" + +-- TODO +renderLose :: Game -> Picture +renderLose _ = text "Lose" \ No newline at end of file diff --git a/lib/render/RenderGame.hs b/lib/render/RenderGame.hs deleted file mode 100644 index aac0ed2..0000000 --- a/lib/render/RenderGame.hs +++ /dev/null @@ -1,19 +0,0 @@ --- Allows to render the played game - -module RenderGame -( initWindow -- Initialize a window to play in -, render -- Rener the game -) where - -import Game -import Graphics.Gloss - ----------------------------------------------------------------------- - --- Render the game -render :: Game -> Picture -render _ = Blank - --- Initialize a window to play in -initWindow :: String -> (Int, Int) -> (Int, Int) -> Display -initWindow title dims offs = InWindow title dims offs diff --git a/rpg-engine.cabal b/rpg-engine.cabal index b068317..754ec51 100644 --- a/rpg-engine.cabal +++ b/rpg-engine.cabal @@ -11,8 +11,8 @@ library gloss >= 1.11 && < 1.14, gloss-juicy >= 0.2.3 exposed-modules: RPGEngine, - Game, - RenderGame + Game, State, + Render executable rpg-engine main-is: Main.hs From fa3d4c5126902c0a59fec282fb0249b8cafe7980 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Wed, 14 Dec 2022 21:20:21 +0100 Subject: [PATCH 04/34] Added VSCode config --- .gitignore | 5 ++++- .vscode/tasks.json | 54 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 .vscode/tasks.json diff --git a/.gitignore b/.gitignore index 56e48cf..e9a934f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,10 +2,13 @@ dist/ dist-newstyle/ out/ +tmp/ +extra/ .idea/ -.vscode/ .DS_Store *.exe *.dll + +stack.yaml.lock diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..6c8f9a1 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,54 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Run (+ build)", + "type": "shell", + "command": "stack run", + "problemMatcher": [], + "dependsOn": [ "Build" ], + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "Build", + "type": "shell", + "command": "stack build", + "problemMatcher": [], + "group": { + "kind": "build", + "isDefault": false + } + }, + { + "label": "Test", + "type": "shell", + "command": "stack test", + "problemMatcher": [], + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "GHCI", + "type": "shell", + "command": "stack ghci ${input:file_to_load}", + "problemMatcher": [], + "group": { + "kind": "build", + "isDefault": true + } + } + ], + "inputs": [ + { + "id": "file_to_load", + "description": "CLI arguments specifying file to load into Stack GHCI", + "default": ".", + "type": "promptString" + }, + ] +} \ No newline at end of file From cdd0c3989cb1c6475b075c75ee17f1fbb9e43d4c Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Wed, 14 Dec 2022 22:20:44 +0100 Subject: [PATCH 05/34] #15 Basic input handling --- lib/RPGEngine.hs | 12 +++++------ lib/control/Input.hs | 23 ++++++++++++++++++++ lib/control/InputHandling.hs | 41 ++++++++++++++++++++++++++++++++++++ rpg-engine.cabal | 1 + 4 files changed, 70 insertions(+), 7 deletions(-) create mode 100644 lib/control/Input.hs create mode 100644 lib/control/InputHandling.hs diff --git a/lib/RPGEngine.hs b/lib/RPGEngine.hs index 44047fb..d5ea2e7 100644 --- a/lib/RPGEngine.hs +++ b/lib/RPGEngine.hs @@ -7,6 +7,8 @@ module RPGEngine import Game import Render +import Input + import Graphics.Gloss ( Color(..) , white @@ -23,10 +25,6 @@ winDimensions = (1280, 720) winOffsets :: (Int, Int) winOffsets = (0, 0) --- Game background color -bgColor :: Color -bgColor = white - ---------------------------------------------------------------------- -- This is the gameloop. @@ -34,6 +32,6 @@ bgColor = white playRPGEngine :: String -> Int -> IO() playRPGEngine title fps = do play window bgColor fps initGame render handleInputs step - where window = initWindow title winDimensions winOffsets - step _ g = g -- TODO Do something with step? - handleInputs _ g = g -- TODO Implement inputHanlders + where window = initWindow title winDimensions winOffsets + step _ g = g -- TODO Do something with step? + handleInputs = handleAllInput diff --git a/lib/control/Input.hs b/lib/control/Input.hs new file mode 100644 index 0000000..e13b523 --- /dev/null +++ b/lib/control/Input.hs @@ -0,0 +1,23 @@ +module Input +( +-- Handle all input for RPG-Engine +handleAllInput +) where + +import Game +import State +import InputHandling + +import Graphics.Gloss.Interface.IO.Game + +---------------------------------------------------------------------- + +handleAllInput :: InputHandler Game +handleAllInput = composeInputHandlers [ + handleSpecialKey KeySpace setNextState + ] + +-- Go to the next stage of the Game +setNextState :: Game -> Game +setNextState game = game{ state = newState } + where newState = nextState $ state game diff --git a/lib/control/InputHandling.hs b/lib/control/InputHandling.hs new file mode 100644 index 0000000..86704e4 --- /dev/null +++ b/lib/control/InputHandling.hs @@ -0,0 +1,41 @@ +-- Allows to create a massive inputHandler that can handle anything +-- after you specify what you want it to do. + +module InputHandling +( InputHandler(..), +-- Compose multiple InputHandlers into one InputHandler that handles +-- all of them. +composeInputHandlers, + +handle, +handleSpecialKey +) where + +import Graphics.Gloss.Interface.IO.Game + +----------------------------- Constants ------------------------------ + +type InputHandler a = Event -> (a -> a) + +---------------------------------------------------------------------- + +composeInputHandlers :: [InputHandler a] -> InputHandler a +composeInputHandlers [] ev a = a +composeInputHandlers (ih:ihs) ev a = composeInputHandlers ihs ev (ih ev a) + +handle :: Event -> (a -> a) -> Event -> (a -> a) +handle (EventKey key _ _ _) = handleKey key +-- handle (EventMotion _) = undefined +-- handle (EventResize _) = undefined +handle _ = (\_ -> (\_ -> id)) + +handleKey :: Key -> (a -> a) -> Event -> (a -> a) +handleKey (SpecialKey key) = handleSpecialKey key +handleKey (Char _ ) = (\_ -> (\_ -> id)) +handleKey (MouseButton _ ) = (\_ -> (\_ -> id)) + +handleSpecialKey :: SpecialKey -> (a -> a) -> Event -> (a -> a) +handleSpecialKey sk1 f (EventKey (SpecialKey sk2) Down _ _) + | sk1 == sk2 = f + | otherwise = id +handleSpecialKey _ _ _ = id diff --git a/rpg-engine.cabal b/rpg-engine.cabal index 754ec51..28aea68 100644 --- a/rpg-engine.cabal +++ b/rpg-engine.cabal @@ -11,6 +11,7 @@ library gloss >= 1.11 && < 1.14, gloss-juicy >= 0.2.3 exposed-modules: RPGEngine, + Input, InputHandling, Game, State, Render From 0257bb82205db7273a5ba69c41eec0ae3589e9af Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Thu, 15 Dec 2022 10:22:30 +0100 Subject: [PATCH 06/34] #15 Input handling made modular --- lib/control/Input.hs | 8 ++++++-- lib/control/InputHandling.hs | 30 ++++++++++++++++++++++-------- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/lib/control/Input.hs b/lib/control/Input.hs index e13b523..30c6a6b 100644 --- a/lib/control/Input.hs +++ b/lib/control/Input.hs @@ -13,8 +13,12 @@ import Graphics.Gloss.Interface.IO.Game ---------------------------------------------------------------------- handleAllInput :: InputHandler Game -handleAllInput = composeInputHandlers [ - handleSpecialKey KeySpace setNextState +handleAllInput ev g@Game{ state = Playing } = handlePlayInputs ev g +handleAllInput ev g = handleAnyKey setNextState ev g + +handlePlayInputs :: InputHandler Game +handlePlayInputs = composeInputHandlers [ + handleKey (Char 'p') (\game -> game{ state = Pause }) ] -- Go to the next stage of the Game diff --git a/lib/control/InputHandling.hs b/lib/control/InputHandling.hs index 86704e4..1b4db4a 100644 --- a/lib/control/InputHandling.hs +++ b/lib/control/InputHandling.hs @@ -7,8 +7,12 @@ module InputHandling -- all of them. composeInputHandlers, -handle, -handleSpecialKey +-- Handle any event +handle, +-- Handle a event by pressing a key +handleKey, +-- Handle any key, equivalent to "Press any key to start" +handleAnyKey ) where import Graphics.Gloss.Interface.IO.Game @@ -23,19 +27,29 @@ composeInputHandlers :: [InputHandler a] -> InputHandler a composeInputHandlers [] ev a = a composeInputHandlers (ih:ihs) ev a = composeInputHandlers ihs ev (ih ev a) -handle :: Event -> (a -> a) -> Event -> (a -> a) +handle :: Event -> (a -> a) -> InputHandler a handle (EventKey key _ _ _) = handleKey key -- handle (EventMotion _) = undefined -- handle (EventResize _) = undefined handle _ = (\_ -> (\_ -> id)) -handleKey :: Key -> (a -> a) -> Event -> (a -> a) -handleKey (SpecialKey key) = handleSpecialKey key -handleKey (Char _ ) = (\_ -> (\_ -> id)) -handleKey (MouseButton _ ) = (\_ -> (\_ -> id)) +handleKey :: Key -> (a -> a) -> InputHandler a +handleKey (SpecialKey sk) = handleSpecialKey sk +handleKey (Char c ) = handleCharKey c +handleKey (MouseButton _ ) = (\_ -> (\_ -> id)) -handleSpecialKey :: SpecialKey -> (a -> a) -> Event -> (a -> a) +handleCharKey :: Char -> (a -> a) -> InputHandler a +handleCharKey c1 f (EventKey (Char c2) Down _ _) + | c1 == c2 = f + | otherwise = id +handleCharKey _ _ _ = id + +handleSpecialKey :: SpecialKey -> (a -> a) -> InputHandler a handleSpecialKey sk1 f (EventKey (SpecialKey sk2) Down _ _) | sk1 == sk2 = f | otherwise = id handleSpecialKey _ _ _ = id + +handleAnyKey :: (a -> a) -> InputHandler a +handleAnyKey f (EventKey _ Down _ _) = f +handleAnyKey _ _ = id From 4c1f25e49db049f6db15c9879baa4cae8278ea73 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Thu, 15 Dec 2022 18:08:33 +0100 Subject: [PATCH 07/34] #4 Added player and object data types --- lib/data/Internals.hs | 61 +++++++++++++++++++++++++++++++++++++++++++ lib/data/Player.hs | 15 +++++++++++ rpg-engine.cabal | 5 +++- 3 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 lib/data/Internals.hs create mode 100644 lib/data/Player.hs diff --git a/lib/data/Internals.hs b/lib/data/Internals.hs new file mode 100644 index 0000000..68f1a8d --- /dev/null +++ b/lib/data/Internals.hs @@ -0,0 +1,61 @@ +-- Represents an item in the game. + +module Internals +( Action(..) +, Object(..) +) where + +----------------------------- Constants ------------------------------ + +data Object = + Item { -- All fields are required + -- Easy way to identify items + id :: String, + -- Horizontal coördinate in the level + x :: Int, + -- Vertical coördinate in the level + y :: Int, + name :: String, + -- Short description of the object + description :: String, + -- Counts how often the object can be used by the player. Either + -- infinite or a natural number + useTimes :: Maybe Int, + -- List of conditional actions when the player is standing on this object + actions :: [Action], + -- Interpretation depends on action with this object. + value :: Maybe Int + } + | Entity { + -- Required fields + -- Easy way to identify items + id :: String, + -- Horizontal coördinate in the level + x :: Int, + -- Vertical coördinate in the level + y :: Int, + name :: String, + -- Short description of the object + description :: String, + -- List of conditional actions when the player is standing on this object + actions :: [Action], + -- Optional fields + -- The direction of the item. e.g. a door has a direction. + direction :: Maybe Direction, + -- Some entities have health points. + hp :: Maybe Int, + -- Interpretation depends on action with this object. + value :: Maybe Int + } + +data Direction = North + | East + | South + | West + deriving (Show) + +type Action = ([Condition], Event) + +type Condition = Bool + +type Event = * \ No newline at end of file diff --git a/lib/data/Player.hs b/lib/data/Player.hs new file mode 100644 index 0000000..642a6c3 --- /dev/null +++ b/lib/data/Player.hs @@ -0,0 +1,15 @@ +-- Represents a player in the game. This player can move around, pick +-- up items and interact with the world. + +module Player +( Player(..) +) where + +import Internals + +----------------------------- Constants ------------------------------ + +data Player = Player { + hp :: Int, + inventory :: [Object] +} \ No newline at end of file diff --git a/rpg-engine.cabal b/rpg-engine.cabal index 28aea68..cf4a6f8 100644 --- a/rpg-engine.cabal +++ b/rpg-engine.cabal @@ -11,8 +11,11 @@ library gloss >= 1.11 && < 1.14, gloss-juicy >= 0.2.3 exposed-modules: RPGEngine, + -- Control Input, InputHandling, - Game, State, + -- Data + Game, Internals, Player, State, + -- Render Render executable rpg-engine From 83659e69b45b84ed19e038356f5d4330200d5459 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Sat, 17 Dec 2022 23:14:04 +0100 Subject: [PATCH 08/34] #18 #14 Inital parser commit Added basic parser functionality & tests for these functionalites. Split tests in several files --- README.md | 249 ++++++++++++++++++++++++++++++++++++++- lib/control/Parse.hs | 132 +++++++++++++++++++++ rpg-engine.cabal | 13 +- stack.yaml | 1 + test/InteractionSpec.hs | 9 ++ test/ParsedToGameSpec.hs | 52 ++++++++ test/ParserSpec.hs | 52 ++++++++ test/RPG-Engine-Test.hs | 7 -- test/RPGEngineSpec.hs | 1 + 9 files changed, 504 insertions(+), 12 deletions(-) create mode 100644 lib/control/Parse.hs create mode 100644 test/InteractionSpec.hs create mode 100644 test/ParsedToGameSpec.hs create mode 100644 test/ParserSpec.hs delete mode 100644 test/RPG-Engine-Test.hs create mode 100644 test/RPGEngineSpec.hs diff --git a/README.md b/README.md index 5f92037..49869ed 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,250 @@ # RPG-Engine -Schrijf een game-engine voor een rollenspel \ No newline at end of file +Schrijf een game-engine voor een rollenspel + +https://pixel-poem.itch.io/dungeon-assetpuck +https://kyrise.itch.io/kyrises-free-16x16-rpg-icon-pack + +# RPG Engine requirements + +## Functional requirements + +- [ ] Parsing of engine configuration file to game object +- [ ] Rendering of all game objects (Levels, objects, entities, ...) +- [ ] A start menu with the possibility of selecting a level +- [ ] An end screen that shows wether or not a player won +- [ ] Support for built-in engine functions + +- [ ] Player can move around in grid-world. +- [ ] Player can pick up objects. +- [ ] Player can use objects. +- [ ] Player can loose and gain health points. +- [ ] Player can interact with other entities (fight enimies, open doors, ...). +- [ ] Player can go to the next level. + +## Not-functional requirements + +- [ ] Use Parsing. +- [ ] Use at least one (1) monad transformer. +- [ ] Write good and plenty of documentation.:w + +- [ ] Write tests (for example, using HSpec). + +--- + +# Plaats om dingen neer te jotten + +``` +Play <--- HandleInput + | + | + v +Level <--- LoadLevel <--- Parse + | + | + v +RenderLevel +``` + +- [ ] State paradigma gebruiken om van startscherm naar playscherm naar pause scherm naar endscherm te gaan + +Nuttige links: + +- https://jakewheat.github.io/intro_to_parsing/ + +``` +Jarne — Today at 22:44 +Da kan hoor en had da eerst, me gloss eeft geen goede text dus... +ListDirectory, en er was ook een fuctie takeBaseName +``` + +# RPG-Engine Documentation + +## Playing the game + +TODO + +- Input commands etc +- An example playthrough + +## Writing your own stages + +A stage description file, conventionally named `.txt` is a file with a JSON-like format. It is used to describe + everything inside a single stage of your game, including anything related to the player, the levels your game contains + and what happens in that level. It is essentially the raw representation of the initial state of a single game. + +> Note: At the moment, every game has a single stage description file. Chaining several files together is not possible yet. + +A stage description file consists of several elements. + +| Element | Short description | +| --------------- | --------------------------------------------------------------------------------------------------------- | +| `Block` | optionally surrounded by `{ ... }`, consists of several `Entry`'s, optionally separated by commas `,` | +| `Entry` | is a `Key` - `Value` pair, optionally separated by a colon `:` | +| `Key` | is a unique, predefined `String` describing `Value` | +| `Value` | is either a `Block` or a `BlockList` or a traditional value, such as `String` or `Int` | +| `BlockList` | is a number of `Block`'s, surrounded by `[ ... ]`, separated by commas, can be empty | + +We'll look at the following example to explain these concepts. + +```javascript +player: { + hp: 50, + inventory: [ + { + id: "dagger", + x: 0, + y: 0, + name: "Dagger", + description: "Basic dagger you found somewhere", + useTimes: infinite, + value: 10, + + actions: {} + } + ] +} + +levels: [ + { + layout: { + | * * * * * * + | * s . . e * + | * * * * * * + }, + items: [], + entities: [] + }, + { + layout: { + | * * * * * * * * + | * s . . . . e * + | * * * * * * * * + }, + items: [ + { + id: "key", + x: 3, + y: 1, + name: "Doorkey", + description: "Unlocks a secret door", + useTimes: 1, + value: 0, + actions: { + [not(inventoryFull())] retrieveItem(key), + [] leave() + } + } + ], + entities: [ + { + id: "door", + x: 4, + y: 1, + name: "Secret door", + description: "This secret door can only be opened with a key", + direction: left, + actions: { + [inventoryContains(key)] useItem(key), + [] leave() + } + } + ] + } +] +``` + +This stage description file consists of a single `Block`. A stage description file always does. This top level `Block` + contains two `Value`s `player` and `levels`, not separated by commas. + +`player` describes a `Block` that represents the player of the game. Its `Entry`s are `hp` (a traditional value) and + `inventory` (a `BlockList` of several other `Block`s). They are both separated by commas this time. It is possible for + the inventory to be an empty list `[]`. + +`levels` is a `BlockList` that contains all the information to construct your game. + +### `layout` syntax + +If `Key` has the value `layout`, `Value` is none of the types discussed so far. Instead `Layout` is specifically made + to describe the layout of a level. This object is surrounded by `{ ... }` and consists of multiple lines, starting with + a vertical line `|` and several characters of the following: + +- `x` is an empty tile a.k.a. void. +- `.` is a tile walkable by the player. +- `*` is a tile not walkable by the player. +- `s` is the starting position of the player. +- `e` is the exit. + +All characters are interspersed with spaces. + +### `actions` syntax + +If `Key` has the value `actions`, the following changes are important for its `Value`, which in this case is a `Block` + with zero or more `Entry`s like so: + +- `Key` has type `ConditionList`. + + A `ConditionList` consists of several `Condition`s, surrounded by `[ ... ]`, separated by commas. A `ConditionList` + can be empty. If so, the conditional is always fulfilled. + + A `Condition` is one of the following: + + - `inventoryFull()`: the players inventory is full. + - `inventoryContains(objectId)`: the players inventory contains an object with id `objectId`. + - `not(condition)`: logical negation of `condition`. + +- `Value` is an `Action`. + + An `Action` is one of the following: + + - `leave()` + - `retrieveItem(objectId)` + - `useItem(objectId)` + - `decreaseHp(entityId, objectId)` + - `increasePlayerHp(objectId)` + +### Back to the example + +If we look at the example, all the objects are + +``` +>Block< + Entry = Key ('player') + >Block< + Entry = Key ('hp') + Value (50) + Entry = Key ('inventory') + >BlockList< + length = 1 + Block + Entry = Key ('id') + Value ("dagger") + ... + Entry = Key ('actions') + empty Block + Entry = Key ('levels') + >BlockList< + length = 2 + >Block< + Entry = Key ('layout') + Layout + + Entry = Key ('items') + empty BlockList + Entry = Key ('entities') + empty BlockList + >Block< + Entry = Key ('layout') + Layout + + Entry = Key ('items') + >BlockList< + length = 1 + >Block< + Entry = Key ('id') + Value ("key") + ... + Entry = Key ('actions') + >Block< + Entry = >ConditionList< + Action ('retrieveItem(key)') + length = 1 + Condition ('not(inventoryFull())')) + Entry = empty ConditionList + Action ('leave()') + Entry = Key ('entities') + >BlockList< + length = 1 + >Block< + Entry = Key ('id') + Value ("door") + ... + Entry = Key ('actions') + >Block< + Entry = >ConditionList< + Action ('useItem(key)') + length = 1 + Condition ('inventoryContains(key)') + Entry = empty ConditionList + Action ('leave()') +``` \ No newline at end of file diff --git a/lib/control/Parse.hs b/lib/control/Parse.hs new file mode 100644 index 0000000..376f29b --- /dev/null +++ b/lib/control/Parse.hs @@ -0,0 +1,132 @@ +module Parse where + +-- TODO Maak wrapper module +-- TODO This module should not be used by anything except for wrapper module and tests + +import Game +import Player +import Text.Parsec +import Text.Parsec.Char +import Text.Parsec.String +import Data.List +import Data.Maybe +import Text.Parsec.Error (Message(UnExpect)) + +-- TODO parseFromFile gebruiken + +-- Parser type +-- type Parser = Parsec String () + +-- A wrapper, which takes a parser and some input and returns a +-- parsed output. +parseWith :: Parser a -> String -> Either ParseError a +parseWith parser = parse parser "" + +ignoreWS :: Parser a -> Parser a +ignoreWS parser = spaces >> parser + +-- Also return anything that has not yet been parsed +parseWithRest :: Parser a -> String -> Either ParseError (a, String) +-- fmap (,) over Parser monad and apply to rest +parseWithRest parser = parse ((,) <$> parser <*> rest) "" + where rest = manyTill anyToken eof + +parseToGame :: Game +parseToGame = undefined + +-- Info in between brackets, '(..)', '[..]', '{..}' or '<..>' +data Brackets a = Brackets a + deriving (Eq, Show) + +parseToPlayer :: Player +parseToPlayer = undefined + +-- any words separated by whitespace +parseWord :: Parser String +parseWord = do many alphaNum + +-- TODO Expand to allow different kinds of brackets, also see Brackets data type. +-- TODO Check if brackets match order. +-- TODO Allow nested brackets. +brackets :: Parser (Brackets String) +brackets = do + ignoreWS $ char '(' + e <- ignoreWS $ many1 alphaNum + ignoreWS $ char ')' + return $ Brackets e + +------------------------ + +data Value = String String + | Integer Int + | Infinite + deriving (Show, Eq) + +-- See documentation for more details, only a short description is +--provided here. +data StructureElement = Block [StructureElement] + | Entry String StructureElement-- Key + Value + | Regular Value -- Regular value, Integer or String or Infinite + | ConditionList [StructureElement] + -- TODO + | Condition -- inventoryFull() etc. + -- TODO + | Action -- leave(), useItem(objectId) etc. + deriving (Show, Eq) + +-- TODO Add ConditionList and Action +structureElement :: Parser StructureElement +structureElement = choice [block, regular] + +-- A Block is a list of Entry s +block :: Parser StructureElement +block = do + ignoreWS $ char '{' + list <- ignoreWS $ many1 entry + ignoreWS $ char '}' + return $ Block list + +entry :: Parser StructureElement +entry = do + key <- ignoreWS $ many1 alphaNum + ignoreWS $ char ':' + value <- ignoreWS structureElement -- TODO Is this the correct one to use? + return $ Entry key value + +regular :: Parser StructureElement +regular = do + value <- ignoreWS $ choice [integer, valueString, infinite] + return $ Regular value + +integer :: Parser Value +integer = do + value <- ignoreWS $ many1 digit + return $ Integer (read value :: Int) + +valueString :: Parser Value +valueString = do + ignoreWS $ char '"' + value <- ignoreWS $ many1 (noneOf ['"']) + ignoreWS $ char '"' + return $ String value + +infinite :: Parser Value +infinite = do + ignoreWS $ string "infinite" + notFollowedBy alphaNum + return Infinite + +conditionList :: Parser StructureElement +conditionList = do + ignoreWS $ char '[' + list <- ignoreWS $ many1 condition + ignoreWS $ char ']' + return $ ConditionList list + +-- TODO +condition :: Parser StructureElement +condition = undefined + +-- TODO YOU ARE HERE +action :: Parser StructureElement +action = undefined diff --git a/rpg-engine.cabal b/rpg-engine.cabal index cf4a6f8..3775de7 100644 --- a/rpg-engine.cabal +++ b/rpg-engine.cabal @@ -8,11 +8,12 @@ library hs-source-dirs: lib, lib/control, lib/data, lib/render build-depends: base >= 4.7 && <5, - gloss >= 1.11 && < 1.14, gloss-juicy >= 0.2.3 + gloss >= 1.11 && < 1.14, gloss-juicy >= 0.2.3, + parsec >= 3.1.15.1 exposed-modules: RPGEngine, -- Control - Input, InputHandling, + Input, InputHandling, Parse, -- Data Game, Internals, Player, State, -- Render @@ -26,7 +27,11 @@ executable rpg-engine test-suite rpg-engine-test type: exitcode-stdio-1.0 - main-is: RPG-Engine-Test.hs + main-is: RPGEngineSpec.hs hs-source-dirs: test default-language: Haskell2010 - build-depends: base >=4.7 && <5, hspec <= 2.10.6, rpg-engine + build-depends: base >=4.7 && <5, hspec <= 2.10.6, hspec-discover, rpg-engine + other-modules: + InteractionSpec, + -- Parsing + ParsedToGameSpec, ParserSpec diff --git a/stack.yaml b/stack.yaml index 2539f5a..344fbba 100644 --- a/stack.yaml +++ b/stack.yaml @@ -42,6 +42,7 @@ extra-deps: # # extra-deps: [] - gloss-juicy-0.2.3@sha256:0c3bca95237cbf91f8b3b1936a0661f1e0457acd80502276d54d6c5210f88b25,1618 +- parsec-3.1.15.1@sha256:8c7a36aaadff12a38817fc3c4ff6c87e3352cffd1a58df640de7ed7a97ad8fa3,4601 # Override default flag values for local packages and extra-deps # flags: {} diff --git a/test/InteractionSpec.hs b/test/InteractionSpec.hs new file mode 100644 index 0000000..63801af --- /dev/null +++ b/test/InteractionSpec.hs @@ -0,0 +1,9 @@ +module InteractionSpec where + +import Test.Hspec + +spec :: Spec +spec = do + describe "Player with Inventory" $ do + it "TODO: Simple test" $ do + pending \ No newline at end of file diff --git a/test/ParsedToGameSpec.hs b/test/ParsedToGameSpec.hs new file mode 100644 index 0000000..b1d3930 --- /dev/null +++ b/test/ParsedToGameSpec.hs @@ -0,0 +1,52 @@ +module ParsedToGameSpec where + +import Test.Hspec +import Parse + +spec :: Spec +spec = do + describe "Game" $ do + it "TODO: Simple game" $ do + pending + it "TODO: More complex game" $ do + pending + it "TODO: Game with multiple levels" $ do + pending + + describe "Player" $ do + it "TODO: Simple player" $ do + pending + + describe "Inventory" $ do + it "TODO: Empty inventory" $ do + pending + it "TODO: Singleton inventory" $ do + pending + it "TODO: Filled inventory" $ do + pending + + describe "Items" $ do + it "TODO: Simple item" $ do + pending + -- Check id + -- Check x + -- Check y + -- Check name + -- Check description + -- Check useTimes + -- Check value + -- Check actions + + describe "Actions" $ do + it "TODO: Simple action" $ do + pending + + describe "Entities" $ do + it "TODO: Simple entity" $ do + pending + + describe "Level" $ do + it "TODO: Simple layout" $ do + pending + it "TODO: Complex layout" $ do + pending \ No newline at end of file diff --git a/test/ParserSpec.hs b/test/ParserSpec.hs new file mode 100644 index 0000000..b5db0cc --- /dev/null +++ b/test/ParserSpec.hs @@ -0,0 +1,52 @@ +module ParserSpec where + +import Test.Hspec +import Parse +import Data.Either + +spec :: Spec +spec = do + describe "Basics of entries" $ do + it "can parse integers" $ do + let correct = Right $ Regular $ Integer 1 + correct `shouldBe` parseWith regular "1" + it "can parse string" $ do + let input = "dit is een string" + correct = Right $ Regular $ String input + correct `shouldBe` parseWith regular ("\"" ++ input ++ "\"") + it "can parse infinite" $ do + let correct = Right $ Regular Infinite + correct `shouldBe` parseWith regular "infinite" + + let wrong = Right $ Regular Infinite + wrong `shouldNotBe` parseWith regular "infinitee" + + it "can parse entries" $ do + let input = "id : \"dagger\"" + correct = Right $ Entry "id" $ Regular $ String "dagger" + correct `shouldBe` parseWith entry input + + let input = "x: 0" + correct = Right $ Entry "x" $ Regular $ Integer 0 + correct `shouldBe` parseWith entry input + + let input = "useTimes: infinite" + correct = Right $ Entry "useTimes" $ Regular Infinite + correct `shouldBe` parseWith entry input + + describe "Special kinds" $ do + it "can parse actions" $ do + let input = "actions: {}" + correct = Right $ Entry "actions" $ Regular Infinite -- TODO Change this + correct `shouldBe` parseWith action input + + it "can parse conditions" $ do + pending + + it "can parse layouts" $ do + pending + + describe "Lists and blocks" $ do + it "can parse entities" $ do + pending + diff --git a/test/RPG-Engine-Test.hs b/test/RPG-Engine-Test.hs deleted file mode 100644 index 39131fd..0000000 --- a/test/RPG-Engine-Test.hs +++ /dev/null @@ -1,7 +0,0 @@ -import Test.Hspec - -main :: IO() -main = hspec $ do - describe "Dummy category" $ do - it "Dummy test" $ do - 0 `shouldBe` 0 \ No newline at end of file diff --git a/test/RPGEngineSpec.hs b/test/RPGEngineSpec.hs new file mode 100644 index 0000000..52ef578 --- /dev/null +++ b/test/RPGEngineSpec.hs @@ -0,0 +1 @@ +{-# OPTIONS_GHC -F -pgmF hspec-discover #-} \ No newline at end of file From 3b0de65de145dddd5ea13f76b73ab76107545b45 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Mon, 19 Dec 2022 22:54:42 +0100 Subject: [PATCH 09/34] #18 & massive structure overhaul --- lib/RPGEngine.hs | 8 +- lib/{control => RPGEngine}/Input.hs | 19 ++- .../Internals/Data}/Game.hs | 10 +- .../Internals/Data}/Internals.hs | 32 +++- .../Internals/Data}/Player.hs | 4 +- .../Internals/Data}/State.hs | 14 +- .../Internals/Input.hs} | 37 ++-- lib/RPGEngine/Internals/Parse.hs | 20 +++ .../Internals/Parse/StructureElement.hs | 161 ++++++++++++++++++ lib/RPGEngine/Parse.hs | 8 + lib/{render => RPGEngine}/Render.hs | 27 +-- lib/control/Parse.hs | 132 -------------- rpg-engine.cabal | 22 ++- stack.yaml | 2 + test/ParsedToGameSpec.hs | 2 +- test/ParserSpec.hs | 120 ++++++++++--- 16 files changed, 397 insertions(+), 221 deletions(-) rename lib/{control => RPGEngine}/Input.hs (69%) rename lib/{data => RPGEngine/Internals/Data}/Game.hs (79%) rename lib/{data => RPGEngine/Internals/Data}/Internals.hs (69%) rename lib/{data => RPGEngine/Internals/Data}/Player.hs (77%) rename lib/{data => RPGEngine/Internals/Data}/State.hs (84%) rename lib/{control/InputHandling.hs => RPGEngine/Internals/Input.hs} (84%) create mode 100644 lib/RPGEngine/Internals/Parse.hs create mode 100644 lib/RPGEngine/Internals/Parse/StructureElement.hs create mode 100644 lib/RPGEngine/Parse.hs rename lib/{render => RPGEngine}/Render.hs (63%) delete mode 100644 lib/control/Parse.hs diff --git a/lib/RPGEngine.hs b/lib/RPGEngine.hs index d5ea2e7..1ab1fa5 100644 --- a/lib/RPGEngine.hs +++ b/lib/RPGEngine.hs @@ -5,9 +5,9 @@ module RPGEngine ( playRPGEngine ) where -import Game -import Render -import Input +import RPGEngine.Internals.Data.Game +import RPGEngine.Render +import RPGEngine.Input import Graphics.Gloss ( Color(..) @@ -33,5 +33,5 @@ playRPGEngine :: String -> Int -> IO() playRPGEngine title fps = do play window bgColor fps initGame render handleInputs step where window = initWindow title winDimensions winOffsets - step _ g = g -- TODO Do something with step? + step _ g = g -- TODO Do something with step? Check health etc. handleInputs = handleAllInput diff --git a/lib/control/Input.hs b/lib/RPGEngine/Input.hs similarity index 69% rename from lib/control/Input.hs rename to lib/RPGEngine/Input.hs index 30c6a6b..337a3bc 100644 --- a/lib/control/Input.hs +++ b/lib/RPGEngine/Input.hs @@ -1,21 +1,25 @@ -module Input -( --- Handle all input for RPG-Engine -handleAllInput +-- Input for RPG-Engine + +module RPGEngine.Input +( handleAllInput ) where -import Game -import State -import InputHandling +import RPGEngine.Internals.Data.Game +import RPGEngine.Internals.Data.State +import RPGEngine.Internals.Input import Graphics.Gloss.Interface.IO.Game ---------------------------------------------------------------------- +-- Handle all input for RPG-Engine handleAllInput :: InputHandler Game handleAllInput ev g@Game{ state = Playing } = handlePlayInputs ev g handleAllInput ev g = handleAnyKey setNextState ev g +---------------------------------------------------------------------- + +-- Input for 'Playing' state handlePlayInputs :: InputHandler Game handlePlayInputs = composeInputHandlers [ handleKey (Char 'p') (\game -> game{ state = Pause }) @@ -25,3 +29,4 @@ handlePlayInputs = composeInputHandlers [ setNextState :: Game -> Game setNextState game = game{ state = newState } where newState = nextState $ state game + diff --git a/lib/data/Game.hs b/lib/RPGEngine/Internals/Data/Game.hs similarity index 79% rename from lib/data/Game.hs rename to lib/RPGEngine/Internals/Data/Game.hs index 3a07903..7aa8ef4 100644 --- a/lib/data/Game.hs +++ b/lib/RPGEngine/Internals/Data/Game.hs @@ -1,13 +1,12 @@ -- Representation of all the game's data -module Game -( Game(..) +module RPGEngine.Internals.Data.Game +( Game(..), --- Initialize the game -, initGame +initGame ) where -import State +import RPGEngine.Internals.Data.State ----------------------------- Constants ------------------------------ @@ -19,6 +18,7 @@ data Game = Game { ---------------------------------------------------------------------- +-- Initialize the game initGame :: Game initGame = Game { state = defaultState diff --git a/lib/data/Internals.hs b/lib/RPGEngine/Internals/Data/Internals.hs similarity index 69% rename from lib/data/Internals.hs rename to lib/RPGEngine/Internals/Data/Internals.hs index 68f1a8d..476772b 100644 --- a/lib/data/Internals.hs +++ b/lib/RPGEngine/Internals/Data/Internals.hs @@ -1,16 +1,22 @@ -- Represents an item in the game. -module Internals +module RPGEngine.Internals.Data.Internals ( Action(..) +, Condition(..) , Object(..) +, EntityId +, ItemId ) where ----------------------------- Constants ------------------------------ +type EntityId = String +type ItemId = String + data Object = Item { -- All fields are required -- Easy way to identify items - id :: String, + id :: ItemId, -- Horizontal coördinate in the level x :: Int, -- Vertical coördinate in the level @@ -22,14 +28,14 @@ data Object = -- infinite or a natural number useTimes :: Maybe Int, -- List of conditional actions when the player is standing on this object - actions :: [Action], + actions :: [([Condition], Action)], -- Interpretation depends on action with this object. value :: Maybe Int } | Entity { -- Required fields -- Easy way to identify items - id :: String, + id :: EntityId, -- Horizontal coördinate in the level x :: Int, -- Vertical coördinate in the level @@ -38,7 +44,7 @@ data Object = -- Short description of the object description :: String, -- List of conditional actions when the player is standing on this object - actions :: [Action], + actions :: [([Condition], Action)], -- Optional fields -- The direction of the item. e.g. a door has a direction. direction :: Maybe Direction, @@ -54,8 +60,18 @@ data Direction = North | West deriving (Show) -type Action = ([Condition], Event) +data Action = Leave + | RetrieveItem ItemId + | UseItem ItemId + | DecreaseHp EntityId ItemId + | IncreasePlayerHp ItemId + | Nothing + deriving (Show, Eq) -type Condition = Bool +data Condition = InventoryFull + | InventoryContains ItemId + | Not Condition + | AlwaysFalse + deriving (Show, Eq) -type Event = * \ No newline at end of file +---------------------------------------------------------------------- diff --git a/lib/data/Player.hs b/lib/RPGEngine/Internals/Data/Player.hs similarity index 77% rename from lib/data/Player.hs rename to lib/RPGEngine/Internals/Data/Player.hs index 642a6c3..c325df0 100644 --- a/lib/data/Player.hs +++ b/lib/RPGEngine/Internals/Data/Player.hs @@ -1,11 +1,11 @@ -- Represents a player in the game. This player can move around, pick -- up items and interact with the world. -module Player +module RPGEngine.Internals.Data.Player ( Player(..) ) where -import Internals +import RPGEngine.Internals.Data.Internals ----------------------------- Constants ------------------------------ diff --git a/lib/data/State.hs b/lib/RPGEngine/Internals/Data/State.hs similarity index 84% rename from lib/data/State.hs rename to lib/RPGEngine/Internals/Data/State.hs index 1ae7a29..b567517 100644 --- a/lib/data/State.hs +++ b/lib/RPGEngine/Internals/Data/State.hs @@ -2,12 +2,10 @@ -- e.g. Main menu, game, pause, win or lose -- Allows to easily go to a next state and change rendering accordingly -module State +module RPGEngine.Internals.Data.State ( State(..) --- Default state of the game, Menu , defaultState --- Get the next state based on the current state , nextState ) where @@ -20,13 +18,17 @@ data State = Menu | Win | Lose ----------------------------------------------------------------------- - +-- Default state of the game, Menu defaultState :: State defaultState = Menu +---------------------------------------------------------------------- + +-- Get the next state based on the current state nextState :: State -> State nextState Menu = Playing nextState Playing = Pause nextState Pause = Playing -nextState _ = Menu \ No newline at end of file +nextState _ = Menu + +---------------------------------------------------------------------- diff --git a/lib/control/InputHandling.hs b/lib/RPGEngine/Internals/Input.hs similarity index 84% rename from lib/control/InputHandling.hs rename to lib/RPGEngine/Internals/Input.hs index 1b4db4a..d74c6d6 100644 --- a/lib/control/InputHandling.hs +++ b/lib/RPGEngine/Internals/Input.hs @@ -1,18 +1,12 @@ -- Allows to create a massive inputHandler that can handle anything -- after you specify what you want it to do. -module InputHandling -( InputHandler(..), --- Compose multiple InputHandlers into one InputHandler that handles --- all of them. -composeInputHandlers, - --- Handle any event -handle, --- Handle a event by pressing a key -handleKey, --- Handle any key, equivalent to "Press any key to start" -handleAnyKey +module RPGEngine.Internals.Input +( InputHandler(..) +, composeInputHandlers +, handle +, handleKey +, handleAnyKey ) where import Graphics.Gloss.Interface.IO.Game @@ -23,20 +17,31 @@ type InputHandler a = Event -> (a -> a) ---------------------------------------------------------------------- +-- Compose multiple InputHandlers into one InputHandler that handles +-- all of them. composeInputHandlers :: [InputHandler a] -> InputHandler a composeInputHandlers [] ev a = a composeInputHandlers (ih:ihs) ev a = composeInputHandlers ihs ev (ih ev a) +-- Handle any event handle :: Event -> (a -> a) -> InputHandler a handle (EventKey key _ _ _) = handleKey key -- handle (EventMotion _) = undefined -- handle (EventResize _) = undefined -handle _ = (\_ -> (\_ -> id)) +handle _ = const (const id) +-- Handle a event by pressing a key handleKey :: Key -> (a -> a) -> InputHandler a handleKey (SpecialKey sk) = handleSpecialKey sk handleKey (Char c ) = handleCharKey c -handleKey (MouseButton _ ) = (\_ -> (\_ -> id)) +handleKey (MouseButton _ ) = const (const id) + +-- Handle any key, equivalent to "Press any key to start" +handleAnyKey :: (a -> a) -> InputHandler a +handleAnyKey f (EventKey _ Down _ _) = f +handleAnyKey _ _ = id + +---------------------------------------------------------------------- handleCharKey :: Char -> (a -> a) -> InputHandler a handleCharKey c1 f (EventKey (Char c2) Down _ _) @@ -49,7 +54,3 @@ handleSpecialKey sk1 f (EventKey (SpecialKey sk2) Down _ _) | sk1 == sk2 = f | otherwise = id handleSpecialKey _ _ _ = id - -handleAnyKey :: (a -> a) -> InputHandler a -handleAnyKey f (EventKey _ Down _ _) = f -handleAnyKey _ _ = id diff --git a/lib/RPGEngine/Internals/Parse.hs b/lib/RPGEngine/Internals/Parse.hs new file mode 100644 index 0000000..1118d85 --- /dev/null +++ b/lib/RPGEngine/Internals/Parse.hs @@ -0,0 +1,20 @@ +module RPGEngine.Internals.Parse where + +import Text.Parsec +import Text.Parsec.String + +-- A wrapper, which takes a parser and some input and returns a +-- parsed output. +parseWith :: Parser a -> String -> Either ParseError a +parseWith parser = parse parser "" + +-- Also return anything that has not yet been parsed +parseWithRest :: Parser a -> String -> Either ParseError (a, String) +-- fmap (,) over Parser monad and apply to rest +parseWithRest parser = parse ((,) <$> parser <*> rest) "" + where rest = manyTill anyToken eof + +-- Ignore all kinds of whitespaces +ignoreWS :: Parser a -> Parser a +ignoreWS parser = choice [skipComment, spaces] >> parser + where skipComment = do{ string "#"; manyTill anyChar endOfLine; return ()} \ No newline at end of file diff --git a/lib/RPGEngine/Internals/Parse/StructureElement.hs b/lib/RPGEngine/Internals/Parse/StructureElement.hs new file mode 100644 index 0000000..f9955fd --- /dev/null +++ b/lib/RPGEngine/Internals/Parse/StructureElement.hs @@ -0,0 +1,161 @@ +module RPGEngine.Internals.Parse.StructureElement where + +import RPGEngine.Internals.Data.Internals (Action(..), Condition(..)) +import RPGEngine.Internals.Parse ( ignoreWS ) + +import Text.Parsec + ( char, + many, + try, + alphaNum, + digit, + noneOf, + oneOf, + between, + choice, + many1, + notFollowedBy, + sepBy ) +import qualified Text.Parsec as P ( string ) +import Text.Parsec.String ( Parser ) +import GHC.IO.Device (RawIO(readNonBlocking)) + +-------------------------- StructureElement -------------------------- + +-- See documentation for more details, only a short description is +-- provided here. +data StructureElement = Block [StructureElement] + | Entry Key StructureElement -- Key + Value + | Regular Value -- Regular value, Integer or String or Infinite + deriving (Show, Eq) + +---------------------------------------------------------------------- + +structureElement :: Parser StructureElement +structureElement = try $ choice [block, entry, regular] + +-- A list of entries +block :: Parser StructureElement +block = try $ do + open <- ignoreWS $ oneOf openingBrackets + middle <- ignoreWS entry `sepBy` ignoreWS (char ',') + let closingBracket = getMatchingClosingBracket open + ignoreWS $ char closingBracket + return $ Block middle + +entry :: Parser StructureElement +entry = try $ do + key <- ignoreWS key + -- TODO Fix this + oneOf ": " -- Can be left out + value <- ignoreWS structureElement + return $ Entry key value + +regular :: Parser StructureElement +regular = try $ Regular <$> value + +--------------------------------- Key -------------------------------- + +data Key = Tag String + | ConditionList [Condition] + deriving (Show, Eq) + +data ConditionArgument = ArgString String + | Condition Condition + deriving (Show, Eq) + +---------------------------------------------------------------------- + +key :: Parser Key +key = try $ choice [conditionList, tag] + +tag :: Parser Key +tag = try $ Tag <$> many1 alphaNum + +conditionList :: Parser Key +conditionList = try $ do + open <- ignoreWS $ oneOf openingBrackets + list <- try $ ignoreWS condition `sepBy` ignoreWS (char ',') + let closingBracket = getMatchingClosingBracket open + ignoreWS $ char closingBracket + return $ ConditionList $ extract list + where extract ((Condition cond):list) = cond:extract list + extract _ = [] + +condition :: Parser ConditionArgument +condition = try $ do + text <- ignoreWS $ many1 $ noneOf illegalCharacters + open <- ignoreWS $ oneOf openingBrackets + cond <- ignoreWS $ choice [condition, argString] + let closingBracket = getMatchingClosingBracket open + ignoreWS $ char closingBracket + return $ Condition $ make text cond + where make "inventoryFull" _ = InventoryFull + make "inventoryContains" (ArgString arg) = InventoryContains arg + make "not" (Condition cond) = Not cond + make _ _ = AlwaysFalse + argString = try $ ArgString <$> many (noneOf illegalCharacters) + +-------------------------------- Value ------------------------------- + +data Value = String String + | Integer Int + | Infinite + | Action Action + | Layout -- TODO Add element + deriving (Show, Eq) + +---------------------------------------------------------------------- + +value :: Parser Value +value = choice [string, integer, infinite, action] + +string :: Parser Value +string = try $ String <$> between (char '\"') (char '\"') reading + where reading = ignoreWS $ many1 $ noneOf illegalCharacters + +integer :: Parser Value +integer = try $ do + value <- ignoreWS $ many1 digit + return $ Integer (read value :: Int) + +infinite :: Parser Value +infinite = try $ do + ignoreWS $ P.string "infinite" + notFollowedBy alphaNum + return Infinite + +action :: Parser Value +action = try $ do + script <- ignoreWS $ many1 $ noneOf "(" + arg <- ignoreWS $ between (char '(') (char ')') $ many $ noneOf ")" + let answer | script == "leave" = Leave + | script == "retrieveItem" = RetrieveItem arg + | script == "useItem" = UseItem arg + | script == "decreaseHp" = DecreaseHp first second + | script == "increasePlayerHp" = IncreasePlayerHp arg + | otherwise = RPGEngine.Internals.Data.Internals.Nothing + (first, ',':second) = break (== ',') arg + return $ Action answer + +-- TODO +layout :: Parser Value +layout = undefined + +------------------------------ Brackets ------------------------------ + +openingBrackets :: [Char] +openingBrackets = "<({[" + +closingBrackets :: [Char] +closingBrackets = ">)}]" + +illegalCharacters :: [Char] +illegalCharacters = ",:\"" ++ openingBrackets ++ closingBrackets + +---------------------------------------------------------------------- + +getMatchingClosingBracket :: Char -> Char +getMatchingClosingBracket opening = closingBrackets !! index + where combo = zip openingBrackets [0 ..] + index = head $ [y | (x, y) <- combo, x == opening] \ No newline at end of file diff --git a/lib/RPGEngine/Parse.hs b/lib/RPGEngine/Parse.hs new file mode 100644 index 0000000..15d3458 --- /dev/null +++ b/lib/RPGEngine/Parse.hs @@ -0,0 +1,8 @@ +module RPGEngine.Parse where + +import RPGEngine.Internals.Data.Game + +-- TODO parseFromFile gebruiken + +parseToGame :: Game +parseToGame = undefined \ No newline at end of file diff --git a/lib/render/Render.hs b/lib/RPGEngine/Render.hs similarity index 63% rename from lib/render/Render.hs rename to lib/RPGEngine/Render.hs index a94fed5..e65d589 100644 --- a/lib/render/Render.hs +++ b/lib/RPGEngine/Render.hs @@ -1,23 +1,29 @@ -- Allows to render the played game -module Render -( --- Initialize a window to play in -initWindow +module RPGEngine.Render +( initWindow +, bgColor --- Render the game , render ) where -import Game(Game(..)) -import State(State(..)) +import RPGEngine.Internals.Data.Game(Game(..)) +import RPGEngine.Internals.Data.State(State(..)) import Graphics.Gloss +----------------------------- Constants ------------------------------ + +-- Game background color +bgColor :: Color +bgColor = white + ---------------------------------------------------------------------- +-- Initialize a window to play in initWindow :: String -> (Int, Int) -> (Int, Int) -> Display -initWindow title dims offs = InWindow title dims offs +initWindow = InWindow +-- Render the game render :: Game -> Picture render g@Game{ state = Menu } = renderMenu g render g@Game{ state = Playing } = renderPlaying g @@ -25,10 +31,11 @@ render g@Game{ state = Pause } = renderPause g render g@Game{ state = Win } = renderWin g render g@Game{ state = Lose } = renderLose g +---------------------------------------------------------------------- -- TODO renderMenu :: Game -> Picture -renderMenu _ = text "Menu" +renderMenu _ = text "[Press any key to start]" -- TODO renderPlaying :: Game -> Picture @@ -36,7 +43,7 @@ renderPlaying _ = text "Playing" -- TODO renderPause :: Game -> Picture -renderPause _ = text "Pause" +renderPause _ = text "[Press any key to continue]" -- TODO renderWin :: Game -> Picture diff --git a/lib/control/Parse.hs b/lib/control/Parse.hs deleted file mode 100644 index 376f29b..0000000 --- a/lib/control/Parse.hs +++ /dev/null @@ -1,132 +0,0 @@ -module Parse where - --- TODO Maak wrapper module --- TODO This module should not be used by anything except for wrapper module and tests - -import Game -import Player -import Text.Parsec -import Text.Parsec.Char -import Text.Parsec.String -import Data.List -import Data.Maybe -import Text.Parsec.Error (Message(UnExpect)) - --- TODO parseFromFile gebruiken - --- Parser type --- type Parser = Parsec String () - --- A wrapper, which takes a parser and some input and returns a --- parsed output. -parseWith :: Parser a -> String -> Either ParseError a -parseWith parser = parse parser "" - -ignoreWS :: Parser a -> Parser a -ignoreWS parser = spaces >> parser - --- Also return anything that has not yet been parsed -parseWithRest :: Parser a -> String -> Either ParseError (a, String) --- fmap (,) over Parser monad and apply to rest -parseWithRest parser = parse ((,) <$> parser <*> rest) "" - where rest = manyTill anyToken eof - -parseToGame :: Game -parseToGame = undefined - --- Info in between brackets, '(..)', '[..]', '{..}' or '<..>' -data Brackets a = Brackets a - deriving (Eq, Show) - -parseToPlayer :: Player -parseToPlayer = undefined - --- any words separated by whitespace -parseWord :: Parser String -parseWord = do many alphaNum - --- TODO Expand to allow different kinds of brackets, also see Brackets data type. --- TODO Check if brackets match order. --- TODO Allow nested brackets. -brackets :: Parser (Brackets String) -brackets = do - ignoreWS $ char '(' - e <- ignoreWS $ many1 alphaNum - ignoreWS $ char ')' - return $ Brackets e - ------------------------- - -data Value = String String - | Integer Int - | Infinite - deriving (Show, Eq) - --- See documentation for more details, only a short description is ---provided here. -data StructureElement = Block [StructureElement] - | Entry String StructureElement-- Key + Value - | Regular Value -- Regular value, Integer or String or Infinite - | ConditionList [StructureElement] - -- TODO - | Condition -- inventoryFull() etc. - -- TODO - | Action -- leave(), useItem(objectId) etc. - deriving (Show, Eq) - --- TODO Add ConditionList and Action -structureElement :: Parser StructureElement -structureElement = choice [block, regular] - --- A Block is a list of Entry s -block :: Parser StructureElement -block = do - ignoreWS $ char '{' - list <- ignoreWS $ many1 entry - ignoreWS $ char '}' - return $ Block list - -entry :: Parser StructureElement -entry = do - key <- ignoreWS $ many1 alphaNum - ignoreWS $ char ':' - value <- ignoreWS structureElement -- TODO Is this the correct one to use? - return $ Entry key value - -regular :: Parser StructureElement -regular = do - value <- ignoreWS $ choice [integer, valueString, infinite] - return $ Regular value - -integer :: Parser Value -integer = do - value <- ignoreWS $ many1 digit - return $ Integer (read value :: Int) - -valueString :: Parser Value -valueString = do - ignoreWS $ char '"' - value <- ignoreWS $ many1 (noneOf ['"']) - ignoreWS $ char '"' - return $ String value - -infinite :: Parser Value -infinite = do - ignoreWS $ string "infinite" - notFollowedBy alphaNum - return Infinite - -conditionList :: Parser StructureElement -conditionList = do - ignoreWS $ char '[' - list <- ignoreWS $ many1 condition - ignoreWS $ char ']' - return $ ConditionList list - --- TODO -condition :: Parser StructureElement -condition = undefined - --- TODO YOU ARE HERE -action :: Parser StructureElement -action = undefined diff --git a/rpg-engine.cabal b/rpg-engine.cabal index 3775de7..bc85730 100644 --- a/rpg-engine.cabal +++ b/rpg-engine.cabal @@ -5,19 +5,25 @@ cabal-version: 1.12 build-type: Simple library - hs-source-dirs: lib, lib/control, lib/data, lib/render + hs-source-dirs: lib build-depends: base >= 4.7 && <5, gloss >= 1.11 && < 1.14, gloss-juicy >= 0.2.3, parsec >= 3.1.15.1 exposed-modules: - RPGEngine, - -- Control - Input, InputHandling, Parse, - -- Data - Game, Internals, Player, State, - -- Render - Render + RPGEngine + + RPGEngine.Input + RPGEngine.Parse + RPGEngine.Render + + RPGEngine.Internals.Data.Game + RPGEngine.Internals.Data.Internals + RPGEngine.Internals.Data.Player + RPGEngine.Internals.Data.State + RPGEngine.Internals.Input + RPGEngine.Internals.Parse + RPGEngine.Internals.Parse.StructureElement executable rpg-engine main-is: Main.hs diff --git a/stack.yaml b/stack.yaml index 344fbba..2f59104 100644 --- a/stack.yaml +++ b/stack.yaml @@ -67,3 +67,5 @@ extra-deps: # # Allow a newer minor version of GHC than the snapshot specifies # compiler-check: newer-minor + +custom-preprocessor-extensions: [] \ No newline at end of file diff --git a/test/ParsedToGameSpec.hs b/test/ParsedToGameSpec.hs index b1d3930..9a6aec6 100644 --- a/test/ParsedToGameSpec.hs +++ b/test/ParsedToGameSpec.hs @@ -1,7 +1,7 @@ module ParsedToGameSpec where import Test.Hspec -import Parse +import RPGEngine.Internals.Parse.StructureElement spec :: Spec spec = do diff --git a/test/ParserSpec.hs b/test/ParserSpec.hs index b5db0cc..301b282 100644 --- a/test/ParserSpec.hs +++ b/test/ParserSpec.hs @@ -1,7 +1,9 @@ module ParserSpec where import Test.Hspec -import Parse +import RPGEngine.Internals.Parse +import RPGEngine.Internals.Parse.StructureElement +import RPGEngine.Internals.Data.Internals import Data.Either spec :: Spec @@ -9,40 +11,118 @@ spec = do describe "Basics of entries" $ do it "can parse integers" $ do let correct = Right $ Regular $ Integer 1 - correct `shouldBe` parseWith regular "1" + parseWith regular "1" `shouldBe` correct it "can parse string" $ do let input = "dit is een string" correct = Right $ Regular $ String input - correct `shouldBe` parseWith regular ("\"" ++ input ++ "\"") + parseWith regular ("\"" ++ input ++ "\"") `shouldBe` correct it "can parse infinite" $ do let correct = Right $ Regular Infinite - correct `shouldBe` parseWith regular "infinite" + parseWith regular "infinite" `shouldBe` correct let wrong = Right $ Regular Infinite - wrong `shouldNotBe` parseWith regular "infinitee" + parseWith regular "infinitee" `shouldNotBe` wrong it "can parse entries" $ do - let input = "id : \"dagger\"" - correct = Right $ Entry "id" $ Regular $ String "dagger" - correct `shouldBe` parseWith entry input + let input = "id: \"dagger\"" + correct = Right $ Entry (Tag "id") $ Regular $ String "dagger" + parseWith entry input `shouldBe` correct let input = "x: 0" - correct = Right $ Entry "x" $ Regular $ Integer 0 - correct `shouldBe` parseWith entry input + correct = Right $ Entry (Tag "x") $ Regular $ Integer 0 + parseWith entry input `shouldBe` correct let input = "useTimes: infinite" - correct = Right $ Entry "useTimes" $ Regular Infinite - correct `shouldBe` parseWith entry input + correct = Right $ Entry (Tag "useTimes") $ Regular Infinite + parseWith entry input `shouldBe` correct - describe "Special kinds" $ do + describe "block: {...}" $ do + it "can parse a block with a single entry" $ do + let input = "{ id: 1}" + correct = Right (Block [ + Entry (Tag "id") $ Regular $ Integer 1 + ], "") + parseWithRest structureElement input `shouldBe` correct + + it "can parse a block with entries" $ do + let input = "{ id: \"key\", x: 3, y: 1}" + correct = Right $ Block [ + Entry (Tag "id") $ Regular $ String "key", + Entry (Tag "x") $ Regular $ Integer 3, + Entry (Tag "y") $ Regular $ Integer 1 + ] + parseWith structureElement input `shouldBe` correct + + describe "Basics" $ do + it "can parse leave()" $ do + let input = "leave()" + correct = Right $ Action Leave + parseWith action input `shouldBe` correct + + it "can parse retrieveItem()" $ do + let input = "retrieveItem(firstId)" + correct = Right $ Action $ RetrieveItem "firstId" + parseWith action input `shouldBe` correct + + it "can parse useItem()" $ do + let input = "useItem(secondId)" + correct = Right $ Action $ UseItem "secondId" + parseWith action input `shouldBe` correct + + it "can parse decreaseHp()" $ do + let input = "decreaseHp(entityId,objectId)" + correct = Right $ Action $ DecreaseHp "entityId" "objectId" + parseWith action input `shouldBe` correct + + it "can parse increasePlayerHp()" $ do + let input = "increasePlayerHp(objectId)" + correct = Right $ Action $ IncreasePlayerHp "objectId" + parseWith action input `shouldBe` correct + + it "can parse inventoryFull()" $ do + let input = "inventoryFull()" + correct = Right (Condition InventoryFull, "") + parseWithRest condition input `shouldBe` correct + + it "can parse inventoryContains()" $ do + let input = "inventoryContains(itemId)" + correct = Right (Condition $ InventoryContains "itemId", "") + parseWithRest condition input `shouldBe` correct + + it "can parse not()" $ do + let input = "not(inventoryFull())" + correct = Right (Condition $ Not InventoryFull, "") + parseWithRest condition input `shouldBe` correct + + let input = "not(inventoryContains(itemId))" + correct = Right (Condition $ Not $ InventoryContains "itemId", "") + parseWithRest condition input `shouldBe` correct + + it "can parse conditionlists" $ do + let input = "[not(inventoryFull())]" + correct = Right (ConditionList [Not InventoryFull], "") + parseWithRest conditionList input `shouldBe` correct + + let input = "[inventoryFull(), inventoryContains(itemId)]" + correct = Right (ConditionList [ + InventoryFull, + InventoryContains "itemId" + ], "") + parseWithRest conditionList input `shouldBe` correct + + let input = "[]" + correct = Right $ ConditionList [] + parseWith conditionList input `shouldBe` correct + it "can parse actions" $ do - let input = "actions: {}" - correct = Right $ Entry "actions" $ Regular Infinite -- TODO Change this - correct `shouldBe` parseWith action input - - it "can parse conditions" $ do - pending - + let input = "actions: { [not(inventoryFull())] retrieveItem(key), [] leave()}" + correct = Right (Entry (Tag "actions") $ Block [ + Entry (ConditionList [Not InventoryFull]) $ Regular $ Action $ RetrieveItem "key", + Entry (ConditionList []) $ Regular $ Action Leave + ], "") + parseWithRest structureElement input `shouldBe` correct + + describe "Layouts" $ do it "can parse layouts" $ do pending From 0720f3b71954a86868b3917c73097c5c60dcc5f9 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Tue, 20 Dec 2022 11:13:14 +0100 Subject: [PATCH 10/34] Another structure overhaul --- lib/RPGEngine.hs | 2 +- lib/RPGEngine/Data/Game.hs | 17 +++ lib/RPGEngine/{Internals => }/Data/State.hs | 11 +- lib/RPGEngine/Data/Types.hs | 115 ++++++++++++++++++ lib/RPGEngine/Input.hs | 6 +- .../{Internals/Input.hs => Input/Core.hs} | 2 +- lib/RPGEngine/Internals/Data/Game.hs | 25 ---- lib/RPGEngine/Internals/Data/Internals.hs | 77 ------------ lib/RPGEngine/Internals/Data/Player.hs | 15 --- lib/RPGEngine/Parse.hs | 2 +- .../{Internals/Parse.hs => Parse/Core.hs} | 2 +- .../{Internals => }/Parse/StructureElement.hs | 8 +- lib/RPGEngine/Render.hs | 3 +- rpg-engine.cabal | 21 ++-- 14 files changed, 158 insertions(+), 148 deletions(-) create mode 100644 lib/RPGEngine/Data/Game.hs rename lib/RPGEngine/{Internals => }/Data/State.hs (80%) create mode 100644 lib/RPGEngine/Data/Types.hs rename lib/RPGEngine/{Internals/Input.hs => Input/Core.hs} (98%) delete mode 100644 lib/RPGEngine/Internals/Data/Game.hs delete mode 100644 lib/RPGEngine/Internals/Data/Internals.hs delete mode 100644 lib/RPGEngine/Internals/Data/Player.hs rename lib/RPGEngine/{Internals/Parse.hs => Parse/Core.hs} (94%) rename lib/RPGEngine/{Internals => }/Parse/StructureElement.hs (94%) diff --git a/lib/RPGEngine.hs b/lib/RPGEngine.hs index 1ab1fa5..ee52fc9 100644 --- a/lib/RPGEngine.hs +++ b/lib/RPGEngine.hs @@ -5,7 +5,7 @@ module RPGEngine ( playRPGEngine ) where -import RPGEngine.Internals.Data.Game +import RPGEngine.Data.Game import RPGEngine.Render import RPGEngine.Input diff --git a/lib/RPGEngine/Data/Game.hs b/lib/RPGEngine/Data/Game.hs new file mode 100644 index 0000000..9f5bd8a --- /dev/null +++ b/lib/RPGEngine/Data/Game.hs @@ -0,0 +1,17 @@ +-- Representation of all the game's data + +module RPGEngine.Data.Game +( Game(..) + ,initGame +) where + +import RPGEngine.Data.Types +import RPGEngine.Data.State + +---------------------------------------------------------------------- + +-- Initialize the game +initGame :: Game +initGame = Game { + state = defaultState +} diff --git a/lib/RPGEngine/Internals/Data/State.hs b/lib/RPGEngine/Data/State.hs similarity index 80% rename from lib/RPGEngine/Internals/Data/State.hs rename to lib/RPGEngine/Data/State.hs index b567517..1953d73 100644 --- a/lib/RPGEngine/Internals/Data/State.hs +++ b/lib/RPGEngine/Data/State.hs @@ -2,21 +2,16 @@ -- e.g. Main menu, game, pause, win or lose -- Allows to easily go to a next state and change rendering accordingly -module RPGEngine.Internals.Data.State +module RPGEngine.Data.State ( State(..) , defaultState , nextState ) where ------------------------------ Constants ------------------------------ +import RPGEngine.Data.Types --- Current state of the game. -data State = Menu - | Playing - | Pause - | Win - | Lose +----------------------------- Constants ------------------------------ -- Default state of the game, Menu defaultState :: State diff --git a/lib/RPGEngine/Data/Types.hs b/lib/RPGEngine/Data/Types.hs new file mode 100644 index 0000000..af812bd --- /dev/null +++ b/lib/RPGEngine/Data/Types.hs @@ -0,0 +1,115 @@ +module RPGEngine.Data.Types where + +-------------------------------- Game -------------------------------- + +-- TODO Add more +data Game = Game { + -- Current state of the game + state :: State +} + +------------------------------- Player ------------------------------- + +data Player = Player { + playerHp :: Maybe Int, + inventory :: [Item] +} + +instance Living Player where + hp = playerHp + +------------------------------- State -------------------------------- + +-- Current state of the game. +data State = Menu + | Playing + | Pause + | Win + | Lose + +------------------------------- Object ------------------------------- + +class Object a where + id :: a -> String + x :: a -> Int + y :: a -> Int + name :: a -> String + description :: a -> String + actions :: a -> [([Condition], Action)] + value :: a -> Maybe Int + +class Living a where + hp :: a -> Maybe Int + +data Item = Item { + itemId :: ItemId, + itemX :: Int, + itemY :: Int, + itemName :: String, + itemDescription :: String, + itemActions :: [([Condition], Action)], + itemValue :: Maybe Int, + useTimes :: Maybe Int +} + +instance Object Item where + id = itemId + x = itemX + y = itemY + name = itemName + description = itemDescription + actions = itemActions + value = itemValue + +data Entity = Entity { + entityId :: EntityId, + entityX :: Int, + entityY :: Int, + entityName :: String, + entityDescription :: String, + entityActions :: [([Condition], Action)], + entityValue :: Maybe Int, + entityHp :: Maybe Int, + direction :: Maybe Direction +} + +instance Object Entity where + id = entityId + x = entityX + y = entityY + name = entityName + description = entityDescription + actions = entityActions + value = entityValue + +instance Living Entity where + hp = entityHp + +type EntityId = String +type ItemId = String + +------------------------------ Condition ----------------------------- + +data Condition = InventoryFull + | InventoryContains ItemId + | Not Condition + | AlwaysFalse + deriving (Show, Eq) + +------------------------------- Action ------------------------------- + +data Action = Leave + | RetrieveItem ItemId + | UseItem ItemId + | DecreaseHp EntityId ItemId + | IncreasePlayerHp ItemId + | Nothing + deriving (Show, Eq) + +------------------------------ Direction ----------------------------- + +data Direction = North + | East + | South + | West + deriving (Show) \ No newline at end of file diff --git a/lib/RPGEngine/Input.hs b/lib/RPGEngine/Input.hs index 337a3bc..767a8d4 100644 --- a/lib/RPGEngine/Input.hs +++ b/lib/RPGEngine/Input.hs @@ -4,9 +4,9 @@ module RPGEngine.Input ( handleAllInput ) where -import RPGEngine.Internals.Data.Game -import RPGEngine.Internals.Data.State -import RPGEngine.Internals.Input +import RPGEngine.Data.Types +import RPGEngine.Data.State +import RPGEngine.Input.Core import Graphics.Gloss.Interface.IO.Game diff --git a/lib/RPGEngine/Internals/Input.hs b/lib/RPGEngine/Input/Core.hs similarity index 98% rename from lib/RPGEngine/Internals/Input.hs rename to lib/RPGEngine/Input/Core.hs index d74c6d6..e2e81b9 100644 --- a/lib/RPGEngine/Internals/Input.hs +++ b/lib/RPGEngine/Input/Core.hs @@ -1,7 +1,7 @@ -- Allows to create a massive inputHandler that can handle anything -- after you specify what you want it to do. -module RPGEngine.Internals.Input +module RPGEngine.Input.Core ( InputHandler(..) , composeInputHandlers , handle diff --git a/lib/RPGEngine/Internals/Data/Game.hs b/lib/RPGEngine/Internals/Data/Game.hs deleted file mode 100644 index 7aa8ef4..0000000 --- a/lib/RPGEngine/Internals/Data/Game.hs +++ /dev/null @@ -1,25 +0,0 @@ --- Representation of all the game's data - -module RPGEngine.Internals.Data.Game -( Game(..), - -initGame -) where - -import RPGEngine.Internals.Data.State - ------------------------------ Constants ------------------------------ - --- TODO Add more -data Game = Game { - -- Current state of the game - state :: State -} - ----------------------------------------------------------------------- - --- Initialize the game -initGame :: Game -initGame = Game { - state = defaultState -} diff --git a/lib/RPGEngine/Internals/Data/Internals.hs b/lib/RPGEngine/Internals/Data/Internals.hs deleted file mode 100644 index 476772b..0000000 --- a/lib/RPGEngine/Internals/Data/Internals.hs +++ /dev/null @@ -1,77 +0,0 @@ --- Represents an item in the game. - -module RPGEngine.Internals.Data.Internals -( Action(..) -, Condition(..) -, Object(..) -, EntityId -, ItemId -) where - ------------------------------ Constants ------------------------------ - -type EntityId = String -type ItemId = String - -data Object = - Item { -- All fields are required - -- Easy way to identify items - id :: ItemId, - -- Horizontal coördinate in the level - x :: Int, - -- Vertical coördinate in the level - y :: Int, - name :: String, - -- Short description of the object - description :: String, - -- Counts how often the object can be used by the player. Either - -- infinite or a natural number - useTimes :: Maybe Int, - -- List of conditional actions when the player is standing on this object - actions :: [([Condition], Action)], - -- Interpretation depends on action with this object. - value :: Maybe Int - } - | Entity { - -- Required fields - -- Easy way to identify items - id :: EntityId, - -- Horizontal coördinate in the level - x :: Int, - -- Vertical coördinate in the level - y :: Int, - name :: String, - -- Short description of the object - description :: String, - -- List of conditional actions when the player is standing on this object - actions :: [([Condition], Action)], - -- Optional fields - -- The direction of the item. e.g. a door has a direction. - direction :: Maybe Direction, - -- Some entities have health points. - hp :: Maybe Int, - -- Interpretation depends on action with this object. - value :: Maybe Int - } - -data Direction = North - | East - | South - | West - deriving (Show) - -data Action = Leave - | RetrieveItem ItemId - | UseItem ItemId - | DecreaseHp EntityId ItemId - | IncreasePlayerHp ItemId - | Nothing - deriving (Show, Eq) - -data Condition = InventoryFull - | InventoryContains ItemId - | Not Condition - | AlwaysFalse - deriving (Show, Eq) - ----------------------------------------------------------------------- diff --git a/lib/RPGEngine/Internals/Data/Player.hs b/lib/RPGEngine/Internals/Data/Player.hs deleted file mode 100644 index c325df0..0000000 --- a/lib/RPGEngine/Internals/Data/Player.hs +++ /dev/null @@ -1,15 +0,0 @@ --- Represents a player in the game. This player can move around, pick --- up items and interact with the world. - -module RPGEngine.Internals.Data.Player -( Player(..) -) where - -import RPGEngine.Internals.Data.Internals - ------------------------------ Constants ------------------------------ - -data Player = Player { - hp :: Int, - inventory :: [Object] -} \ No newline at end of file diff --git a/lib/RPGEngine/Parse.hs b/lib/RPGEngine/Parse.hs index 15d3458..477b150 100644 --- a/lib/RPGEngine/Parse.hs +++ b/lib/RPGEngine/Parse.hs @@ -1,6 +1,6 @@ module RPGEngine.Parse where -import RPGEngine.Internals.Data.Game +import RPGEngine.Data.Types -- TODO parseFromFile gebruiken diff --git a/lib/RPGEngine/Internals/Parse.hs b/lib/RPGEngine/Parse/Core.hs similarity index 94% rename from lib/RPGEngine/Internals/Parse.hs rename to lib/RPGEngine/Parse/Core.hs index 1118d85..7e704ab 100644 --- a/lib/RPGEngine/Internals/Parse.hs +++ b/lib/RPGEngine/Parse/Core.hs @@ -1,4 +1,4 @@ -module RPGEngine.Internals.Parse where +module RPGEngine.Parse.Core where import Text.Parsec import Text.Parsec.String diff --git a/lib/RPGEngine/Internals/Parse/StructureElement.hs b/lib/RPGEngine/Parse/StructureElement.hs similarity index 94% rename from lib/RPGEngine/Internals/Parse/StructureElement.hs rename to lib/RPGEngine/Parse/StructureElement.hs index f9955fd..e8f4b34 100644 --- a/lib/RPGEngine/Internals/Parse/StructureElement.hs +++ b/lib/RPGEngine/Parse/StructureElement.hs @@ -1,7 +1,7 @@ -module RPGEngine.Internals.Parse.StructureElement where +module RPGEngine.Parse.StructureElement where -import RPGEngine.Internals.Data.Internals (Action(..), Condition(..)) -import RPGEngine.Internals.Parse ( ignoreWS ) +import RPGEngine.Data.Types (Action(..), Condition(..)) +import RPGEngine.Parse.Core ( ignoreWS ) import Text.Parsec ( char, @@ -134,7 +134,7 @@ action = try $ do | script == "useItem" = UseItem arg | script == "decreaseHp" = DecreaseHp first second | script == "increasePlayerHp" = IncreasePlayerHp arg - | otherwise = RPGEngine.Internals.Data.Internals.Nothing + | otherwise = RPGEngine.Data.Types.Nothing (first, ',':second) = break (== ',') arg return $ Action answer diff --git a/lib/RPGEngine/Render.hs b/lib/RPGEngine/Render.hs index e65d589..ca39b23 100644 --- a/lib/RPGEngine/Render.hs +++ b/lib/RPGEngine/Render.hs @@ -7,8 +7,7 @@ module RPGEngine.Render , render ) where -import RPGEngine.Internals.Data.Game(Game(..)) -import RPGEngine.Internals.Data.State(State(..)) +import RPGEngine.Data.Types import Graphics.Gloss ----------------------------- Constants ------------------------------ diff --git a/rpg-engine.cabal b/rpg-engine.cabal index bc85730..4968079 100644 --- a/rpg-engine.cabal +++ b/rpg-engine.cabal @@ -13,17 +13,18 @@ library exposed-modules: RPGEngine - RPGEngine.Input - RPGEngine.Parse - RPGEngine.Render + RPGEngine.Data.Game + RPGEngine.Data.Types + RPGEngine.Data.State - RPGEngine.Internals.Data.Game - RPGEngine.Internals.Data.Internals - RPGEngine.Internals.Data.Player - RPGEngine.Internals.Data.State - RPGEngine.Internals.Input - RPGEngine.Internals.Parse - RPGEngine.Internals.Parse.StructureElement + RPGEngine.Input + RPGEngine.Input.Core + + RPGEngine.Parse + RPGEngine.Parse.Core + RPGEngine.Parse.StructureElement + + RPGEngine.Render executable rpg-engine main-is: Main.hs From d4fbcda73b8631c5279b4ebd26aee0ddb252aa1d Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Tue, 20 Dec 2022 16:56:22 +0100 Subject: [PATCH 11/34] Setup --- lib/RPGEngine/{Data/Types.hs => Data.hs} | 33 +- lib/RPGEngine/Data/Game.hs | 12 +- lib/RPGEngine/Data/State.hs | 2 +- lib/RPGEngine/Input.hs | 2 +- lib/RPGEngine/Parse.hs | 19 +- lib/RPGEngine/Parse/Game.hs | 22 ++ lib/RPGEngine/Parse/StructureElement.hs | 56 +++- lib/RPGEngine/Render.hs | 2 +- rpg-engine.cabal | 6 +- test/InteractionSpec.hs | 9 - .../{ParsedToGameSpec.hs => ParseGameSpec.hs} | 4 +- test/ParseStructureElementSpec.hs | 282 ++++++++++++++++++ test/ParserSpec.hs | 132 -------- 13 files changed, 412 insertions(+), 169 deletions(-) rename lib/RPGEngine/{Data/Types.hs => Data.hs} (80%) create mode 100644 lib/RPGEngine/Parse/Game.hs delete mode 100644 test/InteractionSpec.hs rename test/{ParsedToGameSpec.hs => ParseGameSpec.hs} (93%) create mode 100644 test/ParseStructureElementSpec.hs delete mode 100644 test/ParserSpec.hs diff --git a/lib/RPGEngine/Data/Types.hs b/lib/RPGEngine/Data.hs similarity index 80% rename from lib/RPGEngine/Data/Types.hs rename to lib/RPGEngine/Data.hs index af812bd..b91b7f2 100644 --- a/lib/RPGEngine/Data/Types.hs +++ b/lib/RPGEngine/Data.hs @@ -1,13 +1,33 @@ -module RPGEngine.Data.Types where +module RPGEngine.Data where -------------------------------- Game -------------------------------- -- TODO Add more data Game = Game { -- Current state of the game - state :: State + state :: State, + playing :: Level, + levels :: [Level] } +------------------------------- Level -------------------------------- + +data Level = Level { + layout :: Layout, + items :: [Item], + entities :: [Entity] +} + +type Layout = [Strip] +type Strip = [Physical] + +data Physical = Void + | Walkable + | Blocked + | Entrance + | Exit + deriving (Show, Eq) + ------------------------------- Player ------------------------------- data Player = Player { @@ -108,8 +128,9 @@ data Action = Leave ------------------------------ Direction ----------------------------- -data Direction = North - | East - | South +data Direction = North + | East + | South | West - deriving (Show) \ No newline at end of file + | Center -- Equal to 'stay where you are' + deriving (Show, Eq) \ No newline at end of file diff --git a/lib/RPGEngine/Data/Game.hs b/lib/RPGEngine/Data/Game.hs index 9f5bd8a..82750f6 100644 --- a/lib/RPGEngine/Data/Game.hs +++ b/lib/RPGEngine/Data/Game.hs @@ -5,7 +5,7 @@ module RPGEngine.Data.Game ,initGame ) where -import RPGEngine.Data.Types +import RPGEngine.Data import RPGEngine.Data.State ---------------------------------------------------------------------- @@ -13,5 +13,13 @@ import RPGEngine.Data.State -- Initialize the game initGame :: Game initGame = Game { - state = defaultState + state = defaultState, + playing = head levels, + levels = levels } + where levels = [emptyLevel] + emptyLevel = Level { + layout = [], + items = [], + entities = [] + } diff --git a/lib/RPGEngine/Data/State.hs b/lib/RPGEngine/Data/State.hs index 1953d73..42d9d3f 100644 --- a/lib/RPGEngine/Data/State.hs +++ b/lib/RPGEngine/Data/State.hs @@ -9,7 +9,7 @@ module RPGEngine.Data.State , nextState ) where -import RPGEngine.Data.Types +import RPGEngine.Data ----------------------------- Constants ------------------------------ diff --git a/lib/RPGEngine/Input.hs b/lib/RPGEngine/Input.hs index 767a8d4..04b6a6a 100644 --- a/lib/RPGEngine/Input.hs +++ b/lib/RPGEngine/Input.hs @@ -4,7 +4,7 @@ module RPGEngine.Input ( handleAllInput ) where -import RPGEngine.Data.Types +import RPGEngine.Data import RPGEngine.Data.State import RPGEngine.Input.Core diff --git a/lib/RPGEngine/Parse.hs b/lib/RPGEngine/Parse.hs index 477b150..f5838d3 100644 --- a/lib/RPGEngine/Parse.hs +++ b/lib/RPGEngine/Parse.hs @@ -1,8 +1,19 @@ module RPGEngine.Parse where -import RPGEngine.Data.Types +import RPGEngine.Data +import RPGEngine.Parse.StructureElement +import RPGEngine.Parse.Game --- TODO parseFromFile gebruiken +import Text.Parsec.String +import System.IO.Unsafe -parseToGame :: Game -parseToGame = undefined \ No newline at end of file +----------------------------- Constants ------------------------------ + +type FileName = String + +---------------------------------------------------------------------- + +parseToGame :: FileName -> Game +parseToGame filename = structureToGame structure + where (Right structure) = unsafePerformIO io + io = parseFromFile structureElement filename \ No newline at end of file diff --git a/lib/RPGEngine/Parse/Game.hs b/lib/RPGEngine/Parse/Game.hs new file mode 100644 index 0000000..85028f2 --- /dev/null +++ b/lib/RPGEngine/Parse/Game.hs @@ -0,0 +1,22 @@ +module RPGEngine.Parse.Game where + +import RPGEngine.Data +import RPGEngine.Parse.StructureElement (StructureElement) + +-------------------------------- Game -------------------------------- + +-- TODO +structureToGame :: StructureElement -> Game +structureToGame = undefined + +------------------------------- Player ------------------------------- + +-- TODO +structureToPlayer :: StructureElement -> Player +structureToPlayer = undefined + +------------------------------- Levels ------------------------------- + +-- TODO +structureToLevels :: StructureElement -> [Level] +structureToLevels = undefined \ No newline at end of file diff --git a/lib/RPGEngine/Parse/StructureElement.hs b/lib/RPGEngine/Parse/StructureElement.hs index e8f4b34..ce5d8c5 100644 --- a/lib/RPGEngine/Parse/StructureElement.hs +++ b/lib/RPGEngine/Parse/StructureElement.hs @@ -1,6 +1,6 @@ module RPGEngine.Parse.StructureElement where -import RPGEngine.Data.Types (Action(..), Condition(..)) +import RPGEngine.Data (Action(..), Condition(..), Layout, Direction(..), Physical(..), Strip) import RPGEngine.Parse.Core ( ignoreWS ) import Text.Parsec @@ -38,7 +38,7 @@ structureElement = try $ choice [block, entry, regular] block :: Parser StructureElement block = try $ do open <- ignoreWS $ oneOf openingBrackets - middle <- ignoreWS entry `sepBy` ignoreWS (char ',') + middle <- ignoreWS $ choice [entry, block] `sepBy` char ',' let closingBracket = getMatchingClosingBracket open ignoreWS $ char closingBracket return $ Block middle @@ -75,7 +75,7 @@ tag = try $ Tag <$> many1 alphaNum conditionList :: Parser Key conditionList = try $ do open <- ignoreWS $ oneOf openingBrackets - list <- try $ ignoreWS condition `sepBy` ignoreWS (char ',') + list <- ignoreWS condition `sepBy` char ',' let closingBracket = getMatchingClosingBracket open ignoreWS $ char closingBracket return $ ConditionList $ extract list @@ -102,13 +102,14 @@ data Value = String String | Integer Int | Infinite | Action Action - | Layout -- TODO Add element + | Direction Direction + | Layout Layout deriving (Show, Eq) ---------------------------------------------------------------------- value :: Parser Value -value = choice [string, integer, infinite, action] +value = choice [string, integer, infinite, action, direction] string :: Parser Value string = try $ String <$> between (char '\"') (char '\"') reading @@ -134,13 +135,52 @@ action = try $ do | script == "useItem" = UseItem arg | script == "decreaseHp" = DecreaseHp first second | script == "increasePlayerHp" = IncreasePlayerHp arg - | otherwise = RPGEngine.Data.Types.Nothing + | otherwise = RPGEngine.Data.Nothing (first, ',':second) = break (== ',') arg return $ Action answer --- TODO +direction :: Parser Value +direction = try $ do + value <- choice [ + ignoreWS $ P.string "up", + ignoreWS $ P.string "down", + ignoreWS $ P.string "left", + ignoreWS $ P.string "right" + ] + notFollowedBy alphaNum + return $ Direction $ make value + where make "up" = North + make "right" = East + make "down" = South + make "left" = West + make _ = Center + layout :: Parser Value -layout = undefined +layout = try $ do + ignoreWS $ char '|' + list <- ignoreWS strip `sepBy` ignoreWS (char '|') + return $ Layout list + +strip :: Parser Strip +strip = try $ do + physical `sepBy` char ' ' + +physical :: Parser Physical +physical = try $ do + value <- choice [ + char 'x', + char '.', + char '*', + char 's', + char 'e' + ] + return $ make value + where make '.' = Walkable + make '*' = Blocked + make 's' = Entrance + make 'e' = Exit + make _ = Void + ------------------------------ Brackets ------------------------------ diff --git a/lib/RPGEngine/Render.hs b/lib/RPGEngine/Render.hs index ca39b23..03e7855 100644 --- a/lib/RPGEngine/Render.hs +++ b/lib/RPGEngine/Render.hs @@ -7,7 +7,7 @@ module RPGEngine.Render , render ) where -import RPGEngine.Data.Types +import RPGEngine.Data import Graphics.Gloss ----------------------------- Constants ------------------------------ diff --git a/rpg-engine.cabal b/rpg-engine.cabal index 4968079..f6a304b 100644 --- a/rpg-engine.cabal +++ b/rpg-engine.cabal @@ -13,8 +13,8 @@ library exposed-modules: RPGEngine + RPGEngine.Data RPGEngine.Data.Game - RPGEngine.Data.Types RPGEngine.Data.State RPGEngine.Input @@ -22,6 +22,7 @@ library RPGEngine.Parse RPGEngine.Parse.Core + RPGEngine.Parse.Game RPGEngine.Parse.StructureElement RPGEngine.Render @@ -39,6 +40,5 @@ test-suite rpg-engine-test default-language: Haskell2010 build-depends: base >=4.7 && <5, hspec <= 2.10.6, hspec-discover, rpg-engine other-modules: - InteractionSpec, -- Parsing - ParsedToGameSpec, ParserSpec + ParseGameSpec, ParseStructureElementSpec diff --git a/test/InteractionSpec.hs b/test/InteractionSpec.hs deleted file mode 100644 index 63801af..0000000 --- a/test/InteractionSpec.hs +++ /dev/null @@ -1,9 +0,0 @@ -module InteractionSpec where - -import Test.Hspec - -spec :: Spec -spec = do - describe "Player with Inventory" $ do - it "TODO: Simple test" $ do - pending \ No newline at end of file diff --git a/test/ParsedToGameSpec.hs b/test/ParseGameSpec.hs similarity index 93% rename from test/ParsedToGameSpec.hs rename to test/ParseGameSpec.hs index 9a6aec6..a162246 100644 --- a/test/ParsedToGameSpec.hs +++ b/test/ParseGameSpec.hs @@ -1,7 +1,7 @@ -module ParsedToGameSpec where +module ParseGameSpec where import Test.Hspec -import RPGEngine.Internals.Parse.StructureElement +import RPGEngine.Parse.StructureElement spec :: Spec spec = do diff --git a/test/ParseStructureElementSpec.hs b/test/ParseStructureElementSpec.hs new file mode 100644 index 0000000..bc70837 --- /dev/null +++ b/test/ParseStructureElementSpec.hs @@ -0,0 +1,282 @@ +module ParseStructureElementSpec where + +import Test.Hspec + +import RPGEngine.Data +import RPGEngine.Parse.Core +import RPGEngine.Parse.StructureElement + +spec :: Spec +spec = do + describe "StructureElement" $ do + it "can parse blocks" $ do + let input = "{}" + correct = Right $ Block [] + parseWith structureElement input `shouldBe` correct + + let input = "{{}}" + correct = Right $ Block [Block []] + parseWith structureElement input `shouldBe` correct + + let input = "{{}, {}}" + correct = Right $ Block [Block [], Block []] + parseWith structureElement input `shouldBe` correct + + let input = "{ id: 1 }" + correct = Right (Block [ + Entry (Tag "id") $ Regular $ Integer 1 + ], "") + parseWithRest structureElement input `shouldBe` correct + + let input = "{ id: \"key\", x: 3, y: 1}" + correct = Right $ Block [ + Entry (Tag "id") $ Regular $ String "key", + Entry (Tag "x") $ Regular $ Integer 3, + Entry (Tag "y") $ Regular $ Integer 1 + ] + parseWith structureElement input `shouldBe` correct + + let input = "actions: { [not(inventoryFull())] retrieveItem(key), [] leave()}" + correct = Right (Entry (Tag "actions") $ Block [ + Entry (ConditionList [Not InventoryFull]) $ Regular $ Action $ RetrieveItem "key", + Entry (ConditionList []) $ Regular $ Action Leave + ], "") + parseWithRest structureElement input `shouldBe` correct + + let input = "entities: [ { id: \"door\", x: 4, name:\"Secret door\", description: \"This secret door can only be opened with a key\", direction: left, y: 1}]" + correct = Right (Entry (Tag "entities") $ Block [ Block [ + Entry (Tag "id") $ Regular $ String "door", + Entry (Tag "x") $ Regular $ Integer 4, + Entry (Tag "name") $ Regular $ String "Secret door", + Entry (Tag "description") $ Regular $ String "This secret door can only be opened with a key", + Entry (Tag "direction") $ Regular $ Direction West, + Entry (Tag "y") $ Regular $ Integer 1 + ]], "") + parseWithRest structureElement input `shouldBe` correct + + let input = "entities: [ { id: \"door\", x: 4, y: 1, name:\"Secret door\", description: \"This secret door can only be opened with a key\", actions: { [inventoryContains(key)] useItem(key), [] leave() } } ]" + correct = Right (Entry (Tag "entities") $ Block [ Block [ + Entry (Tag "id") $ Regular $ String "door", + Entry (Tag "x") $ Regular $ Integer 4, + Entry (Tag "y") $ Regular $ Integer 1, + Entry (Tag "name") $ Regular $ String "Secret door", + Entry (Tag "description") $ Regular $ String "This secret door can only be opened with a key", + Entry (Tag "actions") $ Block [ + Entry (ConditionList [InventoryContains "key"]) $ Regular $ Action $ UseItem "key", + Entry (ConditionList []) $ Regular $ Action Leave + ] + ]], "") + parseWithRest structureElement input `shouldBe` correct + + let input = "entities: [ { id: \"door\", x: 4, y: 1, name:\"Secret door\", description: \"This secret door can only be opened with a key\", direction: left , actions: { [inventoryContains(key)] useItem(key), [] leave() } } ]" + correct = Right (Entry (Tag "entities") $ Block [ Block [ + Entry (Tag "id") $ Regular $ String "door", + Entry (Tag "x") $ Regular $ Integer 4, + Entry (Tag "y") $ Regular $ Integer 1, + Entry (Tag "name") $ Regular $ String "Secret door", + Entry (Tag "description") $ Regular $ String "This secret door can only be opened with a key", + Entry (Tag "direction") $ Regular $ Direction West, + Entry (Tag "actions") $ Block [ + Entry (ConditionList [InventoryContains "key"]) $ Regular $ Action $ UseItem "key", + Entry (ConditionList []) $ Regular $ Action Leave + ] + ]], "") + parseWithRest structureElement input `shouldBe` correct + + it "can parse entries" $ do + let input = "id: \"dagger\"" + correct = Right $ Entry (Tag "id") $ Regular $ String "dagger" + parseWith entry input `shouldBe` correct + + let input = "x: 0" + correct = Right $ Entry (Tag "x") $ Regular $ Integer 0 + parseWith entry input `shouldBe` correct + + let input = "useTimes: infinite" + correct = Right $ Entry (Tag "useTimes") $ Regular Infinite + parseWith entry input `shouldBe` correct + + let input = "direction: up" + correct = Right $ Entry (Tag "direction") $ Regular $ Direction North + parseWith entry input `shouldBe` correct + + let input = "actions: { [not(inventoryFull())] retrieveItem(key), [] leave()}" + correct = Right (Entry (Tag "actions") $ Block [ + Entry (ConditionList [Not InventoryFull]) $ Regular $ Action $ RetrieveItem "key", + Entry (ConditionList []) $ Regular $ Action Leave + ], "") + parseWithRest structureElement input `shouldBe` correct + + it "can parse regulars" $ do + let input = "this is a string" + correct = Right $ Regular $ String input + parseWith regular ("\"" ++ input ++ "\"") `shouldBe` correct + + let correct = Right $ Regular $ Integer 1 + parseWith regular "1" `shouldBe` correct + + let correct = Right $ Regular Infinite + parseWith regular "infinite" `shouldBe` correct + + let wrong = Right $ Regular Infinite + parseWith regular "infinitee" `shouldNotBe` wrong + + let input = "leave()" + correct = Right $ Regular $ Action Leave + parseWith regular input `shouldBe` correct + + let input = "retrieveItem(firstId)" + correct = Right $ Regular $ Action $ RetrieveItem "firstId" + parseWith regular input `shouldBe` correct + + let input = "useItem(secondId)" + correct = Right $ Regular $ Action $ UseItem "secondId" + parseWith regular input `shouldBe` correct + + let input = "decreaseHp(entityId,objectId)" + correct = Right $ Regular $ Action $ DecreaseHp "entityId" "objectId" + parseWith regular input `shouldBe` correct + + let input = "increasePlayerHp(objectId)" + correct = Right $ Regular $ Action $ IncreasePlayerHp "objectId" + parseWith regular input `shouldBe` correct + + let input = "up" + correct = Right $ Regular $ Direction North + parseWith regular input `shouldBe` correct + + let input = "right" + correct = Right $ Regular $ Direction East + parseWith regular input `shouldBe` correct + + let input = "down" + correct = Right $ Regular $ Direction South + parseWith regular input `shouldBe` correct + + let input = "left" + correct = Right $ Regular $ Direction West + parseWith regular input `shouldBe` correct + + describe "Key" $ do + it "can parse tags" $ do + let input = "simpletag" + correct = Right $ Tag "simpletag" + parseWith tag input `shouldBe` correct + + it "can parse conditionlists" $ do + let input = "[not(inventoryFull())]" + correct = Right (ConditionList [Not InventoryFull], "") + parseWithRest conditionList input `shouldBe` correct + + let input = "[inventoryFull(), inventoryContains(itemId)]" + correct = Right (ConditionList [ + InventoryFull, + InventoryContains "itemId" + ], "") + parseWithRest conditionList input `shouldBe` correct + + let input = "[]" + correct = Right $ ConditionList [] + parseWith conditionList input `shouldBe` correct + + it "can parse conditions" $ do + let input = "inventoryFull()" + correct = Right (Condition InventoryFull, "") + parseWithRest condition input `shouldBe` correct + + let input = "inventoryContains(itemId)" + correct = Right (Condition $ InventoryContains "itemId", "") + parseWithRest condition input `shouldBe` correct + + let input = "not(inventoryFull())" + correct = Right (Condition $ Not InventoryFull, "") + parseWithRest condition input `shouldBe` correct + + let input = "not(inventoryContains(itemId))" + correct = Right (Condition $ Not $ InventoryContains "itemId", "") + parseWithRest condition input `shouldBe` correct + + describe "Value" $ do + it "can parse strings" $ do + let input = "dit is een string" + correct = Right $ String input + parseWith string ("\"" ++ input ++ "\"") `shouldBe` correct + + it "can parse integers" $ do + let correct = Right $ Integer 1 + parseWith integer "1" `shouldBe` correct + + it "can parse infinite" $ do + let correct = Right Infinite + parseWith infinite "infinite" `shouldBe` correct + + let wrong = Right Infinite + parseWith infinite "infinitee" `shouldNotBe` wrong + + it "can parse actions" $ do + let input = "leave()" + correct = Right $ Action Leave + parseWith action input `shouldBe` correct + + let input = "retrieveItem(firstId)" + correct = Right $ Action $ RetrieveItem "firstId" + parseWith action input `shouldBe` correct + + let input = "useItem(secondId)" + correct = Right $ Action $ UseItem "secondId" + parseWith action input `shouldBe` correct + + let input = "decreaseHp(entityId,objectId)" + correct = Right $ Action $ DecreaseHp "entityId" "objectId" + parseWith action input `shouldBe` correct + + let input = "increasePlayerHp(objectId)" + correct = Right $ Action $ IncreasePlayerHp "objectId" + parseWith action input `shouldBe` correct + + it "can parse directions" $ do + let input = "up" + correct = Right $ Direction North + parseWith RPGEngine.Parse.StructureElement.direction input `shouldBe` correct + + let input = "right" + correct = Right $ Direction East + parseWith RPGEngine.Parse.StructureElement.direction input `shouldBe` correct + + let input = "down" + correct = Right $ Direction South + parseWith RPGEngine.Parse.StructureElement.direction input `shouldBe` correct + + let input = "left" + correct = Right $ Direction West + parseWith RPGEngine.Parse.StructureElement.direction input `shouldBe` correct + + it "can parse layouts" $ do + let input = "| * * * * * * * *\n| * s . . . . e *\n| * * * * * * * *" + correct = Right $ Layout [ + [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked], + [Blocked, Entrance, Walkable, Walkable, Walkable, Walkable, Exit, Blocked], + [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked] + ] + parseWith RPGEngine.Parse.StructureElement.layout input `shouldBe` correct + + describe "Brackets" $ do + it "matches closing <" $ do + let input = '<' + correct = '>' + getMatchingClosingBracket input `shouldBe` correct + + it "matches closing (" $ do + let input = '(' + correct = ')' + getMatchingClosingBracket input `shouldBe` correct + + it "matches closing {" $ do + let input = '{' + correct = '}' + getMatchingClosingBracket input `shouldBe` correct + + it "matches closing [" $ do + let input = '[' + correct = ']' + getMatchingClosingBracket input `shouldBe` correct diff --git a/test/ParserSpec.hs b/test/ParserSpec.hs deleted file mode 100644 index 301b282..0000000 --- a/test/ParserSpec.hs +++ /dev/null @@ -1,132 +0,0 @@ -module ParserSpec where - -import Test.Hspec -import RPGEngine.Internals.Parse -import RPGEngine.Internals.Parse.StructureElement -import RPGEngine.Internals.Data.Internals -import Data.Either - -spec :: Spec -spec = do - describe "Basics of entries" $ do - it "can parse integers" $ do - let correct = Right $ Regular $ Integer 1 - parseWith regular "1" `shouldBe` correct - it "can parse string" $ do - let input = "dit is een string" - correct = Right $ Regular $ String input - parseWith regular ("\"" ++ input ++ "\"") `shouldBe` correct - it "can parse infinite" $ do - let correct = Right $ Regular Infinite - parseWith regular "infinite" `shouldBe` correct - - let wrong = Right $ Regular Infinite - parseWith regular "infinitee" `shouldNotBe` wrong - - it "can parse entries" $ do - let input = "id: \"dagger\"" - correct = Right $ Entry (Tag "id") $ Regular $ String "dagger" - parseWith entry input `shouldBe` correct - - let input = "x: 0" - correct = Right $ Entry (Tag "x") $ Regular $ Integer 0 - parseWith entry input `shouldBe` correct - - let input = "useTimes: infinite" - correct = Right $ Entry (Tag "useTimes") $ Regular Infinite - parseWith entry input `shouldBe` correct - - describe "block: {...}" $ do - it "can parse a block with a single entry" $ do - let input = "{ id: 1}" - correct = Right (Block [ - Entry (Tag "id") $ Regular $ Integer 1 - ], "") - parseWithRest structureElement input `shouldBe` correct - - it "can parse a block with entries" $ do - let input = "{ id: \"key\", x: 3, y: 1}" - correct = Right $ Block [ - Entry (Tag "id") $ Regular $ String "key", - Entry (Tag "x") $ Regular $ Integer 3, - Entry (Tag "y") $ Regular $ Integer 1 - ] - parseWith structureElement input `shouldBe` correct - - describe "Basics" $ do - it "can parse leave()" $ do - let input = "leave()" - correct = Right $ Action Leave - parseWith action input `shouldBe` correct - - it "can parse retrieveItem()" $ do - let input = "retrieveItem(firstId)" - correct = Right $ Action $ RetrieveItem "firstId" - parseWith action input `shouldBe` correct - - it "can parse useItem()" $ do - let input = "useItem(secondId)" - correct = Right $ Action $ UseItem "secondId" - parseWith action input `shouldBe` correct - - it "can parse decreaseHp()" $ do - let input = "decreaseHp(entityId,objectId)" - correct = Right $ Action $ DecreaseHp "entityId" "objectId" - parseWith action input `shouldBe` correct - - it "can parse increasePlayerHp()" $ do - let input = "increasePlayerHp(objectId)" - correct = Right $ Action $ IncreasePlayerHp "objectId" - parseWith action input `shouldBe` correct - - it "can parse inventoryFull()" $ do - let input = "inventoryFull()" - correct = Right (Condition InventoryFull, "") - parseWithRest condition input `shouldBe` correct - - it "can parse inventoryContains()" $ do - let input = "inventoryContains(itemId)" - correct = Right (Condition $ InventoryContains "itemId", "") - parseWithRest condition input `shouldBe` correct - - it "can parse not()" $ do - let input = "not(inventoryFull())" - correct = Right (Condition $ Not InventoryFull, "") - parseWithRest condition input `shouldBe` correct - - let input = "not(inventoryContains(itemId))" - correct = Right (Condition $ Not $ InventoryContains "itemId", "") - parseWithRest condition input `shouldBe` correct - - it "can parse conditionlists" $ do - let input = "[not(inventoryFull())]" - correct = Right (ConditionList [Not InventoryFull], "") - parseWithRest conditionList input `shouldBe` correct - - let input = "[inventoryFull(), inventoryContains(itemId)]" - correct = Right (ConditionList [ - InventoryFull, - InventoryContains "itemId" - ], "") - parseWithRest conditionList input `shouldBe` correct - - let input = "[]" - correct = Right $ ConditionList [] - parseWith conditionList input `shouldBe` correct - - it "can parse actions" $ do - let input = "actions: { [not(inventoryFull())] retrieveItem(key), [] leave()}" - correct = Right (Entry (Tag "actions") $ Block [ - Entry (ConditionList [Not InventoryFull]) $ Regular $ Action $ RetrieveItem "key", - Entry (ConditionList []) $ Regular $ Action Leave - ], "") - parseWithRest structureElement input `shouldBe` correct - - describe "Layouts" $ do - it "can parse layouts" $ do - pending - - describe "Lists and blocks" $ do - it "can parse entities" $ do - pending - From de02c7113f901032c07c24e3011146c0c1b5fc45 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Tue, 20 Dec 2022 19:53:40 +0100 Subject: [PATCH 12/34] #18 Started conversion to Game --- lib/RPGEngine.hs | 2 +- lib/RPGEngine/Data.hs | 12 +- lib/RPGEngine/Data/Defaults.hs | 60 ++++++++ lib/RPGEngine/Data/Game.hs | 25 ---- lib/RPGEngine/Data/State.hs | 7 - lib/RPGEngine/Parse.hs | 8 +- lib/RPGEngine/Parse/Game.hs | 97 +++++++++++-- .../{StructureElement.hs => StructElement.hs} | 29 ++-- rpg-engine.cabal | 6 +- test/ParseGameSpec.hs | 132 ++++++++++++++---- ...ementSpec.hs => ParseStructElementSpec.hs} | 34 ++--- 11 files changed, 300 insertions(+), 112 deletions(-) create mode 100644 lib/RPGEngine/Data/Defaults.hs delete mode 100644 lib/RPGEngine/Data/Game.hs rename lib/RPGEngine/Parse/{StructureElement.hs => StructElement.hs} (89%) rename test/{ParseStructureElementSpec.hs => ParseStructElementSpec.hs} (91%) diff --git a/lib/RPGEngine.hs b/lib/RPGEngine.hs index ee52fc9..f9372e7 100644 --- a/lib/RPGEngine.hs +++ b/lib/RPGEngine.hs @@ -5,7 +5,7 @@ module RPGEngine ( playRPGEngine ) where -import RPGEngine.Data.Game +import RPGEngine.Data.Defaults import RPGEngine.Render import RPGEngine.Input diff --git a/lib/RPGEngine/Data.hs b/lib/RPGEngine/Data.hs index b91b7f2..13be117 100644 --- a/lib/RPGEngine/Data.hs +++ b/lib/RPGEngine/Data.hs @@ -16,7 +16,7 @@ data Level = Level { layout :: Layout, items :: [Item], entities :: [Entity] -} +} deriving (Eq, Show) type Layout = [Strip] type Strip = [Physical] @@ -26,14 +26,14 @@ data Physical = Void | Blocked | Entrance | Exit - deriving (Show, Eq) + deriving (Eq, Show) ------------------------------- Player ------------------------------- data Player = Player { playerHp :: Maybe Int, inventory :: [Item] -} +} deriving (Eq, Show) instance Living Player where hp = playerHp @@ -70,7 +70,7 @@ data Item = Item { itemActions :: [([Condition], Action)], itemValue :: Maybe Int, useTimes :: Maybe Int -} +} deriving (Eq, Show) instance Object Item where id = itemId @@ -90,8 +90,8 @@ data Entity = Entity { entityActions :: [([Condition], Action)], entityValue :: Maybe Int, entityHp :: Maybe Int, - direction :: Maybe Direction -} + direction :: Direction +} deriving (Eq, Show) instance Object Entity where id = entityId diff --git a/lib/RPGEngine/Data/Defaults.hs b/lib/RPGEngine/Data/Defaults.hs new file mode 100644 index 0000000..2ef92f2 --- /dev/null +++ b/lib/RPGEngine/Data/Defaults.hs @@ -0,0 +1,60 @@ +module RPGEngine.Data.Defaults where + +import RPGEngine.Data + +defaultEntity :: Entity +defaultEntity = Entity { + entityId = "", + entityX = 0, + entityY = 0, + entityName = "Default", + entityDescription = "", + entityActions = [], + entityValue = Prelude.Nothing, + entityHp = Prelude.Nothing, + direction = Center +} + +-- Initialize the game +initGame :: Game +initGame = Game { + state = defaultState, + playing = defaultLevel, + levels = [defaultLevel] +} + +defaultItem :: Item +defaultItem = Item { + itemId = "", + itemX = 0, + itemY = 0, + itemName = "Default", + itemDescription = "", + itemActions = [], + itemValue = Prelude.Nothing, + useTimes = Prelude.Nothing +} + +defaultLayout :: Layout +defaultLayout = [ + [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked], + [Blocked, Entrance, Walkable, Walkable, Walkable, Walkable, Exit, Blocked], + [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked] + ] + +defaultLevel :: Level +defaultLevel = Level { + layout = defaultLayout, + items = [], + entities = [] +} + +defaultPlayer :: Player +defaultPlayer = Player { + playerHp = Prelude.Nothing, -- Compares to infinity + inventory = [] +} + +-- Default state of the game, Menu +defaultState :: State +defaultState = Menu \ No newline at end of file diff --git a/lib/RPGEngine/Data/Game.hs b/lib/RPGEngine/Data/Game.hs deleted file mode 100644 index 82750f6..0000000 --- a/lib/RPGEngine/Data/Game.hs +++ /dev/null @@ -1,25 +0,0 @@ --- Representation of all the game's data - -module RPGEngine.Data.Game -( Game(..) - ,initGame -) where - -import RPGEngine.Data -import RPGEngine.Data.State - ----------------------------------------------------------------------- - --- Initialize the game -initGame :: Game -initGame = Game { - state = defaultState, - playing = head levels, - levels = levels -} - where levels = [emptyLevel] - emptyLevel = Level { - layout = [], - items = [], - entities = [] - } diff --git a/lib/RPGEngine/Data/State.hs b/lib/RPGEngine/Data/State.hs index 42d9d3f..057bc9e 100644 --- a/lib/RPGEngine/Data/State.hs +++ b/lib/RPGEngine/Data/State.hs @@ -4,19 +4,12 @@ module RPGEngine.Data.State ( State(..) -, defaultState , nextState ) where import RPGEngine.Data ------------------------------ Constants ------------------------------ - --- Default state of the game, Menu -defaultState :: State -defaultState = Menu - ---------------------------------------------------------------------- -- Get the next state based on the current state diff --git a/lib/RPGEngine/Parse.hs b/lib/RPGEngine/Parse.hs index f5838d3..a8736ad 100644 --- a/lib/RPGEngine/Parse.hs +++ b/lib/RPGEngine/Parse.hs @@ -1,7 +1,7 @@ module RPGEngine.Parse where import RPGEngine.Data -import RPGEngine.Parse.StructureElement +import RPGEngine.Parse.StructElement import RPGEngine.Parse.Game import Text.Parsec.String @@ -14,6 +14,6 @@ type FileName = String ---------------------------------------------------------------------- parseToGame :: FileName -> Game -parseToGame filename = structureToGame structure - where (Right structure) = unsafePerformIO io - io = parseFromFile structureElement filename \ No newline at end of file +parseToGame filename = structToGame struct + where (Right struct) = unsafePerformIO io + io = parseFromFile structElement filename \ No newline at end of file diff --git a/lib/RPGEngine/Parse/Game.hs b/lib/RPGEngine/Parse/Game.hs index 85028f2..9999ffd 100644 --- a/lib/RPGEngine/Parse/Game.hs +++ b/lib/RPGEngine/Parse/Game.hs @@ -1,22 +1,101 @@ module RPGEngine.Parse.Game where import RPGEngine.Data -import RPGEngine.Parse.StructureElement (StructureElement) +import RPGEngine.Data.Defaults +import RPGEngine.Parse.StructElement -------------------------------- Game -------------------------------- -- TODO -structureToGame :: StructureElement -> Game -structureToGame = undefined +structToGame :: StructElement -> Game +structToGame = undefined ------------------------------- Player ------------------------------- --- TODO -structureToPlayer :: StructureElement -> Player -structureToPlayer = undefined +structToPlayer :: StructElement -> Player +structToPlayer (Block block) = structToPlayer' block defaultPlayer +structToPlayer _ = defaultPlayer + +structToPlayer' :: [StructElement] -> Player -> Player +structToPlayer' [] p = p +structToPlayer' ((Entry(Tag "hp") val ):es) p = (structToPlayer' es p){ playerHp = structToMaybeInt val } +structToPlayer' ((Entry(Tag "inventory") (Block inv)):es) p = (structToPlayer' es p){ inventory = structToItems inv } +structToPlayer' _ _ = defaultPlayer + +structToActions :: StructElement -> [([Condition], Action)] +structToActions (Block []) = [] +structToActions (Block block) = structToActions' block [] +structToActions _ = [] + +structToActions' :: [StructElement] -> [([Condition], Action)] -> [([Condition], Action)] +structToActions' [] list = list +structToActions' ((Entry(ConditionList cs) (Regular (Action a))):as) list = structToActions' as ((cs, a):list) +structToActions' _ list = list ------------------------------- Levels ------------------------------- --- TODO -structureToLevels :: StructureElement -> [Level] -structureToLevels = undefined \ No newline at end of file +structToLevels :: StructElement -> [Level] +structToLevels (Block struct) = structToLevel <$> struct +structToLevels _ = [defaultLevel] + +structToLevel :: StructElement -> Level +structToLevel (Block entries) = structToLevel' entries defaultLevel +structToLevel _ = defaultLevel + +structToLevel' :: [StructElement] -> Level -> Level +structToLevel' ((Entry(Tag "layout") (Regular (Layout layout))):ls) l = (structToLevel' ls l){ RPGEngine.Data.layout = layout } +structToLevel' ((Entry(Tag "items") (Block items) ):ls) l = (structToLevel' ls l){ items = structToItems items } +structToLevel' ((Entry(Tag "entities") (Block entities) ):ls) l = (structToLevel' ls l){ entities = structToEntities entities } +structToLevel' _ _ = defaultLevel + +------------------------------- Items -------------------------------- + +structToItems :: [StructElement] -> [Item] +structToItems items = structToItem <$> items + +structToItem :: StructElement -> Item +structToItem (Block block) = structToItem' block defaultItem +structToItem _ = defaultItem + +structToItem' :: [StructElement] -> Item -> Item +structToItem' [] i = i +structToItem' ((Entry(Tag "id") (Regular(String id ))):is) i = (structToItem' is i){ itemId = id } +structToItem' ((Entry(Tag "x") (Regular(Integer x ))):is) i = (structToItem' is i){ itemX = x } +structToItem' ((Entry(Tag "y") (Regular(Integer y ))):is) i = (structToItem' is i){ itemY = y } +structToItem' ((Entry(Tag "name") (Regular(String name))):is) i = (structToItem' is i){ itemName = name } +structToItem' ((Entry(Tag "description") (Regular(String desc))):is) i = (structToItem' is i){ itemDescription = desc } +structToItem' ((Entry(Tag "value") val ):is) i = (structToItem' is i){ itemValue = structToMaybeInt val } +structToItem' ((Entry(Tag "actions") actions ):is) i = (structToItem' is i){ itemActions = structToActions actions } +structToItem' ((Entry (Tag "useTimes") useTimes ):is) i = (structToItem' is i){ useTimes = structToMaybeInt useTimes } +structToItem' _ _ = defaultItem + +------------------------------ Entities ------------------------------ + +structToEntities :: [StructElement] -> [Entity] +structToEntities entities = structToEntity <$> entities + +structToEntity :: StructElement -> Entity +structToEntity (Block block) = structToEntity' block defaultEntity +structToEntity _ = defaultEntity + +structToEntity' :: [StructElement] -> Entity -> Entity +structToEntity' [] e = e +structToEntity' ((Entry(Tag "id") (Regular(String id )) ):es) e = (structToEntity' es e){ entityId = id } +structToEntity' ((Entry(Tag "x") (Regular(Integer x )) ):es) e = (structToEntity' es e){ entityX = x } +structToEntity' ((Entry(Tag "y") (Regular(Integer y )) ):es) e = (structToEntity' es e){ entityY = y } +structToEntity' ((Entry(Tag "name") (Regular(String name)) ):es) e = (structToEntity' es e){ entityName = name } +structToEntity' ((Entry(Tag "description") (Regular(String desc)) ):es) e = (structToEntity' es e){ entityDescription = desc } +structToEntity' ((Entry(Tag "actions") actions ):es) e = (structToEntity' es e){ entityActions = structToActions actions } +structToEntity' ((Entry(Tag "value") val ):es) e = (structToEntity' es e){ entityValue = structToMaybeInt val } +structToEntity' ((Entry(Tag "hp") val ):es) e = (structToEntity' es e){ entityHp = structToMaybeInt val } +structToEntity' ((Entry(Tag "direction") (Regular(Direction dir))):es) e = (structToEntity' es e){ RPGEngine.Data.direction = dir } +structToEntity' _ _ = defaultEntity + +---------------------------------------------------------------------- + +structToMaybeInt :: StructElement -> Maybe Int +structToMaybeInt (Regular (Integer val)) = Just val +structToMaybeInt (Regular Infinite) = Prelude.Nothing +structToMaybeInt _ = Prelude.Nothing -- TODO + +---------------------------------------------------------------------- \ No newline at end of file diff --git a/lib/RPGEngine/Parse/StructureElement.hs b/lib/RPGEngine/Parse/StructElement.hs similarity index 89% rename from lib/RPGEngine/Parse/StructureElement.hs rename to lib/RPGEngine/Parse/StructElement.hs index ce5d8c5..35d2b08 100644 --- a/lib/RPGEngine/Parse/StructureElement.hs +++ b/lib/RPGEngine/Parse/StructElement.hs @@ -1,4 +1,4 @@ -module RPGEngine.Parse.StructureElement where +module RPGEngine.Parse.StructElement where import RPGEngine.Data (Action(..), Condition(..), Layout, Direction(..), Physical(..), Strip) import RPGEngine.Parse.Core ( ignoreWS ) @@ -18,24 +18,23 @@ import Text.Parsec sepBy ) import qualified Text.Parsec as P ( string ) import Text.Parsec.String ( Parser ) -import GHC.IO.Device (RawIO(readNonBlocking)) -------------------------- StructureElement -------------------------- -- See documentation for more details, only a short description is -- provided here. -data StructureElement = Block [StructureElement] - | Entry Key StructureElement -- Key + Value - | Regular Value -- Regular value, Integer or String or Infinite - deriving (Show, Eq) +data StructElement = Block [StructElement] + | Entry Key StructElement -- Key + Value + | Regular Value -- Regular value, Integer or String or Infinite + deriving (Eq, Show) ---------------------------------------------------------------------- -structureElement :: Parser StructureElement -structureElement = try $ choice [block, entry, regular] +structElement :: Parser StructElement +structElement = try $ choice [block, entry, regular] -- A list of entries -block :: Parser StructureElement +block :: Parser StructElement block = try $ do open <- ignoreWS $ oneOf openingBrackets middle <- ignoreWS $ choice [entry, block] `sepBy` char ',' @@ -43,26 +42,26 @@ block = try $ do ignoreWS $ char closingBracket return $ Block middle -entry :: Parser StructureElement +entry :: Parser StructElement entry = try $ do key <- ignoreWS key -- TODO Fix this oneOf ": " -- Can be left out - value <- ignoreWS structureElement + value <- ignoreWS structElement return $ Entry key value -regular :: Parser StructureElement +regular :: Parser StructElement regular = try $ Regular <$> value --------------------------------- Key -------------------------------- data Key = Tag String | ConditionList [Condition] - deriving (Show, Eq) + deriving (Eq, Show) data ConditionArgument = ArgString String | Condition Condition - deriving (Show, Eq) + deriving (Eq, Show) ---------------------------------------------------------------------- @@ -104,7 +103,7 @@ data Value = String String | Action Action | Direction Direction | Layout Layout - deriving (Show, Eq) + deriving (Eq, Show) ---------------------------------------------------------------------- diff --git a/rpg-engine.cabal b/rpg-engine.cabal index f6a304b..0823b23 100644 --- a/rpg-engine.cabal +++ b/rpg-engine.cabal @@ -14,7 +14,7 @@ library RPGEngine RPGEngine.Data - RPGEngine.Data.Game + RPGEngine.Data.Defaults RPGEngine.Data.State RPGEngine.Input @@ -23,7 +23,7 @@ library RPGEngine.Parse RPGEngine.Parse.Core RPGEngine.Parse.Game - RPGEngine.Parse.StructureElement + RPGEngine.Parse.StructElement RPGEngine.Render @@ -41,4 +41,4 @@ test-suite rpg-engine-test build-depends: base >=4.7 && <5, hspec <= 2.10.6, hspec-discover, rpg-engine other-modules: -- Parsing - ParseGameSpec, ParseStructureElementSpec + ParseGameSpec, ParseStructElementSpec diff --git a/test/ParseGameSpec.hs b/test/ParseGameSpec.hs index a162246..2a2d7d2 100644 --- a/test/ParseGameSpec.hs +++ b/test/ParseGameSpec.hs @@ -1,7 +1,10 @@ module ParseGameSpec where import Test.Hspec -import RPGEngine.Parse.StructureElement +import RPGEngine.Parse.StructElement +import RPGEngine.Data +import RPGEngine.Parse.Core +import RPGEngine.Parse.Game spec :: Spec spec = do @@ -14,39 +17,118 @@ spec = do pending describe "Player" $ do - it "TODO: Simple player" $ do - pending + it "cannot die" $ do + let input = "player: { hp: infinite, inventory: [] }" + correct = Player { + playerHp = Prelude.Nothing, + inventory = [] + } + Right (Entry (Tag "player") struct) = parseWith structElement input + structToPlayer struct `shouldBe` correct + + it "without inventory" $ do + let input = "player: { hp: 50, inventory: [] }" + correct = Player { + playerHp = Just 50, + inventory = [] + } + Right (Entry (Tag "player") struct) = parseWith structElement input + structToPlayer struct `shouldBe` correct + + it "with inventory" $ do + let input = "player: { hp: 50, inventory: [ { id: \"dagger\", x: 0, y: 0, name: \"Dolk\", description: \"Basis schade tegen monsters\", useTimes: infinite, value: 10, actions: {} } ] }" + correct = Player { + playerHp = Just 50, + inventory = [ + Item { + itemId = "dagger", + itemX = 0, + itemY = 0, + itemName = "Dolk", + itemDescription = "Basis schade tegen monsters", + itemActions = [], + itemValue = Just 10, + useTimes = Prelude.Nothing + } + ] + } + Right (Entry (Tag "player") struct) = parseWith structElement input + structToPlayer struct `shouldBe` correct - describe "Inventory" $ do - it "TODO: Empty inventory" $ do + describe "Layout" $ do + it "simple" $ do pending - it "TODO: Singleton inventory" $ do - pending - it "TODO: Filled inventory" $ do - pending - + describe "Items" $ do - it "TODO: Simple item" $ do - pending - -- Check id - -- Check x - -- Check y - -- Check name - -- Check description - -- Check useTimes - -- Check value - -- Check actions + it "simple" $ do + let input = "{ id: \"dagger\", x: 0, y: 0, name: \"Dagger\", description: \"Basic dagger you found somewhere\", useTimes: infinite, value: 10, actions: {} }" + correct = Item { + itemId = "dagger", + itemX = 0, + itemY = 0, + itemName = "Dagger", + itemDescription = "Basic dagger you found somewhere", + itemValue = Just 10, + itemActions = [], + useTimes = Prelude.Nothing + } + Right struct = parseWith structElement input + structToItem struct `shouldBe` correct + + it "with actions" $ do + let input = "{ id: \"key\", x: 3, y: 1, name: \"Doorkey\", description: \"Unlocks a secret door\", useTimes: 1, value: 0, actions: { [not(inventoryFull())] retrieveItem(key), [] leave() } }" + correct = Item { + itemId = "key", + itemX = 3, + itemY = 1, + itemName = "Doorkey", + itemDescription = "Unlocks a secret door", + itemActions = [ + ([], Leave), + ([Not InventoryFull], RetrieveItem "key") + ], + itemValue = Just 0, + useTimes = Just 1 + } + Right struct = parseWith structElement input + structToItem struct `shouldBe` correct describe "Actions" $ do - it "TODO: Simple action" $ do - pending - + it "no conditions" $ do + let input = "{[] leave()}" + correct = [([], Leave)] + Right struct = parseWith structElement input + structToActions struct `shouldBe` correct + + it "single condition" $ do + let input = "{ [inventoryFull()] useItem(itemId)}" + correct = [([InventoryFull], UseItem "itemId")] + Right struct = parseWith structElement input + structToActions struct `shouldBe` correct + + it "multiple conditions" $ do + let input = "{ [not(inventoryFull()), inventoryContains(itemId)] increasePlayerHp(itemId)}" + correct = [([Not InventoryFull, InventoryContains "itemId"], IncreasePlayerHp "itemId")] + Right struct = parseWith structElement input + structToActions struct `shouldBe` correct + describe "Entities" $ do it "TODO: Simple entity" $ do pending describe "Level" $ do - it "TODO: Simple layout" $ do - pending + it "Simple layout" $ do + let input = "{ layout: { | * * * * * * \n| * s . . e *\n| * * * * * *\n}, items: [], entities: [] }" + correct = Level { + RPGEngine.Data.layout = [ + [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked], + [Blocked, Entrance, Walkable, Walkable, Exit, Blocked], + [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked] + ], + items = [], + entities = [] + } + Right struct = parseWith structElement input + structToLevel struct `shouldBe` correct it "TODO: Complex layout" $ do pending \ No newline at end of file diff --git a/test/ParseStructureElementSpec.hs b/test/ParseStructElementSpec.hs similarity index 91% rename from test/ParseStructureElementSpec.hs rename to test/ParseStructElementSpec.hs index bc70837..0f7464a 100644 --- a/test/ParseStructureElementSpec.hs +++ b/test/ParseStructElementSpec.hs @@ -1,10 +1,10 @@ -module ParseStructureElementSpec where +module ParseStructElementSpec where import Test.Hspec import RPGEngine.Data import RPGEngine.Parse.Core -import RPGEngine.Parse.StructureElement +import RPGEngine.Parse.StructElement spec :: Spec spec = do @@ -12,21 +12,21 @@ spec = do it "can parse blocks" $ do let input = "{}" correct = Right $ Block [] - parseWith structureElement input `shouldBe` correct + parseWith structElement input `shouldBe` correct let input = "{{}}" correct = Right $ Block [Block []] - parseWith structureElement input `shouldBe` correct + parseWith structElement input `shouldBe` correct let input = "{{}, {}}" correct = Right $ Block [Block [], Block []] - parseWith structureElement input `shouldBe` correct + parseWith structElement input `shouldBe` correct let input = "{ id: 1 }" correct = Right (Block [ Entry (Tag "id") $ Regular $ Integer 1 ], "") - parseWithRest structureElement input `shouldBe` correct + parseWithRest structElement input `shouldBe` correct let input = "{ id: \"key\", x: 3, y: 1}" correct = Right $ Block [ @@ -34,14 +34,14 @@ spec = do Entry (Tag "x") $ Regular $ Integer 3, Entry (Tag "y") $ Regular $ Integer 1 ] - parseWith structureElement input `shouldBe` correct + parseWith structElement input `shouldBe` correct let input = "actions: { [not(inventoryFull())] retrieveItem(key), [] leave()}" correct = Right (Entry (Tag "actions") $ Block [ Entry (ConditionList [Not InventoryFull]) $ Regular $ Action $ RetrieveItem "key", Entry (ConditionList []) $ Regular $ Action Leave ], "") - parseWithRest structureElement input `shouldBe` correct + parseWithRest structElement input `shouldBe` correct let input = "entities: [ { id: \"door\", x: 4, name:\"Secret door\", description: \"This secret door can only be opened with a key\", direction: left, y: 1}]" correct = Right (Entry (Tag "entities") $ Block [ Block [ @@ -52,7 +52,7 @@ spec = do Entry (Tag "direction") $ Regular $ Direction West, Entry (Tag "y") $ Regular $ Integer 1 ]], "") - parseWithRest structureElement input `shouldBe` correct + parseWithRest structElement input `shouldBe` correct let input = "entities: [ { id: \"door\", x: 4, y: 1, name:\"Secret door\", description: \"This secret door can only be opened with a key\", actions: { [inventoryContains(key)] useItem(key), [] leave() } } ]" correct = Right (Entry (Tag "entities") $ Block [ Block [ @@ -66,7 +66,7 @@ spec = do Entry (ConditionList []) $ Regular $ Action Leave ] ]], "") - parseWithRest structureElement input `shouldBe` correct + parseWithRest structElement input `shouldBe` correct let input = "entities: [ { id: \"door\", x: 4, y: 1, name:\"Secret door\", description: \"This secret door can only be opened with a key\", direction: left , actions: { [inventoryContains(key)] useItem(key), [] leave() } } ]" correct = Right (Entry (Tag "entities") $ Block [ Block [ @@ -81,7 +81,7 @@ spec = do Entry (ConditionList []) $ Regular $ Action Leave ] ]], "") - parseWithRest structureElement input `shouldBe` correct + parseWithRest structElement input `shouldBe` correct it "can parse entries" $ do let input = "id: \"dagger\"" @@ -105,7 +105,7 @@ spec = do Entry (ConditionList [Not InventoryFull]) $ Regular $ Action $ RetrieveItem "key", Entry (ConditionList []) $ Regular $ Action Leave ], "") - parseWithRest structureElement input `shouldBe` correct + parseWithRest structElement input `shouldBe` correct it "can parse regulars" $ do let input = "this is a string" @@ -237,19 +237,19 @@ spec = do it "can parse directions" $ do let input = "up" correct = Right $ Direction North - parseWith RPGEngine.Parse.StructureElement.direction input `shouldBe` correct + parseWith RPGEngine.Parse.StructElement.direction input `shouldBe` correct let input = "right" correct = Right $ Direction East - parseWith RPGEngine.Parse.StructureElement.direction input `shouldBe` correct + parseWith RPGEngine.Parse.StructElement.direction input `shouldBe` correct let input = "down" correct = Right $ Direction South - parseWith RPGEngine.Parse.StructureElement.direction input `shouldBe` correct + parseWith RPGEngine.Parse.StructElement.direction input `shouldBe` correct let input = "left" correct = Right $ Direction West - parseWith RPGEngine.Parse.StructureElement.direction input `shouldBe` correct + parseWith RPGEngine.Parse.StructElement.direction input `shouldBe` correct it "can parse layouts" $ do let input = "| * * * * * * * *\n| * s . . . . e *\n| * * * * * * * *" @@ -258,7 +258,7 @@ spec = do [Blocked, Entrance, Walkable, Walkable, Walkable, Walkable, Exit, Blocked], [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked] ] - parseWith RPGEngine.Parse.StructureElement.layout input `shouldBe` correct + parseWith RPGEngine.Parse.StructElement.layout input `shouldBe` correct describe "Brackets" $ do it "matches closing <" $ do From fb4bc5bb36cfaf78c5fd619dde6f408edfbaa4b9 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Tue, 20 Dec 2022 22:52:06 +0100 Subject: [PATCH 13/34] #3 #2 Player render and movement --- assets/entities/player.png | Bin 0 -> 1548 bytes assets/unkown.png | Bin 0 -> 870 bytes lib/RPGEngine/Data.hs | 9 +++-- lib/RPGEngine/Data/Defaults.hs | 10 +++--- lib/RPGEngine/Input.hs | 15 +++++++- lib/RPGEngine/Input/Player.hs | 19 ++++++++++ lib/RPGEngine/Render.hs | 15 ++++++-- lib/RPGEngine/Render/Core.hs | 63 +++++++++++++++++++++++++++++++++ lib/RPGEngine/Render/GUI.hs | 10 ++++++ lib/RPGEngine/Render/Level.hs | 10 ++++++ lib/RPGEngine/Render/Player.hs | 11 ++++++ rpg-engine.cabal | 5 +++ 12 files changed, 158 insertions(+), 9 deletions(-) create mode 100644 assets/entities/player.png create mode 100644 assets/unkown.png create mode 100644 lib/RPGEngine/Input/Player.hs create mode 100644 lib/RPGEngine/Render/Core.hs create mode 100644 lib/RPGEngine/Render/GUI.hs create mode 100644 lib/RPGEngine/Render/Level.hs create mode 100644 lib/RPGEngine/Render/Player.hs diff --git a/assets/entities/player.png b/assets/entities/player.png new file mode 100644 index 0000000000000000000000000000000000000000..512b550531a4c32d733baebadb7e7f63c18d5e21 GIT binary patch literal 1548 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4(FKU~I{Bb`J1#c2+1T%1_J8No8Qr zm{>c}+T(D5NZbEqUF*A=U0r01LezJ!=ty4cB&gLAwJ?-@^6Y&JJe?;!dZe*KOr}OB zOg3gtX=#Bd%R%;z#giv5>Yh@O7nC9Rxb^VgAMgM2KmR8#@G5m#q^zOCm41p% zm}P$9wNs(j=1ksiFy(Uiw4HvYT`Xc7Svrk^0$e#$Z**K*H#tes^?9NE|BlzwPb{6V zn#HY;HDrNf+205CAFQLb?M+{navg0vCw}~=1oN)4Mz^zt`}$><7c#NTZ`9uVbp4ga zoBo%wb#;qacygG?NMQuI$ga#rU~}M5|gZGcd47d%8G=SO_u-u)P1x2vmX4}t^>p=fS?83{ F1OUsVNech~ literal 0 HcmV?d00001 diff --git a/assets/unkown.png b/assets/unkown.png new file mode 100644 index 0000000000000000000000000000000000000000..005de405e5848326d1858dc30647bb1e0114870e GIT binary patch literal 870 zcmV-s1DX7ZP)i%R0j>_=3B$BX!ZHof^^&Q0Te#QA zCRc?Ig@cw1D14=Kz0S{yGj)DfY$f6|;8s1gObhERY-Fvx@QCnWK36sx^QbTJr3&+N zQu}2OU=t@o7!92=Yvd>ql3I|={`QU5e6tkAW60a2f+da&S@8x%Elbyw)U@a+d2&=# z?!WW2)8k|H?!GXzKRM3Z5O{asyXrV!M~?G-44y~0C^Y>=$!YRK^lDQZJOKYDoLx3G zeHYHRAn;%&tz=E|$abgG@Olc}i_mieoy+yC8t0tcLt;aI(KYPt!?4` game{ state = Pause }) + -- Pause the game + handleKey (Char 'p') (\game -> game{ state = Pause }), + + -- Player movement + handleKey (SpecialKey KeyUp) $ movePlayer North, + handleKey (SpecialKey KeyRight) $ movePlayer East, + handleKey (SpecialKey KeyDown) $ movePlayer South, + handleKey (SpecialKey KeyLeft) $ movePlayer West, + + handleKey (Char 'w') $ movePlayer North, + handleKey (Char 'd') $ movePlayer East, + handleKey (Char 's') $ movePlayer South, + handleKey (Char 'a') $ movePlayer West ] -- Go to the next stage of the Game diff --git a/lib/RPGEngine/Input/Player.hs b/lib/RPGEngine/Input/Player.hs new file mode 100644 index 0000000..3b77917 --- /dev/null +++ b/lib/RPGEngine/Input/Player.hs @@ -0,0 +1,19 @@ +module RPGEngine.Input.Player +( movePlayer +) where + +import RPGEngine.Data (Game(..), Direction(..), Player(..), X, Y) + +movePlayer :: Direction -> Game -> Game +movePlayer dir g@Game{ player = p@Player{ coord = (x, y) }} = newGame + where newGame = g{ player = newPlayer } + newPlayer = p{ coord = newCoord } + newCoord = (x + xD, y + yD) + (xD, yD) = diffs dir + +diffs :: Direction -> (X, Y) +diffs North = (0, 1) +diffs East = (1, 0) +diffs South = (0, -1) +diffs West = (-1, 0) +diffs Center = (0, 0) diff --git a/lib/RPGEngine/Render.hs b/lib/RPGEngine/Render.hs index 03e7855..1468f77 100644 --- a/lib/RPGEngine/Render.hs +++ b/lib/RPGEngine/Render.hs @@ -8,7 +8,15 @@ module RPGEngine.Render ) where import RPGEngine.Data + ( State(..), + Game(..) ) +import RPGEngine.Render.Level + ( renderLevel ) + import Graphics.Gloss + ( white, pictures, text, Display(InWindow), Color, Picture ) +import RPGEngine.Render.Player (renderPlayer) +import RPGEngine.Render.GUI (renderGUI) ----------------------------- Constants ------------------------------ @@ -36,9 +44,12 @@ render g@Game{ state = Lose } = renderLose g renderMenu :: Game -> Picture renderMenu _ = text "[Press any key to start]" --- TODO renderPlaying :: Game -> Picture -renderPlaying _ = text "Playing" +renderPlaying g@Game{ playing = lvl, player = player } = pictures [ + renderLevel lvl, + renderPlayer player, + renderGUI g + ] -- TODO renderPause :: Game -> Picture diff --git a/lib/RPGEngine/Render/Core.hs b/lib/RPGEngine/Render/Core.hs new file mode 100644 index 0000000..0fe63ab --- /dev/null +++ b/lib/RPGEngine/Render/Core.hs @@ -0,0 +1,63 @@ +module RPGEngine.Render.Core where + +import Graphics.Gloss ( Picture, translate ) +import GHC.IO (unsafePerformIO) +import Graphics.Gloss.Juicy (loadJuicyPNG) +import Data.Maybe (fromJust) +import Graphics.Gloss.Data.Picture (scale) + +----------------------------- Constants ------------------------------ + +-- Default scale +zoom :: Float +zoom = 5.0 + +-- Resolution of the texture +resolution :: Float +resolution = 16 + +assetsFolder :: FilePath +assetsFolder = "assets/" + +unknownImage :: FilePath +unknownImage = "unkown.png" + +allEntities :: [(String, FilePath)] +allEntities = [ + ("player", "player.png"), + ("door", "door.png") + ] + +allItems :: [(String, FilePath)] +allItems = [ + ("dagger", "dagger.png"), + ("key", "key.png" ) + ] + +-- Map of all renders +library :: [(String, Picture)] +library = unknown:entities ++ environment ++ gui ++ items + where unknown = ("unkown", renderPNG (assetsFolder ++ unknownImage)) + entities = map (\(f, s) -> (f, renderPNG (assetsFolder ++ "entities/" ++ s))) allEntities + environment = [] + gui = [] + items = map (\(f, s) -> (f, renderPNG (assetsFolder ++ "items/" ++ s))) allItems + +---------------------------------------------------------------------- + +-- Turn a path to a .png file into a Picture. +renderPNG :: FilePath -> Picture +renderPNG path = scale zoom zoom $ fromJust $ unsafePerformIO $ loadJuicyPNG path + +-- Retrieve an image from the library. If the library does not contain +-- the requested image, a default is returned. +getRender :: String -> Picture +getRender id = get filtered + where filtered = filter ((== id) . fst) library + get [] = snd $ head library + get ((_, res):_) = res + +setRenderPos :: Int -> Int -> Picture -> Picture +setRenderPos x y = translate floatX floatY + where floatX = fromIntegral x * zoom * resolution + floatY = fromIntegral y * zoom * resolution \ No newline at end of file diff --git a/lib/RPGEngine/Render/GUI.hs b/lib/RPGEngine/Render/GUI.hs new file mode 100644 index 0000000..e29b012 --- /dev/null +++ b/lib/RPGEngine/Render/GUI.hs @@ -0,0 +1,10 @@ +module RPGEngine.Render.GUI +( renderGUI +) where + +import RPGEngine.Data (Game) +import Graphics.Gloss (Picture, blank) + +-- TODO +renderGUI :: Game -> Picture +renderGUI _ = blank diff --git a/lib/RPGEngine/Render/Level.hs b/lib/RPGEngine/Render/Level.hs new file mode 100644 index 0000000..267316c --- /dev/null +++ b/lib/RPGEngine/Render/Level.hs @@ -0,0 +1,10 @@ +module RPGEngine.Render.Level +( renderLevel +) where + +import Graphics.Gloss +import RPGEngine.Data + +-- TODO +renderLevel :: Level -> Picture +renderLevel _ = text "Level" \ No newline at end of file diff --git a/lib/RPGEngine/Render/Player.hs b/lib/RPGEngine/Render/Player.hs new file mode 100644 index 0000000..0d5f65c --- /dev/null +++ b/lib/RPGEngine/Render/Player.hs @@ -0,0 +1,11 @@ +module RPGEngine.Render.Player +( renderPlayer +) where + +import RPGEngine.Data (Player(..)) + +import Graphics.Gloss (Picture, text) +import RPGEngine.Render.Core (getRender, setRenderPos) + +renderPlayer :: Player -> Picture +renderPlayer Player{ coord = (x, y) } = setRenderPos x y $ getRender "player" diff --git a/rpg-engine.cabal b/rpg-engine.cabal index 0823b23..51f2809 100644 --- a/rpg-engine.cabal +++ b/rpg-engine.cabal @@ -19,6 +19,7 @@ library RPGEngine.Input RPGEngine.Input.Core + RPGEngine.Input.Player RPGEngine.Parse RPGEngine.Parse.Core @@ -26,6 +27,10 @@ library RPGEngine.Parse.StructElement RPGEngine.Render + RPGEngine.Render.Core + RPGEngine.Render.GUI + RPGEngine.Render.Level + RPGEngine.Render.Player executable rpg-engine main-is: Main.hs From 55212c14403428ac50cd5e8a8fc2d144fbb668bc Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Tue, 20 Dec 2022 23:59:35 +0100 Subject: [PATCH 14/34] #1 Rendering of level --- assets/entities/door.png | Bin 0 -> 1548 bytes assets/environment/entrance.png | Bin 0 -> 1548 bytes assets/environment/exit.png | Bin 0 -> 1548 bytes assets/environment/tile.png | Bin 0 -> 1548 bytes assets/environment/void.png | Bin 0 -> 1548 bytes assets/environment/wall.png | Bin 0 -> 1548 bytes assets/items/dagger.png | Bin 0 -> 734 bytes assets/items/key.png | Bin 0 -> 1548 bytes lib/RPGEngine/Render/Core.hs | 13 ++++++++- lib/RPGEngine/Render/Level.hs | 49 ++++++++++++++++++++++++++++++-- 10 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 assets/entities/door.png create mode 100644 assets/environment/entrance.png create mode 100644 assets/environment/exit.png create mode 100644 assets/environment/tile.png create mode 100644 assets/environment/void.png create mode 100644 assets/environment/wall.png create mode 100644 assets/items/dagger.png create mode 100644 assets/items/key.png diff --git a/assets/entities/door.png b/assets/entities/door.png new file mode 100644 index 0000000000000000000000000000000000000000..f876321bc4b4808bab5eac8ec26ee9069a17944e GIT binary patch literal 1548 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4(FKU~I{Bb`J1#c2+1T%1_J8No8Qr zm{>c}+T(D5NZbEqUF*A=U0r01LezJ!=ty4cB&gLAwJ?-@^6Y&JJe?;!dZe*KOr}OB zOg3gtX=#Bd%R%;z#giv5>Yh@O7nC9Rxb^VgAMgM2KmR8#@G5m#q^zOCm41p% zm}P$9wNs(j=1ksiFy(Uiw4HvYT`Xc7Svrk^0$e#$Z**K*H#tes^?9NE|BlzwPb{6V zn#HY;HDrNf+205CAFQLb?M+{navg0vCw}~=1oN)4Mz^zt`}$><7c#NTZ`9uVbp4ga zoBo%wb#;qacygG?NMQuI$ga#rS!}jlHhlVqjpA_H=O!u@GbwV0r(WQB_##KTsK2;P%^Rj4Ubc+6-o5 z9AE=Zz4}2CJ8dz;e0%91C0q6y71P90hIXSzog+bQ95F)xOb`GW01ZV*%z@bj>hv3>ScGXE%>z zurp_1umd^^c}+T(D5NZbEqUF*A=U0r01LezJ!=ty4cB&gLAwJ?-@^6Y&JJe?;!dZe*KOr}OB zOg3gtX=#Bd%R%;z#giv5>Yh@O7nC9Rxb^VgAMgM2KmR8#@G5m#q^zOCm41p% zm}P$9wNs(j=1ksiFy(Uiw4HvYT`Xc7Svrk^0$e#$Z**K*H#tes^?9NE|BlzwPb{6V zn#HY;HDrNf+205CAFQLb?M+{navg0vCw}~=1oN)4Mz^zt`}$><7c#NTZ`9uVbp4ga zoBo%wb#;qacygG?NMQuI$ga#ke))PZx+yVqjpA_H=O!u@GbwV0r(W5vYVgRaojjh$a=>e*27(5vDo2 zu7xE1Q=4ia>Olrfm^knM+Joo+qq9ML7zW9M)PeZOY!;Zc6+zxGCYaXMP-8HcX9crQ zKKzX$2T}`jG1vfE14Fnbn9At3ei)NM1#eTU?ES6DKI_YK!lkF3nX+JWH7oIObmdKI;Vst0AJ@qssI20 literal 0 HcmV?d00001 diff --git a/assets/environment/exit.png b/assets/environment/exit.png new file mode 100644 index 0000000000000000000000000000000000000000..2d2a66e713055ebdaeadd847457cdd1371a18ce8 GIT binary patch literal 1548 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4(FKU~I{Bb`J1#c2+1T%1_J8No8Qr zm{>c}+T(D5NZbEqUF*A=U0r01LezJ!=ty4cB&gLAwJ?-@^6Y&JJe?;!dZe*KOr}OB zOg3gtX=#Bd%R%;z#giv5>Yh@O7nC9Rxb^VgAMgM2KmR8#@G5m#q^zOCm41p% zm}P$9wNs(j=1ksiFy(Uiw4HvYT`Xc7Svrk^0$e#$Z**K*H#tes^?9NE|BlzwPb{6V zn#HY;HDrNf+205CAFQLb?M+{navg0vCw}~=1oN)4Mz^zt`}$><7c#NTZ`9uVbp4ga zoBo%wb#;qacygG?NMQuI$ga#kjSV7V23rGBB`6d%8G=SO_u-u)O~bRDl5d3w-~BFhT^Cg-xCXS;?ta zKajZ?Y;5vO7^+AVU{Mv80y}wDtTDqy#XtsG14D-WVs%)(@h-55;qis@KzWY;X^YQ- ztzv{3km9aQlHGH5UPUzk#M9MK0}EchaSKeN6WHWY0?}Na6>0#wRtO)PJjH=H3=Dt= sB62#IC02(u6k+LLme_iR(R6?aqFcTmH+Q6+eE|~jboFyt=akR{0PvR=CjbBd literal 0 HcmV?d00001 diff --git a/assets/environment/tile.png b/assets/environment/tile.png new file mode 100644 index 0000000000000000000000000000000000000000..91f4b5d31886b61ce64043fc4209d9bdddb6fd3c GIT binary patch literal 1548 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4(FKU~I{Bb`J1#c2+1T%1_J8No8Qr zm{>c}+T(D5NZbEqUF*A=U0r01LezJ!=ty4cB&gLAwJ?-@^6Y&JJe?;!dZe*KOr}OB zOg3gtX=#Bd%R%;z#giv5>Yh@O7nC9Rxb^VgAMgM2KmR8#@G5m#q^zOCm41p% zm}P$9wNs(j=1ksiFy(Uiw4HvYT`Xc7Svrk^0$e#$Z**K*H#tes^?9NE|BlzwPb{6V zn#HY;HDrNf+205CAFQLb?M+{navg0vCw}~=1oN)4Mz^zt`}$><7c#NTZ`9uVbp4ga zoBo%wb#;qacygG?NMQuI$ga#rP!*7JhA6%D})P?djqeVj;*V!1De#qnU!if1on50Sh}TH`$th{{G6q zgikj}0U?0b079BU@)Q{W@;*fd5DWuCUZB_oAPZPP&H$N2Oo(H15j-7`n;s|%M39R{ b4WL38xL*@Ec}+T(D5NZbEqUF*A=U0r01LezJ!=ty4cB&gLAwJ?-@^6Y&JJe?;!dZe*KOr}OB zOg3gtX=#Bd%R%;z#giv5>Yh@O7nC9Rxb^VgAMgM2KmR8#@G5m#q^zOCm41p% zm}P$9wNs(j=1ksiFy(Uiw4HvYT`Xc7Svrk^0$e#$Z**K*H#tes^?9NE|BlzwPb{6V zn#HY;HDrNf+205CAFQLb?M+{navg0vCw}~=1oN)4Mz^zt`}$><7c#NTZ`9uVbp4ga zoBo%wb#;qacygG?NMQuI$ga#kh53BRHFXGcd47d%8G=SO_u-u)P1xs46V=AE=CMz(lrAsK!wP7#K#w gU^EOs*FVdQ&MBb@0D*YVPyhe` literal 0 HcmV?d00001 diff --git a/assets/environment/wall.png b/assets/environment/wall.png new file mode 100644 index 0000000000000000000000000000000000000000..cfe91c2f7e7c0373745f8ed8a51b25a3176cc860 GIT binary patch literal 1548 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4(FKU~I{Bb`J1#c2+1T%1_J8No8Qr zm{>c}+T(D5NZbEqUF*A=U0r01LezJ!=ty4cB&gLAwJ?-@^6Y&JJe?;!dZe*KOr}OB zOg3gtX=#Bd%R%;z#giv5>Yh@O7nC9Rxb^VgAMgM2KmR8#@G5m#q^zOCm41p% zm}P$9wNs(j=1ksiFy(Uiw4HvYT`Xc7Svrk^0$e#$Z**K*H#tes^?9NE|BlzwPb{6V zn#HY;HDrNf+205CAFQLb?M+{navg0vCw}~=1oN)4Mz^zt`}$><7c#NTZ`9uVbp4ga zoBo%wb#;qacygG?NMQuI$ga#rUNZ`bxezGcd47d%8G=SO_u-u)P1x=%erXAE*ok@N)1neE#tnECynu z1DG60oCQQENu!(bi9r-30S2F73^)ywyZ4llfe9{3CIjpOE_QY@wf|yZ`S|SOk_pf#Cp7%IKOw3SeR|8YG4cK;j@5fRi%FG+Y3m97qq?3%E2B ZBkq(J_-@vlC7@EC!PC{xWt~$(69CJfAY=dl literal 0 HcmV?d00001 diff --git a/assets/items/dagger.png b/assets/items/dagger.png new file mode 100644 index 0000000000000000000000000000000000000000..d1da3c30753e98239a7e53f8a81444a45a5c5be1 GIT binary patch literal 734 zcmV<40wMj0P)i%R0j>_=3B$BX!ZHof^^&Q0Te#QA zCRc?Ig@cw1D14=Kz0S{yGj)DfY$f6|;8s1gObhERY-Fvx@QCnWK36sx^QbTJr3&+N zQu}2OU=t@o7!92=Yvd>ql3I|={`QU5e6tkAW60a2f+da&S@8x%Elbyw)U@a+d2&=# z?!WW2)8k|H?!GXzKRM3Z5O{asyXrV!M~?G-44y~0C^Y>=$!YRK^lDQZJOKYDoLx3G zeHYHRAn;%&tz=E|$abgG@Olc}i_mieoy+yC8t0tcLt;aI(KYPt!?4`((uj4M8yg>gwtsHnKX1YAA^=LC6r~SOV)s@dC&ukd2|Cp$y30X=!Nz+4ukdMOOw8Mg}a% z94H%P2!=~G$MZ3q*>{=Y#f5JuTF_kplLWg2!~waa;`((4R literal 0 HcmV?d00001 diff --git a/assets/items/key.png b/assets/items/key.png new file mode 100644 index 0000000000000000000000000000000000000000..667429626659029db47946e6c28d7d7494682cb5 GIT binary patch literal 1548 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4(FKU~I{Bb`J1#c2+1T%1_J8No8Qr zm{>c}+T(D5NZbEqUF*A=U0r01LezJ!=ty4cB&gLAwJ?-@^6Y&JJe?;!dZe*KOr}OB zOg3gtX=#Bd%R%;z#giv5>Yh@O7nC9Rxb^VgAMgM2KmR8#@G5m#q^zOCm41p% zm}P$9wNs(j=1ksiFy(Uiw4HvYT`Xc7Svrk^0$e#$Z**K*H#tes^?9NE|BlzwPb{6V zn#HY;HDrNf+205CAFQLb?M+{navg0vCw}~=1oN)4Mz^zt`}$><7c#NTZ`9uVbp4ga zoBo%wb#;qacygG?NMQuI$ga#rOr;Pjt|6IXF)-wsU^AD237aU)xiDuk01blydnZ*G6O#r9*40dKNFzHJCjK8N phb)e10#F2(0+2W^IjjZ^9YJS%ORlr)zb*o~*wfX|Wt~$(69BPq2de-8 literal 0 HcmV?d00001 diff --git a/lib/RPGEngine/Render/Core.hs b/lib/RPGEngine/Render/Core.hs index 0fe63ab..0e51063 100644 --- a/lib/RPGEngine/Render/Core.hs +++ b/lib/RPGEngine/Render/Core.hs @@ -5,6 +5,7 @@ import GHC.IO (unsafePerformIO) import Graphics.Gloss.Juicy (loadJuicyPNG) import Data.Maybe (fromJust) import Graphics.Gloss.Data.Picture (scale) +import Graphics.Gloss.Data.Bitmap (BitmapData(..)) ----------------------------- Constants ------------------------------ @@ -28,6 +29,15 @@ allEntities = [ ("door", "door.png") ] +allEnvironment :: [(String, FilePath)] +allEnvironment = [ + ("void", "void.png"), + ("tile", "tile.png"), + ("wall", "wall.png"), + ("entrance", "entrance.png"), + ("exit", "exit.png") + ] + allItems :: [(String, FilePath)] allItems = [ ("dagger", "dagger.png"), @@ -39,7 +49,7 @@ library :: [(String, Picture)] library = unknown:entities ++ environment ++ gui ++ items where unknown = ("unkown", renderPNG (assetsFolder ++ unknownImage)) entities = map (\(f, s) -> (f, renderPNG (assetsFolder ++ "entities/" ++ s))) allEntities - environment = [] + environment = map (\(f, s) -> (f, renderPNG (assetsFolder ++ "environment/" ++ s))) allEnvironment gui = [] items = map (\(f, s) -> (f, renderPNG (assetsFolder ++ "items/" ++ s))) allItems @@ -57,6 +67,7 @@ getRender id = get filtered get [] = snd $ head library get ((_, res):_) = res +-- Move a picture by game coordinates setRenderPos :: Int -> Int -> Picture -> Picture setRenderPos x y = translate floatX floatY where floatX = fromIntegral x * zoom * resolution diff --git a/lib/RPGEngine/Render/Level.hs b/lib/RPGEngine/Render/Level.hs index 267316c..4e01968 100644 --- a/lib/RPGEngine/Render/Level.hs +++ b/lib/RPGEngine/Render/Level.hs @@ -1,10 +1,53 @@ -module RPGEngine.Render.Level +module RPGEngine.Render.Level ( renderLevel ) where import Graphics.Gloss import RPGEngine.Data +import RPGEngine.Render.Core (getRender, setRenderPos, zoom, resolution) --- TODO renderLevel :: Level -> Picture -renderLevel _ = text "Level" \ No newline at end of file +renderLevel Level{ layout = l, items = i, entities = e } = level + where level = pictures [void, layout, items, entities] + void = createVoid + layout = renderLayout l + items = renderItems i + entities = renderEntities e + +renderLayout :: Layout -> Picture +renderLayout strips = pictures [setRenderPos 0 y (renderStrip (strips !! y)) | y <- [0 .. count]] + where count = length strips - 1 + +renderStrip :: [Physical] -> Picture +renderStrip list = pictures physicals + where physicals = [setRenderPos x 0 (image (list !! x)) | x <- [0 .. count]] + image Void = getRender "void" + image Walkable = getRender "tile" + image Blocked = getRender "wall" + image Entrance = pictures [getRender "tile", getRender "entrance"] + image Exit = pictures [getRender "tile", getRender "exit"] + count = length list - 1 + +renderItems :: [Item] -> Picture +renderItems list = pictures $ map renderItem list + +renderItem :: Item -> Picture +renderItem Item{ itemId = id, itemX = x, itemY = y} = setRenderPos x y image + where image = getRender id + +renderEntities :: [Entity] -> Picture +renderEntities list = pictures $ map renderEntity list + +renderEntity :: Entity -> Picture +renderEntity Entity{ entityId = id, entityX = x, entityY = y} = setRenderPos x y image + where image = getRender id + +createVoid :: Picture +createVoid = setRenderPos offX offY $ pictures voids + where voids = [setRenderPos x y void | x <- [0 .. width], y <- [0 .. height]] + void = getRender "void" + intZoom = round zoom :: Int + height = round $ 4320 / resolution / zoom + width = round $ 7680 / resolution / zoom + offX = negate (width `div` 2) + offY = negate (height `div` 2) \ No newline at end of file From 5c8cee810467e85e8aa2f3761072d1784978049e Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Wed, 21 Dec 2022 13:37:38 +0100 Subject: [PATCH 15/34] #11 Start proper report --- .vscode/tasks.json | 19 ++++++++ README.md | 114 +++++++++++++++++++++++++++++++-------------- header.yaml | 15 ++++++ verslag.pdf | Bin 0 -> 45716 bytes 4 files changed, 113 insertions(+), 35 deletions(-) create mode 100644 header.yaml diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 6c8f9a1..c27db8b 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -41,6 +41,25 @@ "kind": "build", "isDefault": true } + }, + { + "label": "Create verslag.pdf", + "type": "shell", + "command": "pandoc", + "args": [ + "-s", + "-o", "verslag.pdf", + "-f", "markdown+smart+header_attributes+yaml_metadata_block+auto_identifiers", + "--pdf-engine", "lualatex", + "--template", "eisvogel", + "header.yaml", + "README.md" + ], + "problemMatcher": [], + "group": { + "kind": "none", + "isDefault": false + } } ], "inputs": [ diff --git a/README.md b/README.md index 49869ed..d173f3f 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,4 @@ -# RPG-Engine - -Schrijf een game-engine voor een rollenspel - -https://pixel-poem.itch.io/dungeon-assetpuck -https://kyrise.itch.io/kyrises-free-16x16-rpg-icon-pack - -# RPG Engine requirements - + + +# RPG-Engine + +RPG-Engine is a game engine for playing and creating your own RPG games. + +If you are interested in the development side of things, [development notes can be found here](#Development-notes). + +This README serves as both documentation and project report, so excuse the details that might not be important for the average user. ## Playing the game +These are the keybinds *in* the game. All other keybinds in the menus should be straightforward. + +| Action | Primary | Secondary | +| ---------- | ------------- | ----------- | +| Move up | `Arrow Up` | `w` | +| Move left | `Arrow Left` | `a` | +| Move down | `Arrow Down` | `s` | +| Move right | `Arrow Right` | `d` | + +### Example playthrough + TODO -- Input commands etc -- An example playthrough +- An example playthrough, with pictures and explanations + +\pagebreak ## Writing your own stages @@ -85,7 +84,8 @@ A stage description file consists of several elements. | `Value` | is either a `Block` or a `BlockList` or a traditional value, such as `String` or `Int` | | `BlockList` | is a number of `Block`'s, surrounded by `[ ... ]`, separated by commas, can be empty | -We'll look at the following example to explain these concepts. +
+We'll look at the following example to explain these concepts. ```javascript player: { @@ -126,7 +126,7 @@ levels: [ id: "key", x: 3, y: 1, - name: "Doorkey", + name: "Door key", description: "Unlocks a secret door", useTimes: 1, value: 0, @@ -153,6 +153,7 @@ levels: [ } ] ``` +
This stage description file consists of a single `Block`. A stage description file always does. This top level `Block` contains two `Value`s `player` and `levels`, not separated by commas. @@ -247,4 +248,47 @@ If we look at the example, all the objects are length = 1 Condition ('inventoryContains(key)') Entry = empty ConditionList + Action ('leave()') -``` \ No newline at end of file +``` + +\pagebreak + +## Development notes + +### Assets & dependencies + +The following assets were used (and modified if specified): + +- Kyrise's Free 16x16 RPG Icon Pack[[1]](#1) + + Every needed asset was taken and put into its own `.png`, instead of in the overview. + +- 2D Pixel Dungeon Asset Pack by Pixel_Poem[[2]](#2) + + Every needed asset was taken and put into its own `.png`, instead of in the overview. + +RPG-Engine makes use of the following libraries: + +- [gloss](https://hackage.haskell.org/package/gloss) for game rendering +- [gloss-juicy](https://hackage.haskell.org/package/gloss-juicy) for rendering images +- [hspec](https://hackage.haskell.org/package/hspec) for testing +- [hspec-discover](https://hackage.haskell.org/package/hspec-discover) for allowing to split test files in multiple files +- [parsec](https://hackage.haskell.org/package/parsec) for parsing configuration files + +### Future development ideas + +The following ideas could (or should) be implemented in the future of this project. + +- [ ] Entity system: With en ES, you can implement moving entities and repeated input. It also resembles the typical + game loop more closely which can make it easier to implement other ideas in the future. + +- [ ] Game music: Ambient game music and sound effects can improve the gaming experience I think. + +\pagebreak + +## References + +[1] [Kyrise's Free 16x16 RPG Icon Pack](https://kyrise.itch.io/kyrises-free-16x16-rpg-icon-pack) © 2018 + by [Kyrise](https://kyrise.itch.io/) is licensed under [CC BY 4.0](http://creativecommons.org/licenses/by/4.0/?ref=chooser-v1) + +[2] [2D Pixel Dungeon Asset Pack](https://pixel-poem.itch.io/dungeon-assetpuck) by [Pixel_Poem](https://pixel-poem.itch.io/) + is not licensed \ No newline at end of file diff --git a/header.yaml b/header.yaml new file mode 100644 index 0000000..21a20a4 --- /dev/null +++ b/header.yaml @@ -0,0 +1,15 @@ +--- +title: "RPG Engine" +author: "Tibo De Peuter" +date: "23 december 2022" +subtitle: "Write a game engine for an RPG game" +# geometry: "left=2.54cm,right=2.54cm,top=1.91cm,bottom=1.91cm" +geometry: "left=2.54cm,right=2.54cm,top=2.54cm,bottom=2.54cm" +titlepage: true +titlepage-rule-height: 4 +toc: true +listings-disable-line-numbers: true +listings-no-page-break: false +subparagraph: true +lang: en-GB +--- diff --git a/verslag.pdf b/verslag.pdf index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..f2dba5d218c4dc468da027f6e2816048b04d1230 100644 GIT binary patch literal 45716 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

d>pSr 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

~)n6Oop+)(gX%XCdl)H)Ip7EVE&#>|`H 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 literal 0 HcmV?d00001 From 0786a410069125cabfd9becd319ceaf77e16e507 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Wed, 21 Dec 2022 14:49:42 +0100 Subject: [PATCH 16/34] #3 Restrict player going places --- lib/RPGEngine/Data.hs | 9 +++--- lib/RPGEngine/Data/Defaults.hs | 7 +++-- lib/RPGEngine/Input/Level.hs | 27 +++++++++++++++++ lib/RPGEngine/Input/Player.hs | 53 +++++++++++++++++++++++++--------- lib/RPGEngine/Render/Player.hs | 2 +- rpg-engine.cabal | 1 + 6 files changed, 78 insertions(+), 21 deletions(-) create mode 100644 lib/RPGEngine/Input/Level.hs diff --git a/lib/RPGEngine/Data.hs b/lib/RPGEngine/Data.hs index 641f109..3a701a2 100644 --- a/lib/RPGEngine/Data.hs +++ b/lib/RPGEngine/Data.hs @@ -14,9 +14,10 @@ data Game = Game { ------------------------------- Level -------------------------------- data Level = Level { - layout :: Layout, - items :: [Item], - entities :: [Entity] + layout :: Layout, + coordlayout :: [(X, Y, Physical)], + items :: [Item], + entities :: [Entity] } deriving (Eq, Show) type Layout = [Strip] @@ -37,7 +38,7 @@ type Y = Int data Player = Player { playerHp :: Maybe Int, inventory :: [Item], - coord :: (X, Y) + position :: (X, Y) } deriving (Eq, Show) instance Living Player where diff --git a/lib/RPGEngine/Data/Defaults.hs b/lib/RPGEngine/Data/Defaults.hs index 75965a4..e3414eb 100644 --- a/lib/RPGEngine/Data/Defaults.hs +++ b/lib/RPGEngine/Data/Defaults.hs @@ -1,6 +1,8 @@ module RPGEngine.Data.Defaults where import RPGEngine.Data +import RPGEngine.Input.Player (spawnPlayer) +import RPGEngine.Input.Level (putCoords) defaultEntity :: Entity defaultEntity = Entity { @@ -21,7 +23,7 @@ initGame = Game { state = defaultState, playing = defaultLevel, levels = [defaultLevel], - player = defaultPlayer + player = spawnPlayer defaultLevel defaultPlayer } defaultItem :: Item @@ -46,6 +48,7 @@ defaultLayout = [ defaultLevel :: Level defaultLevel = Level { layout = defaultLayout, + coordlayout = putCoords defaultLevel, -- TODO This should go items = [], entities = [] } @@ -54,7 +57,7 @@ defaultPlayer :: Player defaultPlayer = Player { playerHp = Prelude.Nothing, -- Compares to infinity inventory = [], - coord = (0, 0) + position = (0, 0) } -- Default state of the game, Menu diff --git a/lib/RPGEngine/Input/Level.hs b/lib/RPGEngine/Input/Level.hs new file mode 100644 index 0000000..63391c9 --- /dev/null +++ b/lib/RPGEngine/Input/Level.hs @@ -0,0 +1,27 @@ +module RPGEngine.Input.Level +( putCoords +, findFirst +, whatIsAt +) where +import RPGEngine.Data (Level (..), Y, X, Physical(..)) + +-- Map all Physicals onto coordinates +putCoords :: Level -> [(X, Y, Physical)] +putCoords l@Level{ layout = lay } = concatMap (\(a, bs) -> map (\(b, c) -> (b, a, c)) bs) numberedList + where numberedStrips = zip [0::Int .. ] lay + numberedList = map (\(x, strip) -> (x, zip [0::Int ..] strip)) numberedStrips + +-- Find first position of a Physical +-- Graceful exit by giving Nothing if there is nothing found. +findFirst :: Level -> Physical -> Maybe (X, Y) +findFirst l@Level{ coordlayout = lay } physical = try + where matches = filter (\(x, y, v) -> v == physical) lay + try | not (null matches) = Just $ (\(x, y, _) -> (x, y)) $ head matches + | otherwise = Nothing + +-- What is located at a given position in the level? +whatIsAt :: (X, Y) -> Level -> Physical +whatIsAt pos lvl@Level{ coordlayout = lay } = try + where matches = map (\(_, _, v) -> v) $ filter (\(x, y, v) -> (x, y) == pos) lay + try | not (null matches) = head matches + | otherwise = Void diff --git a/lib/RPGEngine/Input/Player.hs b/lib/RPGEngine/Input/Player.hs index 3b77917..be56f27 100644 --- a/lib/RPGEngine/Input/Player.hs +++ b/lib/RPGEngine/Input/Player.hs @@ -1,19 +1,44 @@ -module RPGEngine.Input.Player -( movePlayer +module RPGEngine.Input.Player +( spawnPlayer +, movePlayer ) where -import RPGEngine.Data (Game(..), Direction(..), Player(..), X, Y) +import RPGEngine.Data (Game(..), Direction(..), Player(..), X, Y, Physical (..), Level(..)) +import RPGEngine.Input.Level (whatIsAt, findFirst) +import Data.Maybe (fromJust, isNothing) + +----------------------------- Constants ------------------------------ -movePlayer :: Direction -> Game -> Game -movePlayer dir g@Game{ player = p@Player{ coord = (x, y) }} = newGame - where newGame = g{ player = newPlayer } - newPlayer = p{ coord = newCoord } - newCoord = (x + xD, y + yD) - (xD, yD) = diffs dir diffs :: Direction -> (X, Y) -diffs North = (0, 1) -diffs East = (1, 0) -diffs South = (0, -1) -diffs West = (-1, 0) -diffs Center = (0, 0) +diffs North = ( 0, 1) +diffs East = ( 1, 0) +diffs South = ( 0, -1) +diffs West = (-1, 0) +diffs Center = ( 0, 0) + +---------------------------------------------------------------------- + +-- Set the initial position of the player in a given level. +spawnPlayer :: Level -> Player -> Player +spawnPlayer l@Level{ layout = lay } p@Player{ position = prevPos } = p{ position = newPos } + where try = findFirst l Entrance + newPos | isNothing try = prevPos + | otherwise = fromJust try + +-- Move a player in a direction if possible. +movePlayer :: Direction -> Game -> Game +movePlayer dir g@Game{ player = p@Player{ position = (x, y) }} = newGame + where newGame = g{ player = newPlayer } + newPlayer = p{ position = newCoord } + newCoord | isLegalMove dir g = (x + xD, y + yD) + | otherwise = (x, y) + (xD, yD) = diffs dir + +-- Check if a move is legal by checking what is located at the new position. +isLegalMove :: Direction -> Game -> Bool +isLegalMove dir g@Game{ playing = lvl, player = p@Player{ position = (x, y) }} = legality + where legality = physical `elem` [Walkable, Entrance, Exit] + physical = whatIsAt newPos lvl + newPos = (x + xD, y + yD) + (xD, yD) = diffs dir \ No newline at end of file diff --git a/lib/RPGEngine/Render/Player.hs b/lib/RPGEngine/Render/Player.hs index 0d5f65c..7adb8b0 100644 --- a/lib/RPGEngine/Render/Player.hs +++ b/lib/RPGEngine/Render/Player.hs @@ -8,4 +8,4 @@ import Graphics.Gloss (Picture, text) import RPGEngine.Render.Core (getRender, setRenderPos) renderPlayer :: Player -> Picture -renderPlayer Player{ coord = (x, y) } = setRenderPos x y $ getRender "player" +renderPlayer Player{ position = (x, y) } = setRenderPos x y $ getRender "player" \ No newline at end of file diff --git a/rpg-engine.cabal b/rpg-engine.cabal index 51f2809..d7c3090 100644 --- a/rpg-engine.cabal +++ b/rpg-engine.cabal @@ -19,6 +19,7 @@ library RPGEngine.Input RPGEngine.Input.Core + RPGEngine.Input.Level RPGEngine.Input.Player RPGEngine.Parse From 2055ef234e1c9f9a596b56e4332440c4b2bb5036 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Wed, 21 Dec 2022 16:07:05 +0100 Subject: [PATCH 17/34] #9 Added level selection render --- README.md | 34 +++++-- assets/environment/overlay.png | Bin 0 -> 494 bytes assets/{unkown.png => unknown.png} | Bin levels/level2.txt | 8 +- levels/level3.txt | 4 +- levels/level4.txt | 134 ++++++++++++++++++++++++++++ levels/level_more_levels.txt | 138 +++++++++++++++++++++++++++++ lib/RPGEngine/Data.hs | 1 + lib/RPGEngine/Data/State.hs | 2 +- lib/RPGEngine/Input.hs | 9 +- lib/RPGEngine/Input/LvlSelect.hs | 12 +++ lib/RPGEngine/Render.hs | 42 ++++++--- lib/RPGEngine/Render/Core.hs | 19 +++- lib/RPGEngine/Render/LvlSelect.hs | 15 ++++ lib/RPGEngine/Render/Player.hs | 14 ++- rpg-engine.cabal | 3 + verslag.pdf | Bin 45716 -> 47917 bytes 17 files changed, 401 insertions(+), 34 deletions(-) create mode 100644 assets/environment/overlay.png rename assets/{unkown.png => unknown.png} (100%) create mode 100644 levels/level4.txt create mode 100644 levels/level_more_levels.txt create mode 100644 lib/RPGEngine/Input/LvlSelect.hs create mode 100644 lib/RPGEngine/Render/LvlSelect.hs diff --git a/README.md b/README.md index d173f3f..cc195df 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ TODO - An example playthrough, with pictures and explanations -\pagebreak +

\pagebreak
## Writing your own stages @@ -250,10 +250,22 @@ If we look at the example, all the objects are Entry = empty ConditionList + Action ('leave()') ``` -\pagebreak +
\pagebreak
## Development notes +### Engine architecture + +TODO + +#### Monads/Monad stack + +TODO + +### Tests + +TODO + ### Assets & dependencies The following assets were used (and modified if specified): @@ -268,6 +280,7 @@ The following assets were used (and modified if specified): RPG-Engine makes use of the following libraries: +- [directory](https://hackage.haskell.org/package/directory) for listing levels in a directory - [gloss](https://hackage.haskell.org/package/gloss) for game rendering - [gloss-juicy](https://hackage.haskell.org/package/gloss-juicy) for rendering images - [hspec](https://hackage.haskell.org/package/hspec) for testing @@ -278,12 +291,23 @@ RPG-Engine makes use of the following libraries: The following ideas could (or should) be implemented in the future of this project. -- [ ] Entity system: With en ES, you can implement moving entities and repeated input. It also resembles the typical +- [ ] **Entity system:** With en ES, you can implement moving entities and repeated input. It also resembles the typical game loop more closely which can make it easier to implement other ideas in the future. +- [ ] **Game music:** Ambient game music and sound effects can improve the gaming experience I think. +- [ ] **Expand configuration file:** Implement the same methods for parsing stage description files to a configuration file, + containing keybinds, dimension sizes, even window titles, making this a truly customizable engine. +- [ ] **Camera follows player:** The camera should follow the player, making it always center. This allows for larger levels + increases the immersion of the game. -- [ ] Game music: Ambient game music and sound effects can improve the gaming experience I think. +
\pagebreak
-\pagebreak +## Conclusion + +Parsing was way harder than I initially expected. About half of my time on this project was spent writing the parser. + +TODO + +
\pagebreak
## References diff --git a/assets/environment/overlay.png b/assets/environment/overlay.png new file mode 100644 index 0000000000000000000000000000000000000000..b5d500db89f1d838bcfd00c03ec6ee645f4ca48e GIT binary patch literal 494 zcmVq5^NU#-bECLpy zjjzIgpsir5AP9;eV&xC;)ks+HB!L9cS!VCYnK_rcXTcTDXj!Xe3bHxdjLe37%h45| z`xOtpbTUa-OtXyX`S~#auCIHVRJ{wTUjN_wb9E$=mIh62!sD7@+QKpo;SJkRyd&JB zrDF-5~PEFpZvDX>lko1oO3WcIgjq~e<+FCIEP-^Y$qK7r;ZxXji4dC6(wQ}j|@^B;nD3(l|V zT6_;KwxRFQh?%j9)GXVj>+p64ol)q%h4$5IR<(0ZA7F7)e$fr=AHa~@_pbc_HOgor z9cee700009a7bBm000XU000XU0RWnu7ytkOBuPX;R5%f1R27!m%s>HPqChWL>!<+? k45MK%8U~=uF!1RB0A@D State -nextState Menu = Playing +nextState Menu = LvlSelect nextState Playing = Pause nextState Pause = Playing nextState _ = Menu diff --git a/lib/RPGEngine/Input.hs b/lib/RPGEngine/Input.hs index a737d05..2fae6bb 100644 --- a/lib/RPGEngine/Input.hs +++ b/lib/RPGEngine/Input.hs @@ -15,8 +15,9 @@ import Graphics.Gloss.Interface.IO.Game -- Handle all input for RPG-Engine handleAllInput :: InputHandler Game -handleAllInput ev g@Game{ state = Playing } = handlePlayInputs ev g -handleAllInput ev g = handleAnyKey setNextState ev g +handleAllInput ev g@Game{ state = Playing } = handlePlayInputs ev g +handleAllInput ev g@Game{ state = LvlSelect } = handleLvlSelectInput ev g +handleAllInput ev g = handleAnyKey setNextState ev g ---------------------------------------------------------------------- @@ -38,6 +39,10 @@ handlePlayInputs = composeInputHandlers [ handleKey (Char 'a') $ movePlayer West ] +-- Input for selection a level to load +handleLvlSelectInput :: InputHandler Game +handleLvlSelectInput = composeInputHandlers [] + -- Go to the next stage of the Game setNextState :: Game -> Game setNextState game = game{ state = newState } diff --git a/lib/RPGEngine/Input/LvlSelect.hs b/lib/RPGEngine/Input/LvlSelect.hs new file mode 100644 index 0000000..5b1bf35 --- /dev/null +++ b/lib/RPGEngine/Input/LvlSelect.hs @@ -0,0 +1,12 @@ +module RPGEngine.Input.LvlSelect +( getLvlList +) where + +import GHC.IO (unsafePerformIO) +import System.Directory (getDirectoryContents) + +lvlFolder :: FilePath +lvlFolder = "levels" + +getLvlList :: [FilePath] +getLvlList = unsafePerformIO $ getDirectoryContents lvlFolder \ No newline at end of file diff --git a/lib/RPGEngine/Render.hs b/lib/RPGEngine/Render.hs index 1468f77..09b9b66 100644 --- a/lib/RPGEngine/Render.hs +++ b/lib/RPGEngine/Render.hs @@ -9,14 +9,24 @@ module RPGEngine.Render import RPGEngine.Data ( State(..), - Game(..) ) -import RPGEngine.Render.Level + Game(..), Player (..) ) +import RPGEngine.Render.Level ( renderLevel ) - import Graphics.Gloss - ( white, pictures, text, Display(InWindow), Color, Picture ) -import RPGEngine.Render.Player (renderPlayer) + ( white, + pictures, + text, + Display(InWindow), + Color, + Picture, + scale, + translate ) +import RPGEngine.Render.Player (renderPlayer, focusPlayer) import RPGEngine.Render.GUI (renderGUI) +import Graphics.Gloss.Data.Picture (color) +import RPGEngine.Render.Core (overlay) +import RPGEngine.Input.LvlSelect (getLvlList) +import RPGEngine.Render.LvlSelect (renderLvlList) ----------------------------- Constants ------------------------------ @@ -32,11 +42,12 @@ initWindow = InWindow -- Render the game render :: Game -> Picture -render g@Game{ state = Menu } = renderMenu g -render g@Game{ state = Playing } = renderPlaying g -render g@Game{ state = Pause } = renderPause g -render g@Game{ state = Win } = renderWin g -render g@Game{ state = Lose } = renderLose g +render g@Game{ state = Menu } = renderMenu g +render g@Game{ state = LvlSelect } = renderLevelSelection g +render g@Game{ state = Playing } = renderPlaying g +render g@Game{ state = Pause } = renderPause g +render g@Game{ state = Win } = renderWin g +render g@Game{ state = Lose } = renderLose g ---------------------------------------------------------------------- @@ -44,6 +55,10 @@ render g@Game{ state = Lose } = renderLose g renderMenu :: Game -> Picture renderMenu _ = text "[Press any key to start]" +-- TODO +renderLevelSelection :: Game -> Picture +renderLevelSelection _ = renderLvlList getLvlList + renderPlaying :: Game -> Picture renderPlaying g@Game{ playing = lvl, player = player } = pictures [ renderLevel lvl, @@ -51,9 +66,12 @@ renderPlaying g@Game{ playing = lvl, player = player } = pictures [ renderGUI g ] --- TODO renderPause :: Game -> Picture -renderPause _ = text "[Press any key to continue]" +renderPause g = pictures [renderPlaying g, pause] + where pause = pictures [ + overlay, + color white $ scale 0.5 0.5 $ text "[Press any key to continue]" + ] -- TODO renderWin :: Game -> Picture diff --git a/lib/RPGEngine/Render/Core.hs b/lib/RPGEngine/Render/Core.hs index 0e51063..e5155f4 100644 --- a/lib/RPGEngine/Render/Core.hs +++ b/lib/RPGEngine/Render/Core.hs @@ -1,6 +1,6 @@ module RPGEngine.Render.Core where -import Graphics.Gloss ( Picture, translate ) +import Graphics.Gloss ( Picture, translate, pictures ) import GHC.IO (unsafePerformIO) import Graphics.Gloss.Juicy (loadJuicyPNG) import Data.Maybe (fromJust) @@ -21,7 +21,7 @@ assetsFolder :: FilePath assetsFolder = "assets/" unknownImage :: FilePath -unknownImage = "unkown.png" +unknownImage = "unknown.png" allEntities :: [(String, FilePath)] allEntities = [ @@ -32,6 +32,7 @@ allEntities = [ allEnvironment :: [(String, FilePath)] allEnvironment = [ ("void", "void.png"), + ("overlay", "overlay.png"), ("tile", "tile.png"), ("wall", "wall.png"), ("entrance", "entrance.png"), @@ -47,7 +48,7 @@ allItems = [ -- Map of all renders library :: [(String, Picture)] library = unknown:entities ++ environment ++ gui ++ items - where unknown = ("unkown", renderPNG (assetsFolder ++ unknownImage)) + where unknown = ("unknown", renderPNG (assetsFolder ++ unknownImage)) entities = map (\(f, s) -> (f, renderPNG (assetsFolder ++ "entities/" ++ s))) allEntities environment = map (\(f, s) -> (f, renderPNG (assetsFolder ++ "environment/" ++ s))) allEnvironment gui = [] @@ -71,4 +72,14 @@ getRender id = get filtered setRenderPos :: Int -> Int -> Picture -> Picture setRenderPos x y = translate floatX floatY where floatX = fromIntegral x * zoom * resolution - floatY = fromIntegral y * zoom * resolution \ No newline at end of file + floatY = fromIntegral y * zoom * resolution + +overlay :: Picture +overlay = setRenderPos offX offY $ pictures voids + where voids = [setRenderPos x y void | x <- [0 .. width], y <- [0 .. height]] + void = getRender "overlay" + intZoom = round zoom :: Int + height = round $ 4320 / resolution / zoom + width = round $ 7680 / resolution / zoom + offX = negate (width `div` 2) + offY = negate (height `div` 2) \ No newline at end of file diff --git a/lib/RPGEngine/Render/LvlSelect.hs b/lib/RPGEngine/Render/LvlSelect.hs new file mode 100644 index 0000000..b395e9d --- /dev/null +++ b/lib/RPGEngine/Render/LvlSelect.hs @@ -0,0 +1,15 @@ +module RPGEngine.Render.LvlSelect +( renderLvlList +) where + +import Graphics.Gloss ( Picture, pictures, translate, scale ) +import Graphics.Gloss.Data.Picture (blank, text) +import RPGEngine.Render.Core (resolution, zoom) + +-- Render all level names, under each other. +renderLvlList :: [FilePath] -> Picture +renderLvlList list = pictures $ map render entries + where entries = zip [0::Int .. ] list + render (i, path) = scale zoomed zoomed $ translate 0 (offset i) $ text path + zoomed = 0.1 * zoom + offset i = negate (2 * resolution * zoom * fromIntegral i) \ No newline at end of file diff --git a/lib/RPGEngine/Render/Player.hs b/lib/RPGEngine/Render/Player.hs index 7adb8b0..0b6a124 100644 --- a/lib/RPGEngine/Render/Player.hs +++ b/lib/RPGEngine/Render/Player.hs @@ -1,11 +1,17 @@ module RPGEngine.Render.Player ( renderPlayer +, focusPlayer ) where -import RPGEngine.Data (Player(..)) - +import RPGEngine.Data (Player(..), Game(..)) import Graphics.Gloss (Picture, text) -import RPGEngine.Render.Core (getRender, setRenderPos) +import RPGEngine.Render.Core (getRender, setRenderPos, zoom, resolution) +import Graphics.Gloss.Data.Picture (translate) renderPlayer :: Player -> Picture -renderPlayer Player{ position = (x, y) } = setRenderPos x y $ getRender "player" \ No newline at end of file +renderPlayer Player{ position = (x, y) } = setRenderPos x y $ getRender "player" + +focusPlayer :: Game -> Picture -> Picture +focusPlayer Game{ player = Player{ position = (x, y)}} = translate centerX centerY + where centerX = resolution * zoom * fromIntegral (negate x) + centerY = resolution * zoom * fromIntegral (negate y) \ No newline at end of file diff --git a/rpg-engine.cabal b/rpg-engine.cabal index d7c3090..9e11d89 100644 --- a/rpg-engine.cabal +++ b/rpg-engine.cabal @@ -8,6 +8,7 @@ library hs-source-dirs: lib build-depends: base >= 4.7 && <5, + directory >= 1.3.6.0, gloss >= 1.11 && < 1.14, gloss-juicy >= 0.2.3, parsec >= 3.1.15.1 exposed-modules: @@ -20,6 +21,7 @@ library RPGEngine.Input RPGEngine.Input.Core RPGEngine.Input.Level + RPGEngine.Input.LvlSelect RPGEngine.Input.Player RPGEngine.Parse @@ -31,6 +33,7 @@ library RPGEngine.Render.Core RPGEngine.Render.GUI RPGEngine.Render.Level + RPGEngine.Render.LvlSelect RPGEngine.Render.Player executable rpg-engine diff --git a/verslag.pdf b/verslag.pdf index f2dba5d218c4dc468da027f6e2816048b04d1230..24a6e81e610d8d2ab0dd84449bf2170ccd1ec824 100644 GIT binary patch delta 20463 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]rz2DC;fV;g?U+&ol8SI5_v4JBZcVQ@2f&; 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 Tuen1fD+z_t^7CsLYSR7>vKoPk delta 18270 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 R0&HxdP&5Dn!TjU~_!lAx;`{&r From dab6fadad4886424a1c803f11bfc94b9edee2d99 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Wed, 21 Dec 2022 23:30:59 +0100 Subject: [PATCH 18/34] Restructuring, #9 --- README.md | 10 ++ lib/Input.hs | 10 ++ lib/RPGEngine.hs | 31 +--- lib/RPGEngine/Config.hs | 36 ++++ lib/RPGEngine/Data.hs | 163 ++++++++---------- lib/RPGEngine/Data/Default.hs | 67 +++++++ lib/RPGEngine/Data/Defaults.hs | 65 ------- lib/RPGEngine/Data/Game.hs | 22 +++ lib/RPGEngine/Data/Level.hs | 36 ++++ lib/RPGEngine/Data/State.hs | 22 --- lib/RPGEngine/Input.hs | 49 +----- lib/RPGEngine/Input/Core.hs | 25 +-- lib/RPGEngine/Input/Level.hs | 27 --- lib/RPGEngine/Input/LevelSelection.hs | 49 ++++++ lib/RPGEngine/Input/Lose.hs | 13 ++ lib/RPGEngine/Input/LvlSelect.hs | 12 -- lib/RPGEngine/Input/Menu.hs | 36 ++++ lib/RPGEngine/Input/Paused.hs | 12 ++ lib/RPGEngine/Input/Player.hs | 44 ----- lib/RPGEngine/Input/Playing.hs | 80 +++++++++ lib/RPGEngine/Input/Win.hs | 13 ++ lib/RPGEngine/Parse.hs | 27 ++- lib/RPGEngine/Parse/Core.hs | 22 ++- lib/RPGEngine/Parse/Game.hs | 101 ----------- lib/RPGEngine/Parse/StructureToGame.hs | 120 +++++++++++++ .../{StructElement.hs => TextToStructure.hs} | 45 ++--- lib/RPGEngine/Render.hs | 92 +++------- lib/RPGEngine/Render/Core.hs | 45 +++-- lib/RPGEngine/Render/GUI.hs | 10 -- lib/RPGEngine/Render/LevelSelection.hs | 33 ++++ lib/RPGEngine/Render/Lose.hs | 14 ++ lib/RPGEngine/Render/LvlSelect.hs | 15 -- lib/RPGEngine/Render/Menu.hs | 14 ++ lib/RPGEngine/Render/Paused.hs | 20 +++ lib/RPGEngine/Render/Player.hs | 17 -- lib/RPGEngine/Render/{Level.hs => Playing.hs} | 72 ++++++-- lib/RPGEngine/Render/Win.hs | 14 ++ rpg-engine.cabal | 40 +++-- test/{ParseGameSpec.hs => Parser/GameSpec.hs} | 55 +++--- .../StructureSpec.hs} | 43 +++-- test/{RPGEngineSpec.hs => Spec.hs} | 0 41 files changed, 941 insertions(+), 680 deletions(-) create mode 100644 lib/Input.hs create mode 100644 lib/RPGEngine/Config.hs create mode 100644 lib/RPGEngine/Data/Default.hs delete mode 100644 lib/RPGEngine/Data/Defaults.hs create mode 100644 lib/RPGEngine/Data/Game.hs create mode 100644 lib/RPGEngine/Data/Level.hs delete mode 100644 lib/RPGEngine/Data/State.hs delete mode 100644 lib/RPGEngine/Input/Level.hs create mode 100644 lib/RPGEngine/Input/LevelSelection.hs create mode 100644 lib/RPGEngine/Input/Lose.hs delete mode 100644 lib/RPGEngine/Input/LvlSelect.hs create mode 100644 lib/RPGEngine/Input/Menu.hs create mode 100644 lib/RPGEngine/Input/Paused.hs delete mode 100644 lib/RPGEngine/Input/Player.hs create mode 100644 lib/RPGEngine/Input/Playing.hs create mode 100644 lib/RPGEngine/Input/Win.hs delete mode 100644 lib/RPGEngine/Parse/Game.hs create mode 100644 lib/RPGEngine/Parse/StructureToGame.hs rename lib/RPGEngine/Parse/{StructElement.hs => TextToStructure.hs} (86%) delete mode 100644 lib/RPGEngine/Render/GUI.hs create mode 100644 lib/RPGEngine/Render/LevelSelection.hs create mode 100644 lib/RPGEngine/Render/Lose.hs delete mode 100644 lib/RPGEngine/Render/LvlSelect.hs create mode 100644 lib/RPGEngine/Render/Menu.hs create mode 100644 lib/RPGEngine/Render/Paused.hs delete mode 100644 lib/RPGEngine/Render/Player.hs rename lib/RPGEngine/Render/{Level.hs => Playing.hs} (54%) create mode 100644 lib/RPGEngine/Render/Win.hs rename test/{ParseGameSpec.hs => Parser/GameSpec.hs} (78%) rename test/{ParseStructElementSpec.hs => Parser/StructureSpec.hs} (87%) rename test/{RPGEngineSpec.hs => Spec.hs} (100%) diff --git a/README.md b/README.md index cc195df..060cb52 100644 --- a/README.md +++ b/README.md @@ -258,6 +258,16 @@ If we look at the example, all the objects are TODO +`RPGEngine` is the main module. It contains the `playRPGEngine` function which bootstraps the whole game. It is also + the game loop. From here, `RPGEngine` talks to its submodules. + +These submodules are `Config`, `Data`, `Input`, `Parse` & `Render`. They are all responsible for their own part, either + containing the program configuration, data containers, everything needed to handle input, everything needed to parse a + source file & everything needed to render the game. However, each of these submodules has their own submodules to + divide the work. They are conveniently named after the state of the game that they work with, e.g. the main menu has a + module & when the game is playing is a different module. A special one is `Core`, which is kind of like a library for + every piece. It contains functions that are regularly used by the other modules. + #### Monads/Monad stack TODO diff --git a/lib/Input.hs b/lib/Input.hs new file mode 100644 index 0000000..9f63d99 --- /dev/null +++ b/lib/Input.hs @@ -0,0 +1,10 @@ +-- Go to the next stage of the Game +-- setNextState :: Game -> Game +-- setNextState game = game{ state = newState } +-- where newState = nextState $ state game + +-- -- Get the next state based on the current state +-- nextState :: State -> State +-- nextState Menu {} = defaultLvlSelect +-- nextState Pause {} = Playing +-- nextState _ = Menu diff --git a/lib/RPGEngine.hs b/lib/RPGEngine.hs index f9372e7..a2855cf 100644 --- a/lib/RPGEngine.hs +++ b/lib/RPGEngine.hs @@ -5,33 +5,18 @@ module RPGEngine ( playRPGEngine ) where -import RPGEngine.Data.Defaults -import RPGEngine.Render -import RPGEngine.Input +import RPGEngine.Config ( bgColor, winDimensions, winOffsets ) +import RPGEngine.Render ( initWindow, render, initGame ) +import RPGEngine.Input ( handleAllInput ) -import Graphics.Gloss ( - Color(..) - , white - , play - ) - ------------------------------ Constants ------------------------------ - --- Dimensions for main window -winDimensions :: (Int, Int) -winDimensions = (1280, 720) - --- Offsets for main window -winOffsets :: (Int, Int) -winOffsets = (0, 0) +import Graphics.Gloss ( play ) ---------------------------------------------------------------------- --- This is the gameloop. +-- This is the game loop. -- It can receive input and update itself. It is rendered by a renderer. playRPGEngine :: String -> Int -> IO() -playRPGEngine title fps = do - play window bgColor fps initGame render handleInputs step +playRPGEngine title fps = do + play window bgColor fps initGame render handleAllInput step where window = initWindow title winDimensions winOffsets - step _ g = g -- TODO Do something with step? Check health etc. - handleInputs = handleAllInput + step _ g = g -- TODO Do something with step? Check health etc. \ No newline at end of file diff --git a/lib/RPGEngine/Config.hs b/lib/RPGEngine/Config.hs new file mode 100644 index 0000000..a8d719c --- /dev/null +++ b/lib/RPGEngine/Config.hs @@ -0,0 +1,36 @@ +-- This module should ultimately be replaced by a config file parser +module RPGEngine.Config +-- All entries are exported +where + +import Graphics.Gloss + +----------------------- Window configuration ------------------------- + +-- Dimensions for main window +winDimensions :: (Int, Int) +winDimensions = (1280, 720) + +-- Offsets for main window +winOffsets :: (Int, Int) +winOffsets = (0, 0) + +-- Game background color +bgColor :: Color +bgColor = white + +-- Default scale +zoom :: Float +zoom = 5.0 + +-- Resolution of the texture +resolution :: Float +resolution = 16 + +-- Location of the assets folder containing all images +assetsFolder :: FilePath +assetsFolder = "assets/" + +-- Location of the level folder containing all levels +levelFolder :: FilePath +levelFolder = "levels/" \ No newline at end of file diff --git a/lib/RPGEngine/Data.hs b/lib/RPGEngine/Data.hs index 2ebe39b..23b25c8 100644 --- a/lib/RPGEngine/Data.hs +++ b/lib/RPGEngine/Data.hs @@ -1,28 +1,64 @@ -module RPGEngine.Data where +-- Contains all the data containers of the game. +-- Submodules contain accessors for these data containers. +module RPGEngine.Data +-- All data types are exported +where + +import RPGEngine.Input.Core +import RPGEngine.Render.Core ( Renderer ) -------------------------------- Game -------------------------------- --- TODO Add more +-- A game is the base data container. data Game = Game { - -- Current state of the game - state :: State, - playing :: Level, - levels :: [Level], - player :: Player + state :: State, + levels :: [Level], + player :: Player } +------------------------------- State -------------------------------- + +-- Code reusability +data StateBase = StateBase { + renderer :: Renderer Game, + inputHandler :: InputHandler Game +} + + -- Main menu +data State = Menu { base :: StateBase } + -- Select the level you want to play + | LevelSelection { base :: StateBase, + levelList :: [FilePath], + selector :: ListSelector } + -- Playing a level + | Playing { base :: StateBase, + level :: Level } + -- Paused while playing a level + | Paused { base :: StateBase, + level :: Level } + -- Won a level + | Win { base :: StateBase } + -- Lost a level + | Lose { base :: StateBase } + ------------------------------- Level -------------------------------- data Level = Level { - layout :: Layout, - coordlayout :: [(X, Y, Physical)], - items :: [Item], - entities :: [Entity] + layout :: Layout, + -- All Physical pieces but with their coordinates + index :: [(X, Y, Physical)], + items :: [Item], + entities :: [Entity] } deriving (Eq, Show) -type Layout = [Strip] -type Strip = [Physical] +type X = Int +type Y = Int +type Layout = [Strip] +type Strip = [Physical] + +-- A Physical part of the world. A single tile of the world. A block +-- with stuff on it. data Physical = Void | Walkable | Blocked @@ -30,48 +66,12 @@ data Physical = Void | Exit deriving (Eq, Show) -------------------------------- Player ------------------------------- - -type X = Int -type Y = Int - -data Player = Player { - playerHp :: Maybe Int, - inventory :: [Item], - position :: (X, Y) -} deriving (Eq, Show) - -instance Living Player where - hp = playerHp - -------------------------------- State -------------------------------- - --- Current state of the game. -data State = Menu - | LvlSelect - | Playing - | Pause - | Win - | Lose - -------------------------------- Object ------------------------------- - -class Object a where - id :: a -> String - x :: a -> Int - y :: a -> Int - name :: a -> String - description :: a -> String - actions :: a -> [([Condition], Action)] - value :: a -> Maybe Int - -class Living a where - hp :: a -> Maybe Int +-------------------------------- Item -------------------------------- data Item = Item { itemId :: ItemId, - itemX :: Int, - itemY :: Int, + itemX :: X, + itemY :: Y, itemName :: String, itemDescription :: String, itemActions :: [([Condition], Action)], @@ -79,41 +79,37 @@ data Item = Item { useTimes :: Maybe Int } deriving (Eq, Show) -instance Object Item where - id = itemId - x = itemX - y = itemY - name = itemName - description = itemDescription - actions = itemActions - value = itemValue +type ItemId = String + +------------------------------- Entity ------------------------------- data Entity = Entity { entityId :: EntityId, - entityX :: Int, - entityY :: Int, + entityX :: X, + entityY :: Y, entityName :: String, entityDescription :: String, entityActions :: [([Condition], Action)], entityValue :: Maybe Int, - entityHp :: Maybe Int, + entityHp :: HP, direction :: Direction } deriving (Eq, Show) -instance Object Entity where - id = entityId - x = entityX - y = entityY - name = entityName - description = entityDescription - actions = entityActions - value = entityValue - -instance Living Entity where - hp = entityHp - type EntityId = String -type ItemId = String +type HP = Maybe Int + +data Direction = North + | East + | South + | West + | Stay -- No direction + deriving (Eq, Show) + +data Player = Player { + playerHp :: HP, + inventory :: [Item], + position :: (X, Y) +} deriving (Eq, Show) ------------------------------ Condition ----------------------------- @@ -121,7 +117,7 @@ data Condition = InventoryFull | InventoryContains ItemId | Not Condition | AlwaysFalse - deriving (Show, Eq) + deriving (Eq, Show) ------------------------------- Action ------------------------------- @@ -130,14 +126,5 @@ data Action = Leave | UseItem ItemId | DecreaseHp EntityId ItemId | IncreasePlayerHp ItemId - | Nothing - deriving (Show, Eq) - ------------------------------- Direction ----------------------------- - -data Direction = North - | East - | South - | West - | Center -- Equal to 'stay where you are' - deriving (Show, Eq) \ No newline at end of file + | DoNothing + deriving (Eq, Show) \ No newline at end of file diff --git a/lib/RPGEngine/Data/Default.hs b/lib/RPGEngine/Data/Default.hs new file mode 100644 index 0000000..7129a12 --- /dev/null +++ b/lib/RPGEngine/Data/Default.hs @@ -0,0 +1,67 @@ +module RPGEngine.Data.Default +-- Everything is exported +where +import RPGEngine.Data (Entity (..), Game (..), Item (..), Layout, Player (..), Level (..), StateBase (..), State (..), Physical (..), Direction (..)) +import RPGEngine.Input.Core (ListSelector(..)) +import RPGEngine.Render.LevelSelection (renderLevelSelection) +import RPGEngine.Input.Playing (spawnPlayer) +import RPGEngine.Render.Menu (renderMenu) + +------------------------------ Defaults ------------------------------ + +defaultEntity :: Entity +defaultEntity = Entity { + entityId = "", + entityX = 0, + entityY = 0, + entityName = "Default", + entityDescription = "", + entityActions = [], + entityValue = Prelude.Nothing, + entityHp = Prelude.Nothing, + direction = Stay +} + +defaultItem :: Item +defaultItem = Item { + itemId = "", + itemX = 0, + itemY = 0, + itemName = "Default", + itemDescription = "", + itemActions = [], + itemValue = Prelude.Nothing, + useTimes = Prelude.Nothing +} + +defaultLayout :: Layout +defaultLayout = [ + [Blocked, Blocked, Blocked], + [Blocked, Entrance, Blocked], + [Blocked, Blocked, Blocked] + ] + +defaultLevel :: Level +defaultLevel = Level { + layout = defaultLayout, + index = [ + (0, 0, Blocked), + (0, 1, Blocked), + (0, 2, Blocked), + (1, 0, Blocked), + (1, 1, Entrance), + (1, 2, Blocked), + (2, 0, Blocked), + (2, 1, Blocked), + (2, 2, Blocked) + ], + items = [], + entities = [] +} + +defaultPlayer :: Player +defaultPlayer = Player { + playerHp = Prelude.Nothing, -- Compares to infinity + inventory = [], + position = (0, 0) +} \ No newline at end of file diff --git a/lib/RPGEngine/Data/Defaults.hs b/lib/RPGEngine/Data/Defaults.hs deleted file mode 100644 index e3414eb..0000000 --- a/lib/RPGEngine/Data/Defaults.hs +++ /dev/null @@ -1,65 +0,0 @@ -module RPGEngine.Data.Defaults where - -import RPGEngine.Data -import RPGEngine.Input.Player (spawnPlayer) -import RPGEngine.Input.Level (putCoords) - -defaultEntity :: Entity -defaultEntity = Entity { - entityId = "", - entityX = 0, - entityY = 0, - entityName = "Default", - entityDescription = "", - entityActions = [], - entityValue = Prelude.Nothing, - entityHp = Prelude.Nothing, - direction = Center -} - --- Initialize the game -initGame :: Game -initGame = Game { - state = defaultState, - playing = defaultLevel, - levels = [defaultLevel], - player = spawnPlayer defaultLevel defaultPlayer -} - -defaultItem :: Item -defaultItem = Item { - itemId = "", - itemX = 0, - itemY = 0, - itemName = "Default", - itemDescription = "", - itemActions = [], - itemValue = Prelude.Nothing, - useTimes = Prelude.Nothing -} - -defaultLayout :: Layout -defaultLayout = [ - [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked], - [Blocked, Entrance, Walkable, Walkable, Walkable, Walkable, Exit, Blocked], - [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked] - ] - -defaultLevel :: Level -defaultLevel = Level { - layout = defaultLayout, - coordlayout = putCoords defaultLevel, -- TODO This should go - items = [], - entities = [] -} - -defaultPlayer :: Player -defaultPlayer = Player { - playerHp = Prelude.Nothing, -- Compares to infinity - inventory = [], - position = (0, 0) -} - --- Default state of the game, Menu -defaultState :: State -defaultState = Menu \ No newline at end of file diff --git a/lib/RPGEngine/Data/Game.hs b/lib/RPGEngine/Data/Game.hs new file mode 100644 index 0000000..2b21cd5 --- /dev/null +++ b/lib/RPGEngine/Data/Game.hs @@ -0,0 +1,22 @@ +module RPGEngine.Data.Game +( isLegalMove +) where + +import RPGEngine.Data + ( Player(Player, position), + Direction, + Physical(Exit, Walkable, Entrance), + State(Playing, level), + Game(Game, state, player) ) +import RPGEngine.Data.Level (findAt, directionOffsets) + +------------------------------ Exported ------------------------------ + +-- Check if a move is legal by checking what is located at the new position. +isLegalMove :: Direction -> Game -> Bool +isLegalMove dir g@Game{ state = Playing { level = lvl }, player = p@Player{ position = (x, y) }} = legality + where legality = physical `elem` [Walkable, Entrance, Exit] + physical = findAt newPos lvl + newPos = (x + xD, y + yD) + (xD, yD) = directionOffsets dir +isLegalMove _ _ = False \ No newline at end of file diff --git a/lib/RPGEngine/Data/Level.hs b/lib/RPGEngine/Data/Level.hs new file mode 100644 index 0000000..86ef84d --- /dev/null +++ b/lib/RPGEngine/Data/Level.hs @@ -0,0 +1,36 @@ +module RPGEngine.Data.Level +-- Everything is exported +where + +import GHC.IO (unsafePerformIO) +import System.Directory (getDirectoryContents) +import RPGEngine.Input.Core (ListSelector(..)) +import RPGEngine.Data (Level (..), Physical (..), Direction (..), Entity (..), Game (..), Item (..), Player (..), StateBase (..), State (..), X, Y, Layout) +import RPGEngine.Config (levelFolder) + +------------------------------ Exported ------------------------------ + +-- Find first position of a Physical +-- Graceful exit by giving Nothing if there is nothing found. +findFirstOf :: Level -> Physical -> Maybe (X, Y) +findFirstOf l@Level{ index = index } physical = try + where matches = filter (\(x, y, v) -> v == physical) index + try | not (null matches) = Just $ (\(x, y, _) -> (x, y)) $ head matches + | otherwise = Nothing + +-- What is located at a given position in the level? +findAt :: (X, Y) -> Level -> Physical +findAt pos lvl@Level{ index = index } = try + where matches = map (\(_, _, v) -> v) $ filter (\(x, y, v) -> (x, y) == pos) index + try | not (null matches) = head matches + | otherwise = Void + +directionOffsets :: Direction -> (X, Y) +directionOffsets North = ( 0, 1) +directionOffsets East = ( 1, 0) +directionOffsets South = ( 0, -1) +directionOffsets West = (-1, 0) +directionOffsets Stay = ( 0, 0) + +getLevelList :: [FilePath] +getLevelList = drop 2 $ unsafePerformIO $ getDirectoryContents levelFolder \ No newline at end of file diff --git a/lib/RPGEngine/Data/State.hs b/lib/RPGEngine/Data/State.hs deleted file mode 100644 index 0cc5347..0000000 --- a/lib/RPGEngine/Data/State.hs +++ /dev/null @@ -1,22 +0,0 @@ --- Describes the current state of the game, --- e.g. Main menu, game, pause, win or lose --- Allows to easily go to a next state and change rendering accordingly - -module RPGEngine.Data.State -( State(..) - -, nextState -) where - -import RPGEngine.Data - ----------------------------------------------------------------------- - --- Get the next state based on the current state -nextState :: State -> State -nextState Menu = LvlSelect -nextState Playing = Pause -nextState Pause = Playing -nextState _ = Menu - ----------------------------------------------------------------------- diff --git a/lib/RPGEngine/Input.hs b/lib/RPGEngine/Input.hs index 2fae6bb..485affb 100644 --- a/lib/RPGEngine/Input.hs +++ b/lib/RPGEngine/Input.hs @@ -1,50 +1,15 @@ --- Input for RPG-Engine - +-- Implementations for each state can be found in their respective +-- submodules. module RPGEngine.Input ( handleAllInput ) where -import RPGEngine.Data -import RPGEngine.Data.State import RPGEngine.Input.Core -import RPGEngine.Input.Player +import RPGEngine.Data -import Graphics.Gloss.Interface.IO.Game +------------------------------ Exported ------------------------------ ----------------------------------------------------------------------- - --- Handle all input for RPG-Engine +-- Handle all input of all states of the game. handleAllInput :: InputHandler Game -handleAllInput ev g@Game{ state = Playing } = handlePlayInputs ev g -handleAllInput ev g@Game{ state = LvlSelect } = handleLvlSelectInput ev g -handleAllInput ev g = handleAnyKey setNextState ev g - ----------------------------------------------------------------------- - --- Input for 'Playing' state -handlePlayInputs :: InputHandler Game -handlePlayInputs = composeInputHandlers [ - -- Pause the game - handleKey (Char 'p') (\game -> game{ state = Pause }), - - -- Player movement - handleKey (SpecialKey KeyUp) $ movePlayer North, - handleKey (SpecialKey KeyRight) $ movePlayer East, - handleKey (SpecialKey KeyDown) $ movePlayer South, - handleKey (SpecialKey KeyLeft) $ movePlayer West, - - handleKey (Char 'w') $ movePlayer North, - handleKey (Char 'd') $ movePlayer East, - handleKey (Char 's') $ movePlayer South, - handleKey (Char 'a') $ movePlayer West - ] - --- Input for selection a level to load -handleLvlSelectInput :: InputHandler Game -handleLvlSelectInput = composeInputHandlers [] - --- Go to the next stage of the Game -setNextState :: Game -> Game -setNextState game = game{ state = newState } - where newState = nextState $ state game - +handleAllInput ev g@Game{ state = state } = handleInput ev g + where handleInput = inputHandler $ base state \ No newline at end of file diff --git a/lib/RPGEngine/Input/Core.hs b/lib/RPGEngine/Input/Core.hs index e2e81b9..9044c1d 100644 --- a/lib/RPGEngine/Input/Core.hs +++ b/lib/RPGEngine/Input/Core.hs @@ -1,21 +1,26 @@ --- Allows to create a massive inputHandler that can handle anything --- after you specify what you want it to do. - module RPGEngine.Input.Core -( InputHandler(..) +( InputHandler +, ListSelector(..) + , composeInputHandlers , handle , handleKey , handleAnyKey ) where -import Graphics.Gloss.Interface.IO.Game +import Graphics.Gloss.Interface.Pure.Game + ( Event(EventKey), Key(..), KeyState(Down), SpecialKey ) ----------------------------- Constants ------------------------------ type InputHandler a = Event -> (a -> a) ----------------------------------------------------------------------- +data ListSelector = ListSelector { + selection :: Int, + selected :: Bool +} + +------------------------------ Exported ------------------------------ -- Compose multiple InputHandlers into one InputHandler that handles -- all of them. @@ -26,8 +31,8 @@ composeInputHandlers (ih:ihs) ev a = composeInputHandlers ihs ev (ih ev a) -- Handle any event handle :: Event -> (a -> a) -> InputHandler a handle (EventKey key _ _ _) = handleKey key --- handle (EventMotion _) = undefined --- handle (EventResize _) = undefined +-- handle (EventMotion _) = undefined -- TODO +-- handle (EventResize _) = undefined -- TODO handle _ = const (const id) -- Handle a event by pressing a key @@ -41,7 +46,7 @@ handleAnyKey :: (a -> a) -> InputHandler a handleAnyKey f (EventKey _ Down _ _) = f handleAnyKey _ _ = id ----------------------------------------------------------------------- +--------------------------- Help functions --------------------------- handleCharKey :: Char -> (a -> a) -> InputHandler a handleCharKey c1 f (EventKey (Char c2) Down _ _) @@ -53,4 +58,4 @@ handleSpecialKey :: SpecialKey -> (a -> a) -> InputHandler a handleSpecialKey sk1 f (EventKey (SpecialKey sk2) Down _ _) | sk1 == sk2 = f | otherwise = id -handleSpecialKey _ _ _ = id +handleSpecialKey _ _ _ = id \ No newline at end of file diff --git a/lib/RPGEngine/Input/Level.hs b/lib/RPGEngine/Input/Level.hs deleted file mode 100644 index 63391c9..0000000 --- a/lib/RPGEngine/Input/Level.hs +++ /dev/null @@ -1,27 +0,0 @@ -module RPGEngine.Input.Level -( putCoords -, findFirst -, whatIsAt -) where -import RPGEngine.Data (Level (..), Y, X, Physical(..)) - --- Map all Physicals onto coordinates -putCoords :: Level -> [(X, Y, Physical)] -putCoords l@Level{ layout = lay } = concatMap (\(a, bs) -> map (\(b, c) -> (b, a, c)) bs) numberedList - where numberedStrips = zip [0::Int .. ] lay - numberedList = map (\(x, strip) -> (x, zip [0::Int ..] strip)) numberedStrips - --- Find first position of a Physical --- Graceful exit by giving Nothing if there is nothing found. -findFirst :: Level -> Physical -> Maybe (X, Y) -findFirst l@Level{ coordlayout = lay } physical = try - where matches = filter (\(x, y, v) -> v == physical) lay - try | not (null matches) = Just $ (\(x, y, _) -> (x, y)) $ head matches - | otherwise = Nothing - --- What is located at a given position in the level? -whatIsAt :: (X, Y) -> Level -> Physical -whatIsAt pos lvl@Level{ coordlayout = lay } = try - where matches = map (\(_, _, v) -> v) $ filter (\(x, y, v) -> (x, y) == pos) lay - try | not (null matches) = head matches - | otherwise = Void diff --git a/lib/RPGEngine/Input/LevelSelection.hs b/lib/RPGEngine/Input/LevelSelection.hs new file mode 100644 index 0000000..d2f3578 --- /dev/null +++ b/lib/RPGEngine/Input/LevelSelection.hs @@ -0,0 +1,49 @@ +module RPGEngine.Input.LevelSelection +( handleInputLevelSelection +) where + +import RPGEngine.Input.Core + ( composeInputHandlers, handleKey, InputHandler, ListSelector (..) ) + +import RPGEngine.Config ( levelFolder ) +import RPGEngine.Data ( Game (..), Direction (..), State (..), StateBase (..) ) + +import Graphics.Gloss.Interface.IO.Game + ( Key(SpecialKey), SpecialKey(KeySpace) ) +import Graphics.Gloss.Interface.IO.Interact (SpecialKey(..)) +import RPGEngine.Render.Playing (renderPlaying) +import RPGEngine.Input.Playing (handleInputPlaying) +import RPGEngine.Parse (parse) + +------------------------------ Exported ------------------------------ + +handleInputLevelSelection :: InputHandler Game +handleInputLevelSelection = composeInputHandlers [ + handleKey (SpecialKey KeySpace) selectLevel, + + handleKey (SpecialKey KeyUp) $ moveSelector North, + handleKey (SpecialKey KeyDown) $ moveSelector South + ] + +---------------------------------------------------------------------- + +-- Select a level and load it in +selectLevel :: Game -> Game +selectLevel game@Game{ state = LevelSelection{ levelList = list, selector = selector }} = newGame + where newGame = parse $ levelFolder ++ (list !! index) + index = selection selector +selectLevel g = g + +-- Move the selector either up or down +moveSelector :: Direction -> Game -> Game +moveSelector dir game@Game{ state = state@LevelSelection{ levelList = list, selector = selector } } = newGame + where newGame = game{ state = newState } + newState = state{ selector = newSelector } + newSelector | constraint = selector{ selection = newSelection } + | otherwise = selector + constraint = 0 <= newSelection && newSelection < length list + newSelection = selection selector + diff + diff | dir == North = -1 + | dir == South = 1 + | otherwise = 0 +moveSelector _ g = g diff --git a/lib/RPGEngine/Input/Lose.hs b/lib/RPGEngine/Input/Lose.hs new file mode 100644 index 0000000..f9c6d0e --- /dev/null +++ b/lib/RPGEngine/Input/Lose.hs @@ -0,0 +1,13 @@ +module RPGEngine.Input.Lose +( handleInputLose +) where + +import RPGEngine.Input.Core ( InputHandler ) + +import RPGEngine.Data ( Game ) + +------------------------------ Exported ------------------------------ + +-- TODO +handleInputLose :: InputHandler Game +handleInputLose = undefined \ No newline at end of file diff --git a/lib/RPGEngine/Input/LvlSelect.hs b/lib/RPGEngine/Input/LvlSelect.hs deleted file mode 100644 index 5b1bf35..0000000 --- a/lib/RPGEngine/Input/LvlSelect.hs +++ /dev/null @@ -1,12 +0,0 @@ -module RPGEngine.Input.LvlSelect -( getLvlList -) where - -import GHC.IO (unsafePerformIO) -import System.Directory (getDirectoryContents) - -lvlFolder :: FilePath -lvlFolder = "levels" - -getLvlList :: [FilePath] -getLvlList = unsafePerformIO $ getDirectoryContents lvlFolder \ No newline at end of file diff --git a/lib/RPGEngine/Input/Menu.hs b/lib/RPGEngine/Input/Menu.hs new file mode 100644 index 0000000..6903d0d --- /dev/null +++ b/lib/RPGEngine/Input/Menu.hs @@ -0,0 +1,36 @@ +module RPGEngine.Input.Menu +( handleInputMenu +) where + +import RPGEngine.Input.Core ( InputHandler, composeInputHandlers, handleAnyKey, ListSelector (..) ) + +import RPGEngine.Data ( Game (..), State (..), StateBase (..) ) +import RPGEngine.Render.LevelSelection (renderLevelSelection) +import RPGEngine.Input.LevelSelection (handleInputLevelSelection) +import RPGEngine.Data.Level (getLevelList) + +------------------------------ Exported ------------------------------ + +handleInputMenu :: InputHandler Game +handleInputMenu = composeInputHandlers [ + handleAnyKey selectLevel + ] + +---------------------------------------------------------------------- + +selectLevel :: Game -> Game +selectLevel g@Game{ state = state } = g{ state = defaultLevelSelection } + +defaultLevelSelection :: State +defaultLevelSelection = LevelSelection { base = base, selector = defaultSelector, levelList = levels } + where base = StateBase { + renderer = renderLevelSelection, + inputHandler = handleInputLevelSelection + } + levels = getLevelList + +defaultSelector :: ListSelector +defaultSelector = ListSelector { + selection = 0, + selected = False +} \ No newline at end of file diff --git a/lib/RPGEngine/Input/Paused.hs b/lib/RPGEngine/Input/Paused.hs new file mode 100644 index 0000000..03522dd --- /dev/null +++ b/lib/RPGEngine/Input/Paused.hs @@ -0,0 +1,12 @@ +module RPGEngine.Input.Paused +( handleInputPaused +) where + +import RPGEngine.Input.Core ( InputHandler ) + +import RPGEngine.Data ( Game ) + +------------------------------ Exported ------------------------------ + +handleInputPaused :: InputHandler Game +handleInputPaused = undefined diff --git a/lib/RPGEngine/Input/Player.hs b/lib/RPGEngine/Input/Player.hs deleted file mode 100644 index be56f27..0000000 --- a/lib/RPGEngine/Input/Player.hs +++ /dev/null @@ -1,44 +0,0 @@ -module RPGEngine.Input.Player -( spawnPlayer -, movePlayer -) where - -import RPGEngine.Data (Game(..), Direction(..), Player(..), X, Y, Physical (..), Level(..)) -import RPGEngine.Input.Level (whatIsAt, findFirst) -import Data.Maybe (fromJust, isNothing) - ------------------------------ Constants ------------------------------ - - -diffs :: Direction -> (X, Y) -diffs North = ( 0, 1) -diffs East = ( 1, 0) -diffs South = ( 0, -1) -diffs West = (-1, 0) -diffs Center = ( 0, 0) - ----------------------------------------------------------------------- - --- Set the initial position of the player in a given level. -spawnPlayer :: Level -> Player -> Player -spawnPlayer l@Level{ layout = lay } p@Player{ position = prevPos } = p{ position = newPos } - where try = findFirst l Entrance - newPos | isNothing try = prevPos - | otherwise = fromJust try - --- Move a player in a direction if possible. -movePlayer :: Direction -> Game -> Game -movePlayer dir g@Game{ player = p@Player{ position = (x, y) }} = newGame - where newGame = g{ player = newPlayer } - newPlayer = p{ position = newCoord } - newCoord | isLegalMove dir g = (x + xD, y + yD) - | otherwise = (x, y) - (xD, yD) = diffs dir - --- Check if a move is legal by checking what is located at the new position. -isLegalMove :: Direction -> Game -> Bool -isLegalMove dir g@Game{ playing = lvl, player = p@Player{ position = (x, y) }} = legality - where legality = physical `elem` [Walkable, Entrance, Exit] - physical = whatIsAt newPos lvl - newPos = (x + xD, y + yD) - (xD, yD) = diffs dir \ No newline at end of file diff --git a/lib/RPGEngine/Input/Playing.hs b/lib/RPGEngine/Input/Playing.hs new file mode 100644 index 0000000..b2638e0 --- /dev/null +++ b/lib/RPGEngine/Input/Playing.hs @@ -0,0 +1,80 @@ +module RPGEngine.Input.Playing +( handleInputPlaying +, spawnPlayer +) where + +import RPGEngine.Input.Core + ( composeInputHandlers, handleKey, InputHandler ) + +import RPGEngine.Data + ( Player(Player, position), + Direction(West, North, East, South), + Physical(Entrance), + Y, + X, + Level(Level, layout), + State(..), + StateBase(StateBase, renderer, inputHandler), + Game(Game, state, player) ) +import RPGEngine.Data.Level ( findFirstOf, directionOffsets ) +import RPGEngine.Data.Game ( isLegalMove ) +import RPGEngine.Input.Paused ( handleInputPaused ) +import RPGEngine.Render.Paused ( renderPaused ) +import Graphics.Gloss.Interface.IO.Game (Key(..)) +import Graphics.Gloss.Interface.IO.Interact (SpecialKey(..)) +import Data.Maybe (isNothing, fromJust) + +------------------------------ Exported ------------------------------ + +handleInputPlaying :: InputHandler Game +handleInputPlaying = composeInputHandlers [ + -- Pause the game + handleKey (Char 'p') pauseGame, + + -- Player movement + handleKey (SpecialKey KeyUp) $ movePlayer North, + handleKey (SpecialKey KeyRight) $ movePlayer East, + handleKey (SpecialKey KeyDown) $ movePlayer South, + handleKey (SpecialKey KeyLeft) $ movePlayer West, + + handleKey (Char 'w') $ movePlayer North, + handleKey (Char 'd') $ movePlayer East, + handleKey (Char 's') $ movePlayer South, + handleKey (Char 'a') $ movePlayer West + ] + +-- Set the initial position of the player in a given level. +spawnPlayer :: Level -> Player -> Player +spawnPlayer l@Level{ layout = lay } p@Player{ position = prevPos } = p{ position = newPos } + where try = findFirstOf l Entrance + newPos | isNothing try = prevPos + | otherwise = fromJust try + +---------------------------------------------------------------------- + +pauseGame :: Game -> Game +pauseGame g@Game{ state = Playing{ level = level } } = pausedGame + where pausedGame = g{ state = pausedState } + pausedState = Paused{ base = newBase, level = level } + newBase = StateBase { renderer = renderPaused, inputHandler = handleInputPaused } + +-- Move a player in a direction if possible. +movePlayer :: Direction -> Game -> Game +movePlayer dir g@Game{ player = p@Player{ position = (x, y) }} = newGame + where newGame = g{ player = newPlayer } + newPlayer = p{ position = newCoord } + newCoord | isLegalMove dir g = (x + xD, y + yD) + | otherwise = (x, y) + (xD, yD) = directionOffsets dir + +-- TODO +goToNextLevel :: Game -> Game +goToNextLevel = undefined + +---------------------------------------------------------------------- + +-- Map all Physicals onto coordinates +putCoords :: Level -> [(X, Y, Physical)] +putCoords l@Level{ layout = lay } = concatMap (\(a, bs) -> map (\(b, c) -> (b, a, c)) bs) numberedList + where numberedStrips = zip [0::Int .. ] lay + numberedList = map (\(x, strip) -> (x, zip [0::Int ..] strip)) numberedStrips \ No newline at end of file diff --git a/lib/RPGEngine/Input/Win.hs b/lib/RPGEngine/Input/Win.hs new file mode 100644 index 0000000..37434ee --- /dev/null +++ b/lib/RPGEngine/Input/Win.hs @@ -0,0 +1,13 @@ +module RPGEngine.Input.Win +( handleInputWin +) where + +import RPGEngine.Input.Core ( InputHandler ) + +import RPGEngine.Data ( Game ) + +------------------------------ Exported ------------------------------ + +-- TODO +handleInputWin :: InputHandler Game +handleInputWin = undefined \ No newline at end of file diff --git a/lib/RPGEngine/Parse.hs b/lib/RPGEngine/Parse.hs index a8736ad..9b1c976 100644 --- a/lib/RPGEngine/Parse.hs +++ b/lib/RPGEngine/Parse.hs @@ -1,19 +1,16 @@ -module RPGEngine.Parse where +module RPGEngine.Parse +( parse +) where -import RPGEngine.Data -import RPGEngine.Parse.StructElement -import RPGEngine.Parse.Game +import RPGEngine.Data ( Game ) +import RPGEngine.Parse.StructureToGame ( structureToGame ) +import GHC.IO (unsafePerformIO) +import Text.Parsec.String (parseFromFile) +import RPGEngine.Parse.TextToStructure (structure) -import Text.Parsec.String -import System.IO.Unsafe +------------------------------ Exported ------------------------------ ------------------------------ Constants ------------------------------ - -type FileName = String - ----------------------------------------------------------------------- - -parseToGame :: FileName -> Game -parseToGame filename = structToGame struct +parse :: FilePath -> Game +parse filename = structureToGame struct where (Right struct) = unsafePerformIO io - io = parseFromFile structElement filename \ No newline at end of file + io = parseFromFile structure filename \ No newline at end of file diff --git a/lib/RPGEngine/Parse/Core.hs b/lib/RPGEngine/Parse/Core.hs index 7e704ab..ff1be67 100644 --- a/lib/RPGEngine/Parse/Core.hs +++ b/lib/RPGEngine/Parse/Core.hs @@ -1,7 +1,23 @@ -module RPGEngine.Parse.Core where +module RPGEngine.Parse.Core +( parseWith +, parseWithRest +, ignoreWS +) where import Text.Parsec -import Text.Parsec.String + ( ParseError, + anyChar, + endOfLine, + spaces, + string, + anyToken, + choice, + eof, + manyTill, + parse ) +import Text.Parsec.String ( Parser ) + +------------------------------ Exported ------------------------------ -- A wrapper, which takes a parser and some input and returns a -- parsed output. @@ -14,7 +30,7 @@ parseWithRest :: Parser a -> String -> Either ParseError (a, String) parseWithRest parser = parse ((,) <$> parser <*> rest) "" where rest = manyTill anyToken eof --- Ignore all kinds of whitespaces +-- Ignore all kinds of whitespace ignoreWS :: Parser a -> Parser a ignoreWS parser = choice [skipComment, spaces] >> parser where skipComment = do{ string "#"; manyTill anyChar endOfLine; return ()} \ No newline at end of file diff --git a/lib/RPGEngine/Parse/Game.hs b/lib/RPGEngine/Parse/Game.hs deleted file mode 100644 index 9999ffd..0000000 --- a/lib/RPGEngine/Parse/Game.hs +++ /dev/null @@ -1,101 +0,0 @@ -module RPGEngine.Parse.Game where - -import RPGEngine.Data -import RPGEngine.Data.Defaults -import RPGEngine.Parse.StructElement - --------------------------------- Game -------------------------------- - --- TODO -structToGame :: StructElement -> Game -structToGame = undefined - -------------------------------- Player ------------------------------- - -structToPlayer :: StructElement -> Player -structToPlayer (Block block) = structToPlayer' block defaultPlayer -structToPlayer _ = defaultPlayer - -structToPlayer' :: [StructElement] -> Player -> Player -structToPlayer' [] p = p -structToPlayer' ((Entry(Tag "hp") val ):es) p = (structToPlayer' es p){ playerHp = structToMaybeInt val } -structToPlayer' ((Entry(Tag "inventory") (Block inv)):es) p = (structToPlayer' es p){ inventory = structToItems inv } -structToPlayer' _ _ = defaultPlayer - -structToActions :: StructElement -> [([Condition], Action)] -structToActions (Block []) = [] -structToActions (Block block) = structToActions' block [] -structToActions _ = [] - -structToActions' :: [StructElement] -> [([Condition], Action)] -> [([Condition], Action)] -structToActions' [] list = list -structToActions' ((Entry(ConditionList cs) (Regular (Action a))):as) list = structToActions' as ((cs, a):list) -structToActions' _ list = list - -------------------------------- Levels ------------------------------- - -structToLevels :: StructElement -> [Level] -structToLevels (Block struct) = structToLevel <$> struct -structToLevels _ = [defaultLevel] - -structToLevel :: StructElement -> Level -structToLevel (Block entries) = structToLevel' entries defaultLevel -structToLevel _ = defaultLevel - -structToLevel' :: [StructElement] -> Level -> Level -structToLevel' ((Entry(Tag "layout") (Regular (Layout layout))):ls) l = (structToLevel' ls l){ RPGEngine.Data.layout = layout } -structToLevel' ((Entry(Tag "items") (Block items) ):ls) l = (structToLevel' ls l){ items = structToItems items } -structToLevel' ((Entry(Tag "entities") (Block entities) ):ls) l = (structToLevel' ls l){ entities = structToEntities entities } -structToLevel' _ _ = defaultLevel - -------------------------------- Items -------------------------------- - -structToItems :: [StructElement] -> [Item] -structToItems items = structToItem <$> items - -structToItem :: StructElement -> Item -structToItem (Block block) = structToItem' block defaultItem -structToItem _ = defaultItem - -structToItem' :: [StructElement] -> Item -> Item -structToItem' [] i = i -structToItem' ((Entry(Tag "id") (Regular(String id ))):is) i = (structToItem' is i){ itemId = id } -structToItem' ((Entry(Tag "x") (Regular(Integer x ))):is) i = (structToItem' is i){ itemX = x } -structToItem' ((Entry(Tag "y") (Regular(Integer y ))):is) i = (structToItem' is i){ itemY = y } -structToItem' ((Entry(Tag "name") (Regular(String name))):is) i = (structToItem' is i){ itemName = name } -structToItem' ((Entry(Tag "description") (Regular(String desc))):is) i = (structToItem' is i){ itemDescription = desc } -structToItem' ((Entry(Tag "value") val ):is) i = (structToItem' is i){ itemValue = structToMaybeInt val } -structToItem' ((Entry(Tag "actions") actions ):is) i = (structToItem' is i){ itemActions = structToActions actions } -structToItem' ((Entry (Tag "useTimes") useTimes ):is) i = (structToItem' is i){ useTimes = structToMaybeInt useTimes } -structToItem' _ _ = defaultItem - ------------------------------- Entities ------------------------------ - -structToEntities :: [StructElement] -> [Entity] -structToEntities entities = structToEntity <$> entities - -structToEntity :: StructElement -> Entity -structToEntity (Block block) = structToEntity' block defaultEntity -structToEntity _ = defaultEntity - -structToEntity' :: [StructElement] -> Entity -> Entity -structToEntity' [] e = e -structToEntity' ((Entry(Tag "id") (Regular(String id )) ):es) e = (structToEntity' es e){ entityId = id } -structToEntity' ((Entry(Tag "x") (Regular(Integer x )) ):es) e = (structToEntity' es e){ entityX = x } -structToEntity' ((Entry(Tag "y") (Regular(Integer y )) ):es) e = (structToEntity' es e){ entityY = y } -structToEntity' ((Entry(Tag "name") (Regular(String name)) ):es) e = (structToEntity' es e){ entityName = name } -structToEntity' ((Entry(Tag "description") (Regular(String desc)) ):es) e = (structToEntity' es e){ entityDescription = desc } -structToEntity' ((Entry(Tag "actions") actions ):es) e = (structToEntity' es e){ entityActions = structToActions actions } -structToEntity' ((Entry(Tag "value") val ):es) e = (structToEntity' es e){ entityValue = structToMaybeInt val } -structToEntity' ((Entry(Tag "hp") val ):es) e = (structToEntity' es e){ entityHp = structToMaybeInt val } -structToEntity' ((Entry(Tag "direction") (Regular(Direction dir))):es) e = (structToEntity' es e){ RPGEngine.Data.direction = dir } -structToEntity' _ _ = defaultEntity - ----------------------------------------------------------------------- - -structToMaybeInt :: StructElement -> Maybe Int -structToMaybeInt (Regular (Integer val)) = Just val -structToMaybeInt (Regular Infinite) = Prelude.Nothing -structToMaybeInt _ = Prelude.Nothing -- TODO - ----------------------------------------------------------------------- \ No newline at end of file diff --git a/lib/RPGEngine/Parse/StructureToGame.hs b/lib/RPGEngine/Parse/StructureToGame.hs new file mode 100644 index 0000000..ddbcd3d --- /dev/null +++ b/lib/RPGEngine/Parse/StructureToGame.hs @@ -0,0 +1,120 @@ +module RPGEngine.Parse.StructureToGame +-- Everything is exported for testing +where + +import RPGEngine.Data + ( Action, + Condition, + Player(playerHp, inventory), + Entity(entityId, entityX, entityY, entityName, entityDescription, + entityActions, entityValue, entityHp, direction), + Item(itemId, itemX, itemY, itemName, itemDescription, itemValue, + itemActions, useTimes), + Level(layout, items, entities), + Game (..), State (..), StateBase (..) ) +import RPGEngine.Parse.TextToStructure + ( Value(Infinite, Action, Layout, String, Direction, Integer), + Key(Tag, ConditionList), + Structure(..) ) +import RPGEngine.Data.Default (defaultPlayer, defaultLevel, defaultItem, defaultEntity) +import RPGEngine.Render.Playing (renderPlaying) +import RPGEngine.Input.Playing (handleInputPlaying) + +------------------------------ Exported ------------------------------ + +structureToGame :: Structure -> Game +structureToGame (Block [(Entry(Tag "player") playerBlock), (Entry(Tag "levels") levelsBlock)]) = game + where game = Game{ state = newState, levels = newLevels, player = newPlayer } + newState = Playing{ base = playingBase, level = currentLevel } + playingBase = StateBase{ renderer = renderPlaying, inputHandler = handleInputPlaying } + newLevels = structureToLevels levelsBlock + currentLevel = head newLevels + newPlayer = structureToPlayer playerBlock + +------------------------------- Player ------------------------------- + +structureToPlayer :: Structure -> Player +structureToPlayer (Block block) = structureToPlayer' block defaultPlayer +structureToPlayer _ = defaultPlayer + +structureToPlayer' :: [Structure] -> Player -> Player +structureToPlayer' [] p = p +structureToPlayer' ((Entry(Tag "hp") val ):es) p = (structureToPlayer' es p){ playerHp = structureToMaybeInt val } +structureToPlayer' ((Entry(Tag "inventory") (Block inv)):es) p = (structureToPlayer' es p){ inventory = structureToItems inv } +structureToPlayer' _ _ = defaultPlayer + +structureToActions :: Structure -> [([Condition], Action)] +structureToActions (Block []) = [] +structureToActions (Block block) = structureToActions' block [] +structureToActions _ = [] + +structureToActions' :: [Structure] -> [([Condition], Action)] -> [([Condition], Action)] +structureToActions' [] list = list +structureToActions' ((Entry(ConditionList cs) (Regular (Action a))):as) list = structureToActions' as ((cs, a):list) +structureToActions' _ list = list + +------------------------------- Levels ------------------------------- + +structureToLevels :: Structure -> [Level] +structureToLevels (Block struct) = structureToLevel <$> struct +structureToLevels _ = [defaultLevel] + +structureToLevel :: Structure -> Level +structureToLevel (Block entries) = structureToLevel' entries defaultLevel +structureToLevel _ = defaultLevel + +structureToLevel' :: [Structure] -> Level -> Level +structureToLevel' ((Entry(Tag "layout") (Regular (Layout layout))):ls) l = (structureToLevel' ls l){ RPGEngine.Data.layout = layout } +structureToLevel' ((Entry(Tag "items") (Block items) ):ls) l = (structureToLevel' ls l){ items = structureToItems items } +structureToLevel' ((Entry(Tag "entities") (Block entities) ):ls) l = (structureToLevel' ls l){ entities = structureToEntities entities } +structureToLevel' _ _ = defaultLevel + +------------------------------- Items -------------------------------- + +structureToItems :: [Structure] -> [Item] +structureToItems items = structureToItem <$> items + +structureToItem :: Structure -> Item +structureToItem (Block block) = structureToItem' block defaultItem +structureToItem _ = defaultItem + +structureToItem' :: [Structure] -> Item -> Item +structureToItem' [] i = i +structureToItem' ((Entry(Tag "id") (Regular(String id ))):is) i = (structureToItem' is i){ itemId = id } +structureToItem' ((Entry(Tag "x") (Regular(Integer x ))):is) i = (structureToItem' is i){ itemX = x } +structureToItem' ((Entry(Tag "y") (Regular(Integer y ))):is) i = (structureToItem' is i){ itemY = y } +structureToItem' ((Entry(Tag "name") (Regular(String name))):is) i = (structureToItem' is i){ itemName = name } +structureToItem' ((Entry(Tag "description") (Regular(String desc))):is) i = (structureToItem' is i){ itemDescription = desc } +structureToItem' ((Entry(Tag "value") val ):is) i = (structureToItem' is i){ itemValue = structureToMaybeInt val } +structureToItem' ((Entry(Tag "actions") actions ):is) i = (structureToItem' is i){ itemActions = structureToActions actions } +structureToItem' ((Entry (Tag "useTimes") useTimes ):is) i = (structureToItem' is i){ useTimes = structureToMaybeInt useTimes } +structureToItem' _ _ = defaultItem + +------------------------------ Entities ------------------------------ + +structureToEntities :: [Structure] -> [Entity] +structureToEntities entities = structureToEntity <$> entities + +structureToEntity :: Structure -> Entity +structureToEntity (Block block) = structureToEntity' block defaultEntity +structureToEntity _ = defaultEntity + +structureToEntity' :: [Structure] -> Entity -> Entity +structureToEntity' [] e = e +structureToEntity' ((Entry(Tag "id") (Regular(String id )) ):es) e = (structureToEntity' es e){ entityId = id } +structureToEntity' ((Entry(Tag "x") (Regular(Integer x )) ):es) e = (structureToEntity' es e){ entityX = x } +structureToEntity' ((Entry(Tag "y") (Regular(Integer y )) ):es) e = (structureToEntity' es e){ entityY = y } +structureToEntity' ((Entry(Tag "name") (Regular(String name)) ):es) e = (structureToEntity' es e){ entityName = name } +structureToEntity' ((Entry(Tag "description") (Regular(String desc)) ):es) e = (structureToEntity' es e){ entityDescription = desc } +structureToEntity' ((Entry(Tag "actions") actions ):es) e = (structureToEntity' es e){ entityActions = structureToActions actions } +structureToEntity' ((Entry(Tag "value") val ):es) e = (structureToEntity' es e){ entityValue = structureToMaybeInt val } +structureToEntity' ((Entry(Tag "hp") val ):es) e = (structureToEntity' es e){ entityHp = structureToMaybeInt val } +structureToEntity' ((Entry(Tag "direction") (Regular(Direction dir))):es) e = (structureToEntity' es e){ RPGEngine.Data.direction = dir } +structureToEntity' _ _ = defaultEntity + +---------------------------------------------------------------------- + +structureToMaybeInt :: Structure -> Maybe Int +structureToMaybeInt (Regular (Integer val)) = Just val +structureToMaybeInt (Regular Infinite) = Prelude.Nothing +structureToMaybeInt _ = Prelude.Nothing -- TODO \ No newline at end of file diff --git a/lib/RPGEngine/Parse/StructElement.hs b/lib/RPGEngine/Parse/TextToStructure.hs similarity index 86% rename from lib/RPGEngine/Parse/StructElement.hs rename to lib/RPGEngine/Parse/TextToStructure.hs index 35d2b08..6f1b060 100644 --- a/lib/RPGEngine/Parse/StructElement.hs +++ b/lib/RPGEngine/Parse/TextToStructure.hs @@ -1,13 +1,14 @@ -module RPGEngine.Parse.StructElement where +module RPGEngine.Parse.TextToStructure +-- Everything is exported for testing +where -import RPGEngine.Data (Action(..), Condition(..), Layout, Direction(..), Physical(..), Strip) import RPGEngine.Parse.Core ( ignoreWS ) +import RPGEngine.Data (Action (..), Condition (..), Direction (..), Layout, Strip, Physical (..)) + import Text.Parsec - ( char, - many, - try, - alphaNum, + ( alphaNum, + char, digit, noneOf, oneOf, @@ -15,7 +16,9 @@ import Text.Parsec choice, many1, notFollowedBy, - sepBy ) + sepBy, + many, + try ) import qualified Text.Parsec as P ( string ) import Text.Parsec.String ( Parser ) @@ -23,18 +26,18 @@ import Text.Parsec.String ( Parser ) -- See documentation for more details, only a short description is -- provided here. -data StructElement = Block [StructElement] - | Entry Key StructElement -- Key + Value +data Structure = Block [Structure] + | Entry Key Structure -- Key + Value | Regular Value -- Regular value, Integer or String or Infinite deriving (Eq, Show) ---------------------------------------------------------------------- -structElement :: Parser StructElement -structElement = try $ choice [block, entry, regular] +structure :: Parser Structure +structure = try $ choice [block, entry, regular] -- A list of entries -block :: Parser StructElement +block :: Parser Structure block = try $ do open <- ignoreWS $ oneOf openingBrackets middle <- ignoreWS $ choice [entry, block] `sepBy` char ',' @@ -42,15 +45,15 @@ block = try $ do ignoreWS $ char closingBracket return $ Block middle -entry :: Parser StructElement +entry :: Parser Structure entry = try $ do key <- ignoreWS key -- TODO Fix this oneOf ": " -- Can be left out - value <- ignoreWS structElement + value <- ignoreWS structure return $ Entry key value -regular :: Parser StructElement +regular :: Parser Structure regular = try $ Regular <$> value --------------------------------- Key -------------------------------- @@ -108,7 +111,7 @@ data Value = String String ---------------------------------------------------------------------- value :: Parser Value -value = choice [string, integer, infinite, action, direction] +value = choice [layout, string, integer, infinite, action, direction] string :: Parser Value string = try $ String <$> between (char '\"') (char '\"') reading @@ -134,7 +137,7 @@ action = try $ do | script == "useItem" = UseItem arg | script == "decreaseHp" = DecreaseHp first second | script == "increasePlayerHp" = IncreasePlayerHp arg - | otherwise = RPGEngine.Data.Nothing + | otherwise = DoNothing (first, ',':second) = break (== ',') arg return $ Action answer @@ -152,12 +155,15 @@ direction = try $ do make "right" = East make "down" = South make "left" = West - make _ = Center + make _ = Stay layout :: Parser Value layout = try $ do + open <- ignoreWS $ oneOf openingBrackets ignoreWS $ char '|' - list <- ignoreWS strip `sepBy` ignoreWS (char '|') + list <- ignoreWS $ ignoreWS strip `sepBy` ignoreWS (char '|') + let closing = getMatchingClosingBracket open + ignoreWS $ char closing return $ Layout list strip :: Parser Strip @@ -180,7 +186,6 @@ physical = try $ do make 'e' = Exit make _ = Void - ------------------------------ Brackets ------------------------------ openingBrackets :: [Char] diff --git a/lib/RPGEngine/Render.hs b/lib/RPGEngine/Render.hs index 09b9b66..fb9152b 100644 --- a/lib/RPGEngine/Render.hs +++ b/lib/RPGEngine/Render.hs @@ -1,38 +1,21 @@ --- Allows to render the played game - +-- Implementation for each state can be found in their respective +-- submodules. module RPGEngine.Render ( initWindow -, bgColor - +, initGame , render ) where -import RPGEngine.Data - ( State(..), - Game(..), Player (..) ) -import RPGEngine.Render.Level - ( renderLevel ) -import Graphics.Gloss - ( white, - pictures, - text, - Display(InWindow), - Color, - Picture, - scale, - translate ) -import RPGEngine.Render.Player (renderPlayer, focusPlayer) -import RPGEngine.Render.GUI (renderGUI) -import Graphics.Gloss.Data.Picture (color) -import RPGEngine.Render.Core (overlay) -import RPGEngine.Input.LvlSelect (getLvlList) -import RPGEngine.Render.LvlSelect (renderLvlList) +import RPGEngine.Render.Core ( Renderer(..) ) ------------------------------ Constants ------------------------------ - --- Game background color -bgColor :: Color -bgColor = white +import RPGEngine.Data ( State(..), Game(..), StateBase(..) ) +import Graphics.Gloss ( Display ) +import Graphics.Gloss.Data.Display ( Display(InWindow) ) +import Graphics.Gloss.Data.Picture (Picture) +import RPGEngine.Data.Default (defaultLevel, defaultPlayer) +import RPGEngine.Input.Playing (spawnPlayer) +import RPGEngine.Render.Menu (renderMenu) +import RPGEngine.Input.Menu (handleInputMenu) ---------------------------------------------------------------------- @@ -40,43 +23,16 @@ bgColor = white initWindow :: String -> (Int, Int) -> (Int, Int) -> Display initWindow = InWindow --- Render the game +-- Initialize the game +initGame :: Game +initGame = Game { + state = Menu{ base = StateBase{ renderer = renderMenu, inputHandler = handleInputMenu }}, + levels = [defaultLevel], + player = spawnPlayer defaultLevel defaultPlayer +} + +-- Render all different states render :: Game -> Picture -render g@Game{ state = Menu } = renderMenu g -render g@Game{ state = LvlSelect } = renderLevelSelection g -render g@Game{ state = Playing } = renderPlaying g -render g@Game{ state = Pause } = renderPause g -render g@Game{ state = Win } = renderWin g -render g@Game{ state = Lose } = renderLose g - ----------------------------------------------------------------------- - --- TODO -renderMenu :: Game -> Picture -renderMenu _ = text "[Press any key to start]" - --- TODO -renderLevelSelection :: Game -> Picture -renderLevelSelection _ = renderLvlList getLvlList - -renderPlaying :: Game -> Picture -renderPlaying g@Game{ playing = lvl, player = player } = pictures [ - renderLevel lvl, - renderPlayer player, - renderGUI g - ] - -renderPause :: Game -> Picture -renderPause g = pictures [renderPlaying g, pause] - where pause = pictures [ - overlay, - color white $ scale 0.5 0.5 $ text "[Press any key to continue]" - ] - --- TODO -renderWin :: Game -> Picture -renderWin _ = text "Win" - --- TODO -renderLose :: Game -> Picture -renderLose _ = text "Lose" \ No newline at end of file +render g@Game{ state = state } = renderFunc g + where stateBase = base state + renderFunc = renderer stateBase \ No newline at end of file diff --git a/lib/RPGEngine/Render/Core.hs b/lib/RPGEngine/Render/Core.hs index e5155f4..014843e 100644 --- a/lib/RPGEngine/Render/Core.hs +++ b/lib/RPGEngine/Render/Core.hs @@ -1,24 +1,21 @@ -module RPGEngine.Render.Core where +module RPGEngine.Render.Core +( Renderer -import Graphics.Gloss ( Picture, translate, pictures ) -import GHC.IO (unsafePerformIO) -import Graphics.Gloss.Juicy (loadJuicyPNG) -import Data.Maybe (fromJust) -import Graphics.Gloss.Data.Picture (scale) -import Graphics.Gloss.Data.Bitmap (BitmapData(..)) +, getRender +, setRenderPos +, overlay +) where + +import RPGEngine.Config + +import Data.Maybe +import Graphics.Gloss +import GHC.IO +import Graphics.Gloss.Juicy ----------------------------- Constants ------------------------------ --- Default scale -zoom :: Float -zoom = 5.0 - --- Resolution of the texture -resolution :: Float -resolution = 16 - -assetsFolder :: FilePath -assetsFolder = "assets/" +type Renderer a = a -> Picture unknownImage :: FilePath unknownImage = "unknown.png" @@ -54,11 +51,7 @@ library = unknown:entities ++ environment ++ gui ++ items gui = [] items = map (\(f, s) -> (f, renderPNG (assetsFolder ++ "items/" ++ s))) allItems ----------------------------------------------------------------------- - --- Turn a path to a .png file into a Picture. -renderPNG :: FilePath -> Picture -renderPNG path = scale zoom zoom $ fromJust $ unsafePerformIO $ loadJuicyPNG path +------------------------------ Exported ------------------------------ -- Retrieve an image from the library. If the library does not contain -- the requested image, a default is returned. @@ -82,4 +75,10 @@ overlay = setRenderPos offX offY $ pictures voids height = round $ 4320 / resolution / zoom width = round $ 7680 / resolution / zoom offX = negate (width `div` 2) - offY = negate (height `div` 2) \ No newline at end of file + offY = negate (height `div` 2) + +---------------------------------------------------------------------- + +-- Turn a path to a .png file into a Picture. +renderPNG :: FilePath -> Picture +renderPNG path = scale zoom zoom $ fromJust $ unsafePerformIO $ loadJuicyPNG path \ No newline at end of file diff --git a/lib/RPGEngine/Render/GUI.hs b/lib/RPGEngine/Render/GUI.hs deleted file mode 100644 index e29b012..0000000 --- a/lib/RPGEngine/Render/GUI.hs +++ /dev/null @@ -1,10 +0,0 @@ -module RPGEngine.Render.GUI -( renderGUI -) where - -import RPGEngine.Data (Game) -import Graphics.Gloss (Picture, blank) - --- TODO -renderGUI :: Game -> Picture -renderGUI _ = blank diff --git a/lib/RPGEngine/Render/LevelSelection.hs b/lib/RPGEngine/Render/LevelSelection.hs new file mode 100644 index 0000000..ee3da56 --- /dev/null +++ b/lib/RPGEngine/Render/LevelSelection.hs @@ -0,0 +1,33 @@ +module RPGEngine.Render.LevelSelection +( renderLevelSelection +) where + +import RPGEngine.Config ( resolution, zoom ) +import RPGEngine.Data ( Game (..), State (..) ) +import RPGEngine.Data.Level ( getLevelList ) +import RPGEngine.Render.Core ( Renderer ) + +import Graphics.Gloss + ( pictures, text, translate, blank, Picture, color ) +import Graphics.Gloss.Data.Picture (scale) +import RPGEngine.Input.Core (ListSelector (..)) +import Graphics.Gloss.Data.Color (red) + +------------------------------ Exported ------------------------------ + +renderLevelSelection :: Renderer Game +renderLevelSelection Game{ state = state } = result + where result = renderLevelList state + +---------------------------------------------------------------------- + +renderLevelList :: Renderer State +renderLevelList LevelSelection{ levelList = list, selector = selector } = everything + where everything = pictures $ map render entries + sel = selection selector + entries = zip [0::Int .. ] list + render (i, path) | i == sel = color red $ scale zoomed zoomed $ translate 0 (offset i) $ text path + | otherwise = scale zoomed zoomed $ translate 0 (offset i) $ text path + zoomed = 0.1 * zoom + offset i = negate (2 * resolution * zoom * fromIntegral i) +renderLevelList _ = blank \ No newline at end of file diff --git a/lib/RPGEngine/Render/Lose.hs b/lib/RPGEngine/Render/Lose.hs new file mode 100644 index 0000000..b3266e9 --- /dev/null +++ b/lib/RPGEngine/Render/Lose.hs @@ -0,0 +1,14 @@ +module RPGEngine.Render.Lose +( renderLose +) where + +import RPGEngine.Render.Core ( Renderer ) + +import RPGEngine.Data ( Game ) +import Graphics.Gloss ( text ) + +---------------------------------------------------------------------- + +-- TODO +renderLose :: Renderer Game +renderLose _ = text "Win" \ No newline at end of file diff --git a/lib/RPGEngine/Render/LvlSelect.hs b/lib/RPGEngine/Render/LvlSelect.hs deleted file mode 100644 index b395e9d..0000000 --- a/lib/RPGEngine/Render/LvlSelect.hs +++ /dev/null @@ -1,15 +0,0 @@ -module RPGEngine.Render.LvlSelect -( renderLvlList -) where - -import Graphics.Gloss ( Picture, pictures, translate, scale ) -import Graphics.Gloss.Data.Picture (blank, text) -import RPGEngine.Render.Core (resolution, zoom) - --- Render all level names, under each other. -renderLvlList :: [FilePath] -> Picture -renderLvlList list = pictures $ map render entries - where entries = zip [0::Int .. ] list - render (i, path) = scale zoomed zoomed $ translate 0 (offset i) $ text path - zoomed = 0.1 * zoom - offset i = negate (2 * resolution * zoom * fromIntegral i) \ No newline at end of file diff --git a/lib/RPGEngine/Render/Menu.hs b/lib/RPGEngine/Render/Menu.hs new file mode 100644 index 0000000..26ec414 --- /dev/null +++ b/lib/RPGEngine/Render/Menu.hs @@ -0,0 +1,14 @@ +module RPGEngine.Render.Menu +( renderMenu +) where + +import RPGEngine.Render.Core ( Renderer ) + +import RPGEngine.Data ( Game ) +import Graphics.Gloss (text) + +---------------------------------------------------------------------- + +-- TODO +renderMenu :: Renderer Game +renderMenu _ = text "[Press any key to start]" \ No newline at end of file diff --git a/lib/RPGEngine/Render/Paused.hs b/lib/RPGEngine/Render/Paused.hs new file mode 100644 index 0000000..3a49a64 --- /dev/null +++ b/lib/RPGEngine/Render/Paused.hs @@ -0,0 +1,20 @@ +module RPGEngine.Render.Paused +( renderPaused +) where + +import RPGEngine.Render.Core ( Renderer, overlay ) + +import RPGEngine.Data ( Game ) +import Graphics.Gloss ( pictures, scale, text ) +import RPGEngine.Render.Playing ( renderPlaying ) +import Graphics.Gloss.Data.Picture (color) +import Graphics.Gloss.Data.Color (white) + +------------------------------ Exported ------------------------------ + +renderPaused :: Renderer Game +renderPaused g = pictures [renderPlaying g, pause] + where pause = pictures [ + overlay, + color white $ scale 0.5 0.5 $ text "[Press any key to continue]" + ] \ No newline at end of file diff --git a/lib/RPGEngine/Render/Player.hs b/lib/RPGEngine/Render/Player.hs deleted file mode 100644 index 0b6a124..0000000 --- a/lib/RPGEngine/Render/Player.hs +++ /dev/null @@ -1,17 +0,0 @@ -module RPGEngine.Render.Player -( renderPlayer -, focusPlayer -) where - -import RPGEngine.Data (Player(..), Game(..)) -import Graphics.Gloss (Picture, text) -import RPGEngine.Render.Core (getRender, setRenderPos, zoom, resolution) -import Graphics.Gloss.Data.Picture (translate) - -renderPlayer :: Player -> Picture -renderPlayer Player{ position = (x, y) } = setRenderPos x y $ getRender "player" - -focusPlayer :: Game -> Picture -> Picture -focusPlayer Game{ player = Player{ position = (x, y)}} = translate centerX centerY - where centerX = resolution * zoom * fromIntegral (negate x) - centerY = resolution * zoom * fromIntegral (negate y) \ No newline at end of file diff --git a/lib/RPGEngine/Render/Level.hs b/lib/RPGEngine/Render/Playing.hs similarity index 54% rename from lib/RPGEngine/Render/Level.hs rename to lib/RPGEngine/Render/Playing.hs index 4e01968..0f075ba 100644 --- a/lib/RPGEngine/Render/Level.hs +++ b/lib/RPGEngine/Render/Playing.hs @@ -1,12 +1,48 @@ -module RPGEngine.Render.Level -( renderLevel +module RPGEngine.Render.Playing +( renderPlaying ) where -import Graphics.Gloss -import RPGEngine.Data -import RPGEngine.Render.Core (getRender, setRenderPos, zoom, resolution) +import RPGEngine.Render.Core ( Renderer, getRender, setRenderPos ) -renderLevel :: Level -> Picture +import RPGEngine.Data + ( Player(..), + Entity(..), + Item(..), + Physical(..), + Layout, + Level(..), + State(..), + Game(..) ) +import Graphics.Gloss ( Picture, pictures ) +import Graphics.Gloss.Data.Picture (translate) +import RPGEngine.Config (resolution, zoom) + +------------------------------ Exported ------------------------------ + +renderPlaying :: Renderer Game +renderPlaying g@Game{ state = Playing { level = lvl }, player = player } = pictures [ + renderLevel lvl, + renderPlayer player + ] + +------------------------------- Player ------------------------------- + +renderPlayer :: Renderer Player +renderPlayer Player{ position = (x, y) } = move picture + where move = setRenderPos x y + picture = getRender "player" + +-- Center the player in the middle of the screen. +-- Not in use at the moment, might be useful later. +focusPlayer :: Game -> Picture -> Picture +focusPlayer Game{ player = Player{ position = (x, y)}} = move + where move = translate centerX centerY + centerX = resolution * zoom * fromIntegral (negate x) + centerY = resolution * zoom * fromIntegral (negate y) + +------------------------------- Level -------------------------------- + +renderLevel :: Renderer Level renderLevel Level{ layout = l, items = i, entities = e } = level where level = pictures [void, layout, items, entities] void = createVoid @@ -28,6 +64,18 @@ renderStrip list = pictures physicals image Exit = pictures [getRender "tile", getRender "exit"] count = length list - 1 +createVoid :: Picture +createVoid = setRenderPos offX offY $ pictures voids + where voids = [setRenderPos x y void | x <- [0 .. width], y <- [0 .. height]] + void = getRender "void" + intZoom = round zoom :: Int + height = round $ 4320 / resolution / zoom + width = round $ 7680 / resolution / zoom + offX = negate (width `div` 2) + offY = negate (height `div` 2) + +-------------------------- Items & Entities -------------------------- + renderItems :: [Item] -> Picture renderItems list = pictures $ map renderItem list @@ -40,14 +88,4 @@ renderEntities list = pictures $ map renderEntity list renderEntity :: Entity -> Picture renderEntity Entity{ entityId = id, entityX = x, entityY = y} = setRenderPos x y image - where image = getRender id - -createVoid :: Picture -createVoid = setRenderPos offX offY $ pictures voids - where voids = [setRenderPos x y void | x <- [0 .. width], y <- [0 .. height]] - void = getRender "void" - intZoom = round zoom :: Int - height = round $ 4320 / resolution / zoom - width = round $ 7680 / resolution / zoom - offX = negate (width `div` 2) - offY = negate (height `div` 2) \ No newline at end of file + where image = getRender id \ No newline at end of file diff --git a/lib/RPGEngine/Render/Win.hs b/lib/RPGEngine/Render/Win.hs new file mode 100644 index 0000000..55d893b --- /dev/null +++ b/lib/RPGEngine/Render/Win.hs @@ -0,0 +1,14 @@ +module RPGEngine.Render.Win +( renderWin +) where + +import RPGEngine.Render.Core ( Renderer ) + +import RPGEngine.Data ( Game ) +import Graphics.Gloss (text) + +---------------------------------------------------------------------- + +-- TODO +renderWin :: Renderer Game +renderWin _ = text "Win" \ No newline at end of file diff --git a/rpg-engine.cabal b/rpg-engine.cabal index 9e11d89..9043757 100644 --- a/rpg-engine.cabal +++ b/rpg-engine.cabal @@ -13,28 +13,36 @@ library parsec >= 3.1.15.1 exposed-modules: RPGEngine - + + RPGEngine.Config + RPGEngine.Data - RPGEngine.Data.Defaults - RPGEngine.Data.State + RPGEngine.Data.Default + RPGEngine.Data.Game + RPGEngine.Data.Level RPGEngine.Input RPGEngine.Input.Core - RPGEngine.Input.Level - RPGEngine.Input.LvlSelect - RPGEngine.Input.Player + RPGEngine.Input.LevelSelection + RPGEngine.Input.Lose + RPGEngine.Input.Menu + RPGEngine.Input.Paused + RPGEngine.Input.Playing + RPGEngine.Input.Win RPGEngine.Parse RPGEngine.Parse.Core - RPGEngine.Parse.Game - RPGEngine.Parse.StructElement - + RPGEngine.Parse.TextToStructure + RPGEngine.Parse.StructureToGame + RPGEngine.Render RPGEngine.Render.Core - RPGEngine.Render.GUI - RPGEngine.Render.Level - RPGEngine.Render.LvlSelect - RPGEngine.Render.Player + RPGEngine.Render.LevelSelection + RPGEngine.Render.Lose + RPGEngine.Render.Menu + RPGEngine.Render.Paused + RPGEngine.Render.Playing + RPGEngine.Render.Win executable rpg-engine main-is: Main.hs @@ -44,10 +52,10 @@ executable rpg-engine test-suite rpg-engine-test type: exitcode-stdio-1.0 - main-is: RPGEngineSpec.hs + main-is: Spec.hs hs-source-dirs: test default-language: Haskell2010 build-depends: base >=4.7 && <5, hspec <= 2.10.6, hspec-discover, rpg-engine other-modules: - -- Parsing - ParseGameSpec, ParseStructElementSpec + Parser.GameSpec + Parser.StructureSpec diff --git a/test/ParseGameSpec.hs b/test/Parser/GameSpec.hs similarity index 78% rename from test/ParseGameSpec.hs rename to test/Parser/GameSpec.hs index 2a2d7d2..1f167a3 100644 --- a/test/ParseGameSpec.hs +++ b/test/Parser/GameSpec.hs @@ -1,10 +1,11 @@ -module ParseGameSpec where +module Parser.GameSpec where import Test.Hspec -import RPGEngine.Parse.StructElement + import RPGEngine.Data import RPGEngine.Parse.Core -import RPGEngine.Parse.Game +import RPGEngine.Parse.TextToStructure +import RPGEngine.Parse.StructureToGame spec :: Spec spec = do @@ -21,19 +22,21 @@ spec = do let input = "player: { hp: infinite, inventory: [] }" correct = Player { playerHp = Prelude.Nothing, - inventory = [] + inventory = [], + position = (0, 0) } - Right (Entry (Tag "player") struct) = parseWith structElement input - structToPlayer struct `shouldBe` correct + Right (Entry (Tag "player") struct) = parseWith structure input + structureToPlayer struct `shouldBe` correct it "without inventory" $ do let input = "player: { hp: 50, inventory: [] }" correct = Player { playerHp = Just 50, - inventory = [] + inventory = [], + position = (0, 0) } - Right (Entry (Tag "player") struct) = parseWith structElement input - structToPlayer struct `shouldBe` correct + Right (Entry (Tag "player") struct) = parseWith structure input + structureToPlayer struct `shouldBe` correct it "with inventory" $ do let input = "player: { hp: 50, inventory: [ { id: \"dagger\", x: 0, y: 0, name: \"Dolk\", description: \"Basis schade tegen monsters\", useTimes: infinite, value: 10, actions: {} } ] }" @@ -50,10 +53,11 @@ spec = do itemValue = Just 10, useTimes = Prelude.Nothing } - ] + ], + position = (0, 0) } - Right (Entry (Tag "player") struct) = parseWith structElement input - structToPlayer struct `shouldBe` correct + Right (Entry (Tag "player") struct) = parseWith structure input + structureToPlayer struct `shouldBe` correct describe "Layout" $ do it "simple" $ do @@ -72,8 +76,8 @@ spec = do itemActions = [], useTimes = Prelude.Nothing } - Right struct = parseWith structElement input - structToItem struct `shouldBe` correct + Right struct = parseWith structure input + structureToItem struct `shouldBe` correct it "with actions" $ do let input = "{ id: \"key\", x: 3, y: 1, name: \"Doorkey\", description: \"Unlocks a secret door\", useTimes: 1, value: 0, actions: { [not(inventoryFull())] retrieveItem(key), [] leave() } }" @@ -90,27 +94,27 @@ spec = do itemValue = Just 0, useTimes = Just 1 } - Right struct = parseWith structElement input - structToItem struct `shouldBe` correct + Right struct = parseWith structure input + structureToItem struct `shouldBe` correct describe "Actions" $ do it "no conditions" $ do let input = "{[] leave()}" correct = [([], Leave)] - Right struct = parseWith structElement input - structToActions struct `shouldBe` correct + Right struct = parseWith structure input + structureToActions struct `shouldBe` correct it "single condition" $ do let input = "{ [inventoryFull()] useItem(itemId)}" correct = [([InventoryFull], UseItem "itemId")] - Right struct = parseWith structElement input - structToActions struct `shouldBe` correct + Right struct = parseWith structure input + structureToActions struct `shouldBe` correct it "multiple conditions" $ do let input = "{ [not(inventoryFull()), inventoryContains(itemId)] increasePlayerHp(itemId)}" correct = [([Not InventoryFull, InventoryContains "itemId"], IncreasePlayerHp "itemId")] - Right struct = parseWith structElement input - structToActions struct `shouldBe` correct + Right struct = parseWith structure input + structureToActions struct `shouldBe` correct describe "Entities" $ do it "TODO: Simple entity" $ do @@ -118,7 +122,7 @@ spec = do describe "Level" $ do it "Simple layout" $ do - let input = "{ layout: { | * * * * * * \n| * s . . e *\n| * * * * * *\n}, items: [], entities: [] }" + let input = "{ layout: { | * * * * * * \n| * s . . e *\n| * * * * * * }, items: [], entities: [] }" correct = Level { RPGEngine.Data.layout = [ [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked], @@ -128,7 +132,8 @@ spec = do items = [], entities = [] } - Right struct = parseWith structElement input - structToLevel struct `shouldBe` correct + Right struct = parseWith structure input + structureToLevel struct `shouldBe` correct + it "TODO: Complex layout" $ do pending \ No newline at end of file diff --git a/test/ParseStructElementSpec.hs b/test/Parser/StructureSpec.hs similarity index 87% rename from test/ParseStructElementSpec.hs rename to test/Parser/StructureSpec.hs index 0f7464a..e9296b8 100644 --- a/test/ParseStructElementSpec.hs +++ b/test/Parser/StructureSpec.hs @@ -1,10 +1,10 @@ -module ParseStructElementSpec where +module Parser.StructureSpec where import Test.Hspec import RPGEngine.Data import RPGEngine.Parse.Core -import RPGEngine.Parse.StructElement +import RPGEngine.Parse.TextToStructure spec :: Spec spec = do @@ -12,21 +12,21 @@ spec = do it "can parse blocks" $ do let input = "{}" correct = Right $ Block [] - parseWith structElement input `shouldBe` correct + parseWith structure input `shouldBe` correct let input = "{{}}" correct = Right $ Block [Block []] - parseWith structElement input `shouldBe` correct + parseWith structure input `shouldBe` correct let input = "{{}, {}}" correct = Right $ Block [Block [], Block []] - parseWith structElement input `shouldBe` correct + parseWith structure input `shouldBe` correct let input = "{ id: 1 }" correct = Right (Block [ Entry (Tag "id") $ Regular $ Integer 1 ], "") - parseWithRest structElement input `shouldBe` correct + parseWithRest structure input `shouldBe` correct let input = "{ id: \"key\", x: 3, y: 1}" correct = Right $ Block [ @@ -34,14 +34,14 @@ spec = do Entry (Tag "x") $ Regular $ Integer 3, Entry (Tag "y") $ Regular $ Integer 1 ] - parseWith structElement input `shouldBe` correct + parseWith structure input `shouldBe` correct let input = "actions: { [not(inventoryFull())] retrieveItem(key), [] leave()}" correct = Right (Entry (Tag "actions") $ Block [ Entry (ConditionList [Not InventoryFull]) $ Regular $ Action $ RetrieveItem "key", Entry (ConditionList []) $ Regular $ Action Leave ], "") - parseWithRest structElement input `shouldBe` correct + parseWithRest structure input `shouldBe` correct let input = "entities: [ { id: \"door\", x: 4, name:\"Secret door\", description: \"This secret door can only be opened with a key\", direction: left, y: 1}]" correct = Right (Entry (Tag "entities") $ Block [ Block [ @@ -52,7 +52,7 @@ spec = do Entry (Tag "direction") $ Regular $ Direction West, Entry (Tag "y") $ Regular $ Integer 1 ]], "") - parseWithRest structElement input `shouldBe` correct + parseWithRest structure input `shouldBe` correct let input = "entities: [ { id: \"door\", x: 4, y: 1, name:\"Secret door\", description: \"This secret door can only be opened with a key\", actions: { [inventoryContains(key)] useItem(key), [] leave() } } ]" correct = Right (Entry (Tag "entities") $ Block [ Block [ @@ -66,7 +66,7 @@ spec = do Entry (ConditionList []) $ Regular $ Action Leave ] ]], "") - parseWithRest structElement input `shouldBe` correct + parseWithRest structure input `shouldBe` correct let input = "entities: [ { id: \"door\", x: 4, y: 1, name:\"Secret door\", description: \"This secret door can only be opened with a key\", direction: left , actions: { [inventoryContains(key)] useItem(key), [] leave() } } ]" correct = Right (Entry (Tag "entities") $ Block [ Block [ @@ -81,7 +81,7 @@ spec = do Entry (ConditionList []) $ Regular $ Action Leave ] ]], "") - parseWithRest structElement input `shouldBe` correct + parseWithRest structure input `shouldBe` correct it "can parse entries" $ do let input = "id: \"dagger\"" @@ -105,7 +105,7 @@ spec = do Entry (ConditionList [Not InventoryFull]) $ Regular $ Action $ RetrieveItem "key", Entry (ConditionList []) $ Regular $ Action Leave ], "") - parseWithRest structElement input `shouldBe` correct + parseWithRest structure input `shouldBe` correct it "can parse regulars" $ do let input = "this is a string" @@ -237,19 +237,19 @@ spec = do it "can parse directions" $ do let input = "up" correct = Right $ Direction North - parseWith RPGEngine.Parse.StructElement.direction input `shouldBe` correct + parseWith RPGEngine.Parse.TextToStructure.direction input `shouldBe` correct let input = "right" correct = Right $ Direction East - parseWith RPGEngine.Parse.StructElement.direction input `shouldBe` correct + parseWith RPGEngine.Parse.TextToStructure.direction input `shouldBe` correct let input = "down" correct = Right $ Direction South - parseWith RPGEngine.Parse.StructElement.direction input `shouldBe` correct + parseWith RPGEngine.Parse.TextToStructure.direction input `shouldBe` correct let input = "left" correct = Right $ Direction West - parseWith RPGEngine.Parse.StructElement.direction input `shouldBe` correct + parseWith RPGEngine.Parse.TextToStructure.direction input `shouldBe` correct it "can parse layouts" $ do let input = "| * * * * * * * *\n| * s . . . . e *\n| * * * * * * * *" @@ -258,7 +258,16 @@ spec = do [Blocked, Entrance, Walkable, Walkable, Walkable, Walkable, Exit, Blocked], [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked] ] - parseWith RPGEngine.Parse.StructElement.layout input `shouldBe` correct + parseWith RPGEngine.Parse.TextToStructure.layout input `shouldBe` correct + + let input = "{ |* * * * * * * *|* s . . . . e *|* * * * * * * * }" + -- correct = Right $ Entry (Tag "layout") $ Regular $ Layout [ + correct = Right $ Layout [ + [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked], + [Blocked, Entrance, Walkable, Walkable, Walkable, Walkable, Exit, Blocked], + [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked] + ] + parseWith RPGEngine.Parse.TextToStructure.value input `shouldBe` correct describe "Brackets" $ do it "matches closing <" $ do diff --git a/test/RPGEngineSpec.hs b/test/Spec.hs similarity index 100% rename from test/RPGEngineSpec.hs rename to test/Spec.hs From b7278d6afcf2aff3ee0823a980a2ed4fca49b74d Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Thu, 22 Dec 2022 09:43:17 +0100 Subject: [PATCH 19/34] Jumpbacks and continue --- lib/RPGEngine/Data.hs | 30 ++++++++++++++------------ lib/RPGEngine/Data/Game.hs | 6 +++--- lib/RPGEngine/Input/Paused.hs | 11 +++++++--- lib/RPGEngine/Input/Playing.hs | 13 ++++++----- lib/RPGEngine/Render.hs | 9 ++++---- lib/RPGEngine/Render/LevelSelection.hs | 4 ++-- lib/RPGEngine/Render/Lose.hs | 4 ++-- lib/RPGEngine/Render/Menu.hs | 4 ++-- lib/RPGEngine/Render/Paused.hs | 9 ++++---- lib/RPGEngine/Render/Playing.hs | 9 +++++--- lib/RPGEngine/Render/Win.hs | 4 ++-- 11 files changed, 59 insertions(+), 44 deletions(-) diff --git a/lib/RPGEngine/Data.hs b/lib/RPGEngine/Data.hs index 23b25c8..a47cded 100644 --- a/lib/RPGEngine/Data.hs +++ b/lib/RPGEngine/Data.hs @@ -11,35 +11,37 @@ import RPGEngine.Render.Core ( Renderer ) -- A game is the base data container. data Game = Game { - state :: State, - levels :: [Level], - player :: Player + state :: State } ------------------------------- State -------------------------------- -- Code reusability data StateBase = StateBase { - renderer :: Renderer Game, + renderer :: Renderer State, inputHandler :: InputHandler Game } -- Main menu -data State = Menu { base :: StateBase } +data State = Menu { base :: StateBase } -- Select the level you want to play - | LevelSelection { base :: StateBase, - levelList :: [FilePath], - selector :: ListSelector } + | LevelSelection { base :: StateBase, + levelList :: [FilePath], + selector :: ListSelector } -- Playing a level - | Playing { base :: StateBase, - level :: Level } + | Playing { base :: StateBase, + levels :: [Level], + level :: Level, + player :: Player, + restart :: State } -- Paused while playing a level - | Paused { base :: StateBase, - level :: Level } + | Paused { base :: StateBase, + continue :: State } -- Won a level - | Win { base :: StateBase } + | Win { base :: StateBase } -- Lost a level - | Lose { base :: StateBase } + | Lose { base :: StateBase, + restart :: State } ------------------------------- Level -------------------------------- diff --git a/lib/RPGEngine/Data/Game.hs b/lib/RPGEngine/Data/Game.hs index 2b21cd5..37fe826 100644 --- a/lib/RPGEngine/Data/Game.hs +++ b/lib/RPGEngine/Data/Game.hs @@ -6,15 +6,15 @@ import RPGEngine.Data ( Player(Player, position), Direction, Physical(Exit, Walkable, Entrance), - State(Playing, level), - Game(Game, state, player) ) + State(..), + Game(..) ) import RPGEngine.Data.Level (findAt, directionOffsets) ------------------------------ Exported ------------------------------ -- Check if a move is legal by checking what is located at the new position. isLegalMove :: Direction -> Game -> Bool -isLegalMove dir g@Game{ state = Playing { level = lvl }, player = p@Player{ position = (x, y) }} = legality +isLegalMove dir g@Game{ state = Playing { level = lvl, player = p@Player{ position = (x, y) }}} = legality where legality = physical `elem` [Walkable, Entrance, Exit] physical = findAt newPos lvl newPos = (x + xD, y + yD) diff --git a/lib/RPGEngine/Input/Paused.hs b/lib/RPGEngine/Input/Paused.hs index 03522dd..5420296 100644 --- a/lib/RPGEngine/Input/Paused.hs +++ b/lib/RPGEngine/Input/Paused.hs @@ -2,11 +2,16 @@ module RPGEngine.Input.Paused ( handleInputPaused ) where -import RPGEngine.Input.Core ( InputHandler ) +import RPGEngine.Input.Core ( InputHandler, handleAnyKey ) -import RPGEngine.Data ( Game ) +import RPGEngine.Data ( Game (..), State (..) ) ------------------------------ Exported ------------------------------ handleInputPaused :: InputHandler Game -handleInputPaused = undefined +handleInputPaused = handleAnyKey continueGame + +continueGame :: Game -> Game +continueGame g@Game{ state = Paused{ continue = state }} = newGame + where newGame = g{ state = state } +continueGame g = g \ No newline at end of file diff --git a/lib/RPGEngine/Input/Playing.hs b/lib/RPGEngine/Input/Playing.hs index b2638e0..c02a548 100644 --- a/lib/RPGEngine/Input/Playing.hs +++ b/lib/RPGEngine/Input/Playing.hs @@ -15,7 +15,7 @@ import RPGEngine.Data Level(Level, layout), State(..), StateBase(StateBase, renderer, inputHandler), - Game(Game, state, player) ) + Game(..) ) import RPGEngine.Data.Level ( findFirstOf, directionOffsets ) import RPGEngine.Data.Game ( isLegalMove ) import RPGEngine.Input.Paused ( handleInputPaused ) @@ -53,19 +53,22 @@ spawnPlayer l@Level{ layout = lay } p@Player{ position = prevPos } = p{ position ---------------------------------------------------------------------- pauseGame :: Game -> Game -pauseGame g@Game{ state = Playing{ level = level } } = pausedGame +pauseGame g@Game{ state = playing@Playing{} } = pausedGame where pausedGame = g{ state = pausedState } - pausedState = Paused{ base = newBase, level = level } + pausedState = Paused{ base = newBase, continue = playing } newBase = StateBase { renderer = renderPaused, inputHandler = handleInputPaused } +pauseGame g = g -- Move a player in a direction if possible. movePlayer :: Direction -> Game -> Game -movePlayer dir g@Game{ player = p@Player{ position = (x, y) }} = newGame - where newGame = g{ player = newPlayer } +movePlayer dir g@Game{ state = s@Playing{ player = p@Player{ position = (x, y) }}} = newGame + where newGame = g{ state = newState } + newState = s{ player = newPlayer } newPlayer = p{ position = newCoord } newCoord | isLegalMove dir g = (x + xD, y + yD) | otherwise = (x, y) (xD, yD) = directionOffsets dir +movePlayer _ g = g -- TODO goToNextLevel :: Game -> Game diff --git a/lib/RPGEngine/Render.hs b/lib/RPGEngine/Render.hs index fb9152b..0699d29 100644 --- a/lib/RPGEngine/Render.hs +++ b/lib/RPGEngine/Render.hs @@ -26,13 +26,14 @@ initWindow = InWindow -- Initialize the game initGame :: Game initGame = Game { - state = Menu{ base = StateBase{ renderer = renderMenu, inputHandler = handleInputMenu }}, - levels = [defaultLevel], - player = spawnPlayer defaultLevel defaultPlayer + state = Menu{ base = StateBase{ + renderer = renderMenu, + inputHandler = handleInputMenu + }} } -- Render all different states render :: Game -> Picture -render g@Game{ state = state } = renderFunc g +render g@Game{ state = state } = renderFunc state where stateBase = base state renderFunc = renderer stateBase \ No newline at end of file diff --git a/lib/RPGEngine/Render/LevelSelection.hs b/lib/RPGEngine/Render/LevelSelection.hs index ee3da56..f00e157 100644 --- a/lib/RPGEngine/Render/LevelSelection.hs +++ b/lib/RPGEngine/Render/LevelSelection.hs @@ -15,8 +15,8 @@ import Graphics.Gloss.Data.Color (red) ------------------------------ Exported ------------------------------ -renderLevelSelection :: Renderer Game -renderLevelSelection Game{ state = state } = result +renderLevelSelection :: Renderer State +renderLevelSelection state = result where result = renderLevelList state ---------------------------------------------------------------------- diff --git a/lib/RPGEngine/Render/Lose.hs b/lib/RPGEngine/Render/Lose.hs index b3266e9..3154bea 100644 --- a/lib/RPGEngine/Render/Lose.hs +++ b/lib/RPGEngine/Render/Lose.hs @@ -4,11 +4,11 @@ module RPGEngine.Render.Lose import RPGEngine.Render.Core ( Renderer ) -import RPGEngine.Data ( Game ) +import RPGEngine.Data ( State ) import Graphics.Gloss ( text ) ---------------------------------------------------------------------- -- TODO -renderLose :: Renderer Game +renderLose :: Renderer State renderLose _ = text "Win" \ No newline at end of file diff --git a/lib/RPGEngine/Render/Menu.hs b/lib/RPGEngine/Render/Menu.hs index 26ec414..6905814 100644 --- a/lib/RPGEngine/Render/Menu.hs +++ b/lib/RPGEngine/Render/Menu.hs @@ -4,11 +4,11 @@ module RPGEngine.Render.Menu import RPGEngine.Render.Core ( Renderer ) -import RPGEngine.Data ( Game ) +import RPGEngine.Data ( State ) import Graphics.Gloss (text) ---------------------------------------------------------------------- -- TODO -renderMenu :: Renderer Game +renderMenu :: Renderer State renderMenu _ = text "[Press any key to start]" \ No newline at end of file diff --git a/lib/RPGEngine/Render/Paused.hs b/lib/RPGEngine/Render/Paused.hs index 3a49a64..bc54169 100644 --- a/lib/RPGEngine/Render/Paused.hs +++ b/lib/RPGEngine/Render/Paused.hs @@ -4,7 +4,7 @@ module RPGEngine.Render.Paused import RPGEngine.Render.Core ( Renderer, overlay ) -import RPGEngine.Data ( Game ) +import RPGEngine.Data ( State (..) ) import Graphics.Gloss ( pictures, scale, text ) import RPGEngine.Render.Playing ( renderPlaying ) import Graphics.Gloss.Data.Picture (color) @@ -12,9 +12,10 @@ import Graphics.Gloss.Data.Color (white) ------------------------------ Exported ------------------------------ -renderPaused :: Renderer Game -renderPaused g = pictures [renderPlaying g, pause] - where pause = pictures [ +renderPaused :: Renderer State +renderPaused state = pictures [playing, pause] + where playing = renderPlaying $ continue state + pause = pictures [ overlay, color white $ scale 0.5 0.5 $ text "[Press any key to continue]" ] \ No newline at end of file diff --git a/lib/RPGEngine/Render/Playing.hs b/lib/RPGEngine/Render/Playing.hs index 0f075ba..dd9f739 100644 --- a/lib/RPGEngine/Render/Playing.hs +++ b/lib/RPGEngine/Render/Playing.hs @@ -16,14 +16,17 @@ import RPGEngine.Data import Graphics.Gloss ( Picture, pictures ) import Graphics.Gloss.Data.Picture (translate) import RPGEngine.Config (resolution, zoom) +import Graphics.Gloss (text) +import Graphics.Gloss (blank) ------------------------------ Exported ------------------------------ -renderPlaying :: Renderer Game -renderPlaying g@Game{ state = Playing { level = lvl }, player = player } = pictures [ +renderPlaying :: Renderer State +renderPlaying Playing { level = lvl, player = player } = pictures [ renderLevel lvl, renderPlayer player ] +renderPlaying _ = blank ------------------------------- Player ------------------------------- @@ -35,7 +38,7 @@ renderPlayer Player{ position = (x, y) } = move picture -- Center the player in the middle of the screen. -- Not in use at the moment, might be useful later. focusPlayer :: Game -> Picture -> Picture -focusPlayer Game{ player = Player{ position = (x, y)}} = move +focusPlayer Game{ state = Playing{ player = Player{ position = (x, y) }}} = move where move = translate centerX centerY centerX = resolution * zoom * fromIntegral (negate x) centerY = resolution * zoom * fromIntegral (negate y) diff --git a/lib/RPGEngine/Render/Win.hs b/lib/RPGEngine/Render/Win.hs index 55d893b..62c93b5 100644 --- a/lib/RPGEngine/Render/Win.hs +++ b/lib/RPGEngine/Render/Win.hs @@ -4,11 +4,11 @@ module RPGEngine.Render.Win import RPGEngine.Render.Core ( Renderer ) -import RPGEngine.Data ( Game ) +import RPGEngine.Data ( State ) import Graphics.Gloss (text) ---------------------------------------------------------------------- -- TODO -renderWin :: Renderer Game +renderWin :: Renderer State renderWin _ = text "Win" \ No newline at end of file From f529fc52372c8297ee9874e3a3595f571471dfcf Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Thu, 22 Dec 2022 13:31:46 +0100 Subject: [PATCH 20/34] Fix dependency loop --- lib/Input.hs | 10 ----- lib/RPGEngine.hs | 62 +++++++++++++++++++++++++- lib/RPGEngine/Data.hs | 23 +++------- lib/RPGEngine/Data/Default.hs | 27 +++++++---- lib/RPGEngine/Data/Game.hs | 18 +++++++- lib/RPGEngine/Data/Level.hs | 2 +- lib/RPGEngine/Input.hs | 19 ++++++-- lib/RPGEngine/Input/Core.hs | 2 + lib/RPGEngine/Input/LevelSelection.hs | 17 +++---- lib/RPGEngine/Input/Lose.hs | 5 +-- lib/RPGEngine/Input/Menu.hs | 29 ++++-------- lib/RPGEngine/Input/Paused.hs | 9 ++-- lib/RPGEngine/Input/Playing.hs | 56 +++++++++++------------ lib/RPGEngine/Input/Win.hs | 15 ++++--- lib/RPGEngine/Parse.hs | 4 +- lib/RPGEngine/Parse/StructureToGame.hs | 18 ++++---- lib/RPGEngine/Parse/TextToStructure.hs | 6 +-- lib/RPGEngine/Render.hs | 39 ++++++++-------- lib/RPGEngine/Render/LevelSelection.hs | 15 +++---- lib/RPGEngine/Render/Lose.hs | 13 +++--- lib/RPGEngine/Render/Menu.hs | 7 +-- lib/RPGEngine/Render/Paused.hs | 11 +++-- lib/RPGEngine/Render/Playing.hs | 20 +++------ lib/RPGEngine/Render/Win.hs | 11 ++--- rpg-engine.cabal | 12 ++--- 25 files changed, 251 insertions(+), 199 deletions(-) delete mode 100644 lib/Input.hs diff --git a/lib/Input.hs b/lib/Input.hs deleted file mode 100644 index 9f63d99..0000000 --- a/lib/Input.hs +++ /dev/null @@ -1,10 +0,0 @@ --- Go to the next stage of the Game --- setNextState :: Game -> Game --- setNextState game = game{ state = newState } --- where newState = nextState $ state game - --- -- Get the next state based on the current state --- nextState :: State -> State --- nextState Menu {} = defaultLvlSelect --- nextState Pause {} = Playing --- nextState _ = Menu diff --git a/lib/RPGEngine.hs b/lib/RPGEngine.hs index a2855cf..db19c09 100644 --- a/lib/RPGEngine.hs +++ b/lib/RPGEngine.hs @@ -6,8 +6,11 @@ module RPGEngine ) where import RPGEngine.Config ( bgColor, winDimensions, winOffsets ) -import RPGEngine.Render ( initWindow, render, initGame ) +import RPGEngine.Render ( initWindow, render ) import RPGEngine.Input ( handleAllInput ) +import RPGEngine.Input.Playing ( checkPlaying, spawnPlayer ) +import RPGEngine.Data (Game (..), State (..), Layout, Level (..), Physical (..)) +import RPGEngine.Data.Default (defaultLevel, defaultPlayer) import Graphics.Gloss ( play ) @@ -19,4 +22,59 @@ playRPGEngine :: String -> Int -> IO() playRPGEngine title fps = do play window bgColor fps initGame render handleAllInput step where window = initWindow title winDimensions winOffsets - step _ g = g -- TODO Do something with step? Check health etc. \ No newline at end of file + step _ = checkPlaying -- TODO Do something with step? Check health etc. + +-- TODO revert this +-- Initialize the game +initGame :: Game +-- initGame = Game { +-- state = Menu{ base = StateBase{ +-- renderer = renderMenu, +-- inputHandler = handleInputMenu +-- }} +-- } +initGame = Game{ + state = initState +} + where initState = Playing{ + levels = [defaultLevel, otherLevel], + count = 0, + level = defaultLevel, + player = spawnPlayer defaultLevel defaultPlayer, + restart = initState + } + +-- TODO remove this +otherLayout :: Layout +otherLayout = [ + [Blocked, Blocked, Blocked], + [Blocked, Entrance, Blocked], + [Blocked, Walkable, Blocked], + [Blocked, Exit, Blocked], + [Blocked, Blocked, Blocked] + ] + +-- TODO remove this +otherLevel :: Level +otherLevel = Level { + layout = otherLayout, + index = [ + (0, 0, Blocked), + (1, 0, Blocked), + (2, 0, Blocked), + (0, 1, Blocked), + (1, 1, Entrance), + (2, 1, Blocked), + (0, 2, Blocked), + (1, 2, Walkable), + (2, 2, Blocked), + (0, 3, Blocked), + (1, 3, Exit), + (2, 3, Blocked), + (0, 4, Blocked), + (1, 4, Blocked), + (2, 4, Blocked) + ], + items = [], + entities = [] +} \ No newline at end of file diff --git a/lib/RPGEngine/Data.hs b/lib/RPGEngine/Data.hs index a47cded..2c6cfa1 100644 --- a/lib/RPGEngine/Data.hs +++ b/lib/RPGEngine/Data.hs @@ -16,32 +16,23 @@ data Game = Game { ------------------------------- State -------------------------------- --- Code reusability -data StateBase = StateBase { - renderer :: Renderer State, - inputHandler :: InputHandler Game -} - -- Main menu -data State = Menu { base :: StateBase } +data State = Menu -- Select the level you want to play - | LevelSelection { base :: StateBase, - levelList :: [FilePath], + | LevelSelection { levelList :: [FilePath], selector :: ListSelector } -- Playing a level - | Playing { base :: StateBase, - levels :: [Level], + | Playing { levels :: [Level], + count :: Int, level :: Level, player :: Player, restart :: State } -- Paused while playing a level - | Paused { base :: StateBase, - continue :: State } + | Paused { continue :: State } -- Won a level - | Win { base :: StateBase } + | Win -- Lost a level - | Lose { base :: StateBase, - restart :: State } + | Lose { restart :: State } ------------------------------- Level -------------------------------- diff --git a/lib/RPGEngine/Data/Default.hs b/lib/RPGEngine/Data/Default.hs index 7129a12..f877f7f 100644 --- a/lib/RPGEngine/Data/Default.hs +++ b/lib/RPGEngine/Data/Default.hs @@ -1,11 +1,8 @@ module RPGEngine.Data.Default -- Everything is exported where -import RPGEngine.Data (Entity (..), Game (..), Item (..), Layout, Player (..), Level (..), StateBase (..), State (..), Physical (..), Direction (..)) +import RPGEngine.Data (Entity (..), Game (..), Item (..), Layout, Player (..), Level (..), State (..), Physical (..), Direction (..)) import RPGEngine.Input.Core (ListSelector(..)) -import RPGEngine.Render.LevelSelection (renderLevelSelection) -import RPGEngine.Input.Playing (spawnPlayer) -import RPGEngine.Render.Menu (renderMenu) ------------------------------ Defaults ------------------------------ @@ -36,9 +33,9 @@ defaultItem = Item { defaultLayout :: Layout defaultLayout = [ - [Blocked, Blocked, Blocked], - [Blocked, Entrance, Blocked], - [Blocked, Blocked, Blocked] + [Blocked, Blocked, Blocked, Blocked, Blocked], + [Blocked, Entrance, Walkable, Exit, Blocked], + [Blocked, Blocked, Blocked, Blocked, Blocked] ] defaultLevel :: Level @@ -52,8 +49,14 @@ defaultLevel = Level { (1, 1, Entrance), (1, 2, Blocked), (2, 0, Blocked), - (2, 1, Blocked), - (2, 2, Blocked) + (2, 1, Walkable), + (2, 2, Blocked), + (3, 0, Blocked), + (3, 1, Exit), + (3, 2, Blocked), + (4, 0, Blocked), + (4, 1, Blocked), + (4, 2, Blocked) ], items = [], entities = [] @@ -64,4 +67,10 @@ defaultPlayer = Player { playerHp = Prelude.Nothing, -- Compares to infinity inventory = [], position = (0, 0) +} + +defaultSelector :: ListSelector +defaultSelector = ListSelector { + selection = 0, + selected = False } \ No newline at end of file diff --git a/lib/RPGEngine/Data/Game.hs b/lib/RPGEngine/Data/Game.hs index 37fe826..bd1cae4 100644 --- a/lib/RPGEngine/Data/Game.hs +++ b/lib/RPGEngine/Data/Game.hs @@ -1,5 +1,7 @@ module RPGEngine.Data.Game ( isLegalMove +, isPlayerAtExit +, isPlayerDead ) where import RPGEngine.Data @@ -14,9 +16,21 @@ import RPGEngine.Data.Level (findAt, directionOffsets) -- Check if a move is legal by checking what is located at the new position. isLegalMove :: Direction -> Game -> Bool -isLegalMove dir g@Game{ state = Playing { level = lvl, player = p@Player{ position = (x, y) }}} = legality +isLegalMove dir g@Game{ state = Playing{ level = lvl, player = p@Player{ position = (x, y) }}} = legality where legality = physical `elem` [Walkable, Entrance, Exit] physical = findAt newPos lvl newPos = (x + xD, y + yD) (xD, yD) = directionOffsets dir -isLegalMove _ _ = False \ No newline at end of file +isLegalMove _ _ = False + +-- Check if a player is standing on an exit +isPlayerAtExit :: Game -> Bool +isPlayerAtExit g@Game{ state = Playing{ player = player, level = level }} = atExit + where playerPos = position player + atPos = findAt playerPos level + atExit = atPos == Exit +isPlayerAtExit _ = False + +-- Check if the players health is <= 0, which means the player is dead. +isPlayerDead :: Game -> Bool +isPlayerDead _ = False \ No newline at end of file diff --git a/lib/RPGEngine/Data/Level.hs b/lib/RPGEngine/Data/Level.hs index 86ef84d..f41a0c1 100644 --- a/lib/RPGEngine/Data/Level.hs +++ b/lib/RPGEngine/Data/Level.hs @@ -5,7 +5,7 @@ where import GHC.IO (unsafePerformIO) import System.Directory (getDirectoryContents) import RPGEngine.Input.Core (ListSelector(..)) -import RPGEngine.Data (Level (..), Physical (..), Direction (..), Entity (..), Game (..), Item (..), Player (..), StateBase (..), State (..), X, Y, Layout) +import RPGEngine.Data (Level (..), Physical (..), Direction (..), Entity (..), Game (..), Item (..), Player (..), State (..), X, Y, Layout) import RPGEngine.Config (levelFolder) ------------------------------ Exported ------------------------------ diff --git a/lib/RPGEngine/Input.hs b/lib/RPGEngine/Input.hs index 485affb..1314c9e 100644 --- a/lib/RPGEngine/Input.hs +++ b/lib/RPGEngine/Input.hs @@ -4,12 +4,23 @@ module RPGEngine.Input ( handleAllInput ) where -import RPGEngine.Input.Core -import RPGEngine.Data +import RPGEngine.Input.Core ( InputHandler, composeInputHandlers, handleAnyKey ) + +import RPGEngine.Data ( Game(..), State(..) ) +import RPGEngine.Input.Menu ( handleInputMenu ) +import RPGEngine.Input.LevelSelection (handleInputLevelSelection) +import RPGEngine.Input.Playing ( handleInputPlaying ) +import RPGEngine.Input.Paused ( handleInputPaused ) +import RPGEngine.Input.Win ( handleInputWin ) +import RPGEngine.Input.Lose ( handleInputLose ) ------------------------------ Exported ------------------------------ -- Handle all input of all states of the game. handleAllInput :: InputHandler Game -handleAllInput ev g@Game{ state = state } = handleInput ev g - where handleInput = inputHandler $ base state \ No newline at end of file +handleAllInput ev g@Game{ state = Menu } = handleInputMenu ev g +handleAllInput ev g@Game{ state = LevelSelection{} } = handleInputLevelSelection ev g +handleAllInput ev g@Game{ state = Playing{} } = handleInputPlaying ev g +handleAllInput ev g@Game{ state = Paused{} } = handleInputPaused ev g +handleAllInput ev g@Game{ state = Win } = handleInputWin ev g +handleAllInput ev g@Game{ state = Lose{} } = handleInputLose ev g \ No newline at end of file diff --git a/lib/RPGEngine/Input/Core.hs b/lib/RPGEngine/Input/Core.hs index 9044c1d..780a87b 100644 --- a/lib/RPGEngine/Input/Core.hs +++ b/lib/RPGEngine/Input/Core.hs @@ -6,6 +6,8 @@ module RPGEngine.Input.Core , handle , handleKey , handleAnyKey + +, SpecialKey(..) ) where import Graphics.Gloss.Interface.Pure.Game diff --git a/lib/RPGEngine/Input/LevelSelection.hs b/lib/RPGEngine/Input/LevelSelection.hs index d2f3578..33bdce8 100644 --- a/lib/RPGEngine/Input/LevelSelection.hs +++ b/lib/RPGEngine/Input/LevelSelection.hs @@ -1,18 +1,13 @@ -module RPGEngine.Input.LevelSelection +module RPGEngine.Input.LevelSelection ( handleInputLevelSelection ) where -import RPGEngine.Input.Core - ( composeInputHandlers, handleKey, InputHandler, ListSelector (..) ) +import RPGEngine.Input.Core (InputHandler, composeInputHandlers, handleKey, ListSelector (..)) -import RPGEngine.Config ( levelFolder ) -import RPGEngine.Data ( Game (..), Direction (..), State (..), StateBase (..) ) - -import Graphics.Gloss.Interface.IO.Game - ( Key(SpecialKey), SpecialKey(KeySpace) ) +import RPGEngine.Data (Game (..), State (..), Direction (..)) +import Graphics.Gloss.Interface.IO.Game (Key(..)) import Graphics.Gloss.Interface.IO.Interact (SpecialKey(..)) -import RPGEngine.Render.Playing (renderPlaying) -import RPGEngine.Input.Playing (handleInputPlaying) +import RPGEngine.Config (levelFolder) import RPGEngine.Parse (parse) ------------------------------ Exported ------------------------------ @@ -46,4 +41,4 @@ moveSelector dir game@Game{ state = state@LevelSelection{ levelList = list, sele diff | dir == North = -1 | dir == South = 1 | otherwise = 0 -moveSelector _ g = g +moveSelector _ g = g \ No newline at end of file diff --git a/lib/RPGEngine/Input/Lose.hs b/lib/RPGEngine/Input/Lose.hs index f9c6d0e..007a25f 100644 --- a/lib/RPGEngine/Input/Lose.hs +++ b/lib/RPGEngine/Input/Lose.hs @@ -2,12 +2,11 @@ module RPGEngine.Input.Lose ( handleInputLose ) where -import RPGEngine.Input.Core ( InputHandler ) +import RPGEngine.Input.Core (InputHandler) -import RPGEngine.Data ( Game ) +import RPGEngine.Data (Game) ------------------------------ Exported ------------------------------ --- TODO handleInputLose :: InputHandler Game handleInputLose = undefined \ No newline at end of file diff --git a/lib/RPGEngine/Input/Menu.hs b/lib/RPGEngine/Input/Menu.hs index 6903d0d..9dd27d8 100644 --- a/lib/RPGEngine/Input/Menu.hs +++ b/lib/RPGEngine/Input/Menu.hs @@ -2,35 +2,22 @@ module RPGEngine.Input.Menu ( handleInputMenu ) where -import RPGEngine.Input.Core ( InputHandler, composeInputHandlers, handleAnyKey, ListSelector (..) ) - -import RPGEngine.Data ( Game (..), State (..), StateBase (..) ) -import RPGEngine.Render.LevelSelection (renderLevelSelection) -import RPGEngine.Input.LevelSelection (handleInputLevelSelection) +import RPGEngine.Input.Core (InputHandler, composeInputHandlers, handleAnyKey) +import RPGEngine.Data (Game (state), State (..)) +import RPGEngine.Data.Default (defaultSelector) import RPGEngine.Data.Level (getLevelList) ------------------------------ Exported ------------------------------ handleInputMenu :: InputHandler Game handleInputMenu = composeInputHandlers [ - handleAnyKey selectLevel + handleAnyKey (\game -> game{ state = startLevelSelection }) ] ---------------------------------------------------------------------- -selectLevel :: Game -> Game -selectLevel g@Game{ state = state } = g{ state = defaultLevelSelection } - -defaultLevelSelection :: State -defaultLevelSelection = LevelSelection { base = base, selector = defaultSelector, levelList = levels } - where base = StateBase { - renderer = renderLevelSelection, - inputHandler = handleInputLevelSelection - } - levels = getLevelList - -defaultSelector :: ListSelector -defaultSelector = ListSelector { - selection = 0, - selected = False +startLevelSelection :: State +startLevelSelection = LevelSelection { + levelList = getLevelList, + selector = defaultSelector } \ No newline at end of file diff --git a/lib/RPGEngine/Input/Paused.hs b/lib/RPGEngine/Input/Paused.hs index 5420296..7ef6c63 100644 --- a/lib/RPGEngine/Input/Paused.hs +++ b/lib/RPGEngine/Input/Paused.hs @@ -1,16 +1,17 @@ -module RPGEngine.Input.Paused +module RPGEngine.Input.Paused ( handleInputPaused ) where -import RPGEngine.Input.Core ( InputHandler, handleAnyKey ) - -import RPGEngine.Data ( Game (..), State (..) ) +import RPGEngine.Input.Core (InputHandler, handleAnyKey) +import RPGEngine.Data (Game (..), State (continue, Paused)) ------------------------------ Exported ------------------------------ handleInputPaused :: InputHandler Game handleInputPaused = handleAnyKey continueGame +---------------------------------------------------------------------- + continueGame :: Game -> Game continueGame g@Game{ state = Paused{ continue = state }} = newGame where newGame = g{ state = state } diff --git a/lib/RPGEngine/Input/Playing.hs b/lib/RPGEngine/Input/Playing.hs index c02a548..e8b6ab0 100644 --- a/lib/RPGEngine/Input/Playing.hs +++ b/lib/RPGEngine/Input/Playing.hs @@ -1,28 +1,17 @@ -module RPGEngine.Input.Playing +module RPGEngine.Input.Playing ( handleInputPlaying +, checkPlaying , spawnPlayer ) where -import RPGEngine.Input.Core - ( composeInputHandlers, handleKey, InputHandler ) +import RPGEngine.Input.Core (InputHandler, handleKey, composeInputHandlers) -import RPGEngine.Data - ( Player(Player, position), - Direction(West, North, East, South), - Physical(Entrance), - Y, - X, - Level(Level, layout), - State(..), - StateBase(StateBase, renderer, inputHandler), - Game(..) ) -import RPGEngine.Data.Level ( findFirstOf, directionOffsets ) -import RPGEngine.Data.Game ( isLegalMove ) -import RPGEngine.Input.Paused ( handleInputPaused ) -import RPGEngine.Render.Paused ( renderPaused ) +import RPGEngine.Data (Game (..), Layout(..), Level(..), Physical(..), Player(..), State(..), X, Y, Direction (..)) +import RPGEngine.Data.Game (isLegalMove, isPlayerDead, isPlayerAtExit) +import RPGEngine.Data.Level (directionOffsets, findFirstOf) +import Data.Maybe (fromJust, isNothing) import Graphics.Gloss.Interface.IO.Game (Key(..)) import Graphics.Gloss.Interface.IO.Interact (SpecialKey(..)) -import Data.Maybe (isNothing, fromJust) ------------------------------ Exported ------------------------------ @@ -43,6 +32,8 @@ handleInputPlaying = composeInputHandlers [ handleKey (Char 'a') $ movePlayer West ] +---------------------------------------------------------------------- + -- Set the initial position of the player in a given level. spawnPlayer :: Level -> Player -> Player spawnPlayer l@Level{ layout = lay } p@Player{ position = prevPos } = p{ position = newPos } @@ -50,15 +41,30 @@ spawnPlayer l@Level{ layout = lay } p@Player{ position = prevPos } = p{ position newPos | isNothing try = prevPos | otherwise = fromJust try ----------------------------------------------------------------------- +checkPlaying :: Game -> Game +checkPlaying g@Game{ state = s@Playing{ restart = restart }} = newGame + where newGame | isPlayerDead g = loseGame + | isPlayerAtExit g = g{ state = goToNextLevel s } + | otherwise = g + loseGame = g{ state = restart } +checkPlaying g = g pauseGame :: Game -> Game pauseGame g@Game{ state = playing@Playing{} } = pausedGame - where pausedGame = g{ state = pausedState } - pausedState = Paused{ base = newBase, continue = playing } - newBase = StateBase { renderer = renderPaused, inputHandler = handleInputPaused } + where pausedGame = g{ state = Paused playing } pauseGame g = g +-- Go to next level if there is a next level, otherwise, initialize win state. +goToNextLevel :: State -> State +goToNextLevel s@Playing{ levels = levels, level = current, count = count, player = player } = nextState + where -- Either the next level or winState + nextState | (count + 1) < length levels = nextLevelState + | otherwise = Win + nextLevelState = s{ level = nextLevel, count = count + 1, player = movedPlayer } + nextLevel = levels !! (count + 1) + movedPlayer = spawnPlayer nextLevel player +goToNextLevel s = s + -- Move a player in a direction if possible. movePlayer :: Direction -> Game -> Game movePlayer dir g@Game{ state = s@Playing{ player = p@Player{ position = (x, y) }}} = newGame @@ -70,12 +76,6 @@ movePlayer dir g@Game{ state = s@Playing{ player = p@Player{ position = (x, y) } (xD, yD) = directionOffsets dir movePlayer _ g = g --- TODO -goToNextLevel :: Game -> Game -goToNextLevel = undefined - ----------------------------------------------------------------------- - -- Map all Physicals onto coordinates putCoords :: Level -> [(X, Y, Physical)] putCoords l@Level{ layout = lay } = concatMap (\(a, bs) -> map (\(b, c) -> (b, a, c)) bs) numberedList diff --git a/lib/RPGEngine/Input/Win.hs b/lib/RPGEngine/Input/Win.hs index 37434ee..3eeaf5d 100644 --- a/lib/RPGEngine/Input/Win.hs +++ b/lib/RPGEngine/Input/Win.hs @@ -1,13 +1,16 @@ -module RPGEngine.Input.Win +module RPGEngine.Input.Win ( handleInputWin ) where -import RPGEngine.Input.Core ( InputHandler ) - -import RPGEngine.Data ( Game ) +import RPGEngine.Input.Core (InputHandler, handleAnyKey) +import RPGEngine.Data (Game (..), State (Menu)) ------------------------------ Exported ------------------------------ --- TODO handleInputWin :: InputHandler Game -handleInputWin = undefined \ No newline at end of file +handleInputWin = handleAnyKey goToMenu + +---------------------------------------------------------------------- + +goToMenu :: Game -> Game +goToMenu g = g{ state = Menu } \ No newline at end of file diff --git a/lib/RPGEngine/Parse.hs b/lib/RPGEngine/Parse.hs index 9b1c976..c12e8da 100644 --- a/lib/RPGEngine/Parse.hs +++ b/lib/RPGEngine/Parse.hs @@ -13,4 +13,6 @@ import RPGEngine.Parse.TextToStructure (structure) parse :: FilePath -> Game parse filename = structureToGame struct where (Right struct) = unsafePerformIO io - io = parseFromFile structure filename \ No newline at end of file + io = parseFromFile structure filename + +tempParse = parseFromFile \ No newline at end of file diff --git a/lib/RPGEngine/Parse/StructureToGame.hs b/lib/RPGEngine/Parse/StructureToGame.hs index ddbcd3d..7e81274 100644 --- a/lib/RPGEngine/Parse/StructureToGame.hs +++ b/lib/RPGEngine/Parse/StructureToGame.hs @@ -11,24 +11,24 @@ import RPGEngine.Data Item(itemId, itemX, itemY, itemName, itemDescription, itemValue, itemActions, useTimes), Level(layout, items, entities), - Game (..), State (..), StateBase (..) ) + Game (..), State (..) ) import RPGEngine.Parse.TextToStructure ( Value(Infinite, Action, Layout, String, Direction, Integer), Key(Tag, ConditionList), Structure(..) ) import RPGEngine.Data.Default (defaultPlayer, defaultLevel, defaultItem, defaultEntity) -import RPGEngine.Render.Playing (renderPlaying) -import RPGEngine.Input.Playing (handleInputPlaying) ------------------------------ Exported ------------------------------ structureToGame :: Structure -> Game -structureToGame (Block [(Entry(Tag "player") playerBlock), (Entry(Tag "levels") levelsBlock)]) = game - where game = Game{ state = newState, levels = newLevels, player = newPlayer } - newState = Playing{ base = playingBase, level = currentLevel } - playingBase = StateBase{ renderer = renderPlaying, inputHandler = handleInputPlaying } - newLevels = structureToLevels levelsBlock - currentLevel = head newLevels +-- structureToGame [Entry(Tag "player") playerBlock, Entry(Tag "levels") levelsBlock] = game +structureToGame (Entry (Tag "player") playerBlock) = game + where game = Game{ state = newState } + newState = Playing{ levels = newLevels, level = currentLevel, player = newPlayer, restart = newState } + -- newLevels = structureToLevels levelsBlock + -- currentLevel = head newLevels + newLevels = [defaultLevel] + currentLevel = defaultLevel newPlayer = structureToPlayer playerBlock ------------------------------- Player ------------------------------- diff --git a/lib/RPGEngine/Parse/TextToStructure.hs b/lib/RPGEngine/Parse/TextToStructure.hs index 6f1b060..dc76003 100644 --- a/lib/RPGEngine/Parse/TextToStructure.hs +++ b/lib/RPGEngine/Parse/TextToStructure.hs @@ -27,9 +27,9 @@ import Text.Parsec.String ( Parser ) -- See documentation for more details, only a short description is -- provided here. data Structure = Block [Structure] - | Entry Key Structure -- Key + Value - | Regular Value -- Regular value, Integer or String or Infinite - deriving (Eq, Show) + | Entry Key Structure -- Key + Value + | Regular Value -- Regular value, Integer or String or Infinite + deriving (Eq, Show) ---------------------------------------------------------------------- diff --git a/lib/RPGEngine/Render.hs b/lib/RPGEngine/Render.hs index 0699d29..907f3e5 100644 --- a/lib/RPGEngine/Render.hs +++ b/lib/RPGEngine/Render.hs @@ -2,20 +2,22 @@ -- submodules. module RPGEngine.Render ( initWindow -, initGame , render ) where import RPGEngine.Render.Core ( Renderer(..) ) -import RPGEngine.Data ( State(..), Game(..), StateBase(..) ) -import Graphics.Gloss ( Display ) -import Graphics.Gloss.Data.Display ( Display(InWindow) ) -import Graphics.Gloss.Data.Picture (Picture) -import RPGEngine.Data.Default (defaultLevel, defaultPlayer) -import RPGEngine.Input.Playing (spawnPlayer) -import RPGEngine.Render.Menu (renderMenu) -import RPGEngine.Input.Menu (handleInputMenu) +import RPGEngine.Data (Game(..), State (..)) +import RPGEngine.Render.Menu( renderMenu ) +import RPGEngine.Render.LevelSelection ( renderLevelSelection ) +import RPGEngine.Render.Playing ( renderPlaying ) +import RPGEngine.Render.Paused ( renderPaused ) +import RPGEngine.Render.Win ( renderWin ) +import RPGEngine.Render.Lose ( renderLose ) + +import Graphics.Gloss (Display) +import Graphics.Gloss.Data.Picture (Picture, blank) +import Graphics.Gloss.Data.Display (Display(..)) ---------------------------------------------------------------------- @@ -23,17 +25,12 @@ import RPGEngine.Input.Menu (handleInputMenu) initWindow :: String -> (Int, Int) -> (Int, Int) -> Display initWindow = InWindow --- Initialize the game -initGame :: Game -initGame = Game { - state = Menu{ base = StateBase{ - renderer = renderMenu, - inputHandler = handleInputMenu - }} -} - -- Render all different states render :: Game -> Picture -render g@Game{ state = state } = renderFunc state - where stateBase = base state - renderFunc = renderer stateBase \ No newline at end of file +render Game{ state = s@Menu } = renderMenu s +render Game{ state = s@LevelSelection{} } = renderLevelSelection s +render Game{ state = s@Playing{} } = renderPlaying s +render Game{ state = s@Paused{} } = renderPaused s +render Game{ state = s@Win } = renderWin s +render Game{ state = s@Lose{} } = renderLose s +render _ = blank \ No newline at end of file diff --git a/lib/RPGEngine/Render/LevelSelection.hs b/lib/RPGEngine/Render/LevelSelection.hs index f00e157..f6e0912 100644 --- a/lib/RPGEngine/Render/LevelSelection.hs +++ b/lib/RPGEngine/Render/LevelSelection.hs @@ -2,16 +2,15 @@ module RPGEngine.Render.LevelSelection ( renderLevelSelection ) where -import RPGEngine.Config ( resolution, zoom ) -import RPGEngine.Data ( Game (..), State (..) ) -import RPGEngine.Data.Level ( getLevelList ) -import RPGEngine.Render.Core ( Renderer ) +import RPGEngine.Render.Core (Renderer) -import Graphics.Gloss - ( pictures, text, translate, blank, Picture, color ) -import Graphics.Gloss.Data.Picture (scale) -import RPGEngine.Input.Core (ListSelector (..)) +import RPGEngine.Config (resolution, zoom) +import RPGEngine.Data (State (..)) + +import Graphics.Gloss ( pictures, color, text, translate, blank ) import Graphics.Gloss.Data.Color (red) +import Graphics.Gloss.Data.Picture (scale) +import RPGEngine.Input.Core (ListSelector(..)) ------------------------------ Exported ------------------------------ diff --git a/lib/RPGEngine/Render/Lose.hs b/lib/RPGEngine/Render/Lose.hs index 3154bea..1e6d730 100644 --- a/lib/RPGEngine/Render/Lose.hs +++ b/lib/RPGEngine/Render/Lose.hs @@ -1,14 +1,15 @@ -module RPGEngine.Render.Lose +module RPGEngine.Render.Lose ( renderLose ) where -import RPGEngine.Render.Core ( Renderer ) +import RPGEngine.Render.Core (Renderer) -import RPGEngine.Data ( State ) -import Graphics.Gloss ( text ) +import RPGEngine.Data (State) ----------------------------------------------------------------------- +import Graphics.Gloss (text) + +------------------------------ Exported ------------------------------ -- TODO renderLose :: Renderer State -renderLose _ = text "Win" \ No newline at end of file +renderLose _ = text "You lose" \ No newline at end of file diff --git a/lib/RPGEngine/Render/Menu.hs b/lib/RPGEngine/Render/Menu.hs index 6905814..e5f66d8 100644 --- a/lib/RPGEngine/Render/Menu.hs +++ b/lib/RPGEngine/Render/Menu.hs @@ -2,12 +2,13 @@ module RPGEngine.Render.Menu ( renderMenu ) where -import RPGEngine.Render.Core ( Renderer ) +import RPGEngine.Render.Core (Renderer) + +import RPGEngine.Data (State) -import RPGEngine.Data ( State ) import Graphics.Gloss (text) ----------------------------------------------------------------------- +------------------------------ Exported ------------------------------ -- TODO renderMenu :: Renderer State diff --git a/lib/RPGEngine/Render/Paused.hs b/lib/RPGEngine/Render/Paused.hs index bc54169..6fa3d95 100644 --- a/lib/RPGEngine/Render/Paused.hs +++ b/lib/RPGEngine/Render/Paused.hs @@ -2,13 +2,12 @@ module RPGEngine.Render.Paused ( renderPaused ) where -import RPGEngine.Render.Core ( Renderer, overlay ) +import RPGEngine.Render.Core (Renderer, overlay) -import RPGEngine.Data ( State (..) ) -import Graphics.Gloss ( pictures, scale, text ) -import RPGEngine.Render.Playing ( renderPlaying ) -import Graphics.Gloss.Data.Picture (color) -import Graphics.Gloss.Data.Color (white) +import RPGEngine.Data (State(..)) +import RPGEngine.Render.Playing (renderPlaying) + +import Graphics.Gloss (pictures, white, color, Color(..), text, scale) ------------------------------ Exported ------------------------------ diff --git a/lib/RPGEngine/Render/Playing.hs b/lib/RPGEngine/Render/Playing.hs index dd9f739..6c5a47b 100644 --- a/lib/RPGEngine/Render/Playing.hs +++ b/lib/RPGEngine/Render/Playing.hs @@ -2,22 +2,13 @@ module RPGEngine.Render.Playing ( renderPlaying ) where -import RPGEngine.Render.Core ( Renderer, getRender, setRenderPos ) +import RPGEngine.Render.Core (Renderer, getRender, setRenderPos) -import RPGEngine.Data - ( Player(..), - Entity(..), - Item(..), - Physical(..), - Layout, - Level(..), - State(..), - Game(..) ) -import Graphics.Gloss ( Picture, pictures ) -import Graphics.Gloss.Data.Picture (translate) import RPGEngine.Config (resolution, zoom) -import Graphics.Gloss (text) -import Graphics.Gloss (blank) +import RPGEngine.Data (State(..), Player (..), Game (..), Level (..), Layout, Physical (..), Item (..), Entity (..)) + +import Graphics.Gloss ( pictures, Picture, translate ) +import Graphics.Gloss.Data.Picture (blank) ------------------------------ Exported ------------------------------ @@ -42,6 +33,7 @@ focusPlayer Game{ state = Playing{ player = Player{ position = (x, y) }}} = move where move = translate centerX centerY centerX = resolution * zoom * fromIntegral (negate x) centerY = resolution * zoom * fromIntegral (negate y) +focusPlayer _ = id ------------------------------- Level -------------------------------- diff --git a/lib/RPGEngine/Render/Win.hs b/lib/RPGEngine/Render/Win.hs index 62c93b5..189cef8 100644 --- a/lib/RPGEngine/Render/Win.hs +++ b/lib/RPGEngine/Render/Win.hs @@ -1,14 +1,15 @@ -module RPGEngine.Render.Win +module RPGEngine.Render.Win ( renderWin ) where -import RPGEngine.Render.Core ( Renderer ) +import RPGEngine.Render.Core (Renderer) + +import RPGEngine.Data (State) -import RPGEngine.Data ( State ) import Graphics.Gloss (text) ----------------------------------------------------------------------- +------------------------------ Exported ------------------------------ -- TODO renderWin :: Renderer State -renderWin _ = text "Win" \ No newline at end of file +renderWin _ = text "You win!\nPress any key to return to the menu." \ No newline at end of file diff --git a/rpg-engine.cabal b/rpg-engine.cabal index 9043757..76a2eee 100644 --- a/rpg-engine.cabal +++ b/rpg-engine.cabal @@ -23,12 +23,12 @@ library RPGEngine.Input RPGEngine.Input.Core - RPGEngine.Input.LevelSelection - RPGEngine.Input.Lose RPGEngine.Input.Menu - RPGEngine.Input.Paused + RPGEngine.Input.LevelSelection RPGEngine.Input.Playing + RPGEngine.Input.Paused RPGEngine.Input.Win + RPGEngine.Input.Lose RPGEngine.Parse RPGEngine.Parse.Core @@ -37,12 +37,12 @@ library RPGEngine.Render RPGEngine.Render.Core - RPGEngine.Render.LevelSelection - RPGEngine.Render.Lose RPGEngine.Render.Menu - RPGEngine.Render.Paused + RPGEngine.Render.LevelSelection RPGEngine.Render.Playing + RPGEngine.Render.Paused RPGEngine.Render.Win + RPGEngine.Render.Lose executable rpg-engine main-is: Main.hs From d0302c3156ac1bd9c007748cdb59998ebc687202 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Thu, 22 Dec 2022 14:35:58 +0100 Subject: [PATCH 21/34] #5 Render inventory when pressing `i` --- README.md | 13 ++++++----- lib/RPGEngine/Config.hs | 4 ++++ lib/RPGEngine/Data.hs | 8 ++++--- lib/RPGEngine/Input/Core.hs | 30 ++++++++++++------------ lib/RPGEngine/Input/LevelSelection.hs | 8 +++---- lib/RPGEngine/Input/Playing.hs | 32 +++++++++++++++++--------- lib/RPGEngine/Render.hs | 3 +-- lib/RPGEngine/Render/LevelSelection.hs | 9 ++++---- lib/RPGEngine/Render/Playing.hs | 21 ++++++++++++----- 9 files changed, 76 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 060cb52..b82a687 100644 --- a/README.md +++ b/README.md @@ -51,12 +51,13 @@ This README serves as both documentation and project report, so excuse the detai These are the keybinds *in* the game. All other keybinds in the menus should be straightforward. -| Action | Primary | Secondary | -| ---------- | ------------- | ----------- | -| Move up | `Arrow Up` | `w` | -| Move left | `Arrow Left` | `a` | -| Move down | `Arrow Down` | `s` | -| Move right | `Arrow Right` | `d` | +| Action | Primary | Secondary | +| -------------- | ------------- | ----------- | +| Move up | `Arrow Up` | `w` | +| Move left | `Arrow Left` | `a` | +| Move down | `Arrow Down` | `s` | +| Move right | `Arrow Right` | `d` | +| Show inventory | `i` | | ### Example playthrough diff --git a/lib/RPGEngine/Config.hs b/lib/RPGEngine/Config.hs index a8d719c..b752af4 100644 --- a/lib/RPGEngine/Config.hs +++ b/lib/RPGEngine/Config.hs @@ -23,6 +23,10 @@ bgColor = white zoom :: Float zoom = 5.0 +-- UI scale +uizoom :: Float +uizoom = 0.5 + -- Resolution of the texture resolution :: Float resolution = 16 diff --git a/lib/RPGEngine/Data.hs b/lib/RPGEngine/Data.hs index 2c6cfa1..01efa75 100644 --- a/lib/RPGEngine/Data.hs +++ b/lib/RPGEngine/Data.hs @@ -99,9 +99,11 @@ data Direction = North deriving (Eq, Show) data Player = Player { - playerHp :: HP, - inventory :: [Item], - position :: (X, Y) + playerHp :: HP, + inventory :: [Item], + position :: (X, Y), + showHp :: Bool, + showInventory :: Bool } deriving (Eq, Show) ------------------------------ Condition ----------------------------- diff --git a/lib/RPGEngine/Input/Core.hs b/lib/RPGEngine/Input/Core.hs index 780a87b..07ea182 100644 --- a/lib/RPGEngine/Input/Core.hs +++ b/lib/RPGEngine/Input/Core.hs @@ -32,16 +32,16 @@ composeInputHandlers (ih:ihs) ev a = composeInputHandlers ihs ev (ih ev a) -- Handle any event handle :: Event -> (a -> a) -> InputHandler a -handle (EventKey key _ _ _) = handleKey key +handle (EventKey key state _ _) = handleKey key state -- handle (EventMotion _) = undefined -- TODO -- handle (EventResize _) = undefined -- TODO -handle _ = const (const id) +handle _ = const (const id) -- Handle a event by pressing a key -handleKey :: Key -> (a -> a) -> InputHandler a -handleKey (SpecialKey sk) = handleSpecialKey sk -handleKey (Char c ) = handleCharKey c -handleKey (MouseButton _ ) = const (const id) +handleKey :: Key -> KeyState -> (a -> a) -> InputHandler a +handleKey (SpecialKey sk) s = handleSpecialKey sk s +handleKey (Char c ) s = handleCharKey c s +handleKey (MouseButton _ ) _ = const (const id) -- Handle any key, equivalent to "Press any key to start" handleAnyKey :: (a -> a) -> InputHandler a @@ -50,14 +50,14 @@ handleAnyKey _ _ = id --------------------------- Help functions --------------------------- -handleCharKey :: Char -> (a -> a) -> InputHandler a -handleCharKey c1 f (EventKey (Char c2) Down _ _) - | c1 == c2 = f - | otherwise = id -handleCharKey _ _ _ = id +handleCharKey :: Char -> KeyState -> (a -> a) -> InputHandler a +handleCharKey c1 s1 f (EventKey (Char c2) s2 _ _) + | c1 == c2 && s1 == s2 = f + | otherwise = id +handleCharKey _ _ _ _ = id -handleSpecialKey :: SpecialKey -> (a -> a) -> InputHandler a -handleSpecialKey sk1 f (EventKey (SpecialKey sk2) Down _ _) - | sk1 == sk2 = f +handleSpecialKey :: SpecialKey -> KeyState -> (a -> a) -> InputHandler a +handleSpecialKey sk1 s1 f (EventKey (SpecialKey sk2) s2 _ _) + | sk1 == sk2 && s1 == s2 = f | otherwise = id -handleSpecialKey _ _ _ = id \ No newline at end of file +handleSpecialKey _ _ _ _ = id \ No newline at end of file diff --git a/lib/RPGEngine/Input/LevelSelection.hs b/lib/RPGEngine/Input/LevelSelection.hs index 33bdce8..5266782 100644 --- a/lib/RPGEngine/Input/LevelSelection.hs +++ b/lib/RPGEngine/Input/LevelSelection.hs @@ -6,7 +6,7 @@ import RPGEngine.Input.Core (InputHandler, composeInputHandlers, handleKey, List import RPGEngine.Data (Game (..), State (..), Direction (..)) import Graphics.Gloss.Interface.IO.Game (Key(..)) -import Graphics.Gloss.Interface.IO.Interact (SpecialKey(..)) +import Graphics.Gloss.Interface.IO.Interact (SpecialKey(..), KeyState(..)) import RPGEngine.Config (levelFolder) import RPGEngine.Parse (parse) @@ -14,10 +14,10 @@ import RPGEngine.Parse (parse) handleInputLevelSelection :: InputHandler Game handleInputLevelSelection = composeInputHandlers [ - handleKey (SpecialKey KeySpace) selectLevel, + handleKey (SpecialKey KeySpace) Down selectLevel, - handleKey (SpecialKey KeyUp) $ moveSelector North, - handleKey (SpecialKey KeyDown) $ moveSelector South + handleKey (SpecialKey KeyUp) Down $ moveSelector North, + handleKey (SpecialKey KeyDown) Down $ moveSelector South ] ---------------------------------------------------------------------- diff --git a/lib/RPGEngine/Input/Playing.hs b/lib/RPGEngine/Input/Playing.hs index e8b6ab0..6ca8f47 100644 --- a/lib/RPGEngine/Input/Playing.hs +++ b/lib/RPGEngine/Input/Playing.hs @@ -4,32 +4,36 @@ module RPGEngine.Input.Playing , spawnPlayer ) where -import RPGEngine.Input.Core (InputHandler, handleKey, composeInputHandlers) +import RPGEngine.Input.Core (InputHandler, handle, handleKey, composeInputHandlers) import RPGEngine.Data (Game (..), Layout(..), Level(..), Physical(..), Player(..), State(..), X, Y, Direction (..)) import RPGEngine.Data.Game (isLegalMove, isPlayerDead, isPlayerAtExit) import RPGEngine.Data.Level (directionOffsets, findFirstOf) + import Data.Maybe (fromJust, isNothing) import Graphics.Gloss.Interface.IO.Game (Key(..)) -import Graphics.Gloss.Interface.IO.Interact (SpecialKey(..)) +import Graphics.Gloss.Interface.IO.Interact (SpecialKey(..), KeyState(..), Event(..), KeyState(..)) ------------------------------ Exported ------------------------------ handleInputPlaying :: InputHandler Game handleInputPlaying = composeInputHandlers [ -- Pause the game - handleKey (Char 'p') pauseGame, + handleKey (Char 'p') Down pauseGame, -- Player movement - handleKey (SpecialKey KeyUp) $ movePlayer North, - handleKey (SpecialKey KeyRight) $ movePlayer East, - handleKey (SpecialKey KeyDown) $ movePlayer South, - handleKey (SpecialKey KeyLeft) $ movePlayer West, + handleKey (SpecialKey KeyUp) Down $ movePlayer North, + handleKey (SpecialKey KeyRight) Down $ movePlayer East, + handleKey (SpecialKey KeyDown) Down $ movePlayer South, + handleKey (SpecialKey KeyLeft) Down $ movePlayer West, - handleKey (Char 'w') $ movePlayer North, - handleKey (Char 'd') $ movePlayer East, - handleKey (Char 's') $ movePlayer South, - handleKey (Char 'a') $ movePlayer West + handleKey (Char 'w') Down $ movePlayer North, + handleKey (Char 'd') Down $ movePlayer East, + handleKey (Char 's') Down $ movePlayer South, + handleKey (Char 'a') Down $ movePlayer West, + + handleKey (Char 'i') Down $ toggleInventoryShown True, + handleKey (Char 'i') Up $ toggleInventoryShown False ] ---------------------------------------------------------------------- @@ -76,6 +80,12 @@ movePlayer dir g@Game{ state = s@Playing{ player = p@Player{ position = (x, y) } (xD, yD) = directionOffsets dir movePlayer _ g = g +toggleInventoryShown :: Bool -> Game -> Game +toggleInventoryShown shown g@Game{ state = s@Playing{ player = p }}= newGame + where newGame = g{ state = newState } + newState = s{ player = newPlayer } + newPlayer = p{ showInventory = shown } + -- Map all Physicals onto coordinates putCoords :: Level -> [(X, Y, Physical)] putCoords l@Level{ layout = lay } = concatMap (\(a, bs) -> map (\(b, c) -> (b, a, c)) bs) numberedList diff --git a/lib/RPGEngine/Render.hs b/lib/RPGEngine/Render.hs index 907f3e5..a9c2e74 100644 --- a/lib/RPGEngine/Render.hs +++ b/lib/RPGEngine/Render.hs @@ -32,5 +32,4 @@ render Game{ state = s@LevelSelection{} } = renderLevelSelection s render Game{ state = s@Playing{} } = renderPlaying s render Game{ state = s@Paused{} } = renderPaused s render Game{ state = s@Win } = renderWin s -render Game{ state = s@Lose{} } = renderLose s -render _ = blank \ No newline at end of file +render Game{ state = s@Lose{} } = renderLose s \ No newline at end of file diff --git a/lib/RPGEngine/Render/LevelSelection.hs b/lib/RPGEngine/Render/LevelSelection.hs index f6e0912..85dc81c 100644 --- a/lib/RPGEngine/Render/LevelSelection.hs +++ b/lib/RPGEngine/Render/LevelSelection.hs @@ -4,7 +4,7 @@ module RPGEngine.Render.LevelSelection import RPGEngine.Render.Core (Renderer) -import RPGEngine.Config (resolution, zoom) +import RPGEngine.Config (resolution, zoom, uizoom) import RPGEngine.Data (State (..)) import Graphics.Gloss ( pictures, color, text, translate, blank ) @@ -25,8 +25,7 @@ renderLevelList LevelSelection{ levelList = list, selector = selector } = everyt where everything = pictures $ map render entries sel = selection selector entries = zip [0::Int .. ] list - render (i, path) | i == sel = color red $ scale zoomed zoomed $ translate 0 (offset i) $ text path - | otherwise = scale zoomed zoomed $ translate 0 (offset i) $ text path - zoomed = 0.1 * zoom - offset i = negate (2 * resolution * zoom * fromIntegral i) + render (i, path) | i == sel = color red $ scale uizoom uizoom $ translate 0 (offset i) $ text path + | otherwise = scale uizoom uizoom $ translate 0 (offset i) $ text path + offset i = negate (250 * uizoom * fromIntegral i) renderLevelList _ = blank \ No newline at end of file diff --git a/lib/RPGEngine/Render/Playing.hs b/lib/RPGEngine/Render/Playing.hs index 6c5a47b..b996d80 100644 --- a/lib/RPGEngine/Render/Playing.hs +++ b/lib/RPGEngine/Render/Playing.hs @@ -2,20 +2,21 @@ module RPGEngine.Render.Playing ( renderPlaying ) where -import RPGEngine.Render.Core (Renderer, getRender, setRenderPos) +import RPGEngine.Render.Core (Renderer, getRender, setRenderPos, overlay) -import RPGEngine.Config (resolution, zoom) +import RPGEngine.Config (resolution, zoom, uizoom) import RPGEngine.Data (State(..), Player (..), Game (..), Level (..), Layout, Physical (..), Item (..), Entity (..)) -import Graphics.Gloss ( pictures, Picture, translate ) -import Graphics.Gloss.Data.Picture (blank) +import Graphics.Gloss ( pictures, Picture, translate, white ) +import Graphics.Gloss.Data.Picture ( blank, text, color, scale ) ------------------------------ Exported ------------------------------ renderPlaying :: Renderer State renderPlaying Playing { level = lvl, player = player } = pictures [ renderLevel lvl, - renderPlayer player + renderPlayer player, + renderInventory player ] renderPlaying _ = blank @@ -83,4 +84,12 @@ renderEntities list = pictures $ map renderEntity list renderEntity :: Entity -> Picture renderEntity Entity{ entityId = id, entityX = x, entityY = y} = setRenderPos x y image - where image = getRender id \ No newline at end of file + where image = getRender id + +renderInventory :: Player -> Picture +renderInventory Player{ showInventory = False } = blank +renderInventory Player{ inventory = list } = pictures [overlay, title, items] + where title = translate 0 (offset (-1)) $ scale uizoom uizoom $ color white $ text "Inventory" + items = pictures $ map move $ zip [0::Int ..] (map (getRender . itemId) list) + move (i, pic) = translate 0 (offset i) pic + offset i = negate (zoom * resolution * fromIntegral i) \ No newline at end of file From 1dc8aac4c7a4f471c4c2e6e17f78a8cffc483e99 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Thu, 22 Dec 2022 15:32:49 +0100 Subject: [PATCH 22/34] Render HP --- README.md | 2 -- assets/gui/health.png | Bin 0 -> 237 bytes lib/RPGEngine/Render/Core.hs | 11 ++++++++--- lib/RPGEngine/Render/Playing.hs | 30 +++++++++++++++++++++++------- verslag.pdf | Bin 47917 -> 50036 bytes 5 files changed, 31 insertions(+), 12 deletions(-) create mode 100644 assets/gui/health.png diff --git a/README.md b/README.md index b82a687..832883e 100644 --- a/README.md +++ b/README.md @@ -282,8 +282,6 @@ These submodules are `Config`, `Data`, `Input`, `Parse` & `Render`. They are all The following assets were used (and modified if specified): - Kyrise's Free 16x16 RPG Icon Pack[[1]](#1) - - Every needed asset was taken and put into its own `.png`, instead of in the overview. - 2D Pixel Dungeon Asset Pack by Pixel_Poem[[2]](#2) diff --git a/assets/gui/health.png b/assets/gui/health.png new file mode 100644 index 0000000000000000000000000000000000000000..a2adedad807ba78764642cb2f8bcaec6e734c814 GIT binary patch literal 237 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1quc!6r`^#}Etuy}buH4=C`s?ydgv=%(PVKJSu+ z^X{w-7SZYK?G4LN+QM(g@oU~QZ=>0>_OwrEJX>AL@>JQ4=Rl (f, renderPNG (assetsFolder ++ "entities/" ++ s))) allEntities + entities = map (\(f, s) -> (f, renderPNG (assetsFolder ++ "entities/" ++ s))) allEntities environment = map (\(f, s) -> (f, renderPNG (assetsFolder ++ "environment/" ++ s))) allEnvironment - gui = [] - items = map (\(f, s) -> (f, renderPNG (assetsFolder ++ "items/" ++ s))) allItems + gui = map (\(f, s) -> (f, renderPNG (assetsFolder ++ "gui/" ++ s))) allGui + items = map (\(f, s) -> (f, renderPNG (assetsFolder ++ "items/" ++ s))) allItems ------------------------------ Exported ------------------------------ diff --git a/lib/RPGEngine/Render/Playing.hs b/lib/RPGEngine/Render/Playing.hs index b996d80..6a2f589 100644 --- a/lib/RPGEngine/Render/Playing.hs +++ b/lib/RPGEngine/Render/Playing.hs @@ -5,8 +5,9 @@ module RPGEngine.Render.Playing import RPGEngine.Render.Core (Renderer, getRender, setRenderPos, overlay) import RPGEngine.Config (resolution, zoom, uizoom) -import RPGEngine.Data (State(..), Player (..), Game (..), Level (..), Layout, Physical (..), Item (..), Entity (..)) +import RPGEngine.Data (State(..), Player (..), Game (..), Level (..), Layout, Physical (..), Item (..), Entity (..), HP) +import Data.Maybe ( fromJust ) import Graphics.Gloss ( pictures, Picture, translate, white ) import Graphics.Gloss.Data.Picture ( blank, text, color, scale ) @@ -23,9 +24,9 @@ renderPlaying _ = blank ------------------------------- Player ------------------------------- renderPlayer :: Renderer Player -renderPlayer Player{ position = (x, y) } = move picture - where move = setRenderPos x y - picture = getRender "player" +renderPlayer Player{ position = (x, y), playerHp = playerHp } = move picture + where move = setRenderPos x y + picture = withHealthBar playerHp $ getRender "player" -- Center the player in the middle of the screen. -- Not in use at the moment, might be useful later. @@ -83,8 +84,8 @@ renderEntities :: [Entity] -> Picture renderEntities list = pictures $ map renderEntity list renderEntity :: Entity -> Picture -renderEntity Entity{ entityId = id, entityX = x, entityY = y} = setRenderPos x y image - where image = getRender id +renderEntity Entity{ entityId = id, entityX = x, entityY = y, entityHp = hp} = setRenderPos x y image + where image = withHealthBar hp $ getRender id renderInventory :: Player -> Picture renderInventory Player{ showInventory = False } = blank @@ -92,4 +93,19 @@ renderInventory Player{ inventory = list } = pictures [overlay, title, items] where title = translate 0 (offset (-1)) $ scale uizoom uizoom $ color white $ text "Inventory" items = pictures $ map move $ zip [0::Int ..] (map (getRender . itemId) list) move (i, pic) = translate 0 (offset i) pic - offset i = negate (zoom * resolution * fromIntegral i) \ No newline at end of file + offset i = negate (zoom * resolution * fromIntegral i) + +withHealthBar :: HP -> Picture -> Picture +withHealthBar (Nothing) renderedEntity = renderedEntity +withHealthBar (Just hp) renderedEntity = pictures [renderedEntity, positionedBar] + where positionedBar = scale smaller smaller $ translate left up renderedBar + renderedBar = pictures [heart, counter] + heart = scale by by $ getRender "health" + counter = translate right down $ scale scaler scaler $ color white $ text $ show hp + left = negate $ uizoom * resolution * scaler + right = uizoom * resolution * 0.05 + up = uizoom * resolution + down = negate $ resolution * uizoom * 0.15 + smaller = resolution * uizoom + by = uizoom * 0.1 + scaler = by * 0.5 \ No newline at end of file diff --git a/verslag.pdf b/verslag.pdf index 24a6e81e610d8d2ab0dd84449bf2170ccd1ec824..c974837aa88e97c54e182754cc11fc07894d420f 100644 GIT binary patch delta 24646 zcmZs>V{mRuv^BhA+qP}nww)c@PM+AdZQHi9<7CIS?f0B>zxw{%`=h(Mx<_|)ty*iY zImVbZ&%lqNAoZO7 z&fyGiU-hI`RV7{KxZamSMM|gIWC=cAq+)m1&SPB$2tx;ZX%kiQ4#!;Ions0R0SX=z z+%!BF0a8)!d7UpW*)SntyV+ge@=met?R&ajytm50InF-k-Rdv6;4xtUDf=i(zsR58 z3?sh0Y4CLlE`Eg^2kw#Iu0MNsTSh*-pNp|EEc7lfcW1ZXAlsiPaqf8LlU>(4w|`Y2 zw0B?Oa(}kI9AB^cr7l0`g4yBo7{2>_uAlM0a+AqaV|+Mj@3T_HB)3!>jMrJ;bRKzqHDuP4|K-+Y?fPwpXZO`*ZD)NpBgm;%h*jZv86oN4E=N2_C~l6~F@3ryld>TXy7Of;Ejjz#DmtZKGd zAoS?^#FBq7(vfUO0Ti`k9T>{OQu5N!}tp5D+MNIA_vf*@6i@0}9q@4h^}O zrD*&4U{p5^jK(e0cl8YQ`B#3=r;WE*igDp)K=yy>vI^GSS3k7w2M zdxchI!!EJ{&8y@PZ#wR0Ika$T@Z@1ita+gM_6Cen5|}T@7)?+S69?>A(;WhGh$H)N z5fy3HEUiZ_*@j_ek;zPEJyMI;UwB5=luG_Z{o9R|d7wN1I9x-_S>&kae!TezHpR_bVkz_g_luOKS?NM?S@X3 z$<5=dEHU|@kf`y2_~kURIW8Ct)W*;tieAdo0$2SGT^#t{s899YlZdE%4;n=WkQqWy?c(!ENFx`2E;`2ADyp=DhN@!`+PIe7Y9`ChDBkS;oCn zl_WBGCz(yO>-pvm*O|SUCrv@MN&X=)6l1^cD-R4_F?vD%Qa7^iE>Y!!it9B7o5UgL z&&=l-oOB&n2{;y#biJOIuhQ0w>^w%a>87t3-m@{l*7aIc%4E&lO}S$f*R9|;uZD$O zm8AIwN=dysnntKh$@U)w$gBIDh6tc>N0r7mfhD(0_ zw&JtsiL}Qwc?_;3aU*mX=EKL)!T|mfnmjYgsLzJ?%=5pa|?YGH?ejg zzZ^qJ^`)fPD%Bb{VSZwV?%npE`=*|{P<#}Ckk16YqO5c{N?-qzR)OPZ#^wh_I=hyO z9kzqOHfp(g6(jh>m`$K*YYp;oC{rGj6_^Yr0Ls;;vFS9LZGDwoVtVz7T%wf7lw6_) z6$GeCP z%{1FkESgGlNsrTMRH=K|>|&{gQ0ycq(w{0lGGJ!lgWWnLB5)*E3!fCyDg0-$`7?Mh ze5J&w58Omjbc*zLXc13!G~*8WWHF2n4`m!A(74cyT2u)fGh3-eLPshr!wi%qCBYnG zT%Cn>`Po+cH{)$Y!*q_-1dAyRj4qxOur}E4if6aLH*{~FJd`v-TGYBhUnc`zW9??4 zStpRr!CRqB^D6;?t}T9e%WZ(I9txGvEaF0~_m-?ZQW%}*c85AehO?f*kXIO4?gE58R6xV=x0NpN#)KARo&8-PO9JTEyh%ePmQ#Wejj4ZWKzh-6ySgj$@ zhzkgPv2N|9SQ;gSvxHdmkoY#7v6AVAKeiT9cVy?}s90{|nO8(nBt3DA@5~{0XktUv zNz|pzckx@x_PJ@?DiK~Q)52rXov$^R%jvlF#Jxiu5975-XAU04RPEd9so$1rBN7Ow zeGqH`y|v=Xg_9oI;H{oz;jE`xJ|#01o}TzK(`#$>EnE(RBVzd`?0wndA6dNP|Gl5{Ul?UlGZ_xxfE}^tgXR%rW@!}80x}*0EL|tQ z7w$M&$VrO8Y4WCwG$Gk!!mf*M+65b&C_|<}{|dF>y(;j0b?e*oMFyusm=Wnwqvx|j z1TO%ZMj<86c-;EN`soSlmlh7NV0j*-pm#rz;!-INt zDlZJ+4*mXEqHIsd)AH+ zJK8z>*jc{m+Hs3F9D?Q73$_o6h{ob~xkbSHn6v^CvyEx+X8#?>E+2^Ll|P!VaE$FO zgbTjgyn6g`2XAb4Q7KJ{O+?Mh)`|VVFWUm(onyZa=ctJB;#{HxxmeYF*5Cr?Vj~$? zx~N06Ni4Kw0-nCV$ScEY`F)-vV@6EmE+tGaj#5MlB?TFD+(-$2W!ZN_brQma3ERn8 ze`gcr(d6syknEZIKJkQ(ZQ}*P3F|+JNr1@7UR2L5NV`7z!NjllW%B!yQmDWar%DoF zyyke6%~81016Q~CpyMG!!(#96r^+&vzm^b4Vd#FTQS0>gjp#jwQ_eAzNs3j)in%3< zl3ogPH~lo%OB*Iahf&?;NK>|%=s+@-QM`Zq6hm_@XQjf3(W-j!miffGcarB|LIZw0 z+=fCLZt_OeYZHN}Cr_xt6lI4Yh6o5yKTuJ1R6dF1O!M(k44F>(md-WNZ942LYM1nW z--K1{)CzpuSa_LO#ynJ}&oRI~+BhJ%6u#of6~mJ>{uys3oy#Rs{9&-9E??%mW=WyC z!(4;-&r#9y;U6!&aaomzS%wYchOmC>$rJwZWdM%|LA zSPwlXN1L4Du5pl?wJ`m$JZ}z!DZJzVZ05c)yO>Ynw-_!ePe8bl0w01`dDD;oh-ApY zFyp%=8w^o9R^T+}o^qQ9lwtDq>2Nd+fuMQLm>AX&@hv93gl-1S>V^Zj;4B$S6FIE#)U?ZTE3(eS$>!p%vPCXK?dY?U;eO$ynP+sycTXDhD3gJug( z%NhPeU@)`_=6^>YE)GIQ!hb^^FE3$I77!jNGcybGe@v?9A0|b@f!wpIaSD++v@0)& zNa$Juemmo^1Dj_vty_xVoVk;?i)<3#LAlHx9hhG|Ey=us0Yk>{(>p#TAb`Ka|9uzz z4y-I10Z6m|dH>}@%;0c$zF&kid9ZbpW>BXbIEo-iHFOn~hQ1#lzy6r_d{-wQ@#*zlUA5T@_|pD!KF>XQ2&nhl*T-00 z-`&|p|=#A0y58!dtZgajC(;(2f$>&tQ1;;2V6+^EJb*4Tcs*j}p{+>}jsjC9UDnYrMSWTd2nCApvu8J)7b8`xQ{a|-H^|5BfULoKwGl1 z(Q})SQH7f$T^SyJF_5~Z#RMtb%z@_p;!#rt= zX*@F1J5*frnBbYiuxcBuI(LgoVJLk0-8;APfDV#KHfBO75wPmra%*XaDs;7?6kx-r zWwlg8+25H3p%waGc8B7bJ^a^bUzhu2H~Mjp)U$-z-NW!V5toC9`wyZhq?H>eAql&L zM@OH!wucY@^^fA%hjmSGke1({9{zk|NW614gwqwGbS(%hw!z)qHU=n{<_s(U^G23X z&-=^V=PG0TX{9vigFG748nr(;DF8cI=qdm}IX<+1p$FJVkER9@Nqc3SOZ2GO4aC$!B2r(xe>z=JA^Pq7Bo2SM@0mMvLy}5)fH+`JCfo z8nL;ivSNaeFw9o*0h;t?2f4Z$u-Bxg-> z>zdCTA1Qe|b64>7qI=?g3pkJY)UTK@NSjH4Nk^iC(nf^o_5tvy!>3&3?+4V%E88rb zb@4&-Rcdkkl3zyW|3k;pFKTd4@Sdc33fl_MPsDY;6`uCbn?YMUu@fM*`=wnU;+Z|-bDIEt^ zh!yMBV{scMakj@V@x<5N4ijns(tc}tdY1J*T5~j@Up=9KW zFXgHPjk1V1K;)-3qjlD1S)`~R*R1SEH=D6|PEi05nkhT78dOWkrufz989ty;$;7J1 zK~g;zc+E1*u1!n)gg8g}MN(&R0coWGVpy-lN}+66tzUt)Gtl`fbY(Q~EmE?}15xhj6SqykMz}A#4$|+Aa`( zRAkR^0wA-p6mXbh4$jzEr@!$11wZ84k}*A#tA*3MlnD)lSW6ex6dk~Zq+VooK-#hM*p z3$qPlHiVF$W(z_6mG>!>8@E(e9lxEYx+AFj35bLrNNqUYUYb3xx6>1wW2qJbNH~oX z0tY8K!479Rk-9k{QcY5JpiDhXt=c)ri$x z+#%0&2{o|FT)wdiO}&8dlUq6&{MGaARYoS#WyS9Q^?-+UGEr?MY?sG=fSFl4aVzt) zE?%Ypr&3p<0W#MA>~DShNkpfjIu?0=^`OiUdA zGA@Cjr<7FVH44d_?Xrsm?XD4>kN-?Q|;J0-n9lCrZiH>*ROSl_vQ^?#8r zC;Wsv zZ@1YZDuO))DQ}9Hk+mGGUhjJE-WScPA)_5VkOSdc+>7ckboESFZXbxp zZ$SbQ7W4eDCg9DCuU2~9p4!NH8E2~z-#Q$&EG(wygN{Nk!f`)rYkV;ko;2f0voi7s zxYjG}XxL>$F}q4trX6-gyXKqDB7mptRy1}aM!H?ueNjyOg8RMmT)X2#JU_6T`qSWS z@UgmB;<=JbFT2!{WJpt7Y#dG2{b1HxA3<47Kt59E)=7>yn$+>3Emn3F9cZ1oQz2egIt_DSD>l)HJ<`Q%>^}drmZ75vpk>oNPoOWx1t{?5AdJ*B4Wt7q}jm z`V6$b*GNG^kfZV5M!asM;M4QKr`wD=;0#(1VFTp|<2t@D72W#tp-hC0WrPadAJ~EN zlLCGro=^bCLq`8fHW{aJ)C;;CJ=_BA_Q3ira**n7s&776QB5`Z0cfeML(}SYc71;M zxboDrAG#sosQ2IY*$iuQhw@9iv+3_dogoO>g7ZfGE~*A(tnQimQ{%wjPZ0h+s_<&sxZ+C8h5OnuLm5-&gcqoG)p_FI|ozfYFajF2y1eT zJzjFKh96`)Z3mlp0vs&V0-w78+3+Y%DorXg(5WfRqD`UUR03@#Fvews;)=^Ku%?o- zIMKB$?y4boB+qjdAN8p+wE1OkkPV8<@{PtuKme!c$6BGJo|-aLPpEDFWs-51-1`pr zE*i@lwT`JYZn0X#KaB}?3noO-imhG#dxYc6_x?8u_G7XYladOF^hwg^onHfRBb3%;YvQwx}9h;jHVs! zp@j2`+1G#KuKrGKIQyU~6kvMUESMKp8+j)LG0ig9PxQchw=>Uv;2fA=WZNtq3Gd=d zWeofZUG%G${5=Ex!>Tic5vF@bI#__55G$ZPSVmA6Bv~$AQI0yjGzq)Z5l2!HUn|Vt zMZDn9G?j!7fyCC$*r{;D0z8jql2LvjL)TJ=CB)CUJMIfSqEMMw8lXhIUApnge~UDur_l1t<`jk&BvBO97Y$+>u{kweiO zNyn~oH_xT*gRxXOLz>KPkzH?tos>fqo?0PX4b~js8fB4KX^AGMNI>AM7;q*LD73Vm z{s>Yz49Q$W2@tLr+Mjz=bevHXV@`V)JR5Iske)lW$g4;7vJt%7$B$Hp{l8g!H0cd1a9}#W+rP_0+Ia-!mP{hF&4_6AS~>UljFUOU&Q?N z#E?Wg4?y6!8&H?E-PGOc_zxrZ>+Ls$&0F#)sE`yunf7JzAqnbc9;Y9&AT8|0S{vU@ zB%u8mh$N_f{hw}fUIYG_H$0>&EzS35b@8FCw5w0EH0+TrR&Leux+>+$6VXhO!Ze{J7HaNsa^^MtB%R z2cY%2Xpr$c(M9hX{ zs1_6!A@K-t$m8f$Lxi^kw`Ap1wKzO^0lB_BzpzA|v?~*W*S3<%K6*@EJt4Nt$ziK4 z=t?IzgPe*v^CBah@qFIEw2Fn|sf(QPw!>pkup-s1ip6ojzQ2VBLG$a40GzYv z2ZB4a5}{Ort;NZ9wG9GjD;Gzt_@V;7$G4j96+MeBe|e7d;_xknwBYz`0lHAVWWVnAe?s7@U7>(SIK ze>{($I!o%fGzxFFAm3CoSNC)b7(n8_*9)w$kAtd{yfFD81t56>F1-G7{F(}+`t}K zkBTJ_Zh187hp^%I_!|y%MGr!g4 zTt-mTwTdyn)pY$*7WZ@##*vnWQI*$uJ##mqvp>?yrQeg2m$N={@9NIPd!e60kLA|z z=>XH&rTnZ2SHe)`HHjO84X`$zOE$=(Q?K&<<2nKEl%iNsn#rk!uJagr_{+uq$Jf=X z5h13;sc=DH-lH}Cn_aAWd+#RWF>86`(}ch!sx5__#PQ+lDm^|Ee&G4JYH@jDf6J!> z;V+fWS60II&odG55BhztrvWa5#s%|5?81FBo)z3n4xW|HX3WS9IUxQ?rO(r13%{I# z4-0}^F3~S=_84$Q^bBquWo4D>&4pn!ZL5C}8lf6$~41uOyp%G}S-2LgEKuV)Cs!hZjR zfPfM^U^lWFxL+&YkQD#^<$)Pl|Gfw(D+}BI)H3;Udh5!1?wq(BXRAd2VWMN1#$vyW z94}#S9M6d)B@-(-E4^^hv20sO+|XV%Z?a2Joa{*y?J$$p=onF*B3{v6Ir9E%|Ka6_ za`SNAJLl*2oxYQObK~8YS3s2!U1-@kH_WBKNG{jv78kinw;~@6y)?Tb2A5F;7mM(+ ziUmL#mBFsK;qK5rmy`0fkfQs?e|%GyFOukT-`LxUTH{9*^LAtN`&sH~vx(^@c%wp{ zs)>&GzW+v52)0nJ+qCHZQ7E*r4ebfD7XHoloynO4$Y&okXcI+2E6zZ~M9W`pXNZYe z6d$%Km7tGZ5R*9jJZKKc{39u+8?Juv&j)Vh;}l0CD2$KA$tqnILCnEDvtRx?&Z$?< zzG7yq`lzgD;UJl9_R6?nlu2}m$$v?ZzF|Qg9A2SIY?PCbxA_iz7nCNR8(5zsRZz@ zM4+Os3b6Xc7XKdno{TN3r6NL!V@1gp-B=Q;LQR@H6Cq+~WpCJIEVPm~*lQQ=x=T)>f@rOV~XYr}DZRS1QmYK!H z>GaW-Ld5)GRw`%eh}tueSLC~Vs+u0BbCaWNMj|sT3t;WH{@MCArzdcR{fDjP;@Yls z%eb@V(yd`5=$d>B!o^+RU5i^u{-SPxK`nY)InwTn8iQ?g65ns ze|Fy!rJ9YF6|yc*Cy)UG1OTY{H6rq&d~vl8t{;DGg4^XI2lZJ`tXesrVm)XB2HJej zApM)1iw;&igA{X)gX=cY5!gWXfU})_JEYAR_u!%%A&_ORZ#;|n@ zLye$4J`oXOIB5_NX_<*m{2NK|Fn9u+ssMt@votwzQ+;jr0rp+4dM`h?CBr96sAr7% zEYa^=U_Xx;5N{@*`j{|*dpx(prT%lP_f_(73u8aBVlKnc0f%X$JSR?Y`Fa6U{oZ$F zsGgcRLk6~YBz8awv?d(u&%N<0C6D{h_fHm0UvvL9Iyik9W&gR*VwE{dl=w-6h(hCa zMr{V#B+W_eRwWls6`g7x_gtd9#~3f-SO+z_H&Ii zZEfx!GHYzlusF7L24n9XODJ49h)i0B9p&v#G8?_ z{~3$2&kj&94`Ha>!-dc@V>f2tfw)4pcdg~|u9wVHDey`>;rHzc$3cde7SX0cjp^Qd zlI&@d-n&lmENEmV0CtaD3wp`n;V`S6xiYs4d1f)gD(MO4iYpXP^GsG>^ZC>CT=fBwoM)2 zI}FenIZBH=%wzSUv3!tKm#npXaO~IG1lMt6b6)@$m1aS|_2%t6C{YS^9TyOmyRT5& zoGb@WF|u)F!9b^iWg|1i80?-@=?HGBf)&y1En2(Cq3Cu^2==gQ&ASYE@0uaewCA<0 zrfjK|v~^KkpT3;rB9Y8CDMk6Dbyk2!_W&Tt4WbhJYj#@wR_(|h9X*1vn}_9BIB~D~ zT_9lu7HVd;~q>JY04P_uj%^dpMxEtuU>p%3)F=i>SSF*#b8qoaDs7m+xeKJrvRe1 z#EQB0+}5Y_^~P8LYc2L&=}v^^rm5$yDkD`W2DmYEr~`%AzFd2wuXIq3IEAX}B&E-; zJiBRSC>nRdN?P&sM5MY{^$Kl2GHgJoaWB!~(6Fk=Tt%?|)L_b;E`(d__C%gr&F1u0 z3S98G-hYPB7b;f2^=`@D-ED3On5e# zYI8wT?b!P5Ko+em{l_*_WGEULs;-aev8nf|Ky(Rmz~kE=ACaRP26CtiAe zp!jHaAs;`N2%Cj#N`#~bs}>0ZQ1lN6HAfmQt5L>CULe54MlC>^T*pM6qXFfqmEY2A z2qdV6I-9^wO41kkc6qY^^aDOh%3A{-k)`?COAHF>UfwcfeSf))v%WnkR4M7;mWW3j zkcZIpKS>}92u9SkMg-27aOQ^i?MY{m8;&$x($a)mDaoU8q);nZm=oM$N~s@pTVlq8 zYV2IJ#$w5na#<=2(k1Pt*Z^4zh24Zc){8cTUTLZ3cAH;QI);{~^E&PB8Is;JIa_TU z7xm|-2npB3{5@cGZ%rTWSU^17zrZ~P1ax%Sm-&n%%#+{u(~uxA$~Q>{G0unCvK1Q( z6LV(2!rZMTv|LfsnFt440MX&v7L4E{GP)yBZ#0>jME2A#1*cL4>VWu81{ODk_?AJ+ zdI>EWEO$P|=iXLR_CF%k3G3}H%|Kr@E9zgI$wUXt+o7TpTtK{5WQ%`)>QF4z;i^g}keG^M|-Xryc*HR2Si(&#CVQRI^L#`%GqLfv}H9wdweSFz!Fmv=jA`Mjw) ztHpiknEJ*1TBFBw>G6C*Aw8wj*cYm25a>+AkI}Q?P|Dz-!2r&+*)_m|Ud@vsNNsa4 zFWFUvc3~wQq5tE6@Na@_`FF#pVtFN9y6x73eW zm==`|RoqbQ1!<20uy;e`u=~X#DWa^hCF>X}8^aDbae47|XTX0S@$9vHrNvK*KSGi(*vhv<8D@@T*M)EMrov{W^T#9&s= zX^)LiB11-~R9RZC6s-`J$;)T;@0)HX4vKl>C>I4z@s&o7P0z+9XognYZ6~v+T-tga z-B#K)WG|#7@7}%O-*u3Ej@dZ)TGv^}KQReglZysu#eiIPH2wAg@s)tA5F8bZ()Su4 zKue(^R|oeoxWroQOz4`lhO|t&^c2LVUc4#pBxicUmNiHl1+}x3xGKz~7yscOw3U8P z&578<>~}IT3US&P@@`}VQZtIHpHJI4Bi!hrI!))LmFBw$mx$(6BI`VLAo3$P0Rl84 z*-4WzCxEZxO{18Bhbv~bX12q1l<+02cf>&aS}!eEdJY;+y+tLp=ej#iFLIJuJ&pUd zPRcryKe`A*x{BE9jvwR}{-^MG7uvhRC1^kCzQ=@_;fx(%sJo?FJc}~6M*N8;=RTV z(K(}p@lhZ0RuVR)rVz=S^KNX8eD2`FQTwaD6Sz>SmmYXy!EBZMp5GyHjkvoK>^Wdj z4A7;AcY_l1!H}}4{>^>~J&9Ku{64FacW)Ahq06aQ+o^(m$~-fJQX^)5&=vfom zapX2{>BMz^@rhcVWF``>zI~h7Nnij6HNZ<4;rXeVH7WTLs<4pD!F{~63cpyw`G(AZ z0S4JU5C`V%=@lZ)|ER2Rt-5fNU{Y1#DVHQvA^SJaTl5~8! zas*{PbBBWq;J~zGmRCkatCw^d1B8q_s|FV3W*;i`#QTdCi|-*mDi)5#ae|H)y@NP@ z!GVt=8Y2e0BZc~hA@hn4Ad<$NhkVmgCM+LFoR*79U^r37Oo74E+imE|iuj;`+;M6G zagAVZwqgZFF@=51)$~Tbl^ir)fgQgJ-V$UnwI87Wl`g1R3ncYUyJ`zo2TX(*>d`$P zzK`AXk?U`%Y8Q2*=kbu~^#s((?PPh&^(h0SJZ*2j)6N%+HQ?#YBs3#bT06OEz^E9t zqjFX%`8*qKT(rFfaX4+?cqvMjQy}XVf3z01vo;E%ZudKs5Fxhe4nN7+X&I`>apiG2 z;d6S`((VUgE+1_l?@-Wm0Cg+WfUKOLt>s$&llHU>CYE6?8a>@)mS@@uIiicd$;rWF zETwk$E^4#kQD=3}#N@s&-pp`3;Uw<6?vXfs6bif30STL@?k=mU=g1(7{z{0}68^Xn zsE34N;FWq?jf_%zC0B9bAuza(>=FUzd zSac1+_xLpsf&@szSx-d*4O=foO0yGc=L!2}B@ObFSnoaX(Hn3)81Z9#y0Gksohv~k zuzhkoy~T+#2FTSF?8f&bZyk`jA{7&cHebthIi$!4{)82lGmQ#tRMIIgloUVeoRyqq zEhv;YVC&aOjdw}Td41xX##gHUE13W>{;T4E zu(NUgTNodcJYe@%5Ha+|FY<~=*~_8c{f<5Y5gAjFi#0-6^+@_ML>qXcaVDVc#!Dbc zQVk6a>M$-N=k2F~k7FmdZI8EZ+qV3g_|ACaeA9$o7I1yOdu*md@1&ZgkVrGE`9kw*SVi3bM6?#>^pq3Xc(!;=zrP6aAczslGs$fpa4A zGNi-cQ7lgVi(EWr(f{0LE0G<2XAq5kv4}+TnElCs*Y2LZ1HEwqQWB#ha;ZcujMSd- zu>R8d+qe6*@~%m}-eI{ZepfYPAMK}sT7vPl=I5&RQueu8H*+C=b7qP6352(mE$~0O z-G7>2lC=pI(T}hB*Ut|ClM*Z&b_w)ZpkEaThJ*w}1}y&Q*RO>bNalYwp#JmL{;Tt` zF*E&tI-e$#F3RdkUi>s@u(%O!afDeRf`%v+eZD%7kRupaif;rWX!5dTZXoDIAbJ#C zt(uakD3T~*yq>P2mI;Pv@UDJKZ{>0?%j13S4&3QW%RdSqvR^=Uhq=HB;2J6p5fTzV zj>BrbFeJBXCA~x3CK6t;eR7DSHawQmU8#Bl&@6;s!)G`)(LOH)u53p-5VP;73=;VJ ztrh&!Z_aNqp%bZZETIT8u+5s%ZxzdwGcnti5&VaTc)MR!#t3#4Sr{)+BAQqnWg~nQ zbRUU#t8c-QYh=OrHh|WoNS0njdGJnF`O_%&u~~fWZLyXyoA`M$i~q_P}Ic) zAlj^30ht^UJe52Ey?%a+ctxQ>MsECk+h2h+-w63hs*R4=K&6X?SGdMG!cXkv>@wbI zSTen^PZKo;^>LS{1t9Ygu2;t5@;+Bhk!BOCEXPJ(1HW;T+SB`QTm zAJzyaV~RrBx^`X;+GI>SI<~m*A9S)qbekM{i5_L=p`WMTM!J#x2J!)v+)#Lo%+}-|oSD zFu?mM(KjNNz~%6`eEg9S0T7@A7-@aiAVl1U^u&{unw27zQjjF#O3W9xT}XX%{aAHb zKQH#+7?SK9-`YGCZu&38zg2$IH`)sWNWr?l0gUcO!!7QI{FPRhdaQw>u#b%R3>ZU* z$omy!7U5-F5tmBnLlYr_MV!ckf6TzK2F)0QIh9ef&_VR&9NIF{rjPCc19^)^3pb@L zIo)MlanemvO#*yBpP(p*@&%r!q!00xAPmT@DlOwtKGFr{{3;94$T@OW!S)wK?enKv z48;~ctdC*lqllsd?e4DqygF)ZFO*#hlFn=t&i30#K@QST@5+&z<@(7pg10z(U6qho zV*J)fc{#!7n6K!RL|;IF^3ME!Y9v4jfoQmk?M`M}9xCx4_Bt}pYWftNQ=TeFeGj+i zr${ZHJ`de3OML#R*xD>)DDmOkKUX~E#R?3F`MB-0wJ*i*l0QvmZtG+kBxZj zd5?dS^p*CbKq3YJ10w->ji-sNsM-S<5YqD7i@`F4PMKJNktKk4fH>bVM3- z+^SD0s#{C(nANlabCnQmtAcEmXICsO!WnC`vNY1c)QiUNvTAvEf@w#eHSc`E2Z11$J%J0<#KpDH3w!%DyYvyK!vti4zrcWBkvIRq+>?d0 zIwl#?ksd?}mML*`;B+a=#_boLj(m?gyyo30- z!7GVKZq(HRAn8T#b2%r~QO2_$gN@$f15n%dd#0j?p4BIKSjII4G%y zq%C;^(9Tr1&>oeE|E8l4JvHAGf~UzvJ2I?-=+}H@;r63Y@RC=g!7P}O^(cs)49FfB zRA^*J7iS@;;n8ww#yB4NPx*y zhE%eaZbGLX;08(ja4H{G+Cj(eU1r8qb`r_{-Vsmh$7U13SkdJJR3yulS{!|+kbb?8 zmlyW;_rSnqTwr1!)6BS$vE0|0Y{t9f=f_bk znkEq>)sJdRj(tw)NZMQGpEiD_kg}gR?H);+MwnQ`G;QBV=4OYW8aZ0dR|ro$@IqiQ zYZ?iJp?%41O`owpNIRCYEKko|ltD)V)?GCw%5Q(A)D16m;Jp%--*`Bk`RKZk>u8FZ zg}s{6rVxq@X9ZfjjWVeC8;Ny&8(OcEaF)NlHjoCqN^+7Tx`17dFi}_4PR>rPj#|h> z!u-ENzA7jVpy@W~5*&g%f#A+At_ceScZcBa!EJE}9^9P}SlnHLJBzyq36RBIZoco{ zx-b93|1eeE)2C`)rlz{ioYS9&K-b>-yDS@4iu(_Ml2$of$di=$$PA&i<)Q}eXLbGb z45Nvb*t^El92D9Nq&8vg1WcWNJCNiHB&6(^kq&Pp^kqwl;69CS!fn(iyav z$K0W#*}Po6FN=~*BtJ3!R5X(XHKXrc465HUz4=+$E-T<-P`Yp zZ?&SOt3}{B5Xu1tbZ)b&)W~bQfv9(?rMt!H(-z0XM5HZRF^xLcULjosmo6A6RT~49 zl_lW$`H~k5ijHRk-8Kb}md(J7CWiJuC$O)rq-_3Qnt0j$r`~=GFYm@ZfQoKy?}0SS zDgUl@@MWtR#iJ&Z1;p2)im4F(ZY3p5e5gmqAkwtWhe)IK`H?9QdskgvrZ9Aq{``bz z+f$*hVf-dkmG@9#W}1_Ww|RgpqV^CgDJM5IsZyjdpwo5sVfrfFp5g(OU}28mEurYG z8-rP#J;7kdB-FRh)qCOD`xoIKcca>@+f#M|N$|;#nX^=72x&TS(Xkzdz z@rTB|-785QUDqSGuHyYQYQ%$iu7I$1)5eGfJqTff9czKGmQTgyxs~Vc+ z(@VFln(d)5Bn6w<-xH7McM+lHPr{TCu*1vRQ9^9iGY}R38PXLa*PabK|ngeFEpWh@CBLu{~mZ{-DDDlV#r5+fulag37^Q{E&O}BkGY@8n87g$lR1MbZ5 z60uaOVs)9OaJ*=;WM<9{>y~*%v_c5|ocRJ2gV>wHvcmvp%L3IpQXyzjVi^@HQlIDrPa~dx`6`$Df}#0DBSsD~j`-i|KU+XDEI$V7gOY4&EE+r^#(Yi>BdlTAg17+ z!WS9AWg&Dn(na#Se1uBEcRM3{QsM`g0GZkPFXz^OxH@I#ORUn0b7h|xG&A*-sg?-h zWM=z=V4;3tSC@Jw{+5p3gWvicY}DGg8{>-esH2SX@L5O?YV9f7-5}0>*ZL;kw^#{& zV;g)GQ~sD*R6m$4x*EMPCJ$VLM2l+?QK#VsicH_IBlis^XKk-^YNn2X1V zwtkU+E<{;sP+u+?s6F8nyDQ5icfjvbzxwjbOKNkGnH>2tdww?E zcFTLh1L2q0HV19tDsg;IAHy8>5@Ggiic)WC{iLJ88BQW^&MzQEk+xM-9Jykg$?=rv zG`Y*-57>=yo(ArVfKjt2`Wuelp1Lp|ce*s#Fzes&&X%<9K1gQ_)?9=NS`J`E{`t)N z&XGFmqi}8dwtZ30WD!PoZ);sqXXgx&Ae~iw^?6hyrc+$6Q|wvYp+jXsR(niAH)-q^ zS*qGq-xi7YNt8ny)TKGt2*O1sv%Oon_QF8bakx)R@|CD^I0YSRtzI}itjzdZGegXU zDejVcK5e}LA|z)~cFb?2FuH`eO0ErK?I7ztB@nrlhOzQ|B0&d$rzp*Z#gGGc|1$t} zLA;TKTks|*XFHC|Q!Z6zO(ko&ML}}DVw^i@hu(R~MXwKcJSC1p6Mqy+l!C<^Wq)zZ zR?R0t*BmIc?kDT7huKwXF6N7Ge+$3B=?g8qE}g9dA*;&XD*kz|R36-1c)!%@)u*V& zeo;0vn%|bIaO-hMwIkp5ZoBlkDeNUPsNt=~wrn~Vpz2B-vg2%p!$Sy) zQyU}EFgJFFB_+BGoS3@L)D=Q3oXK8R|sm+A_&PBvbTPH5L4KxMmk+7LqK zJh$xGAuW8DqwqeQ+xs&*H(tZEwTj9PgS3fr`#_VoC#S*XF&{sp`}96N$c8lE;bCJ% z!uRVo=mJr?_k7uvKcJwLW09`R->cOOwT9tmJFdSye3RHsh7n$|U{h5U3*f`KH#U1p z5JKMk9n75GN4C!TtbSUc8F_0D@B}g=oK6oALFQds8>Pc218!*=<{P5}&_|Z0OhhTG zsVX^KMqmtu^)%3qHr1!GUt0bAIqhCMh40~YDyOG^%PxJai0#f>Nfqn(qA?sB34GXYHxA*|2{X#iFha|}qpa-wmt!nnm-2$!`mDYYw?D3Y2AH1cVPs%Q zLr`vMTOW@(vL|WR9B?cTwZCV zUAc{+f9ivt5EEw8OSOd8_A-Vj~L+!Z}o31gj6~x zhSk?+m3I2495`t0#l%$Ymf?6Sm}2^lxpw{jork+U7tn zjF%HXK5fpOSs`J0i7VQ}*IL0@0vSKFSm$vrrtXqTBE@8gvmh}{aQuQECVq(R=Un^( zKl64r(2@}sc|CI8Tn$3nK#F)x;pvG2T-Svbrtxo`#PWxAMAdo5QWy>%xVJBEEm4RIaG(r{kDtGl(UpGhJps6eZgwMIqBNx=R8Thhn4l{VDNr zpTM`?7FA@)C7W(eWo|X<{U!=A@0L5~`EBYm;jmxW7_T8eqHTO;rfV0)$qqk|9`a+u z4*98oT+G(8E6f;PpN_AhVKTPv6$m}zVd+fp84W^yW8HE z`Xu=(hm8&pv9Dg*KR#Wde|p^jnD#Jn{7QyNeY^%)0DI#$=e>0g%R0XIYp7(xL!B)f zKHZq`Y!y9G(!yK?F0=}>b2#MDTkQVv5QbQ9d(;6C_J;7Z)J&B!o*R1PxB3WlEY^ zV^4g^M*&7?P2GWM<%7WE#+q0$XM47SW27vJ-BOUBG7YqtJ;jw+`vasmqY~6x;8M>T+Y%dcZ~W3_Q={lAy$Yv*h5H7T}MJh4^1*CSyG?N*)oBhpRp)226p# zQ$O_c3<1Ey4dUhD;Y#W%r}+QzcKKIC9=?C>7;-E@5Dlla%|C9KMu3;^pHGGwiC0iC zDIfYC^6gFJu@awfTn;cpF>_8S$x&UL3!l#JeYD5@CIYh&y`C1O6|mS@SlbUk zjQG)ZFe4$Gc|?kWw-@V0GztH8hR3FCg}a3EMU*%AkKos}-7j2lrs@pbQOa*5q}w91 z=&>-l{?ryU`pC{V5JEXs1w=xCHn@adIw*D@LZfMHCNN*L(*GjMBwK$qvt>ca1T62& z$H(=;3fJQu`Aox;sBgNTj!b779?EDLJfg$26BUYRRg|vLzf3|;F^o0bSc<%6Jj}<$ z7#k)bQiF=gK1`{L!9AnP)et^lph6xIS|{_%hX}(p>76VSSj@DR9?*wZo6H?DT1y`f z5%`8oy8RQi94(@$_9kY@#u*p;E$eT*d;XjL#jo_!%4gO1WI!Ws!lca(%bIT#X478} z*GWXJx2;EDv)D8A#0p<|2u~Y%ZqRbdVoS;TfXk@VJXeSxD0tE54g)?Z1!WBOqJA54 zReC#65t%4`s;jC_`nMbeE4NKxY{9TvrQAP__l za3MSVw8?9QMpiQ~`Syx@nB8R9dOthA3#ApQEO0UVcJ4LYn<^h+9jOf@6a4Sd5&MxV z)qR)VQ~AcSr|aiRpDrjtqVDb>?O)H7*DzI@bdFx$Qyf^v>dn z$A>WIJ(9$i#5S|KS;-HG2~?`pV@vTsjrv+jymXFu9k<#SgFu@rpQbyq91(APDJjNX z(^U4t=)>kq?JLZxDASBr-l(pPDobynOu}6#j15|Me>TRV81ZWjjn{y^6wAwG=MyMJ!i=2diM;Lt&SMPr5eE_M1z;xvEFD}`a;L|z=7E=83Sp=ppG24TnZ{GJ|$y`+BXEOq7_3N#vB{Gp4<;i=& z&JEbHl@Hd@b+!?PkKz#sT7Ps7BI{GnYqYGX5-h2XUQFQ=U#~2;!wL*Q@c08}|buKgKpWx184%$2A zhQv{?{o~5!idu6 z0x1_cGTndkKdX!45oh%C>(GJ?`NEcH(Ev0s1W)s-E(D{ z@vC<6%?vxlyb9S71Uhyy9COCHuWz$NQFv7t-j`|cCoi?gR7WARE+h@O<^H0C zfxjC;(1dpnZ=+;QHYt$1(kImI$#a;WJs$qVq%!?FnppU0XV}@miqU(1Za* zl;2y?LfO?X#4|Em{=A-=CZ~*V{T;>tSHDBW_0Tnja69I&gvRfI4*CQC;BVJ>t3Z2jhnw3o4s-$H5yFL z+Wo;(vg>w@K2pRa?`#J2?Vp_N?Cd<1E-odoZAd)M?8u^n%nb#N=e-}mg^8aX&hg?az)2*+lpkfFJ) z^9Pwg8AnA+Pti1m;YlEqz39PP2q0<9GWR(b(6RuiX*Hi*5k2KN$~v4}JGEHkpgvyV zdKfnTkl^Yy4`&x;!EG z##A+ahp3f%SClIT!9c-}W<(Dd81$wQrklp+1F=9x^YYm9_B;E@Bw=2~T+P%m9ZJ|O#KZvIUMi=4q zOD&>L_q8YjyScIi#`#uY*U&Go;B*pTR5uMOdvxJI@!SS8@?Y-*hB;j zJX;!%ifF>Heg^}%7okcu`^WD}^2|Up;)|Iuh60c=L#>~C`ol+cDV%5R%i0~cKep4U zOn6II^P8}4%s+gRw$R9VNeGIrv}q)-qrPOscbQh6j!-m|2&Y&hzo+ZHr&BHdzsstu zn5kK~Jdvcdx8Vr_Ora8<2f2Dte{;!PsaeBsmNtkbsyG9w{ux#d?kf-Ru5&Y$W~fDd zuvgAHyzjTyBl0#3vO9Y77oxFNc0-YTPi#?gZuooh9}(FNRE!r|S|At%QBDrY%GHU4 zxLn4nFWv5S?=kqfOw=wyRA`0Tzp-aEb#@5`WL91Thw{c;RVOh#yYMkrT{ttF$?#Ej z99(C#u2J0QSsvpuvOP@Mf2?6?uNML>{?212&%$?ZvRCt0DC6TRjEb5t3?A#I>m=&B zpInqTkflbIasC5Er8}MsT<3PQoE-MXxFKawoAy3T+$Z9+h(M$RL8A+z`IM=Va;y9;<~$Z=+tj=Xbj6+$UNcYL1Z0P$ z196A8b16V%kl|ftZ;s-r8K)x={TT&*19NaDbv-(INWHyC2UDV^%eY(#;In|~NW}eV zp{%3I3DmXi&vzWgBYbF(8+R@qxoV{)ATQ~B(d0W;G}JA9gMKkxCteqnh&8xuD)QyP z7z16G8MLff{y@PeOROG~+h-2y$|O8$(V4|$EI)-q@=e7JCX-foY*Q*rwTCKP_qN6| z^>9|lt&)JWnowHbH+A-gz?+m75(BGf^Tdc-F3!WKcT!rD2Bu^rz3o48ew!|h@SNGISNPeQ+7$x6ZePZ)lnH0VoZ@kDIufWh6aZ6#Ve&J3m*0}~- zNI3RE`m#M9F3hP@H5xeFcnzTrt)M7m*5rwRT-PO*r4Ne7o|GMwzDda04afnv2e7Px;=iwyQ;!BBKhi{RTN^pSvbJ zHPy0Bjt^Mn>t@hoV8W^&>~&tKNK=}uf3=PVVq1zCJlCX%YsRP6RE4c!wmI2b5$8E} zB%%pjcYWiE6k2)ARzB}@{5NvuS+m9{u03s1U-<#o|kOU1y%Sr@Cmn+O=Gz?>YjGT;xG^GsS$ zg{f2uS%5ZV%O1>*0+g(ZJz8?x>s7=RYwm}CWNA8eYE^01UtejUGOnLgT2|Z09q7QE zyxVMI1nsy7WW}EDNl&}EY?9Nibvo%E#WS+q691!lLf}aPwa`I$xo8Y&IMr=@ENFQC z#hltS`ZU}$Jpb0eZ8aw+HyR$E{|p#Zqv8DQXzBDX6Zn6b)IX$%^v91}y!=vNo{y4X z0cn0|0e)T(KTwhj2$YtT=H~w>_P;<-|BEsF&sYQ`5D)KvXbZv_dF6MiBoXJgl#+O0 zsd6tUKS4q?7y&QJwG1URI1nu=*np2ARzLMM;cz3L`V&4HM(WNaWlQ3e#4ko?x~cX% zcOfgGS>6Ld%Nz09ei7ecs~sj6`GH$xM*P{YS0?F~>P&q$$-9`7g@^|#-!6atLeZcL zi*5-~yRAMyk%#=;&Jd;G+(ob2A*-UBXk?^t+G23r=2O}cTR9_fd1V^pj@2DE346@Q z%DfN2X*6oWd-Wye);jpT>+2E z0dw8x%e(He9p(q_Knk59`glo9+J#F|pUxxCwM#@gCSwRDCwI_ig>6D3GgPNDA~>!? zdixHgnDNHyYyTw?$u5LIk+{>2e=xOXJpKL#XHqj*t){B5(wbF|<=lX_YF5{cD0bk9 z;4x@&ryFbs(;Ek05p@ncMDG{2rk5Ua=tX<8`poLN;dg?a&Xo#VtvNPSVLAIdIbcti zVMUNNC*&qx=iPj8+`kf3sK%(U#F}|Q`2??#hRjmrbws4wO3W1(F7*kf;$U=VBbLgS z_cO*tFG{pTDQ}HGZ!}%`HQMC?O0Ju?%$?@|e7$2IJYvm+E=EK52k0&vD@jUQt*f#j z8^G6Jmy2m;**9cYaLBH3&OjHTuO^V-QQ{J$bH*OV~j4qwyrzdw%tA3wr$(CZTHuP@ARsti1% zoZd2f{uR zw`C&eluFw4vCJ>|j20F}6l%-H*F#zIaQ1$DF7#n};C0vo8K-Zn-99{!K7%;(8tWPe zGFsiA9iD(A9f~ignGWkbl2eZ`oSBj_0zIjG=UDli)r;|h#W!Y4Kr`lOna7njW$e8U zS2y3{dxqHPD=MDr=l=0_0f6&Mw=Ra6&gJp*bm$Yo;}PMj$C^WVhxIquFb`)Ac-8*y ztL~(2h#o@DAa#V#<)maPMa-dYSGROId_Hi3gJ8$v`Km z#lz=Zo39ZmGagUOglb5>lTgDj_BIoqa38TU&U?n23zod)1pf2m{=3_B>svy7xe1)S z$S^dBt7zGb7zq^1rpGkFoZn%&TuzxB$xT{R5EV{!w=m5BNJVrl#aNK~VvM47= z+qDP5YN>9E7o-}|nC2<3RuV(ZmP~=QrVK>W@%Kyzl&|XT(p6Gv3Z2@CpzSGMR2S!p>$fQ=Odwp*6HbP=oWI1E^mtZ1d=#{( zkPdL>qTeN?LK!zc7C}R1OsJAoxokM*dRo(a$f7@i;MT#$N^Q-(>;95cKxZ`+P?BJe z&}>|lYAi76MrJm4O3d8>aSf&_{Dlw?@D)+zhjgO?a0s|>sM2hJFz=O?TM=6iHMQoK zQNof@Ooj+?^q=RS=D%0l6xG_oS+U%D9vS&5!g!c`|;^yg8`%#L*Asp2XUyxE&^<1={C zVyce&-+1u8N>9daFa zXA@~lyr_VyaP<(LH#akSKY2@fA5xhxq^TZH^Zkd$s~1-Re&lc|^@=3);ftXy(U7_$yLh84riF3J~Li!o8CTS$dNGnmH%$el23R=($mGN_bcWK~2l zXPz?ZJjj31;VqWf>>`7svW0Jf35j*K=uSzI^hnYcA-Wnl@`WqPT;d{Wz;*^Qaqc+4NsWB2ZY=JVMa!x!SPv`dI5!h-O_b(E~n(Nqtcl^g&Fx~ zu^pxAHqNGKNem@CGf_R4=rrl*8pd9_^mMOR&OMqlHNm12oYvTS`%;@Fq|qyq40ZG9 zGq7PoLxwHi8*;e%SrO~2>{IC1)UnhKZVh$N8dsIG$6hkjTMcvE0!Bf9&Eu&7Hg-LO zY-H?nRLi53r!c~^T3qP&0lc|x8hqamjH=2|}it7~V zJlUQOm)s9suysll3B+0T#D2w9YEL+*`7jFJ!%K<`U&AcqoSXPvB+8DpJ%MD%v5aSR zLkjy!$vPd;e_tq@9!k{kpmBj%H`_tLXyNV`R3R$%z0Wy`o`9v}}6O z9`~l(6dSkj=gO?>Cf>l4?g~mQRICXPf2`8m1Z`;41;Yz9f%{3q-<3KVWVSjWh2#^8@ zJ?$!}jrSV%1%?`*Q6}Coke(4!piS*eoL!tu4Q>Bx*&A6wvnJI*QZ%_kFF_F@>;s~k zZIHNs!43mN#s9aIEvX@hvMB{M9ujYI+2IFbhO$QIf2-ITk`jQ(n=J8?pzsX5-F`rS zx$%zww~XVzGKwTYavZ?_7kn#Xa2OEhvVh;rJnTZ{*%GBs;+6gQNh6AjQ#;Er2JjKE zgO_w7pRLLumv_-r0CsNlS^SyfXn_&=eHX`da2Jp&8lIItsKR>Q`c-waf5tZu{|J4H z{Mi73_b+7omQLIe6c=v(f^BTt(8-OF<*uAHmVMjm11Qh^hCTU7Fn_vc=eGX5JXkvW ztbcJI!@=g)^!C~H`Yp~7Z1AZXaD-m;*=<;pqNf1LZhuwu+49)q4TeDZazgBbBBHVQ zn(tm|KmS<)iOR+__}Ycx+h&8#zY4?%=1sA>1+KmJTb50`AJPs_t%#;5un4JmY+P7P zeKIUR0C?8hNqc2&=Z1R4&Ncgn8~er?#~ry5WqN*q2W*v^X~q0C{k9)D3fv@mlqaG| zj%FmsM=XqzNr<2dR&~%t>v*qU@s7J0z=H%aAlPzkk?dP<(PNormH0bmgo0%4|9s*r zU>))Tk6ja1qa#YU-rGgLs_?I4YD+0t=#5b&0x;clI8S5BUF(LZ-M`Rx7XD4)r^~ZNxj0 zh-3aWFn?mExr&`!V!&iwarDS|VAVa&xj&+YFcDcnDFHv`PQiEgmAExiuv#B>cQ%&z z1F&(pu;Q$A8s7T%!$%1$CdFqW$7GM?h>?(0-21ilqQt2Qq^zmvHesBxuUv;?h-ai} zPiQ4F*`7U`qiFJpR0iXtI$j1`-TPNV?+i7r9Pw26$513+o1kb%6 z_p57cyo3{+F322D1SV9u#;m0Wxw@#pGHN z4|BAhn2@W8kPoCJMh)$8N%qYPr1VH}W!qOPg4foC}u+Y8N&W|rLB2&7=Ee{YQZ0nr?pivO@@Jnif z%!*}5+-aJ%!?gPKOHdA+yB!^{;7%CyQdFAd6Xf8PGdy51;Js+OFfz%!lwUO=6OAV& z+gwDf)xiVA-Ag7|Etx2>$cXaz`{EZpqHSqEen6T13pDUk1@gbW4<|Dr1L1$&9}f>< z60QOsC=&w{=l`h&w7BCA*${8Osa42E9^Ozz`xyx9j{mHJt=~AQ1{P1CS3JBj9~g58 z^U~bvCj4apokSL$f$g)`YrOioGI?D*|8VlQObHN{qzs#kN=2?6*OS8A^UvEjafWga+ndtds0soU(HAZI_QLEgidH#ut%7{@ z9zSa1XXG-!Z|e5;`tr%zm-ThnC`lZ5?(WFz_YRKi?!RqG8V=5VqlERiuTaMKg+2Cl z>dW!xnW$+qJ-e1AL(Wj{R^EamMN?f!W28JfSS^vFg7M%In+9hjYi?m z+*b~gmz4uFs8tdzjD=kgdmJg|Q{R(1^ot}_dGhwQjub_}*CIXihgL8($A)NBT5aPg z_De4N!3Wc!QQha=eo`l_tQa>-Lw)N))X`b98(oDL$b~Et?SAxjgrh+E6V=YScXGsE z$C{Ob&~~0{7r%xP`|Q3L>sw}gct&9kWEp{r>aB}F{%@nDn` z=0sI?Q1~73p8l&sJ0M+=KVe=Mg0Q(~8TZi^t5lP$W^!VBdS=F$QV7#$_Y#~QY;pyh z?Ehpn@yXAesSX(};~wB7TP@-=F;#5^#YVW%#+FkpxLn zByrtSjWZ;VfMlhBoMZxx4kQRgMIOXm%>VuV_F73bywo6Wr#X0ZOV=q60WtY z2N${^(ATLqTHFiM&i4EH8d{PXGexDtQqBo1Of-GeDl!9jl}D{Nqc#*j;Z=E3Cg>W4 zI{Cpex)to%dYdQK-a5u>*I584>|KMA4aq!aY1S3u-0caQ8l%(Ho=!3W1yH@t z4i+#4@~;CmH+y@*jHQcOQA8ny1b$%eep_Lc{=q2;$vpDh=^>b}xHU54n}&%>6~>ZX zWguo+(sRqIDQ$xnZknu1$IS!BQay-(=|ak^N9(LiLX04T%rH18BOpLkl+*nmx`*rv zTGZ1Kp3Xm8u;w3-NbAVrHbUB4Yq*PwWz@&w_abmiRa*h-VpzoV3ROoZGAZ~$iZ&yq zE`W~1MvNYzg(eeYs~zAu$RgoM_--R9lLj9U9{9lS;g$;Ri+&7t4BhQ&j_a9%<$re? z(u+FO-XWk$nqP^OjV1sdQ6eo1k_ic{!*wOhL|L*fre@4m68?U7s8Y~3mRF>;k_2NZ z+bYk)De7ll4oSm&2imT-OMCvC_1qU><_1#+0(TLYKxY~uoMz0-1)27Nux>d7L%G)| zR+fNH+t(4B12^47r!Yr-<3^^H8RQOTej9qj62zgI&=u=7R$4$WlX4~~dKlAAZKas5 zE;bQ;gBIfvoRD$F$3;CP)tKrM7k%i)s2LJd@y*&)PMM&2io*^b7+!X$7#)+~MXF{T zRZ=zdr-~;%^1gfHRJ^aZ#x&D2)8tk#Ba)Ui#E5KPycf@&*%2^UqYHy4uWmnFU-}UJ ztyF2HfSAt2nhQW~dAdkNm;DxWehMp56|vf<>}m?;r*>VNUdEaYhGfY1$;&~-z^I5J zK;9R{ee~ZwjucE=>p5e>v-P8L7wRP=^{A@EQsaA1{9oO`FF&?A=D8mPu<>Rp>s~9J zX&r~kpD8be&=&X22g2HnB&wLjMbLk3kKX8++RNKksX2h|^~%o#L5b6h&yMB9z^8+< zgXOH$YmFR|HIQ!~*+hooe=Viwi@CHG`$Kp47E-x+yul?j=J?P79a?80jY$@J2PP-y zGBG+$5Durp0~%F&{f{x>-7izoRrKiLd#NzJT7UTzCg~E!Q|*#jR3%Smqk%sWM@nOf zCpc+zggO9%S6UaqYL%)wUSw-~6$3PC-*6Yc^tkUR<6=ctUR#N!zdD9^qni*~3nK}} zaIz08qWsnw4d|UlVXN1kCwvhBmvv`z}4$3^>6b$%k&l zaH)P3SNoj5N%!HzcqVd*qGSf<3aTzC3IY0{$yL!sru|pnrH`#JJZh5 z0fAkHboOz;l)YQO2$gQHubZ@pCpyYF+qAQS?PHo)3Lg0BYyngWxpn_+!lNoDCoV}# zFDFTlNR510`MZ!z85b6W%`d>jkWNG^!PKIfEQ{cxaD_O%(=Sif5R7$+tc9D#H5d;D z2$ZB1c0fr#HDatBnQd(_O1o1GZG(J^$MMI^vQ|bmscPQ@>_}LM-*F|R^w+Fp+f6;6 zS<^FJQ74rok6S6(@VtssmOm=Hy(Di2F&FKiB{sZfWKrf75d=y<7Ff=*#{`*T|6Ue; zKlUkcBxsxtk(X>}bzpqlj$9vKKIgB9wy1llmxP+#%Pq_rb56d)Y^?mb`QN!ABh!EC zT#6<);WZ$*l?GX{Jp1!6u$3;1q$I_Dl%xx8G7xr7&Llxud=N$sjwD7KG(bz{rX9`% zQt0hFs&5C50I#m^O}|EeS7;I#vVoz!KS?NEGm9~!nn*x0`pf36I&(6uVlwAGP(ANe zM&?al<>j4v+z1DX3?vy2)D9|fNxDV%>nO&dH5+64SvL}6MBjmIESjdWRgMHpOdAQ7 zV@R0H!81WAR0d<<#C`oe1;FeOFD?^vwg?@wDZFatEgz;d!hAozBk%37o$b)shKw+g zS{-aecJD*!Pppq?bV6MOW1Y$g0wKgI$(&j>Qjd7&T+>eG})qe$(PvLbX+Fd$SZP}WFt zUIo39LQk`+z*ST{Y=3R#h_U(t8Aywos{oQsE@U}#2%S>~yHm2fv>@q>>g;M=)h5mN zK&zb%-TDl^fAsMW1HkTh{3cQuitt`oMvhJnRQ(?%6@2X;V%V)|UH)=>dGugMen7b> zj8ma@zF(3@8@QtRA%xnoo*gn-;m?^D{Yd9W;a;dHgUy1ifcyYu)Yy0+3`QK1h&$Py zaG-R9ZN5kgau&2R`6`R&{h*7z>}%Rc9n4}6(``7K`A+)HGvI3_JBz^Y49xG_hnabs z9)sZRwQS`1`s%~Ybb;{AtW|4^wyX(FaC!Ib^XmKSU~Gi^#7S&AyV`u$8a3*9)-{(D zCEu97Pw!x0;rDB`FQU{;S5e$jt^VCG_p5d!*!<1VCY*Y5Lv_hgko_Vv){-LKWB zjJfmirfz$7TmH6~p!`O;MHA^VyYx@PFrC9XEZjMw4S-BP$DKZA{jTv=t(!|<=XWCX z))fL@)61T)Wi37eD6M)CG&_n`HmF!*+IKTx>-z;vn(HsR(VWUR$SowuHE1w5oH`RSUtEMV4#SQpt5CT!$SK%D=^^%y;GffD?Px@j?fS zPf!oplMc~?J*8(bAkM4EG&Z_of{Veu2NrPut+E|^uzLBtvT}Unr1`7&J!U2Ip-*lv_G0@70S7&*~f7CEKUl03o-`zI}|i9O+%C z#jDZ6HdBQ^@!o4_IUR>soI#=K26IwMs{mN1O;1l?YS(615tznhG%gO1=e$KnMfOa2 z0o-=yh7HT=?=C`Jppa8Qa0_CcgO7hR*a82)l z-_!~#Rt_W452L6HqH^`9>NvzR_o#FzJr5jgQdFHaw*{C%U|7u}HX1uI$X9dm@&iiT ztb@T4a)Z$tym)$CeSfb$Hql+gqJ4Dhm;J(kzmqT5>({EqCw%!wQe}HB=Bej$J?d{b7H=UDF=i*-@dV1A-Gqtr>M(+h(SU0Y>r_!mqH2eUB zEn%WGiz+9x50enwy#l)K=5)&UBeLBO|Z& zoG`XLm?DbxY^m(Eqo97Ro!O#s|MNN*@8HB*dN4+F32{y~@Xh+?yH)3#Zz~VD-Rhq6 z_qt{N5ojkrZ#O^FRaVsTMMFkQJ_~=X0B_=_Sipw*jpj`krNoyCMl4nO2Oue7J8+Kp zR?J{(uBbJ|!4^8z>KSCmZlD@sjQA?OXr)Rqa6>c}aS1#35i%O8wrrkFh+*SQoJRJd z!8rMa<<-)<=u-p{KXDQ0yDC+}9dR=GxhxyvN^)h_3w|%-ub~buKl+!L{|$XRYjU6M zk+1D}UUTumkfF*_TUg$~3LtM~Wp~Bo+fnxGo1N#l`9A_jr6^#a|HH6J zR1<-K8UGXO@hFn^)Ua4)BTj#Rzac<$|9pZFDjM{U_xCHY19c^?fd7g4ged#J9Fddt zzk7nRFtIcLhc2N5pj1%QyO?Fnz*!2J$d*Nz=L=g4DasX4s}2GQ^TQ$k5{e9xS%WGi zpi85*X|1APhW@33w4(?gKMBNiL)4l3v)o$PqIUTJ*YosdS9lA!{`AhwPRe?}X}g7z z3>N{FOdK+scFwvyH9JPjD%UK?MZ+o7E04fylfX;GM5|^55KbW`58lxlg)k%|h9(3G z$w2Zvx1@{g^!V|T|5B8OA%UJR?%ur{>?zEX-9vt*z#wa56J8%AfJTK-tm@fY1Mch# zZK}z;gQ$tsD1%a!)i|n~RB3Vqj6IQDRi6SFlQAzUfH^eMnXJC2Wz3;3Xj@)Mxh7qXhmH0hMI{ zl}<ybDgml#3<=@R86&Bn(p+vD_owGctzO{~bkI z8TK#&HWXe%Mv0JAVL=lfQF2t_DJgJM;I7bJ(z7CP&88}%Q&umpTKZ3l+9|i4ZzXCi zdMSz{fSMJ(r@!GZ{q6n%133a2MF_{QcNO^YF9ou1Re=IqF0O_5 zt0{9)BC0(t+Tj2X-!V^Q!}NJ~_rg&&T#p0?ES5J4{VmtP3|2ZTM4ng}qnk?$RT%|) ze#Q~Uc$fAMLhV!^$s~{h{sp(NCV33@fmQ;QN?i6jJ1N*K-Atc44>LFA%)#Wvd}p%2 z{+H~+$hs#lj9n!B^CS#D@~W|Li1|o6U0;i(GL1gVWzfW@Fx%rr7V-yN>{ksWF|V5n zQ2p|-sUg>%mO70{w{tn1H}jcma79fL6x5^8-1_4@D$RFDT(@H0JW^ zA{qQqLmzF95r?<CtYK*JGExI7cSYmbE*fK7*$k<^EfdAPj zI^Ivyds&<-0d|RQLT}>l>s!1~r;_yK%jOKfdT;AaIW18>PbodshGvtG302?bm> z1o&gDx)O)pgkk9d8M{zGFkQ9=lnr>Iw;h{xeTLfO=vVA{m5yUeR8X}Nl!m~q+lQ;q zLyC|82lqPVI6;-TOV97b&3K*4HQ`{;^kbn>^BvxaWzOuxqH6Q!`TolofVPdOT$@Au zsDW(4JjB_zB;T-+2KBm8*T&}NPuM=@0}Mosd1Mt z&TdFVADy#cJzZD;^M#9tkvgalVqm+=ohk~`WgZ}6*jplPv6Lt}=#FZW#%D?hTGorl z4BR7`oqE-Ku}FlIuIj!5I77aH4`UA^HzImYkPK8V8u|8)uK|3%9B3WTGP=_apbed< zK)Lr;oV)Cs{@{d4ubm4y=dlWKf_$H}6%o0?U!?d&^3Z>HG|;t){c!9~no`HLT8h&O z7DrR(E}eWf9XRw1PqJT1*uFn<;+Vmfc+(#XsRLB}i4o;B)!k4sM`bbX<66~c{ zkiEul4>Y@S81gMNdPeH_jg)Qr-GN=2^v9u_Bwzivm_Ut)%YZ-64Ygk;r)Hn^I`XxCo(+y3g_XsR@z0>?MAczU`whQhwB=i$2u0d(oo1)0$D>pU@p{j8J%4_aD(4AFnN>v#`#)`LKF(w z!DIQS$xjd;dz?6N|3+m7f!BfU@)Jb-<)LECY1MpE-*u^*=IzSG2ps_(j>3O`!|X~* zY7L#bE&GgT>rq3YH*~k(X(Xi}v-pl8<0Aaz>=YUROOdgUf`^0rcgTtC^4lO7c(KHI z)#DAFojp~*yMP+TA-9QTF6UG#dG|R}l20dF-Op$DK5CuEUA^N+;idHOfm~)YgB}@#<(tJhm>sZI#_rEA|F8KCDkaA zqI&G*)tDmR&R70hv^w&Sce#VMTOq)0Iw+$@lE$cx_U*f;PN@Z5)hm7nyuxoz;l>Xw z^(B&$+lk||`0MGFQIE&V2}iHNR2%{G&u8=>ULNzj+H#kdNWF8Es`d}hr+ZVUy0dbC z09;?hzSQdS0^JnA!yRYxpN2uGzk};9HWJvX6=WoRU%>uM-DQ4$hHNdU)0WW>o_*}eav5LcVnEn?VFnwS@FLj9kcbLicZqNw`2p&=f9W1 z+RL-s6dHwL^67AK4#ZlDF0=(2@Mi4*XHNfR+^r6en+f2y@_d1@H8kBj)sj95G_OUraaarzB$5jw5 zi@UMC%hL-($FxhIWiPm;_sag#ksD670=33c;w(2|QBWZE73hzguebKNr-?ft^7l;P zE*MZ;)8MPu2ht&H;cTZh?Q9n4W1Z;e4M8~Ip2>Y!P z$QKMnqjn-gGm4Cm)Q*&96>JEgz!Wr$QMM<vb@IWJ$G)kZZV%}07MNQ_VC6Ifesp-{q{eqgvpxMn@!M3fV_y&w(?3OUhC z95j?w^sL=P!}&*k=qRd+-cmXN(g_@3w1rr=JGrcNg?!^|eh2)V*%1v`mA`qfP3oe% zUf5T4PW!Ai?fP3=m^@I2bu%SBWzTgOAy)!c3;{4TX!|+92(hNmbeiQg zuZ7e4y|L0Y#O~!fgetQ*Gz+pNwGK&9sd*xDMi9v$XNogFfn6WPjhxoXz}gHE2?7|! zL)Zjn)aT|7bHqr*E{Xx_Vu!k$$C^1O#QA-hJ%B+L=P{Z&mDpeEwHj5{WCkE4Xn~S0 zR`lf_5h+dP7t{C8?lO<_-m&3UQb|$(YQZ0ahBgcq-mAsI^Dt@^soaywD?UxvFnsGN zdVRm+p)`kow19*S6nWR3PRTLv7IWM~SPp=mf*iuLL20l9#AEx)GGG21BI|!tPV%fcsK%R!wcxcO$Fq8 zLcrJCLp78xUC`H z>!443)*1kg{2@$AO(@fTT5II_3&zx-LSCZZTn}bN~Q#ft168F-#7ZGr`kn+|5k+ ztu%jnzjQoK^t`Z(;ZyTlJkaTHkll;ce3alUHY)90fG-T?)EKF@zbXsF3Ehqhnz76H zmi=*>wb1+bp5)Lw`V)Cla3%fl!up8kuKK^cGas^z87&j$_0y$w1HEHedU{((VDK=c z(gZ{pw^6_uS{|RK8onD70|U*V`}uje(bge4xgd*1fFY5)#)yv^@VpAARS`vk2 z2}&enEiV)Xix&bZWT6cTL-PzH`lh;Hmm0Lei1z?8Yw<(qXwq!#O(I< zKG|V#JO}GVlji==foaiyJ{MlZds<3oo1_9(iw1=5)g{C1uS5K{9?ii8VPU&-Ke=}^ z)zbIPiPA}V=z*xKNStk7S)1$ztc)lhxFf{LLzY3pkZkSeusk4;s zJK5M-Ela8%;Rz5jtcAWLLg?Afv-p)H*23^Q5PXR(KZ#ArjNy~_Bzs9e>qu)A84v&z zP_1r%!{>(wbwu_8tX8{E{$_S^z>)tjk->QuR;aIl`fuZk5Qe$Whsj)RPo*V#-2tiN zIe`&i90loY51={gR|hrRqKIHV@(C)$p81yQAYVuz?iL5r(@Sn>x?rU)ZfY>#Ufa}R zZ3LVqBZ*^rCwVzb{!Jf%*9x=0+YdLspL6m8DIE;!*sp5>3e@4T!W2YQh66;9Y80D6 zPGGe$Q!%-j=!_dExPX7Mhf{{8Q~nj#^Q30m{Cl%GS5{>oq_IVML(YTu#1FXj(EPsv z54QjQ{sUoSWlid{zyRo49|)j^-ab;B(L-N{ux2naw}$5{Y8L1(HPU#SC(XlIASZh4 z_w7#bG=!K^Qd4;Hn9q3bCLQVcdauKpX};S*T(F_mwNlEvT0u-4wnEp|qMLegZ4~-8 zvNf%fZfr%Rb;_`}qODGNHPAi0jFQ$xJvsclBE8&|bncpsngt}+#i?JeKPhuQK%b`{xsdOL{!5No%i-m6C5fdSS!CngW{MA{57JNZ*ZQHM#0tZ3A3-5igM0%PP6n#p+oj5 zGQE`IgT>+ljVwSdDMDe33%D&{r}Mcf0#4?DrFcXW%9J>Q=a7p?g`}fXA!!-4{x%O= zMErN6FmF^}gnxTgN62>?O+*$;XepUuaVdF91_v?+yAAn&h8K8&@}X|we@1Kp*f8ib zvQeNH*feiaxfK~eDBO=K@T{yG-Sn&sRfM1Qe{5HA89E{(rhvIZGsL8La3kbIKjst4 zZ_s7joQAv%sSkML@>~C+V!+Ia!f&=B>Hbd!-{?29Z#1{rT*i}D_sl%ztrNVWC@rB& zC1_!!mYk>jxA%I_!glV52Jv}C?Kb@VrANI?P37zSWu8{Mc8bVm$P5mZ4?pmhu|41qziaL9ok ztBlI!8P6%x(OdAR&yDw!)nI)gxtsOb_dM@GA`hOxq5bHVHtp6W)0hh9wN?rKjwTwH z%gk{GX7cw^TWynsOaKU5buwOBIlbW+;*|zG^*p&osCCUOo(lmBRuSIIEQb?~puA_+mV1OviZm|!-OBhdo0M9}XP78hgs5c7Bf!r>zCy)c1v*^VAn8H9>iextIVg$~Z^p?p$Zidtp!Ku}%<n_j7?6%DJac%!vLA+LSSFdy?U?yC zt~sziJ#p|Kz|vx7`EFTa@%=Anc6+Sm`1~Qzv&-}QNc|{|L}6E9-3NYGZBxKI#Vy+{ z=p{e4Vg@N^tW*CSfBw6?O^IH(P9IR<7v6gj)u12j>!^`W9gfB56}%b_LVOdW!@*=% z{I7qjAF#TYP|&lFK6Dsr8%l7l_@v`S+7m@rrXS0M?YggeK+E>acDk;6rtX-B)vyne z1TjRp2m7Pzfgq+25XUdBB;OJ)k|YV%E7+_;%a<17X+b{k%u29;`iI=e#5YF~8;sbj z8xCv0jVXXj87>OJb`^a(_x=O;*nvlSR15(?LwfVM=?UPYk8w>G^%Vc@*Of!zrB|2f)=gParGfo&}Q z0RIfWih$=!-_DCTDR@^a9gju=C2t*Q1K1K;4_&3FvDyi{-qGS(2tx>fo7x5eOVuNX zH8sQi<;yt_Qj)L|mPEWi|Ku`-7}qA$JcQ-BJ{Z(4xwG_kyJrQbE2N$D7gYPILE7Qw z8IkC;7_aGVGuTo=#Y!%6tWvnqU{H=mC;mv%Ca%|Ji~gfpDhih_0#M>IFcO492kfQy zgr5lb5@38K3o-QmFk3KJ*Nx=k0F3f?k>mWEBN-x^vYUIHsOz<9IKIW9_QZh@dONJn zeaxHSMveB*tLleUKf^E6eO!&41N3!4$c#QZn(d5vQJOm5=--sMsa#P+(uxxA_uP9( z-ErK%b|~7c-$u-b&UO{_X1(~>Ex`fG&Sja&ddG7RdQiXI2C}~NjdaWPtsm*2CGFw_@t>!?Uv_yBK=;62MnA~1) zo3B4qLnB0-axE0pa_^;R)U(n?UfUiDpweYg6!EoU@N9s$tp{?;0X$TU2ypTzq&@^# zav+>z{EGCNloIVYm3-Te9Pq@VQOksP#C1O)N=9!fF{9=U`C3rnC~Q-tQEXT&Z^A93 zY+1vAjHRu}u)|wow6W_7sJW4~4mYnDeA!6rG3i7cM$p?22uTmoP016RC}5hoHiY+E zWR)2rBhMDfonSO#0M4*>K`4>|O^uukku8>t)i(i%&xvlGpllQ&F8eu|((2L~B#~;k zTG9&u>rgA`C*7`u8=? zjXpk=n`shWBEulkyx5O4^1N8f!m~!<6cV=6%-N z8AH|#yrZNBfK)UrqB&RXI%l681QGGtBZT%O{*dAGSR5(Tu=pTdKtA=}pIAjrT~{}~ zUNSMp2{{tHRh~V|h^n`I(u_--_!AnjElK5gPGR-@j%{WELP=Y+nP-au+u@qqGpg|@ z1PT)crrohwKvtVZy;McudkjNGgz^SV7TJb#7bf4WBaD;NxQ;DojotabixF)okhC zS(9eQm|SVD0rE`luCLse_3QR)NP{}6d4@jDyinv#z1&Xk^O(cEN!0P-ir&Litij6z zqp^w1+DXjc_w#UPM`W3^))y$K*IkN1EgCOgfS`Qf`q22?r_ydoYcAgm ztS%WZS2=xl3sKj{U_?|07VNh`CpWF{uVq7~RX6Tw4d>p;9;0v}*5*~ATegy19X6_! zU0nh;p90!(2Nv4FD9l3!Y^BN&9$)m66_e&o@5GpP;*(i9JXkqY)2K)gA7UtNKC7xM zpl^7}CYlu9cpk^l%}YzJ_2BAcI?^^PXtAi#@hnS0;;}#6FxG4?GnBJYF}$9?ZXa&w zL=*nxewBjVbJ&jGLxD0vQ`XP@;_D{L=;Jyn;NbVK?Q#9fZNq-g+JzpbDDUr|!+H1z z&JeW$)^wQKBNyli1*VQT)u|%Tv(iRWz#er>k&Z?=YH5zd$2mokg8a>|o&5p6qmR%( zPc#2|+%Ix~WvG6S$RG1Cp`zg!PL)I9<=RkLD@c{a%6$&iRMhPMz^h-}GK8;xiB+c$ zBkpuRL=O0}Nh|IDR=XrG3csCdMqEy{+GO>5r}(xyy0j#@R%4Nef7NB7S$$vY01HF~ zx3pu0-7JOBtc6bX<=1)Kc#V!tc|!t)uW@M%5H!W~3HS>*>`SwK@({eGA?D{iOK&YX z7rk26!@@e|I51l$<9yI7ZH+3I0PNs~b5Z(4sAU7mYk&widYTwoRG7aNe!7)^WPv_+ z-6ry0QA4@6I<2JphTe$XvvDLXbO_Q*o4Mm`!t^C69gzxPEAO1dgVrR zi2C95`0l#hcb-kQVM;&%jL~|{r;ZkBC(Cmz$Rzn22mdU`F1#r_t9Osp)Y!bPK7|{W zVgLPsTp`V$gJn_mw3D5f6Oe9QfNd^Vp%wi0A;0aHaKEptubRawZx(UCL#uKbu}2<= z7D2N{n&$jhyB}Dj$Pcz+1Ivkkum|Ia-*CgtbI%b&9aR-hod<;hh(6upw9j1w<$raU zflGHbY@Nrspu^=?%XXqIxtR=FLMlUDSG*;2*?I#3D{9EMr_fH$+I!5gDT6_8ZsIDS zC|Cn`ikYq$!Tx9)db+K2H|nGu>FB)0-nBc>P_Z$yG1EC8-)2tmL(#JYVoB^U3BYl`0Z)W{ ztG@bmO!ZT-;py|66*YV8;@REvVb3J4{aY)fj1TPrO&eGXmMu1R$-nh#PXm4IT$wcs5{UxlDr`$PPGv$qu?06NE|tvcM3lN6bT1;6%)F8kW4FbR=9~B zHV3ii%D_LttQdH(@czQZmC7Z3st9|zNnV`y?3PL?;Y=jk7I?78VqwdUXUX@KpR${s zq?%{%bTP{cRVm>}e2gTFPfrXSMA*HcWT*}uTF(JTf!g;Hx}Rk}dLj=RwYZYwv9rJ! zGD;PlLCYcmOpl9;O{9!)#&sJu-NwRme%W+9ic~FFqW2d~r5*QUh~|}SHrB5el-HUU z6P5F1KOdK>RoEJq+hCi#{cOVSsXAe@*N6u%)1av%m4l9~b-!`HN#?77f=mZ>uj-zrwKMS}^{fLe4TO3aD$_(hZ_?4vlmS3?L!h zAt}w!LrX~yF_O~V3?K;7-67^ywCH!zrVH4S?lb*?w|YDy|4RdL#Jnq z^<5Tg+D86XRjagjmYfe-y-!yh55QRxLiD<%?Fi4bz>#LR<;{Qtd*@N{riLEov}g zZL{^?UljGQ8y;rmE*yMp4xO+7WGSs}I3L1o2|Etg$S7(BkqLsNhuu(f0GePAjjKAv zmV8hBo?P2`&OI$iX{2Mgkn?N0;|~dpsu=YIvS0EVKc%e#@D81WYiwmRpcp#5m!cPI zUqL8$1?S}$KT8Q4W3(fhw$skPlA0E-kS)rX90*C99Vc0m3-ER`*DEZVY@XCCj$%Yn z`j7XCS1$Y+@y@+sbZlNk6=gEk^fPNexYOXO8On}ue!Unp7}AEdcvYi_tpi{ddVAS7 z%(eLOF6>_=9vS>kKtsgUbL=r6IBxq@klizAD#Zi~4lmL9EPxvI1V=PAem^ic zixhtoUo()a_23KJ8xTx1T?i`m1;rg6 z1w8l!M(M<{5}RTeZb7{`rmII6Ud~#tMZX!15g!c|YHqiS%cjlcl7F2)A1ukU^@@Uo zXT&Hg&|KHt#O-_Ilyl>bzPWiTENDBX9ELZm-?%)+`+W=?M7WES%VxB;@61FQUF#uX zElf!!9$cK~+V)Y*Yre6s>x}yHRn}42hY$ri+nMWDg^?c@5uc%te;l@l3({XIg#4>c zK&^wEB&@+i)0Bc0nk!#a?zdHppkn%-G#dVEoSS@7(?{Rfk{R*%E|bmQBJ^yqEn)=n zP^?m>`bzRj-3Pe2`M|^hf{a)zS5#eC&V)+GZhH2KHK5YEo7!Csxjap`xhD0qvE9Qk zv7g$91LhIWD9b<{WW`70@f83?LRCaNxlOPYn)=E;HW>@h zdPqv5ER5(fyivG6N+=pJCcYJrZep}H4pIBsNEodC*imPoS9isrPR1&eR%|g{XF2+BskAqMVH9ch{Ru-IPw9T2IRKKN>Il zEEiW5R`De5ai{G0gEV@N*1`h7xB{UAYQ#j;kQZV#==4H8kv7r!m=v7kMJzu4zokpY z;0Arjo4rmNb^PQdO^EGmxpI)24T8g5CWzzeo}6?j6?{Zj$NDl3lJr!O}CRxIk) zkHfSvZ@;Fi>o)R{+$r}I`r<0p(Bpp{$7hW2nLb1le$Kjg+dl_L)Lj?ek;q>?k|N}b zt)*-14k;sFzA~>$SYXC|kL0e!a=>x2&X#`+9P+?VEaz}wE~MG+1YMb(j*h4Aj9a7` zxOx{H%l@^@-Z!!HuDR}#zyN_pIo{Gc&r2F*Bk$c8#3OgwMueM4n`18BP05aOInC1N z*K&}mf4XC~z0IL1^ejsf>;L{dQU%;)RRu{B2|rrYb5qYs_|)E=gN=#$sxBMtiK@1w z%0F`w=ci&zokmn<7jn?cdD4f&{jK!2WA^7LT|&XUM)HJ;PMwcEh++C-;?bMvF*aSw``( z9o3w`(}8&LfiqTH=xF}7U&_PsOi+SnSRY?pNgfF^oI+a*bs!=9)z5cSRMMDP(pL6_ z;{_PN2z|q;S1Rz-Qg)a&`&kO?W%+Ae#cvu~v~V$LFIXMA4e`jU6nXn;W@2u55bKDi z)N7lp%vUy9%*-P|Wu;$#7TKoD6km!7HLH3$xF;5WAC^z5GJEGURm?o}GI=5t2<<#j z)fNz*{nP@=6Et>+oacb;8W8_*VqvJ$!L_W~w@b=Z0o3U{5LlPu%XvGXC+41+t&;}2 zV@$J=Ypv`A4nj)R!ifXlw5mJ}vpi-dE*TUTja~si-%=}RLHjAz!>tM7T;P&ISK+*~ z-*=@Q#xIv<+IhYnn#{T|-Tc`F(P%U~M*lKv-YFCFGJV{dJ>HU%QPhN-VkPvI)&1B0 z{?BSAc|J@Yl@Ih^78Zt1@I0gc3(I~WZrM0^Cdroc0{mIo^z(BUWp*rn& z0y<%^NB^ftG#MH9!OVovD5bSGneMvHW9(6-R)cSBf%P&)`n=r z@?j=8@O+mu#b()GoHnLoK+rPODq(6>{dxQB0q&J4Dindf?=gVzA=0wSR->qBabnjL z!m3gJ_}~k*IBiCa!I;8}(}*zmE_pm*>U0>RnVYEM1?8KVhm|r6zZCl%SXoQ}74jhp z@7T&1_h0jufeV7AidlC=hKob+U@z!jC%&<-qmdNw87aWUHKM`68k{MJkxdRul#Ap_ z@g&v>l?{gOs41`G(UL0R7p5{0`Sz!1}L9O_`ig0GW~3u~{3R4qMv>%UR2Pc*QXI-KHLZ)sw}ESs z()WJ&y2x&F?t1Bg1H6&EB``jJE#H>MdO$=%aBq9>aPJoojSK7qZ+y2-swc`o5p<_1 zQGyEoI_=b-#x4MBv?hk^xWvs}`zy`V@((^?+?*1(oF?-%CwiMS-y+WmS8qG*RsdAoP*vY@WDXspaa$>dHh3$rNUB_YL zOM8>=O(n)E&FWtlkCBcZPh|OpR)m5Dm`H?>O5Nbxi^XM;%qenByGFA zO?-lA)^5V$Ej?Pz)pc;GM&4=Oq0}9Esl^yb=ipSFumgD8#0&{YV!#`( z^_Sg2_*`;iPP%wFJu=hekvPjnElpf|(J@&`7*3GkEb@0vB)6N_xbm71j z&*y!$pwQaqT3S8HE>Ap>22mYq<7niJT)lP|RW3c>!q+aHvdCK@GG~bYQeS-dRI~N@ z7lOj5z)_(rjZ=V);*RIRMilD+UGC|dz*IK-R}C!X6E!kt+%UXl!3B2x<(A3S&e>5- zh|+OY(!BWS?3I(7fI3u)b;#*H?UijEw5IVX;(MuyIvcI<|huCqk_gcjEz#)D0OtOHQ0mt5<@0bDT z!Wj>zaCI;Ny_0?RDM~z90N^re^*jU1k8e|*D7@p*7GFXPhZYL(eUCDHeu7ebI+HZw zY_6xU5Jr4?e029cAIgiiE@oy+^0N9ItDsn-Gh8I_`^N^=9d!VhX~ma#9y7^a7f)H1 zq3J_tgaiA{zwDoiwCp#fp})dPt}NJJNl-*zgRFMJ&7XQV98Tv>Q0V3j#}@F6e;eJv zoD57G_oH@Egh9|tmAg+o03=P%cDV1(KWgUR6Z^EoO{R$gso=!2*e?}xlgJrm`AxYa zn(3cJFnc$Wi`vRJw>1S4fnFaU@8s=B{jG^3brau-+-BV^LUv)!)(c?jy;5A{ z!{F6G)j^Eu-FWR?EuX{}IIdPAWZ z`mhvSW`ZPH-WaDDqc2S^)PsZYbX4VO0Y+I(`sjuUd%I?7XAvlV)lQm&Kqb#q16v6W-U<~W zzNMQQ@tzj(*ggP6<<2n%t5g)8bHZ3?k0E9lHPFVu(V7CjM3hq2h4313uf=i)bi z=%m)fQ4A9;j_9<@;tS+!EOG#vE-5kS)A{fpZmuC?2Mg#*ApCD|Kz+bWZ3c%5fLR+NzU`CoRZPC+PPuG zVS9pKGT&1JNO;#~ZLC(e#BN^2b!B6c6{&hb8ocflvIX+*qBIV6#XBR?D}DEbtD#4` z+OOw9Mt_P1x~5qj94yu8dCmR`1o84B*Z-6nh<1jg@9pS);kn%ZGIMF)I@xX10Q^hv zd&Ysxr=}z-0)8A#lBz`8DDUz)h0!#=Z(+9RXnDHLM*L*M#=)#S-+;f(Jeg|FkGCcj z0x2}ALY5o0Oq`}2=qeVM7fE{+!J$NUSPY9fjZDKwftqzz`ixMwG>!0C z-;RlR!?k>AnhD`Luy^;^Ml~Q^q94jtw+1u>V`!u$i`Eiv^?|M_G$o17p`IBlto@sg z0TBcEvZ`sxo}z|M0yQjOamk5F+Er5hkEH8z){aEK%qAm!^VH4=Dmk`}KWVwsSCRb< z3~JW^)g!I+*CD46+1v>A<#Wg@EOpgZiZqwAg^s?Buy#*M&3ZR_ThWS;AE@GCq-3$g z0glvqEe68bq$Ud$j-IanprE0OoUTHb_)OOs+8=ugOUb2bVJ~STq()L-;vNS z%;UbTP|5_=gby&~3>1=~3iK(9+yIr}8s&_7_e|;vgL?3qgD2OLR(>O$j^z^k;|F+~ zTuY(29^@ssdj*VBG&)K8Q<`V{(|G_M(z`U9)_Qi_O|4b0ENZF%`eg<)HrYxpT?O3R z&Z?xYi}-3(*v2C?rj{PPXJA?t4NNdKWl-@_lubAm!?QfpzncJe{A>3V+Xp(NYp|s# zocsz=wD~l=T1egb_Gxgcq#I|(5xIgcx^M|ftoYU(HSwF^#1&PR={9?nipf9wCuFNY z{ILal+JuLI1*L@@n*15XOy1WAoN_5Od!AE>TOE7J_u!#i9YKkqote^5vxvIiFUtu_ zzbWN9SH6`6y9S6)?0xx72$fqL_S3YoKHGE3esmGs6fPqyh{f(q~fOaZ()5I-A$ z!1J5e0ALK@2M9dh=eD-1t0zE!AM_k9)CTY>yV$xuS0ewfl6zK)E6DKyfeIjLQJ|2h zppdMvf}EU?kdUy9f-p!FCy}S+MLRh*`%ghsHDPCU{pRR;jG(KerUIkz5SVc z2#?+4E4d%tjL}+829IPiv4wA>$mjH6qZEVOT_@7C{|f2urh*?tg2O~${q{eIfpE&5 z_nd)acJu@MdC>XEOA(D#W+OM;($?AWOZymVql$`fnWTMSz z&gFM$Pgkf5A92z@lClUrj`$m7Kw}-N%5=ER-MPBW70ItuOgz*(cl*`xzXb~J5QGYH zhQt?Bd)lA|f?be87&Yf>m z>3&f1WI*_iPl$ofIL)BC0Rl1as@IwC&V`%-OnR#4lY4w=+NM!AN6c3A0)=D8v()Sm z^A*z#!jaGanVJ{FguKpUa)F6E4@vo$DzIxBuFWbAM72-0{H=nz`?WFbzv6Pby zab2eOfX2C?J*Y{2`gSj=`$(I0pM5YH4)iMJkdj4&!85hv5K|q3$J&>g_Dxh-juRi3 z84!rTtS4flZ?GACkAN4z%Y+#=nFA!enNw85Bi=%ndK4ae0w2GnDQz9PTr&+SY2LGK r%e$FtJ;|q0 From becd305e01695226e66d920c4681a048272cb689 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Thu, 22 Dec 2022 15:41:47 +0100 Subject: [PATCH 23/34] Closes #7 --- lib/RPGEngine/Config.hs | 10 +++++++--- lib/RPGEngine/Render/Menu.hs | 8 +++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/RPGEngine/Config.hs b/lib/RPGEngine/Config.hs index b752af4..3d4f938 100644 --- a/lib/RPGEngine/Config.hs +++ b/lib/RPGEngine/Config.hs @@ -17,13 +17,17 @@ winOffsets = (0, 0) -- Game background color bgColor :: Color -bgColor = white +bgColor = makeColor (37 / 256) (19 / 256) (26 / 256) 1 + +-- Text color +textColor :: Color +textColor = white -- Default scale zoom :: Float -zoom = 5.0 +zoom = 5 --- UI scale +-- UI scale, number between 0 (small) and 1 (big) uizoom :: Float uizoom = 0.5 diff --git a/lib/RPGEngine/Render/Menu.hs b/lib/RPGEngine/Render/Menu.hs index e5f66d8..473c96c 100644 --- a/lib/RPGEngine/Render/Menu.hs +++ b/lib/RPGEngine/Render/Menu.hs @@ -4,12 +4,14 @@ module RPGEngine.Render.Menu import RPGEngine.Render.Core (Renderer) +import RPGEngine.Config ( uizoom, textColor ) import RPGEngine.Data (State) -import Graphics.Gloss (text) +import Graphics.Gloss (text, scale, color, white, translate) ------------------------------ Exported ------------------------------ --- TODO renderMenu :: Renderer State -renderMenu _ = text "[Press any key to start]" \ No newline at end of file +renderMenu _ = scaled $ center $ color textColor $ text "[Press any key to start]" + where scaled = scale uizoom uizoom + center = translate (-750) 0 \ No newline at end of file From 5cc96cbdba190607634f1b17584b2cfe3350341b Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Thu, 22 Dec 2022 16:25:29 +0100 Subject: [PATCH 24/34] #6 Win/End state #8 End screen #9 level selection --- assets/gui/main.png | Bin 0 -> 5460 bytes lib/RPGEngine/Config.hs | 4 ++++ lib/RPGEngine/Data/Game.hs | 5 +++-- lib/RPGEngine/Input/Lose.hs | 11 ++++++++--- lib/RPGEngine/Input/Playing.hs | 2 +- lib/RPGEngine/Render/Core.hs | 1 + lib/RPGEngine/Render/LevelSelection.hs | 7 ++++--- lib/RPGEngine/Render/Lose.hs | 14 ++++++++++---- lib/RPGEngine/Render/Menu.hs | 17 ++++++++++++----- lib/RPGEngine/Render/Win.hs | 13 ++++++++++--- 10 files changed, 53 insertions(+), 21 deletions(-) create mode 100644 assets/gui/main.png diff --git a/assets/gui/main.png b/assets/gui/main.png new file mode 100644 index 0000000000000000000000000000000000000000..1d2ae6e88aba0dccbbd3024533d794ede19940bf GIT binary patch literal 5460 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 Bool -isPlayerDead _ = False \ No newline at end of file +isPlayerDead g@Game{ state = Playing{ player = Player{ playerHp = (Just hp)}}} = hp <= 0 +isPlayerDead _ = False diff --git a/lib/RPGEngine/Input/Lose.hs b/lib/RPGEngine/Input/Lose.hs index 007a25f..a7ff57e 100644 --- a/lib/RPGEngine/Input/Lose.hs +++ b/lib/RPGEngine/Input/Lose.hs @@ -2,11 +2,16 @@ module RPGEngine.Input.Lose ( handleInputLose ) where -import RPGEngine.Input.Core (InputHandler) +import RPGEngine.Input.Core (InputHandler, handleAnyKey) -import RPGEngine.Data (Game) +import RPGEngine.Data (Game(..), State(..)) ------------------------------ Exported ------------------------------ handleInputLose :: InputHandler Game -handleInputLose = undefined \ No newline at end of file +handleInputLose = handleAnyKey retry + +---------------------------------------------------------------------- + +retry :: Game -> Game +retry g@Game{ state = Lose{ restart = restart }} = g{ state = restart } \ No newline at end of file diff --git a/lib/RPGEngine/Input/Playing.hs b/lib/RPGEngine/Input/Playing.hs index 6ca8f47..3068d24 100644 --- a/lib/RPGEngine/Input/Playing.hs +++ b/lib/RPGEngine/Input/Playing.hs @@ -50,7 +50,7 @@ checkPlaying g@Game{ state = s@Playing{ restart = restart }} = newGame where newGame | isPlayerDead g = loseGame | isPlayerAtExit g = g{ state = goToNextLevel s } | otherwise = g - loseGame = g{ state = restart } + loseGame = g{ state = Lose{ restart = restart }} checkPlaying g = g pauseGame :: Game -> Game diff --git a/lib/RPGEngine/Render/Core.hs b/lib/RPGEngine/Render/Core.hs index e901243..0c6754c 100644 --- a/lib/RPGEngine/Render/Core.hs +++ b/lib/RPGEngine/Render/Core.hs @@ -44,6 +44,7 @@ allItems = [ allGui :: [(String, FilePath)] allGui = [ + ("main", "main.png"), ("health", "health.png") ] diff --git a/lib/RPGEngine/Render/LevelSelection.hs b/lib/RPGEngine/Render/LevelSelection.hs index 85dc81c..082a8a1 100644 --- a/lib/RPGEngine/Render/LevelSelection.hs +++ b/lib/RPGEngine/Render/LevelSelection.hs @@ -4,7 +4,7 @@ module RPGEngine.Render.LevelSelection import RPGEngine.Render.Core (Renderer) -import RPGEngine.Config (resolution, zoom, uizoom) +import RPGEngine.Config (resolution, zoom, uizoom, textColor, selectionColor ) import RPGEngine.Data (State (..)) import Graphics.Gloss ( pictures, color, text, translate, blank ) @@ -25,7 +25,8 @@ renderLevelList LevelSelection{ levelList = list, selector = selector } = everyt where everything = pictures $ map render entries sel = selection selector entries = zip [0::Int .. ] list - render (i, path) | i == sel = color red $ scale uizoom uizoom $ translate 0 (offset i) $ text path - | otherwise = scale uizoom uizoom $ translate 0 (offset i) $ text path + render (i, path) | i == sel = color selectionColor $ make (i, path) + | otherwise = color textColor $ make (i, path) + make (i, path) = scale uizoom uizoom $ translate 0 (offset i) $ text path offset i = negate (250 * uizoom * fromIntegral i) renderLevelList _ = blank \ No newline at end of file diff --git a/lib/RPGEngine/Render/Lose.hs b/lib/RPGEngine/Render/Lose.hs index 1e6d730..cd1cfad 100644 --- a/lib/RPGEngine/Render/Lose.hs +++ b/lib/RPGEngine/Render/Lose.hs @@ -1,15 +1,21 @@ module RPGEngine.Render.Lose ( renderLose ) where - import RPGEngine.Render.Core (Renderer) +import RPGEngine.Config (uizoom, textColor) import RPGEngine.Data (State) -import Graphics.Gloss (text) +import Graphics.Gloss (text, scale, color, translate) + +------------------------------ Constants ----------------------------- + +message :: String +message = "You lose! Press any key to retry." ------------------------------ Exported ------------------------------ --- TODO renderLose :: Renderer State -renderLose _ = text "You lose" \ No newline at end of file +renderLose _ = scaled $ center $ color textColor $ text message + where scaled = scale uizoom uizoom + center = translate (-1200) 0 \ No newline at end of file diff --git a/lib/RPGEngine/Render/Menu.hs b/lib/RPGEngine/Render/Menu.hs index 473c96c..e9251e4 100644 --- a/lib/RPGEngine/Render/Menu.hs +++ b/lib/RPGEngine/Render/Menu.hs @@ -2,16 +2,23 @@ module RPGEngine.Render.Menu ( renderMenu ) where -import RPGEngine.Render.Core (Renderer) +import RPGEngine.Render.Core (Renderer, getRender) import RPGEngine.Config ( uizoom, textColor ) import RPGEngine.Data (State) -import Graphics.Gloss (text, scale, color, white, translate) +import Graphics.Gloss (text, scale, color, translate, pictures) + +------------------------------ Constants ----------------------------- + +message :: String +message = "[Press any key to start]" ------------------------------ Exported ------------------------------ renderMenu :: Renderer State -renderMenu _ = scaled $ center $ color textColor $ text "[Press any key to start]" - where scaled = scale uizoom uizoom - center = translate (-750) 0 \ No newline at end of file +renderMenu _ = pictures [main, pressAny] + where pressAny = scaled $ center $ color textColor $ text message + scaled = scale uizoom uizoom + center = translate (-800) (-320) + main = getRender "main" \ No newline at end of file diff --git a/lib/RPGEngine/Render/Win.hs b/lib/RPGEngine/Render/Win.hs index 189cef8..abaa095 100644 --- a/lib/RPGEngine/Render/Win.hs +++ b/lib/RPGEngine/Render/Win.hs @@ -4,12 +4,19 @@ module RPGEngine.Render.Win import RPGEngine.Render.Core (Renderer) +import RPGEngine.Config (uizoom, textColor) import RPGEngine.Data (State) -import Graphics.Gloss (text) +import Graphics.Gloss (text, scale, color, translate) + +------------------------------ Constants ----------------------------- + +message :: String +message = "You win! Press any key to return to the menu." ------------------------------ Exported ------------------------------ --- TODO renderWin :: Renderer State -renderWin _ = text "You win!\nPress any key to return to the menu." \ No newline at end of file +renderWin _ = scaled $ center $ color textColor $ text message + where scaled = scale uizoom uizoom + center = translate (-1500) 0 \ No newline at end of file From f3bce9912095270350536ed0f0dcf0b716a12daf Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Thu, 22 Dec 2022 22:05:25 +0100 Subject: [PATCH 25/34] #10 #18 Fix parsing --- README.md | 2 + levels/level1.txt | 2 +- levels/level2.txt | 10 +-- levels/level3.txt | 2 +- levels/level4.txt | 2 +- levels/level_more_levels.txt | 10 +-- lib/RPGEngine.hs | 55 +++++++++------ lib/RPGEngine/Data.hs | 3 +- lib/RPGEngine/Data/Default.hs | 27 ++++++- lib/RPGEngine/Input/Core.hs | 2 +- lib/RPGEngine/Input/Playing.hs | 11 ++- lib/RPGEngine/Parse.hs | 6 +- lib/RPGEngine/Parse/StructureToGame.hs | 25 +++---- lib/RPGEngine/Parse/TextToStructure.hs | 19 ++--- rpg-engine.cabal | 8 ++- test/Parser/GameSpec.hs | 91 +++++++++++++++++------- test/Parser/StructureSpec.hs | 98 ++++++++++++++++++++++++-- test/Spec.hs | 19 ++++- 18 files changed, 289 insertions(+), 103 deletions(-) diff --git a/README.md b/README.md index 832883e..455e7af 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,8 @@ These are the keybinds *in* the game. All other keybinds in the menus should be | Move down | `Arrow Down` | `s` | | Move right | `Arrow Right` | `d` | | Show inventory | `i` | | +| Restart level | `r` | | +| Quit game | `Esc` | | ### Example playthrough diff --git a/levels/level1.txt b/levels/level1.txt index 42ba56a..02bc322 100644 --- a/levels/level1.txt +++ b/levels/level1.txt @@ -15,4 +15,4 @@ levels: [ entities: [] } -] +] \ No newline at end of file diff --git a/levels/level2.txt b/levels/level2.txt index ca3a220..641cc56 100644 --- a/levels/level2.txt +++ b/levels/level2.txt @@ -19,8 +19,8 @@ levels: [ items: [ { id: "key", - x: 0, - y: 1, + x: 1, + y: 2, name: "Sleutel", description: "Deze sleutel kan een deur openen", useTimes: 1, @@ -35,8 +35,8 @@ levels: [ entities: [ { id: "door", - x: 0, - y: 3, + x: 1, + y: 4, name: "Deur", description: "Deze deur kan geopend worden met een sleutel", direction: up, @@ -48,4 +48,4 @@ levels: [ } ] } -] +] \ No newline at end of file diff --git a/levels/level3.txt b/levels/level3.txt index f943fe7..349c63c 100644 --- a/levels/level3.txt +++ b/levels/level3.txt @@ -76,4 +76,4 @@ levels: [ } ] } -] +] \ No newline at end of file diff --git a/levels/level4.txt b/levels/level4.txt index f7ec761..ba5b8e5 100644 --- a/levels/level4.txt +++ b/levels/level4.txt @@ -131,4 +131,4 @@ levels: [ } ] } -] +] \ No newline at end of file diff --git a/levels/level_more_levels.txt b/levels/level_more_levels.txt index 879dc6b..3dd557e 100644 --- a/levels/level_more_levels.txt +++ b/levels/level_more_levels.txt @@ -1,9 +1,6 @@ -# Dit gehele bestand is een Block - -# Dit is een entry: key + value -player: { # Value is hier een block +player: { hp: 50, - inventory: [ # BlockList + inventory: [ { id: "dagger", x: 0, @@ -18,7 +15,6 @@ player: { # Value is hier een block ] } -# Dit is een entry levels: [ { layout: { @@ -135,4 +131,4 @@ levels: [ } ] } -] +] \ No newline at end of file diff --git a/lib/RPGEngine.hs b/lib/RPGEngine.hs index db19c09..e7cbf72 100644 --- a/lib/RPGEngine.hs +++ b/lib/RPGEngine.hs @@ -9,7 +9,7 @@ import RPGEngine.Config ( bgColor, winDimensions, winOffsets ) import RPGEngine.Render ( initWindow, render ) import RPGEngine.Input ( handleAllInput ) import RPGEngine.Input.Playing ( checkPlaying, spawnPlayer ) -import RPGEngine.Data (Game (..), State (..), Layout, Level (..), Physical (..)) +import RPGEngine.Data (Game (..), State (..), Layout, Level (..), Physical (..), Entity(..), Direction(..), Player(..)) import RPGEngine.Data.Default (defaultLevel, defaultPlayer) import Graphics.Gloss ( play ) @@ -27,22 +27,15 @@ playRPGEngine title fps = do -- TODO revert this -- Initialize the game initGame :: Game --- initGame = Game { --- state = Menu{ base = StateBase{ --- renderer = renderMenu, --- inputHandler = handleInputMenu --- }} --- } -initGame = Game{ - state = initState -} - where initState = Playing{ - levels = [defaultLevel, otherLevel], - count = 0, - level = defaultLevel, - player = spawnPlayer defaultLevel defaultPlayer, - restart = initState - } +initGame = Game { state = Menu } +-- initGame = Game{ state = initState } +-- where initState = Playing{ +-- levels = [defaultLevel, otherLevel], +-- count = 0, +-- level = defaultLevel, +-- player = spawnPlayer defaultLevel defaultPlayer, +-- restart = initState +-- } -- TODO remove this otherLayout :: Layout @@ -50,6 +43,8 @@ otherLayout = [ [Blocked, Blocked, Blocked], [Blocked, Entrance, Blocked], [Blocked, Walkable, Blocked], + [Blocked, Walkable, Blocked], + [Blocked, Walkable, Blocked], [Blocked, Exit, Blocked], [Blocked, Blocked, Blocked] ] @@ -69,12 +64,30 @@ otherLevel = Level { (1, 2, Walkable), (2, 2, Blocked), (0, 3, Blocked), - (1, 3, Exit), + (1, 3, Walkable), (2, 3, Blocked), (0, 4, Blocked), - (1, 4, Blocked), - (2, 4, Blocked) + (1, 4, Walkable), + (2, 4, Blocked), + (0, 5, Blocked), + (1, 5, Exit), + (2, 5, Blocked), + (0, 6, Blocked), + (1, 6, Blocked), + (2, 6, Blocked) ], items = [], - entities = [] + entities = [ + Entity{ + entityId = "door", + entityX = 1, + entityY = 3, + entityName = "Epic door", + entityDescription = "epic description", + entityActions = [], + entityValue = Nothing, + entityHp = Nothing, + direction = North + } + ] } \ No newline at end of file diff --git a/lib/RPGEngine/Data.hs b/lib/RPGEngine/Data.hs index 01efa75..534c36b 100644 --- a/lib/RPGEngine/Data.hs +++ b/lib/RPGEngine/Data.hs @@ -12,7 +12,7 @@ import RPGEngine.Render.Core ( Renderer ) -- A game is the base data container. data Game = Game { state :: State -} +} deriving (Eq, Show) ------------------------------- State -------------------------------- @@ -33,6 +33,7 @@ data State = Menu | Win -- Lost a level | Lose { restart :: State } + deriving (Eq, Show) ------------------------------- Level -------------------------------- diff --git a/lib/RPGEngine/Data/Default.hs b/lib/RPGEngine/Data/Default.hs index f877f7f..c2e2814 100644 --- a/lib/RPGEngine/Data/Default.hs +++ b/lib/RPGEngine/Data/Default.hs @@ -64,9 +64,30 @@ defaultLevel = Level { defaultPlayer :: Player defaultPlayer = Player { - playerHp = Prelude.Nothing, -- Compares to infinity - inventory = [], - position = (0, 0) + -- playerHp = Prelude.Nothing, -- Compares to infinity + playerHp = Just 50, + inventory = [ Item{ + itemId = "key", + itemX = 0, + itemY = 0, + itemName = "Epic key", + itemDescription = "MyKey", + itemActions = [], + itemValue = Nothing, + useTimes = Nothing + }, Item{ + itemId = "dagger", + itemX = 0, + itemY = 0, + itemName = "My dagger", + itemDescription = "dagger", + itemActions = [], + itemValue = Nothing, + useTimes = Nothing + }], + position = (0, 0), + showInventory = False, + showHp = True } defaultSelector :: ListSelector diff --git a/lib/RPGEngine/Input/Core.hs b/lib/RPGEngine/Input/Core.hs index 07ea182..467e149 100644 --- a/lib/RPGEngine/Input/Core.hs +++ b/lib/RPGEngine/Input/Core.hs @@ -20,7 +20,7 @@ type InputHandler a = Event -> (a -> a) data ListSelector = ListSelector { selection :: Int, selected :: Bool -} +} deriving (Eq, Show) ------------------------------ Exported ------------------------------ diff --git a/lib/RPGEngine/Input/Playing.hs b/lib/RPGEngine/Input/Playing.hs index 3068d24..9703560 100644 --- a/lib/RPGEngine/Input/Playing.hs +++ b/lib/RPGEngine/Input/Playing.hs @@ -2,6 +2,7 @@ module RPGEngine.Input.Playing ( handleInputPlaying , checkPlaying , spawnPlayer +, putCoords ) where import RPGEngine.Input.Core (InputHandler, handle, handleKey, composeInputHandlers) @@ -32,6 +33,8 @@ handleInputPlaying = composeInputHandlers [ handleKey (Char 's') Down $ movePlayer South, handleKey (Char 'a') Down $ movePlayer West, + handleKey (Char 'r') Down restartGame, + handleKey (Char 'i') Down $ toggleInventoryShown True, handleKey (Char 'i') Up $ toggleInventoryShown False ] @@ -58,13 +61,15 @@ pauseGame g@Game{ state = playing@Playing{} } = pausedGame where pausedGame = g{ state = Paused playing } pauseGame g = g +restartGame :: Game -> Game +restartGame g@Game{ state = playing@Playing{ restart = restarted } } = g{ state = restarted } + -- Go to next level if there is a next level, otherwise, initialize win state. goToNextLevel :: State -> State goToNextLevel s@Playing{ levels = levels, level = current, count = count, player = player } = nextState - where -- Either the next level or winState - nextState | (count + 1) < length levels = nextLevelState + where nextState | (count + 1) < length levels = nextLevelState | otherwise = Win - nextLevelState = s{ level = nextLevel, count = count + 1, player = movedPlayer } + nextLevelState = s{ level = nextLevel, count = count + 1, player = movedPlayer, restart = nextLevelState } nextLevel = levels !! (count + 1) movedPlayer = spawnPlayer nextLevel player goToNextLevel s = s diff --git a/lib/RPGEngine/Parse.hs b/lib/RPGEngine/Parse.hs index c12e8da..c63afd3 100644 --- a/lib/RPGEngine/Parse.hs +++ b/lib/RPGEngine/Parse.hs @@ -6,13 +6,11 @@ import RPGEngine.Data ( Game ) import RPGEngine.Parse.StructureToGame ( structureToGame ) import GHC.IO (unsafePerformIO) import Text.Parsec.String (parseFromFile) -import RPGEngine.Parse.TextToStructure (structure) +import RPGEngine.Parse.TextToStructure ( gameFile ) ------------------------------ Exported ------------------------------ parse :: FilePath -> Game parse filename = structureToGame struct where (Right struct) = unsafePerformIO io - io = parseFromFile structure filename - -tempParse = parseFromFile \ No newline at end of file + io = parseFromFile gameFile filename \ No newline at end of file diff --git a/lib/RPGEngine/Parse/StructureToGame.hs b/lib/RPGEngine/Parse/StructureToGame.hs index 7e81274..09e9e83 100644 --- a/lib/RPGEngine/Parse/StructureToGame.hs +++ b/lib/RPGEngine/Parse/StructureToGame.hs @@ -10,26 +10,25 @@ import RPGEngine.Data entityActions, entityValue, entityHp, direction), Item(itemId, itemX, itemY, itemName, itemDescription, itemValue, itemActions, useTimes), - Level(layout, items, entities), + Level(layout, items, entities, index), Game (..), State (..) ) import RPGEngine.Parse.TextToStructure ( Value(Infinite, Action, Layout, String, Direction, Integer), Key(Tag, ConditionList), Structure(..) ) import RPGEngine.Data.Default (defaultPlayer, defaultLevel, defaultItem, defaultEntity) +import RPGEngine.Input.Playing (putCoords, spawnPlayer) ------------------------------ Exported ------------------------------ -structureToGame :: Structure -> Game --- structureToGame [Entry(Tag "player") playerBlock, Entry(Tag "levels") levelsBlock] = game -structureToGame (Entry (Tag "player") playerBlock) = game - where game = Game{ state = newState } - newState = Playing{ levels = newLevels, level = currentLevel, player = newPlayer, restart = newState } - -- newLevels = structureToLevels levelsBlock - -- currentLevel = head newLevels - newLevels = [defaultLevel] - currentLevel = defaultLevel - newPlayer = structureToPlayer playerBlock +structureToGame :: [Structure] -> Game +structureToGame [Entry (Tag "player") playerBlock, Entry (Tag "levels") levelsBlock] = game + where game = Game newState + newState = Playing newLevels 0 currentLevel newPlayer newState + newLevels = structureToLevels levelsBlock + currentLevel = head newLevels + newPlayer = spawnPlayer currentLevel $ structureToPlayer playerBlock +structureToGame _ = Game Menu ------------------------------- Player ------------------------------- @@ -60,7 +59,9 @@ structureToLevels (Block struct) = structureToLevel <$> struct structureToLevels _ = [defaultLevel] structureToLevel :: Structure -> Level -structureToLevel (Block entries) = structureToLevel' entries defaultLevel +structureToLevel (Block entries) = indexIsSet + where indexIsSet = level{ index = putCoords level } + level = structureToLevel' entries defaultLevel structureToLevel _ = defaultLevel structureToLevel' :: [Structure] -> Level -> Level diff --git a/lib/RPGEngine/Parse/TextToStructure.hs b/lib/RPGEngine/Parse/TextToStructure.hs index dc76003..fa2486e 100644 --- a/lib/RPGEngine/Parse/TextToStructure.hs +++ b/lib/RPGEngine/Parse/TextToStructure.hs @@ -18,9 +18,13 @@ import Text.Parsec notFollowedBy, sepBy, many, - try ) + try, spaces, endOfLine ) import qualified Text.Parsec as P ( string ) import Text.Parsec.String ( Parser ) +import Text.Parsec.Combinator (lookAhead) + +gameFile :: Parser [Structure] +gameFile = try $ do many1 $ ignoreWS structure -------------------------- StructureElement -------------------------- @@ -111,7 +115,7 @@ data Value = String String ---------------------------------------------------------------------- value :: Parser Value -value = choice [layout, string, integer, infinite, action, direction] +value = choice [layout, string, integer, infinite, direction, action] string :: Parser Value string = try $ String <$> between (char '\"') (char '\"') reading @@ -149,7 +153,7 @@ direction = try $ do ignoreWS $ P.string "left", ignoreWS $ P.string "right" ] - notFollowedBy alphaNum + -- lookAhead $ char ',' return $ Direction $ make value where make "up" = North make "right" = East @@ -160,15 +164,12 @@ direction = try $ do layout :: Parser Value layout = try $ do open <- ignoreWS $ oneOf openingBrackets - ignoreWS $ char '|' - list <- ignoreWS $ ignoreWS strip `sepBy` ignoreWS (char '|') let closing = getMatchingClosingBracket open - ignoreWS $ char closing - return $ Layout list + value <- many1 strip <* ignoreWS (char closing) + return $ Layout value strip :: Parser Strip -strip = try $ do - physical `sepBy` char ' ' +strip = try $ do ignoreWS (char '|') *> ignoreWS (physical `sepBy` char ' ') physical :: Parser Physical physical = try $ do diff --git a/rpg-engine.cabal b/rpg-engine.cabal index 76a2eee..3323cdb 100644 --- a/rpg-engine.cabal +++ b/rpg-engine.cabal @@ -55,7 +55,11 @@ test-suite rpg-engine-test main-is: Spec.hs hs-source-dirs: test default-language: Haskell2010 - build-depends: base >=4.7 && <5, hspec <= 2.10.6, hspec-discover, rpg-engine + build-depends: + base >=4.7 && <5, + rpg-engine, + hspec <= 2.10.6, hspec-discover, + parsec >= 3.1.15.1 other-modules: Parser.GameSpec - Parser.StructureSpec + Parser.StructureSpec \ No newline at end of file diff --git a/test/Parser/GameSpec.hs b/test/Parser/GameSpec.hs index 1f167a3..b2b7371 100644 --- a/test/Parser/GameSpec.hs +++ b/test/Parser/GameSpec.hs @@ -6,24 +6,50 @@ import RPGEngine.Data import RPGEngine.Parse.Core import RPGEngine.Parse.TextToStructure import RPGEngine.Parse.StructureToGame +import RPGEngine.Parse.TextToStructure (gameFile) spec :: Spec spec = do describe "Game" $ do - it "TODO: Simple game" $ do - pending - it "TODO: More complex game" $ do - pending - it "TODO: Game with multiple levels" $ do - pending + -- TODO There is a weird bug that caused this to go in an infinite loop. Fix later. + xit "Simple game" $ do + let input = "player: {\n hp: 50,\n inventory: []\n}\n\nlevels: [\n {\n layout: {\n | * * * * * *\n | * s . . e *\n | * * * * * *\n },\n \n items: [],\n\n entities: []\n\n\n }\n]" + correct = Game { + state = Playing { + levels = [], + count = 0, + level = Level { + RPGEngine.Data.layout = [], + index = [], + items = [], + entities = [] + }, + player = Player { + playerHp = Just 50, + inventory = [], + position = (0, 0), + showHp = True, + showInventory = False + }, + restart = Menu + } + } + (Right struct) = parseWith gameFile input + structureToGame struct `shouldBe` correct + it "More complex game" $ do + pendingWith "fix parsing first" + it "Game with multiple levels" $ do + pendingWith "fix parsing first" describe "Player" $ do it "cannot die" $ do let input = "player: { hp: infinite, inventory: [] }" correct = Player { - playerHp = Prelude.Nothing, - inventory = [], - position = (0, 0) + playerHp = Prelude.Nothing, + inventory = [], + position = (0, 0), + showHp = True, + showInventory = False } Right (Entry (Tag "player") struct) = parseWith structure input structureToPlayer struct `shouldBe` correct @@ -31,9 +57,11 @@ spec = do it "without inventory" $ do let input = "player: { hp: 50, inventory: [] }" correct = Player { - playerHp = Just 50, - inventory = [], - position = (0, 0) + playerHp = Just 50, + inventory = [], + position = (0, 0), + showHp = True, + showInventory = False } Right (Entry (Tag "player") struct) = parseWith structure input structureToPlayer struct `shouldBe` correct @@ -54,14 +82,12 @@ spec = do useTimes = Prelude.Nothing } ], - position = (0, 0) + position = (0, 0), + showHp = True, + showInventory = False } Right (Entry (Tag "player") struct) = parseWith structure input structureToPlayer struct `shouldBe` correct - - describe "Layout" $ do - it "simple" $ do - pending describe "Items" $ do it "simple" $ do @@ -117,23 +143,40 @@ spec = do structureToActions struct `shouldBe` correct describe "Entities" $ do - it "TODO: Simple entity" $ do - pending + it "Simple entity" $ do + pendingWith "fix parsing first" describe "Level" $ do it "Simple layout" $ do - let input = "{ layout: { | * * * * * * \n| * s . . e *\n| * * * * * * }, items: [], entities: [] }" + let input = "{ layout: { | * * * * * *\n| * s . . e *\n| * * * * * *\n }, items: [], entities: [] }" correct = Level { RPGEngine.Data.layout = [ [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked], [Blocked, Entrance, Walkable, Walkable, Exit, Blocked], [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked] ], + index = [ + (0, 0, Blocked), + (1, 0, Blocked), + (2, 0, Blocked), + (3, 0, Blocked), + (4, 0, Blocked), + (5, 0, Blocked), + (0, 1, Blocked), + (1, 1, Entrance), + (2, 1, Walkable), + (3, 1, Walkable), + (4, 1, Exit), + (5, 1, Blocked), + (0, 2, Blocked), + (1, 2, Blocked), + (2, 2, Blocked), + (3, 2, Blocked), + (4, 2, Blocked), + (5, 2, Blocked) + ], items = [], entities = [] } Right struct = parseWith structure input - structureToLevel struct `shouldBe` correct - - it "TODO: Complex layout" $ do - pending \ No newline at end of file + structureToLevel struct `shouldBe` correct \ No newline at end of file diff --git a/test/Parser/StructureSpec.hs b/test/Parser/StructureSpec.hs index e9296b8..b084a02 100644 --- a/test/Parser/StructureSpec.hs +++ b/test/Parser/StructureSpec.hs @@ -5,6 +5,8 @@ import Test.Hspec import RPGEngine.Data import RPGEngine.Parse.Core import RPGEngine.Parse.TextToStructure +import Text.Parsec.String (parseFromFile) +import GHC.IO (unsafePerformIO) spec :: Spec spec = do @@ -68,7 +70,7 @@ spec = do ]], "") parseWithRest structure input `shouldBe` correct - let input = "entities: [ { id: \"door\", x: 4, y: 1, name:\"Secret door\", description: \"This secret door can only be opened with a key\", direction: left , actions: { [inventoryContains(key)] useItem(key), [] leave() } } ]" + let input = "entities: [ { id: \"door\", x: 4, y: 1, name:\"Secret door\", description: \"This secret door can only be opened with a key\", direction: left, actions: { [inventoryContains(key)] useItem(key), [] leave() } } ]" correct = Right (Entry (Tag "entities") $ Block [ Block [ Entry (Tag "id") $ Regular $ String "door", Entry (Tag "x") $ Regular $ Integer 4, @@ -83,6 +85,17 @@ spec = do ]], "") parseWithRest structure input `shouldBe` correct + it "combines actions and direction" $ do + let input = "entities: [ { direction: left, actions: { [inventoryContains(key)] useItem(key), [] leave() } } ]" + correct = Right (Entry (Tag "entities") $ Block [ Block [ + Entry (Tag "direction") $ Regular $ Direction West, + Entry (Tag "actions") $ Block [ + Entry (ConditionList [InventoryContains "key"]) $ Regular $ Action $ UseItem "key", + Entry (ConditionList []) $ Regular $ Action Leave + ] + ]], "") + parseWithRest structure input `shouldBe` correct + it "can parse entries" $ do let input = "id: \"dagger\"" correct = Right $ Entry (Tag "id") $ Regular $ String "dagger" @@ -252,22 +265,21 @@ spec = do parseWith RPGEngine.Parse.TextToStructure.direction input `shouldBe` correct it "can parse layouts" $ do - let input = "| * * * * * * * *\n| * s . . . . e *\n| * * * * * * * *" + let input = "{ | * * * * * * * *\n | * s . . . . e *\n | * * * * * * * *\n }" correct = Right $ Layout [ [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked], [Blocked, Entrance, Walkable, Walkable, Walkable, Walkable, Exit, Blocked], [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked] ] - parseWith RPGEngine.Parse.TextToStructure.layout input `shouldBe` correct + parseWith value input `shouldBe` correct - let input = "{ |* * * * * * * *|* s . . . . e *|* * * * * * * * }" - -- correct = Right $ Entry (Tag "layout") $ Regular $ Layout [ - correct = Right $ Layout [ + let input = "layout: { | * * * * * * * *\n | * s . . . . e *\n | * * * * * * * *\n }" + correct = Right $ Entry (Tag "layout") $ Regular $ Layout [ [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked], [Blocked, Entrance, Walkable, Walkable, Walkable, Walkable, Exit, Blocked], [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked] ] - parseWith RPGEngine.Parse.TextToStructure.value input `shouldBe` correct + parseWith structure input `shouldBe` correct describe "Brackets" $ do it "matches closing <" $ do @@ -289,3 +301,75 @@ spec = do let input = '[' correct = ']' getMatchingClosingBracket input `shouldBe` correct + + describe "Full game file" $ do + it "single level" $ do + let input = "player: {\n hp: 50,\n inventory: []\n}\n\nlevels: [\n {\n layout: {\n | * * * * * *\n | * s . . e *\n | * * * * * *\n },\n \n items: [],\n\n entities: []\n\n\n }\n]" + correct = Right [ + Entry (Tag "player") $ Block [ + Entry (Tag "hp") $ Regular $ Integer 50, + Entry (Tag "inventory") $ Block [] + ], + Entry (Tag "levels") $ Block [ Block [ + Entry (Tag "layout") $ Regular $ Layout [ + [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked], + [Blocked, Entrance, Walkable, Walkable, Exit, Blocked], + [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked] + ], + Entry (Tag "items") $ Block [], + Entry (Tag "entities") $ Block [] + ]] + ] + parseWith gameFile input `shouldBe` correct + + it "two levels" $ do + let input = "player: {\n hp: 50,\n inventory: []\n}\n\nlevels: [\n {\n layout: {\n | * * * * * *\n | * s . . e *\n | * * * * * *\n },\n \n items: [],\n\n entities: []\n },\n {\n layout: {\n | * * *\n | * e *\n | * . *\n | * . *\n | * . *\n | * . *\n | * s *\n | * * *\n },\n\n items: [],\n\n entities: []\n }\n]" + correct = Right [ + Entry (Tag "player") $ Block [ + Entry (Tag "hp") $ Regular $ Integer 50, + Entry (Tag "inventory") $ Block [] + ], + Entry (Tag "levels") $ Block [ + Block [ + Entry (Tag "layout") $ Regular $ Layout [ + [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked], + [Blocked, Entrance, Walkable, Walkable, Exit, Blocked], + [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked] + ], + Entry (Tag "items") $ Block [], + Entry (Tag "entities") $ Block [] + ], Block [ + Entry (Tag "layout") $ Regular $ Layout [ + [Blocked,Blocked,Blocked], + [Blocked,Exit,Blocked], + [Blocked,Walkable,Blocked], + [Blocked,Walkable,Blocked], + [Blocked,Walkable,Blocked], + [Blocked,Walkable,Blocked], + [Blocked,Entrance,Blocked], + [Blocked,Blocked,Blocked] + ], + Entry (Tag "items") $ Block [], + Entry (Tag "entities") $ Block [] + ] + ] + ] + parseWith gameFile input `shouldBe` correct + + it "from file" $ do + let correct = Right [ + Entry (Tag "player") $ Block [ + Entry (Tag "hp") $ Regular $ Integer 50, + Entry (Tag "inventory") $ Block [] + ], + Entry (Tag "levels") $ Block [ Block [ + Entry (Tag "layout") $ Regular $ Layout [ + [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked], + [Blocked, Entrance, Walkable, Walkable, Exit, Blocked], + [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked] + ], + Entry (Tag "items") $ Block [], + Entry (Tag "entities") $ Block [] + ]] + ] + unsafePerformIO (parseFromFile gameFile "levels/level1.txt") `shouldBe` correct \ No newline at end of file diff --git a/test/Spec.hs b/test/Spec.hs index 52ef578..bf4362e 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -1 +1,18 @@ -{-# OPTIONS_GHC -F -pgmF hspec-discover #-} \ No newline at end of file +{-# OPTIONS_GHC -F -pgmF hspec-discover #-} + +-------------------------- How to use Hspec -------------------------- + +-- If a test has not yet been written: +-- Use `pending` or `pendingWith`. +-- it "Description" $ do +-- pendingWith "Reason" + +-- Temporarily disable running a test: +-- Replace `it` with `xit` +-- xit "Description" $ do ... + +-- Temporarily only run a specific test: +-- Put `focus` in front. +-- it "Description" $ do ... +-- becomes +-- focus $ it "Description" $ do ... \ No newline at end of file From ef784c2dbc9babf8a113c2d90734c4eefaf9f50a Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Thu, 22 Dec 2022 23:06:59 +0100 Subject: [PATCH 26/34] Fix rendering issue --- assets/entities/devil.png | Bin 0 -> 1548 bytes assets/items/potion.png | Bin 0 -> 1548 bytes assets/items/sword.png | Bin 0 -> 247 bytes levels/level3.txt | 12 ++++++------ levels/level4.txt | 20 ++++++++++---------- lib/RPGEngine/Input/Playing.hs | 2 +- lib/RPGEngine/Render/Core.hs | 7 +++++-- lib/RPGEngine/Render/Playing.hs | 2 +- test/Parser/GameSpec.hs | 10 +++++----- 9 files changed, 28 insertions(+), 25 deletions(-) create mode 100644 assets/entities/devil.png create mode 100644 assets/items/potion.png create mode 100644 assets/items/sword.png diff --git a/assets/entities/devil.png b/assets/entities/devil.png new file mode 100644 index 0000000000000000000000000000000000000000..1ab3ef5e835c0ab5e02a4d93a4f4271c14cef8c3 GIT binary patch literal 1548 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4(FKU~I{Bb`J1#c2+1T%1_J8No8Qr zm{>c}+T(D5NZbEqUF*A=U0r01LezJ!=ty4cB&gLAwJ?-@^6Y&JJe?;!dZe*KOr}OB zOg3gtX=#Bd%R%;z#giv5>Yh@O7nC9Rxb^VgAMgM2KmR8#@G5m#q^zOCm41p% zm}P$9wNs(j=1ksiFy(Uiw4HvYT`Xc7Svrk^0$e#$Z**K*H#tes^?9NE|BlzwPb{6V zn#HY;HDrNf+205CAFQLb?M+{navg0vCw}~=1oN)4Mz^zt`}$><7c#NTZ`9uVbp4ga zoBo%wb#;qacygG?NMQuI$ga#rQ?o4*iNVU|?X8_H=O!u@GbwV0r%=sDcJSRaojj*~Wr2C#}BnA4J12 zKEqgWNv=4wjbZ9FsC0%K*hRPBK4U~z4;KL022%iH+s@dQTt zOdtkK1I)?DnnCg~gFtL_0QUk|5ztl`h_Lg5G2t}GU10c@Hc}+T(D5NZbEqUF*A=U0r01LezJ!=ty4cB&gLAwJ?-@^6Y&JJe?;!dZe*KOr}OB zOg3gtX=#Bd%R%;z#giv5>Yh@O7nC9Rxb^VgAMgM2KmR8#@G5m#q^zOCm41p% zm}P$9wNs(j=1ksiFy(Uiw4HvYT`Xc7Svrk^0$e#$Z**K*H#tes^?9NE|BlzwPb{6V zn#HY;HDrNf+205CAFQLb?M+{navg0vCw}~=1oN)4Mz^zt`}$><7c#NTZ`9uVbp4ga zoBo%wb#;qacygG?NMQuI$ga#rOrRI)6Uj%)r1R?djqeVj;*V!1De#P{ptYs=`wLK^UJWS#U{$G|q}O z21}m?K4JfYOxA}G%vk;8?65E?OR~FBFLKon;}ftOa+NW>#8y!YsRGxrvV@r z{1OlcW0-5O8G>N|%=vd;e}t=_ar8a|$PjD+iV_Csns?uN2R8)7$goxfdkr}(FkArB zfvy=O4hs{Q8W4@u0A*2rWHn&6w;1+hkKzT8N|-mW*@i7DF${ni0=5>7K=wPD)BtiT WC-|!PZafnXa)PI;pUXO@geCxxa~uZ% literal 0 HcmV?d00001 diff --git a/assets/items/sword.png b/assets/items/sword.png new file mode 100644 index 0000000000000000000000000000000000000000..ba6438963489b3d5e2cb53c187f28576e595b3c5 GIT binary patch literal 247 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1quc!5&W+#}Etus}oQ2HYf(IiZ}nK}B_nFTJXTiq6$e zny_`7HbXCq;jx0s5QZ+67J(f+g6AH2$m=b5vvm3bW^hh?Ow?e7D|vdw7Zz&|M6ku6{1-oD!M [(X, Y, Physical)] putCoords l@Level{ layout = lay } = concatMap (\(a, bs) -> map (\(b, c) -> (b, a, c)) bs) numberedList - where numberedStrips = zip [0::Int .. ] lay + where numberedStrips = reverse $ zip [0::Int .. ] $ reverse lay numberedList = map (\(x, strip) -> (x, zip [0::Int ..] strip)) numberedStrips \ No newline at end of file diff --git a/lib/RPGEngine/Render/Core.hs b/lib/RPGEngine/Render/Core.hs index 0c6754c..b04c9fe 100644 --- a/lib/RPGEngine/Render/Core.hs +++ b/lib/RPGEngine/Render/Core.hs @@ -23,7 +23,8 @@ unknownImage = "unknown.png" allEntities :: [(String, FilePath)] allEntities = [ ("player", "player.png"), - ("door", "door.png") + ("devil", "devil.png" ), + ("door", "door.png") ] allEnvironment :: [(String, FilePath)] @@ -39,7 +40,9 @@ allEnvironment = [ allItems :: [(String, FilePath)] allItems = [ ("dagger", "dagger.png"), - ("key", "key.png" ) + ("key", "key.png" ), + ("potion", "potion.png"), + ("sword", "sword.png" ) ] allGui :: [(String, FilePath)] diff --git a/lib/RPGEngine/Render/Playing.hs b/lib/RPGEngine/Render/Playing.hs index 6a2f589..f902178 100644 --- a/lib/RPGEngine/Render/Playing.hs +++ b/lib/RPGEngine/Render/Playing.hs @@ -48,7 +48,7 @@ renderLevel Level{ layout = l, items = i, entities = e } = level entities = renderEntities e renderLayout :: Layout -> Picture -renderLayout strips = pictures [setRenderPos 0 y (renderStrip (strips !! y)) | y <- [0 .. count]] +renderLayout strips = pictures [setRenderPos 0 (count - y) (renderStrip (strips !! y)) | y <- [0 .. count]] where count = length strips - 1 renderStrip :: [Physical] -> Picture diff --git a/test/Parser/GameSpec.hs b/test/Parser/GameSpec.hs index b2b7371..c441a22 100644 --- a/test/Parser/GameSpec.hs +++ b/test/Parser/GameSpec.hs @@ -11,8 +11,8 @@ import RPGEngine.Parse.TextToStructure (gameFile) spec :: Spec spec = do describe "Game" $ do - -- TODO There is a weird bug that caused this to go in an infinite loop. Fix later. - xit "Simple game" $ do + it "Simple game" $ do + pendingWith "There is a weird bug that caused this to go in an infinite loop. Fix later." let input = "player: {\n hp: 50,\n inventory: []\n}\n\nlevels: [\n {\n layout: {\n | * * * * * *\n | * s . . e *\n | * * * * * *\n },\n \n items: [],\n\n entities: []\n\n\n }\n]" correct = Game { state = Playing { @@ -37,9 +37,9 @@ spec = do (Right struct) = parseWith gameFile input structureToGame struct `shouldBe` correct it "More complex game" $ do - pendingWith "fix parsing first" + pendingWith "Still need to write this" it "Game with multiple levels" $ do - pendingWith "fix parsing first" + pendingWith "Still need to write this" describe "Player" $ do it "cannot die" $ do @@ -144,7 +144,7 @@ spec = do describe "Entities" $ do it "Simple entity" $ do - pendingWith "fix parsing first" + pendingWith "still need to write this" describe "Level" $ do it "Simple layout" $ do From 9addf1ed07f0d0fdeec44b8c6637b64b06bd07be Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Fri, 23 Dec 2022 09:42:34 +0100 Subject: [PATCH 27/34] #4 Setup interaction --- README.md | 3 +- lib/RPGEngine/Data.hs | 26 +++++--- lib/RPGEngine/Data/Level.hs | 41 ++++++++++++- lib/RPGEngine/Input.hs | 5 +- lib/RPGEngine/Input/ActionSelection.hs | 76 ++++++++++++++++++++++++ lib/RPGEngine/Input/LevelSelection.hs | 9 +-- lib/RPGEngine/Input/Playing.hs | 55 +++++++++++++---- lib/RPGEngine/Render.hs | 8 ++- lib/RPGEngine/Render/ActionSelection.hs | 26 ++++++++ lib/RPGEngine/Render/LevelSelection.hs | 2 +- lib/RPGEngine/Render/Playing.hs | 3 +- rpg-engine.cabal | 2 + verslag.pdf | Bin 50036 -> 50213 bytes 13 files changed, 223 insertions(+), 33 deletions(-) create mode 100644 lib/RPGEngine/Input/ActionSelection.hs create mode 100644 lib/RPGEngine/Render/ActionSelection.hs diff --git a/README.md b/README.md index 455e7af..51913b2 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,8 @@ These are the keybinds *in* the game. All other keybinds in the menus should be | Move left | `Arrow Left` | `a` | | Move down | `Arrow Down` | `s` | | Move right | `Arrow Right` | `d` | -| Show inventory | `i` | | +| Interaction | `Space` | `f` | +| Show inventory | `i` | `Tab` | | Restart level | `r` | | | Quit game | `Esc` | | diff --git a/lib/RPGEngine/Data.hs b/lib/RPGEngine/Data.hs index 534c36b..46c65d2 100644 --- a/lib/RPGEngine/Data.hs +++ b/lib/RPGEngine/Data.hs @@ -19,22 +19,30 @@ data Game = Game { -- Main menu data State = Menu -- Select the level you want to play - | LevelSelection { levelList :: [FilePath], - selector :: ListSelector } + | LevelSelection { levelList :: [FilePath], + selector :: ListSelector } -- Playing a level - | Playing { levels :: [Level], - count :: Int, - level :: Level, - player :: Player, - restart :: State } + | Playing { levels :: [Level], + count :: Int, + level :: Level, + player :: Player, + restart :: State } + -- Selecting an action + | ActionSelection { actionList :: [Action], + selector :: ListSelector, + -- The player of this state will be used to interact + continue :: State } -- Paused while playing a level - | Paused { continue :: State } + | Paused { continue :: State } -- Won a level | Win -- Lost a level - | Lose { restart :: State } + | Lose { restart :: State } + | Error Message deriving (Eq, Show) +type Message = String + ------------------------------- Level -------------------------------- data Level = Level { diff --git a/lib/RPGEngine/Data/Level.hs b/lib/RPGEngine/Data/Level.hs index f41a0c1..f415844 100644 --- a/lib/RPGEngine/Data/Level.hs +++ b/lib/RPGEngine/Data/Level.hs @@ -5,8 +5,9 @@ where import GHC.IO (unsafePerformIO) import System.Directory (getDirectoryContents) import RPGEngine.Input.Core (ListSelector(..)) -import RPGEngine.Data (Level (..), Physical (..), Direction (..), Entity (..), Game (..), Item (..), Player (..), State (..), X, Y, Layout) +import RPGEngine.Data (Action(..), Level (..), Physical (..), Direction (..), Entity (..), Game (..), Item (..), Player (..), State (..), X, Y, Layout, Condition (InventoryFull, InventoryContains, Not, AlwaysFalse)) import RPGEngine.Config (levelFolder) +import Data.Foldable (find) ------------------------------ Exported ------------------------------ @@ -25,6 +26,17 @@ findAt pos lvl@Level{ index = index } = try try | not (null matches) = head matches | otherwise = Void +hasAt :: (X, Y) -> Level -> Maybe (Either Item Entity) +hasAt pos level = match firstItem firstEntity + where match :: Maybe Item -> Maybe Entity -> Maybe (Either Item Entity) + match (Just a) _ = Just $ Left a + match _ (Just a) = Just $ Right a + match _ _ = Nothing + firstEntity = find ((== pos) . getECoord) $ entities level + getECoord e = (entityX e, entityY e) + firstItem = find ((== pos) . getICoord) $ items level + getICoord i = (itemX i, itemY i) + directionOffsets :: Direction -> (X, Y) directionOffsets North = ( 0, 1) directionOffsets East = ( 1, 0) @@ -33,4 +45,29 @@ directionOffsets West = (-1, 0) directionOffsets Stay = ( 0, 0) getLevelList :: [FilePath] -getLevelList = drop 2 $ unsafePerformIO $ getDirectoryContents levelFolder \ No newline at end of file +getLevelList = drop 2 $ unsafePerformIO $ getDirectoryContents levelFolder + +-- Get the actions of either an entity or an item +getActions :: Either Item Entity -> [([Condition], Action)] +getActions (Left item) = itemActions item +getActions (Right entity) = entityActions entity + +getActionText :: Action -> String +getActionText Leave = "Leave" +getActionText (RetrieveItem _) = "Pick up" +getActionText (UseItem _) = "Use item" +getActionText _ = "ERROR" + +-- TODO Check conditions +-- Filter based on the conditions, keep only the actions of which the +-- conditions are met. +filterActions :: [([Condition], Action)] -> [Action] +filterActions [] = [] +filterActions ((conditions, action):others) = action:filterActions others + +-- Check if a condition is met or not. +meetsCondition :: Condition -> Bool +meetsCondition InventoryFull = False -- TODO +meetsCondition (InventoryContains id) = True -- TODO +meetsCondition (Not condition) = not $ meetsCondition condition +meetsCondition AlwaysFalse = False \ No newline at end of file diff --git a/lib/RPGEngine/Input.hs b/lib/RPGEngine/Input.hs index 1314c9e..8fc594c 100644 --- a/lib/RPGEngine/Input.hs +++ b/lib/RPGEngine/Input.hs @@ -13,6 +13,7 @@ import RPGEngine.Input.Playing ( handleInputPlaying ) import RPGEngine.Input.Paused ( handleInputPaused ) import RPGEngine.Input.Win ( handleInputWin ) import RPGEngine.Input.Lose ( handleInputLose ) +import RPGEngine.Input.ActionSelection (handleInputActionSelection) ------------------------------ Exported ------------------------------ @@ -23,4 +24,6 @@ handleAllInput ev g@Game{ state = LevelSelection{} } = handleInputLevelSelection handleAllInput ev g@Game{ state = Playing{} } = handleInputPlaying ev g handleAllInput ev g@Game{ state = Paused{} } = handleInputPaused ev g handleAllInput ev g@Game{ state = Win } = handleInputWin ev g -handleAllInput ev g@Game{ state = Lose{} } = handleInputLose ev g \ No newline at end of file +handleAllInput ev g@Game{ state = Lose{} } = handleInputLose ev g +handleAllInput ev g@Game{ state = ActionSelection{}} = handleInputActionSelection ev g +handleAllInput ev g@Game{ state = Error _ } = handleAnyKey (\game -> game{ state = Menu}) ev g \ No newline at end of file diff --git a/lib/RPGEngine/Input/ActionSelection.hs b/lib/RPGEngine/Input/ActionSelection.hs new file mode 100644 index 0000000..99e6cfe --- /dev/null +++ b/lib/RPGEngine/Input/ActionSelection.hs @@ -0,0 +1,76 @@ +module RPGEngine.Input.ActionSelection +( handleInputActionSelection +) where + +import RPGEngine.Input.Core (InputHandler, handleKey, composeInputHandlers, ListSelector (selection)) + +import RPGEngine.Data (Game (..), State (..), Direction (..), Action (..), ItemId, EntityId) +import Graphics.Gloss.Interface.IO.Game (Key(SpecialKey), SpecialKey (KeyUp, KeyDown)) +import Graphics.Gloss.Interface.IO.Interact + ( SpecialKey(..), KeyState(..) ) + +------------------------------ Exported ------------------------------ + +handleInputActionSelection :: InputHandler Game +handleInputActionSelection = composeInputHandlers [ + handleKey (SpecialKey KeySpace) Down selectAction, + + handleKey (SpecialKey KeyUp) Down $ moveSelector North, + handleKey (SpecialKey KeyDown) Down $ moveSelector South + ] + +---------------------------------------------------------------------- + +selectAction :: Game -> Game +selectAction game@Game{ state = ActionSelection list selection continue } = newGame + where newGame = game{ state = execute selectedAction continue } + selectedAction = Leave +selectAction g = g + +-- TODO Lift this code from LevelSelection +-- Move the selector either up or down +moveSelector :: Direction -> Game -> Game +moveSelector dir game@Game{ state = state@(ActionSelection list selector _) } = newGame + where newGame = game{ state = newState } + newState = state{ selector = newSelector } + newSelector | constraint = selector{ selection = newSelection } + | otherwise = selector + constraint = 0 <= newSelection && newSelection < length list + newSelection = selection selector + diff + diff | dir == North = -1 + | dir == South = 1 + | otherwise = 0 +moveSelector _ g = g{ state = Error "Something went wrong while moving the selector up or down"} + +------------------------------ Actions ------------------------------- + +execute :: Action -> State -> State +execute (RetrieveItem id ) s = pickUpItem id s +execute (UseItem id ) s = useItem id s +execute (DecreaseHp eid iid) s = decreaseHp eid iid s +execute (IncreasePlayerHp iid) s = increasePlayerHp iid s +execute _ s = s + +-- Pick up the item with itemId and put it in the players inventory +pickUpItem :: ItemId -> State -> State +pickUpItem _ s = s -- TODO + +-- Use an item on the player +useItem :: ItemId -> State -> State -- TODO +useItem _ s = s -- TODO + +-- Attack an entity using an item +decreaseHp :: EntityId -> ItemId -> State -> State +decreaseHp _ _ s = s +-- TODO DecreaseHp of monster +-- TODO Check if monster is dead +-- TODO Entity attack player +-- TODO Decrease durability of item +-- TODO Break item if durability below zero + +-- Heal a bit +increasePlayerHp :: ItemId -> State -> State +increasePlayerHp _ s = s +-- TODO Increase playerHp +-- TODO Decrease durability of item +-- TODO Remove item if durability below zero \ No newline at end of file diff --git a/lib/RPGEngine/Input/LevelSelection.hs b/lib/RPGEngine/Input/LevelSelection.hs index 5266782..84420c0 100644 --- a/lib/RPGEngine/Input/LevelSelection.hs +++ b/lib/RPGEngine/Input/LevelSelection.hs @@ -24,14 +24,15 @@ handleInputLevelSelection = composeInputHandlers [ -- Select a level and load it in selectLevel :: Game -> Game -selectLevel game@Game{ state = LevelSelection{ levelList = list, selector = selector }} = newGame +selectLevel game@Game{ state = LevelSelection list selector } = newGame where newGame = parse $ levelFolder ++ (list !! index) index = selection selector -selectLevel g = g +selectLevel g = g{ state = Error "Something went wrong while selecting a level"} +-- TODO Lift this code from ActionSelection -- Move the selector either up or down moveSelector :: Direction -> Game -> Game -moveSelector dir game@Game{ state = state@LevelSelection{ levelList = list, selector = selector } } = newGame +moveSelector dir game@Game{ state = state@(LevelSelection list selector) } = newGame where newGame = game{ state = newState } newState = state{ selector = newSelector } newSelector | constraint = selector{ selection = newSelection } @@ -41,4 +42,4 @@ moveSelector dir game@Game{ state = state@LevelSelection{ levelList = list, sele diff | dir == North = -1 | dir == South = 1 | otherwise = 0 -moveSelector _ g = g \ No newline at end of file +moveSelector _ g = g{ state = Error "Something went wrong while moving the selector up or down"} \ No newline at end of file diff --git a/lib/RPGEngine/Input/Playing.hs b/lib/RPGEngine/Input/Playing.hs index cff2b86..f2c3f1a 100644 --- a/lib/RPGEngine/Input/Playing.hs +++ b/lib/RPGEngine/Input/Playing.hs @@ -5,15 +5,16 @@ module RPGEngine.Input.Playing , putCoords ) where -import RPGEngine.Input.Core (InputHandler, handle, handleKey, composeInputHandlers) +import RPGEngine.Input.Core (InputHandler, handle, handleKey, composeInputHandlers, ListSelector (..)) -import RPGEngine.Data (Game (..), Layout(..), Level(..), Physical(..), Player(..), State(..), X, Y, Direction (..)) +import RPGEngine.Data (Game (..), Layout(..), Level(..), Physical(..), Player(..), State(..), X, Y, Direction (..), Entity (..), Item (..)) import RPGEngine.Data.Game (isLegalMove, isPlayerDead, isPlayerAtExit) -import RPGEngine.Data.Level (directionOffsets, findFirstOf) +import RPGEngine.Data.Level (directionOffsets, findFirstOf, hasAt, filterActions, getActions) import Data.Maybe (fromJust, isNothing) import Graphics.Gloss.Interface.IO.Game (Key(..)) import Graphics.Gloss.Interface.IO.Interact (SpecialKey(..), KeyState(..), Event(..), KeyState(..)) +import Prelude hiding (interact) ------------------------------ Exported ------------------------------ @@ -28,15 +29,21 @@ handleInputPlaying = composeInputHandlers [ handleKey (SpecialKey KeyDown) Down $ movePlayer South, handleKey (SpecialKey KeyLeft) Down $ movePlayer West, - handleKey (Char 'w') Down $ movePlayer North, - handleKey (Char 'd') Down $ movePlayer East, - handleKey (Char 's') Down $ movePlayer South, - handleKey (Char 'a') Down $ movePlayer West, + handleKey (Char 'w') Down $ movePlayer North, + handleKey (Char 'd') Down $ movePlayer East, + handleKey (Char 's') Down $ movePlayer South, + handleKey (Char 'a') Down $ movePlayer West, - handleKey (Char 'r') Down restartGame, + -- Interaction with entities and items + handleKey (SpecialKey KeySpace) Down checkForInteraction, + handleKey (Char 'f') Down checkForInteraction, - handleKey (Char 'i') Down $ toggleInventoryShown True, - handleKey (Char 'i') Up $ toggleInventoryShown False + handleKey (Char 'i') Down $ toggleInventoryShown True, + handleKey (Char 'i') Up $ toggleInventoryShown False, + handleKey (SpecialKey KeyTab) Down $ toggleInventoryShown True, + handleKey (SpecialKey KeyTab) Up $ toggleInventoryShown False, + + handleKey (Char 'r') Down restartGame ] ---------------------------------------------------------------------- @@ -63,6 +70,7 @@ pauseGame g = g restartGame :: Game -> Game restartGame g@Game{ state = playing@Playing{ restart = restarted } } = g{ state = restarted } +restartGame g = g{ state = Error "something went wrong while restarting the level"} -- Go to next level if there is a next level, otherwise, initialize win state. goToNextLevel :: State -> State @@ -83,16 +91,39 @@ movePlayer dir g@Game{ state = s@Playing{ player = p@Player{ position = (x, y) } newCoord | isLegalMove dir g = (x + xD, y + yD) | otherwise = (x, y) (xD, yD) = directionOffsets dir -movePlayer _ g = g +movePlayer _ g = g{ state = Error "something went wrong while moving the player" } + +checkForInteraction :: Game -> Game +checkForInteraction g@Game{ state = Playing{ level = level, player = player }} = newGame + where newGame | canInteract = interact g + | otherwise = g + canInteract = not $ null $ hasAt pos level + pos = position player +checkForInteraction g = g{ state = Error "something went wrong while checking for entities to interact with" } + +interact :: Game -> Game +interact g@Game{ state = s@Playing{ level = level, player = player } } = g{ state = newState } + where newState = ActionSelection actionList selector continue + actionList = filterActions $ getActions $ fromJust $ hasAt pos level + selector = ListSelector 0 False + pos = position player + continue = s +interact g = g{ state = Error "something went wrong while interacting with object"} toggleInventoryShown :: Bool -> Game -> Game toggleInventoryShown shown g@Game{ state = s@Playing{ player = p }}= newGame where newGame = g{ state = newState } newState = s{ player = newPlayer } newPlayer = p{ showInventory = shown } +toggleInventoryShown _ g = g{ state = Error "something went wrong while working inventory" } -- Map all Physicals onto coordinates putCoords :: Level -> [(X, Y, Physical)] putCoords l@Level{ layout = lay } = concatMap (\(a, bs) -> map (\(b, c) -> (b, a, c)) bs) numberedList where numberedStrips = reverse $ zip [0::Int .. ] $ reverse lay - numberedList = map (\(x, strip) -> (x, zip [0::Int ..] strip)) numberedStrips \ No newline at end of file + numberedList = map (\(x, strip) -> (x, zip [0::Int ..] strip)) numberedStrips + +-- putCoords l = concatMap numberColumns intermediate +-- where numberColumns (rowIndex, numberedRow) = map (\(colIndex, cell) -> (colIndex, rowIndex, cell)) numberedRow +-- intermediate = zip [0 .. ] numberedRows +-- numberedRows = zip [0::X .. ] $ layout l \ No newline at end of file diff --git a/lib/RPGEngine/Render.hs b/lib/RPGEngine/Render.hs index a9c2e74..2c2e158 100644 --- a/lib/RPGEngine/Render.hs +++ b/lib/RPGEngine/Render.hs @@ -15,9 +15,11 @@ import RPGEngine.Render.Paused ( renderPaused ) import RPGEngine.Render.Win ( renderWin ) import RPGEngine.Render.Lose ( renderLose ) -import Graphics.Gloss (Display) +import Graphics.Gloss ( Display, text, color ) import Graphics.Gloss.Data.Picture (Picture, blank) import Graphics.Gloss.Data.Display (Display(..)) +import RPGEngine.Render.ActionSelection (renderActionSelection) +import RPGEngine.Config (textColor) ---------------------------------------------------------------------- @@ -32,4 +34,6 @@ render Game{ state = s@LevelSelection{} } = renderLevelSelection s render Game{ state = s@Playing{} } = renderPlaying s render Game{ state = s@Paused{} } = renderPaused s render Game{ state = s@Win } = renderWin s -render Game{ state = s@Lose{} } = renderLose s \ No newline at end of file +render Game{ state = s@Lose{} } = renderLose s +render Game{ state = s@ActionSelection{}} = renderActionSelection s +render Game{ state = Error message } = color textColor $ text message \ No newline at end of file diff --git a/lib/RPGEngine/Render/ActionSelection.hs b/lib/RPGEngine/Render/ActionSelection.hs new file mode 100644 index 0000000..164719e --- /dev/null +++ b/lib/RPGEngine/Render/ActionSelection.hs @@ -0,0 +1,26 @@ +module RPGEngine.Render.ActionSelection +( renderActionSelection +) where + +import RPGEngine.Data (State (..), Action (..)) +import Graphics.Gloss + ( Picture, text, pictures, translate, scale, color ) +import Graphics.Gloss.Data.Picture (blank) +import RPGEngine.Data.Level (getActionText) +import RPGEngine.Config (uizoom, selectionColor, textColor) +import RPGEngine.Input.Core (ListSelector(selection)) +import RPGEngine.Render.Playing (renderPlaying) +import RPGEngine.Render.Core (overlay) + +------------------------------ Exported ------------------------------ + +renderActionSelection :: State -> Picture +renderActionSelection (ActionSelection list selector continue) = everything + where numberedTexts = zip [0::Int ..] $ map getActionText list + sel = selection selector + everything = pictures $ [renderPlaying continue, overlay] ++ map render numberedTexts + render (i, t) | i == sel = color selectionColor $ make (i, t) + | otherwise = color textColor $ make (i, t) + make (i, t) = scale uizoom uizoom $ translate 0 (offset i) $ text t + offset i = negate (250 * uizoom * fromIntegral i) +renderActionSelection _ = blank \ No newline at end of file diff --git a/lib/RPGEngine/Render/LevelSelection.hs b/lib/RPGEngine/Render/LevelSelection.hs index 082a8a1..e952b17 100644 --- a/lib/RPGEngine/Render/LevelSelection.hs +++ b/lib/RPGEngine/Render/LevelSelection.hs @@ -21,7 +21,7 @@ renderLevelSelection state = result ---------------------------------------------------------------------- renderLevelList :: Renderer State -renderLevelList LevelSelection{ levelList = list, selector = selector } = everything +renderLevelList (LevelSelection list selector) = everything where everything = pictures $ map render entries sel = selection selector entries = zip [0::Int .. ] list diff --git a/lib/RPGEngine/Render/Playing.hs b/lib/RPGEngine/Render/Playing.hs index f902178..98252c2 100644 --- a/lib/RPGEngine/Render/Playing.hs +++ b/lib/RPGEngine/Render/Playing.hs @@ -42,7 +42,8 @@ focusPlayer _ = id renderLevel :: Renderer Level renderLevel Level{ layout = l, items = i, entities = e } = level where level = pictures [void, layout, items, entities] - void = createVoid + -- void = createVoid + void = blank layout = renderLayout l items = renderItems i entities = renderEntities e diff --git a/rpg-engine.cabal b/rpg-engine.cabal index 3323cdb..bfd1afa 100644 --- a/rpg-engine.cabal +++ b/rpg-engine.cabal @@ -23,6 +23,7 @@ library RPGEngine.Input RPGEngine.Input.Core + RPGEngine.Input.ActionSelection RPGEngine.Input.Menu RPGEngine.Input.LevelSelection RPGEngine.Input.Playing @@ -37,6 +38,7 @@ library RPGEngine.Render RPGEngine.Render.Core + RPGEngine.Render.ActionSelection RPGEngine.Render.Menu RPGEngine.Render.LevelSelection RPGEngine.Render.Playing diff --git a/verslag.pdf b/verslag.pdf index c974837aa88e97c54e182754cc11fc07894d420f..d607e19ef4b64e0453b857b332f480b7f4627c32 100644 GIT binary patch delta 14200 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 Date: Fri, 23 Dec 2022 10:03:53 +0100 Subject: [PATCH 28/34] #4 Conditions --- lib/RPGEngine/Config.hs | 8 ++++++- lib/RPGEngine/Data/Level.hs | 40 ++++++++++++++++++++++------------ lib/RPGEngine/Input/Playing.hs | 2 +- 3 files changed, 34 insertions(+), 16 deletions(-) diff --git a/lib/RPGEngine/Config.hs b/lib/RPGEngine/Config.hs index 66af492..49d0cc7 100644 --- a/lib/RPGEngine/Config.hs +++ b/lib/RPGEngine/Config.hs @@ -45,4 +45,10 @@ assetsFolder = "assets/" -- Location of the level folder containing all levels levelFolder :: FilePath -levelFolder = "levels/" \ No newline at end of file +levelFolder = "levels/" + +------------------------- Game configuration ------------------------- + +-- How many items can a player keep in their inventory? +inventorySize :: Int +inventorySize = 5 \ No newline at end of file diff --git a/lib/RPGEngine/Data/Level.hs b/lib/RPGEngine/Data/Level.hs index f415844..7265e25 100644 --- a/lib/RPGEngine/Data/Level.hs +++ b/lib/RPGEngine/Data/Level.hs @@ -5,8 +5,8 @@ where import GHC.IO (unsafePerformIO) import System.Directory (getDirectoryContents) import RPGEngine.Input.Core (ListSelector(..)) -import RPGEngine.Data (Action(..), Level (..), Physical (..), Direction (..), Entity (..), Game (..), Item (..), Player (..), State (..), X, Y, Layout, Condition (InventoryFull, InventoryContains, Not, AlwaysFalse)) -import RPGEngine.Config (levelFolder) +import RPGEngine.Data (Action(..), Level (..), Physical (..), Direction (..), Entity (..), Game (..), Item (..), Player (..), State (..), X, Y, Layout, Condition (InventoryFull, InventoryContains, Not, AlwaysFalse), ItemId) +import RPGEngine.Config (levelFolder, inventorySize) import Data.Foldable (find) ------------------------------ Exported ------------------------------ @@ -53,21 +53,33 @@ getActions (Left item) = itemActions item getActions (Right entity) = entityActions entity getActionText :: Action -> String -getActionText Leave = "Leave" -getActionText (RetrieveItem _) = "Pick up" -getActionText (UseItem _) = "Use item" +getActionText Leave = "Leave" +getActionText (RetrieveItem _) = "Pick up" +getActionText (UseItem _) = "Use item" +getActionText (IncreasePlayerHp _) = "Take a healing potion" +getActionText (DecreaseHp _ used) = "Attack using " ++ used getActionText _ = "ERROR" --- TODO Check conditions -- Filter based on the conditions, keep only the actions of which the -- conditions are met. -filterActions :: [([Condition], Action)] -> [Action] -filterActions [] = [] -filterActions ((conditions, action):others) = action:filterActions others +-- Should receive a Playing state +filterActions :: State -> [([Condition], Action)] -> [Action] +filterActions _ [] = [] +filterActions s (entry:others) = met entry $ filterActions s others + where met (conditions, action) l | all (meetsCondition s) conditions = action:l + | otherwise = l -- Check if a condition is met or not. -meetsCondition :: Condition -> Bool -meetsCondition InventoryFull = False -- TODO -meetsCondition (InventoryContains id) = True -- TODO -meetsCondition (Not condition) = not $ meetsCondition condition -meetsCondition AlwaysFalse = False \ No newline at end of file +meetsCondition :: State -> Condition -> Bool +meetsCondition s InventoryFull = isInventoryFull $ player s +meetsCondition s (InventoryContains id) = inventoryContains id $ player s +meetsCondition s (Not condition) = not $ meetsCondition s condition +meetsCondition _ AlwaysFalse = False + +-- Check if the inventory of the player is full. +isInventoryFull :: Player -> Bool +isInventoryFull p = inventorySize <= length (inventory p) + +-- Check if the inventory of the player contains an item. +inventoryContains :: ItemId -> Player -> Bool +inventoryContains id p = any ((== id) . itemId) $ inventory p \ No newline at end of file diff --git a/lib/RPGEngine/Input/Playing.hs b/lib/RPGEngine/Input/Playing.hs index f2c3f1a..02e70d6 100644 --- a/lib/RPGEngine/Input/Playing.hs +++ b/lib/RPGEngine/Input/Playing.hs @@ -104,7 +104,7 @@ checkForInteraction g = g{ state = Error "something went wrong while checking fo interact :: Game -> Game interact g@Game{ state = s@Playing{ level = level, player = player } } = g{ state = newState } where newState = ActionSelection actionList selector continue - actionList = filterActions $ getActions $ fromJust $ hasAt pos level + actionList = filterActions s $ getActions $ fromJust $ hasAt pos level selector = ListSelector 0 False pos = position player continue = s From b108b2ed656573da7bd78e7ad5704648716a98fc Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Fri, 23 Dec 2022 10:21:56 +0100 Subject: [PATCH 29/34] #4 Pick up items --- lib/RPGEngine/Data/Level.hs | 9 +++++++++ lib/RPGEngine/Input/ActionSelection.hs | 18 ++++++++++++++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/lib/RPGEngine/Data/Level.hs b/lib/RPGEngine/Data/Level.hs index 7265e25..e3bf9f2 100644 --- a/lib/RPGEngine/Data/Level.hs +++ b/lib/RPGEngine/Data/Level.hs @@ -37,6 +37,15 @@ hasAt pos level = match firstItem firstEntity firstItem = find ((== pos) . getICoord) $ items level getICoord i = (itemX i, itemY i) +getWithId :: String -> Level -> Maybe (Either Item Entity) +getWithId id level = match firstItem firstEntity + where match :: Maybe Item -> Maybe Entity -> Maybe (Either Item Entity) + match (Just a) _ = Just $ Left a + match _ (Just a) = Just $ Right a + match _ _ = Nothing + firstEntity = find ((== id) . entityId) $ entities level + firstItem = find ((== id) . itemId) $ items level + directionOffsets :: Direction -> (X, Y) directionOffsets North = ( 0, 1) directionOffsets East = ( 1, 0) diff --git a/lib/RPGEngine/Input/ActionSelection.hs b/lib/RPGEngine/Input/ActionSelection.hs index 99e6cfe..d454df9 100644 --- a/lib/RPGEngine/Input/ActionSelection.hs +++ b/lib/RPGEngine/Input/ActionSelection.hs @@ -4,10 +4,11 @@ module RPGEngine.Input.ActionSelection import RPGEngine.Input.Core (InputHandler, handleKey, composeInputHandlers, ListSelector (selection)) -import RPGEngine.Data (Game (..), State (..), Direction (..), Action (..), ItemId, EntityId) +import RPGEngine.Data (Game (..), State (..), Direction (..), Action (..), ItemId, EntityId, Level (..), Player (inventory)) import Graphics.Gloss.Interface.IO.Game (Key(SpecialKey), SpecialKey (KeyUp, KeyDown)) import Graphics.Gloss.Interface.IO.Interact ( SpecialKey(..), KeyState(..) ) +import RPGEngine.Data.Level (getWithId) ------------------------------ Exported ------------------------------ @@ -22,9 +23,10 @@ handleInputActionSelection = composeInputHandlers [ ---------------------------------------------------------------------- selectAction :: Game -> Game -selectAction game@Game{ state = ActionSelection list selection continue } = newGame +selectAction game@Game{ state = ActionSelection list selector continue } = newGame where newGame = game{ state = execute selectedAction continue } - selectedAction = Leave + selectedAction = list !! index + index = selection selector selectAction g = g -- TODO Lift this code from LevelSelection @@ -52,8 +54,16 @@ execute (IncreasePlayerHp iid) s = increasePlayerHp iid s execute _ s = s -- Pick up the item with itemId and put it in the players inventory +-- Should receive a Playing state pickUpItem :: ItemId -> State -> State -pickUpItem _ s = s -- TODO +pickUpItem id s@Playing{ level = level, player = player } = newState + where (Just (Left pickedUpItem)) = getWithId id level + newState = s{ level = newLevel, player = newPlayer } + newLevel = level{ items = filteredItems } + filteredItems = filter (/= pickedUpItem) $ items level + newPlayer = player{ inventory = newInventory } + newInventory = pickedUpItem:inventory player +pickUpItem _ _ = Error "Something went wrong while picking up an item" -- Use an item on the player useItem :: ItemId -> State -> State -- TODO From f2844138366b2ca433ca5fdeeadce9b515c84e72 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Fri, 23 Dec 2022 10:50:42 +0100 Subject: [PATCH 30/34] #4 Increase playerhp --- lib/RPGEngine/Input/ActionSelection.hs | 37 +++++++++++++++++++------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/lib/RPGEngine/Input/ActionSelection.hs b/lib/RPGEngine/Input/ActionSelection.hs index d454df9..0a34015 100644 --- a/lib/RPGEngine/Input/ActionSelection.hs +++ b/lib/RPGEngine/Input/ActionSelection.hs @@ -4,11 +4,12 @@ module RPGEngine.Input.ActionSelection import RPGEngine.Input.Core (InputHandler, handleKey, composeInputHandlers, ListSelector (selection)) -import RPGEngine.Data (Game (..), State (..), Direction (..), Action (..), ItemId, EntityId, Level (..), Player (inventory)) +import RPGEngine.Data (Game (..), State (..), Direction (..), Action (..), ItemId, EntityId, Level (..), Player (inventory, playerHp, Player), Item (..), HP) import Graphics.Gloss.Interface.IO.Game (Key(SpecialKey), SpecialKey (KeyUp, KeyDown)) import Graphics.Gloss.Interface.IO.Interact ( SpecialKey(..), KeyState(..) ) import RPGEngine.Data.Level (getWithId) +import Data.Foldable (find) ------------------------------ Exported ------------------------------ @@ -50,7 +51,8 @@ execute :: Action -> State -> State execute (RetrieveItem id ) s = pickUpItem id s execute (UseItem id ) s = useItem id s execute (DecreaseHp eid iid) s = decreaseHp eid iid s -execute (IncreasePlayerHp iid) s = increasePlayerHp iid s +execute (IncreasePlayerHp iid) s = healedPlayer + where healedPlayer = s{ player = increasePlayerHp iid (player s)} execute _ s = s -- Pick up the item with itemId and put it in the players inventory @@ -58,14 +60,14 @@ execute _ s = s pickUpItem :: ItemId -> State -> State pickUpItem id s@Playing{ level = level, player = player } = newState where (Just (Left pickedUpItem)) = getWithId id level - newState = s{ level = newLevel, player = newPlayer } - newLevel = level{ items = filteredItems } + newState = s{ level = newLevel, player = newPlayer } + newLevel = level{ items = filteredItems } filteredItems = filter (/= pickedUpItem) $ items level newPlayer = player{ inventory = newInventory } newInventory = pickedUpItem:inventory player pickUpItem _ _ = Error "Something went wrong while picking up an item" --- Use an item on the player +-- Use an item useItem :: ItemId -> State -> State -- TODO useItem _ s = s -- TODO @@ -79,8 +81,23 @@ decreaseHp _ _ s = s -- TODO Break item if durability below zero -- Heal a bit -increasePlayerHp :: ItemId -> State -> State -increasePlayerHp _ s = s --- TODO Increase playerHp --- TODO Decrease durability of item --- TODO Remove item if durability below zero \ No newline at end of file +-- Should receive a Player +increasePlayerHp :: ItemId -> Player -> Player +increasePlayerHp id p@Player{ playerHp = hp, inventory = inventory} = newPlayer + where newPlayer = p{ playerHp = newHp, inventory = newInventory newItem } + (Just usedItem) = find ((== id) . itemId) inventory + newItem = decreaseDurability usedItem + newInventory (Just item) = item:filteredInventory + newInventory _ = filteredInventory + filteredInventory =filter (/= usedItem) inventory + newHp = changeHealth hp (itemValue usedItem) + +decreaseDurability :: Item -> Maybe Item +decreaseDurability item@Item{ useTimes = Nothing } = Just item -- Infinite uses, never breaks +decreaseDurability item@Item{ useTimes = Just val } | 0 < val - 1 = Just item{ useTimes = Just (val - 1) } + | otherwise = Nothing -- Broken + +-- Change given health by a given amount +changeHealth :: HP -> HP -> HP +changeHealth (Just health) (Just difference) = Just (health + difference) +changeHealth health _ = health \ No newline at end of file From 11eb00ea0b437dc78821c89619365c2dcda1fca8 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Fri, 23 Dec 2022 12:06:46 +0100 Subject: [PATCH 31/34] #4 Attack --- lib/RPGEngine/Data/Level.hs | 8 +++++- lib/RPGEngine/Input/ActionSelection.hs | 39 ++++++++++++++++++++------ lib/RPGEngine/Parse/TextToStructure.hs | 2 +- src/Main.hs | 2 +- test/Parser/GameSpec.hs | 23 +++++++++------ test/Parser/StructureSpec.hs | 6 +++- 6 files changed, 59 insertions(+), 21 deletions(-) diff --git a/lib/RPGEngine/Data/Level.hs b/lib/RPGEngine/Data/Level.hs index e3bf9f2..875514d 100644 --- a/lib/RPGEngine/Data/Level.hs +++ b/lib/RPGEngine/Data/Level.hs @@ -91,4 +91,10 @@ isInventoryFull p = inventorySize <= length (inventory p) -- Check if the inventory of the player contains an item. inventoryContains :: ItemId -> Player -> Bool -inventoryContains id p = any ((== id) . itemId) $ inventory p \ No newline at end of file +inventoryContains id p = any ((== id) . itemId) $ inventory p + +-- Retrieve an item from inventory +itemFromInventory :: ItemId -> [Item] -> (Maybe Item, [Item]) +itemFromInventory iid list = (match, filteredList) + where match = find ((== iid) . itemId) list + filteredList = filter ((/= iid) . itemId) list \ No newline at end of file diff --git a/lib/RPGEngine/Input/ActionSelection.hs b/lib/RPGEngine/Input/ActionSelection.hs index 0a34015..ad7692b 100644 --- a/lib/RPGEngine/Input/ActionSelection.hs +++ b/lib/RPGEngine/Input/ActionSelection.hs @@ -4,11 +4,11 @@ module RPGEngine.Input.ActionSelection import RPGEngine.Input.Core (InputHandler, handleKey, composeInputHandlers, ListSelector (selection)) -import RPGEngine.Data (Game (..), State (..), Direction (..), Action (..), ItemId, EntityId, Level (..), Player (inventory, playerHp, Player), Item (..), HP) +import RPGEngine.Data (Game (..), State (..), Direction (..), Action (..), ItemId, EntityId, Level (..), Player (inventory, playerHp, Player), Item (..), HP, Entity (..)) import Graphics.Gloss.Interface.IO.Game (Key(SpecialKey), SpecialKey (KeyUp, KeyDown)) import Graphics.Gloss.Interface.IO.Interact ( SpecialKey(..), KeyState(..) ) -import RPGEngine.Data.Level (getWithId) +import RPGEngine.Data.Level (getWithId, itemFromInventory) import Data.Foldable (find) ------------------------------ Exported ------------------------------ @@ -72,13 +72,30 @@ useItem :: ItemId -> State -> State -- TODO useItem _ s = s -- TODO -- Attack an entity using an item +-- Should receive a Playing state decreaseHp :: EntityId -> ItemId -> State -> State -decreaseHp _ _ s = s --- TODO DecreaseHp of monster --- TODO Check if monster is dead --- TODO Entity attack player --- TODO Decrease durability of item --- TODO Break item if durability below zero +decreaseHp eid iid s@Playing{ level = level, player = player } = newState + where newState = s{ level = newLevel, player = newPlayer } + -- Change player + (Just usingItem) = find ((== iid) . itemId) (inventory player) + usedItem = decreaseDurability usingItem + newInventory = filter (/= usingItem) $ inventory player + newPlayer = player{ inventory = putItemBack usedItem newInventory, playerHp = newHp } + putItemBack Nothing inv = inv + putItemBack (Just item) inv = item:inv + newHp = changeHealth (playerHp player) damageGetAmount -- Damage dealt by entity + damageDealAmount = itemValue usingItem + -- Change entity + (Just (Right attackedEntity)) = getWithId eid level + newLevel = level{ entities = putEntityBack dealtWithEntity newEntities } + newEntities = filter ((/= eid) . entityId) $ entities level + dealtWithEntity = decreaseHealth attackedEntity damageDealAmount + putEntityBack Nothing list = list + putEntityBack (Just ent) list = ent:list + damageGetAmount = inverse (entityValue attackedEntity) + inverse (Just val) = Just (-val) + inverse Nothing = Nothing +decreaseHp _ _ _ = Error "something went wrong while attacking" -- Heal a bit -- Should receive a Player @@ -97,6 +114,12 @@ decreaseDurability item@Item{ useTimes = Nothing } = Just item -- Infinite uses decreaseDurability item@Item{ useTimes = Just val } | 0 < val - 1 = Just item{ useTimes = Just (val - 1) } | otherwise = Nothing -- Broken +decreaseHealth :: Entity -> Maybe Int -> Maybe Entity +decreaseHealth entity@Entity{ entityHp = Nothing } _ = Just entity +decreaseHealth entity@Entity{ entityHp = Just val } (Just i) | 0 < val - i = Just entity{ entityHp = Just (val - i) } + | otherwise = Nothing +decreaseHealth entity _ = Just entity + -- Change given health by a given amount changeHealth :: HP -> HP -> HP changeHealth (Just health) (Just difference) = Just (health + difference) diff --git a/lib/RPGEngine/Parse/TextToStructure.hs b/lib/RPGEngine/Parse/TextToStructure.hs index fa2486e..d3c7ba0 100644 --- a/lib/RPGEngine/Parse/TextToStructure.hs +++ b/lib/RPGEngine/Parse/TextToStructure.hs @@ -139,7 +139,7 @@ action = try $ do let answer | script == "leave" = Leave | script == "retrieveItem" = RetrieveItem arg | script == "useItem" = UseItem arg - | script == "decreaseHp" = DecreaseHp first second + | script == "decreaseHp" = DecreaseHp first (filter (/= ' ') second) -- TODO Work this hack away | script == "increasePlayerHp" = IncreasePlayerHp arg | otherwise = DoNothing (first, ',':second) = break (== ',') arg diff --git a/src/Main.hs b/src/Main.hs index bb69131..0e997a8 100644 --- a/src/Main.hs +++ b/src/Main.hs @@ -1,4 +1,4 @@ -import RPGEngine +import RPGEngine ( playRPGEngine ) ----------------------------- Constants ------------------------------ diff --git a/test/Parser/GameSpec.hs b/test/Parser/GameSpec.hs index c441a22..13ad9e6 100644 --- a/test/Parser/GameSpec.hs +++ b/test/Parser/GameSpec.hs @@ -6,7 +6,6 @@ import RPGEngine.Data import RPGEngine.Parse.Core import RPGEngine.Parse.TextToStructure import RPGEngine.Parse.StructureToGame -import RPGEngine.Parse.TextToStructure (gameFile) spec :: Spec spec = do @@ -40,7 +39,7 @@ spec = do pendingWith "Still need to write this" it "Game with multiple levels" $ do pendingWith "Still need to write this" - + describe "Player" $ do it "cannot die" $ do let input = "player: { hp: infinite, inventory: [] }" @@ -65,7 +64,7 @@ spec = do } Right (Entry (Tag "player") struct) = parseWith structure input structureToPlayer struct `shouldBe` correct - + it "with inventory" $ do let input = "player: { hp: 50, inventory: [ { id: \"dagger\", x: 0, y: 0, name: \"Dolk\", description: \"Basis schade tegen monsters\", useTimes: infinite, value: 10, actions: {} } ] }" correct = Player { @@ -95,7 +94,7 @@ spec = do correct = Item { itemId = "dagger", itemX = 0, - itemY = 0, + itemY = 0, itemName = "Dagger", itemDescription = "Basic dagger you found somewhere", itemValue = Just 10, @@ -104,7 +103,7 @@ spec = do } Right struct = parseWith structure input structureToItem struct `shouldBe` correct - + it "with actions" $ do let input = "{ id: \"key\", x: 3, y: 1, name: \"Doorkey\", description: \"Unlocks a secret door\", useTimes: 1, value: 0, actions: { [not(inventoryFull())] retrieveItem(key), [] leave() } }" correct = Item { @@ -122,30 +121,36 @@ spec = do } Right struct = parseWith structure input structureToItem struct `shouldBe` correct - + describe "Actions" $ do it "no conditions" $ do let input = "{[] leave()}" correct = [([], Leave)] Right struct = parseWith structure input structureToActions struct `shouldBe` correct - + it "single condition" $ do let input = "{ [inventoryFull()] useItem(itemId)}" correct = [([InventoryFull], UseItem "itemId")] Right struct = parseWith structure input structureToActions struct `shouldBe` correct - + it "multiple conditions" $ do let input = "{ [not(inventoryFull()), inventoryContains(itemId)] increasePlayerHp(itemId)}" correct = [([Not InventoryFull, InventoryContains "itemId"], IncreasePlayerHp "itemId")] Right struct = parseWith structure input structureToActions struct `shouldBe` correct + + it "DecreaseHp(entityid, itemid)" $ do + let input = "{ [] decreaseHp(devil, sword) }" + correct = [([], DecreaseHp "devil" "sword")] + Right struct = parseWith structure input + structureToActions struct `shouldBe` correct describe "Entities" $ do it "Simple entity" $ do pendingWith "still need to write this" - + describe "Level" $ do it "Simple layout" $ do let input = "{ layout: { | * * * * * *\n| * s . . e *\n| * * * * * *\n }, items: [], entities: [] }" diff --git a/test/Parser/StructureSpec.hs b/test/Parser/StructureSpec.hs index b084a02..e4c34f5 100644 --- a/test/Parser/StructureSpec.hs +++ b/test/Parser/StructureSpec.hs @@ -146,10 +146,14 @@ spec = do correct = Right $ Regular $ Action $ UseItem "secondId" parseWith regular input `shouldBe` correct - let input = "decreaseHp(entityId,objectId)" + let input = "decreaseHp(entityId, objectId)" correct = Right $ Regular $ Action $ DecreaseHp "entityId" "objectId" parseWith regular input `shouldBe` correct + let input = "decreaseHp(entityId,objectId)" + correct = Right $ Regular $ Action $ DecreaseHp "entityId" "objectId" + parseWith regular input `shouldBe` correct + let input = "increasePlayerHp(objectId)" correct = Right $ Regular $ Action $ IncreasePlayerHp "objectId" parseWith regular input `shouldBe` correct From c3f7e477033a1a09944f480530660c13005094e3 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Fri, 23 Dec 2022 13:10:15 +0100 Subject: [PATCH 32/34] #4 Decreasehp --- lib/RPGEngine/Input/ActionSelection.hs | 23 +++++++++++++++++++---- lib/RPGEngine/Input/Playing.hs | 21 ++++++++++++++++++++- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/lib/RPGEngine/Input/ActionSelection.hs b/lib/RPGEngine/Input/ActionSelection.hs index ad7692b..d0ed414 100644 --- a/lib/RPGEngine/Input/ActionSelection.hs +++ b/lib/RPGEngine/Input/ActionSelection.hs @@ -68,8 +68,23 @@ pickUpItem id s@Playing{ level = level, player = player } = newState pickUpItem _ _ = Error "Something went wrong while picking up an item" -- Use an item -useItem :: ItemId -> State -> State -- TODO -useItem _ s = s -- TODO +-- Should receive a Playing state +useItem :: ItemId -> State -> State +useItem iid s@Playing{ level = level, player = player} = newState + where newState = s{ level = newLevel, player = newPlayer } + -- Remove item from inventory if necessary + (Just usingItem) = find ((== iid) . itemId) $ inventory player + usedItem = decreaseDurability usingItem + newInventory = filter (/= usingItem) $ inventory player + newPlayer = player{ inventory = putItemBack usedItem newInventory } + putItemBack Nothing inv = inv + putItemBack (Just item) inv = item:inv + -- Remove entity if necessary + allEntities = entities level + entitiesWithUseItem = filter (any ((== UseItem iid) . snd) . entityActions) allEntities + attackedEntity = head entitiesWithUseItem + newLevel = level{ entities = filter (/= attackedEntity) $ entities level} +useItem _ _ = Error "Something went wrong while using an item" -- Attack an entity using an item -- Should receive a Playing state @@ -77,7 +92,7 @@ decreaseHp :: EntityId -> ItemId -> State -> State decreaseHp eid iid s@Playing{ level = level, player = player } = newState where newState = s{ level = newLevel, player = newPlayer } -- Change player - (Just usingItem) = find ((== iid) . itemId) (inventory player) + (Just usingItem) = find ((== iid) . itemId) $ inventory player usedItem = decreaseDurability usingItem newInventory = filter (/= usingItem) $ inventory player newPlayer = player{ inventory = putItemBack usedItem newInventory, playerHp = newHp } @@ -89,7 +104,7 @@ decreaseHp eid iid s@Playing{ level = level, player = player } = newState (Just (Right attackedEntity)) = getWithId eid level newLevel = level{ entities = putEntityBack dealtWithEntity newEntities } newEntities = filter ((/= eid) . entityId) $ entities level - dealtWithEntity = decreaseHealth attackedEntity damageDealAmount + dealtWithEntity = decreaseHealth attackedEntity damageDealAmount putEntityBack Nothing list = list putEntityBack (Just ent) list = ent:list damageGetAmount = inverse (entityValue attackedEntity) diff --git a/lib/RPGEngine/Input/Playing.hs b/lib/RPGEngine/Input/Playing.hs index 02e70d6..8025611 100644 --- a/lib/RPGEngine/Input/Playing.hs +++ b/lib/RPGEngine/Input/Playing.hs @@ -84,7 +84,7 @@ goToNextLevel s = s -- Move a player in a direction if possible. movePlayer :: Direction -> Game -> Game -movePlayer dir g@Game{ state = s@Playing{ player = p@Player{ position = (x, y) }}} = newGame +movePlayer dir g@Game{ state = s@Playing{ player = p@Player{ position = (x, y) }}} = tryForceInteraction newGame g where newGame = g{ state = newState } newState = s{ player = newPlayer } newPlayer = p{ position = newCoord } @@ -93,6 +93,25 @@ movePlayer dir g@Game{ state = s@Playing{ player = p@Player{ position = (x, y) } (xD, yD) = directionOffsets dir movePlayer _ g = g{ state = Error "something went wrong while moving the player" } +-- TODO Clean this function +-- Try to force an interaction. If there is an entity, you have to +-- interact with it. If it is an item, the user should trigger this +-- themselves. If forced, the player should not move to the new position. +tryForceInteraction :: Game -> Game -> Game +tryForceInteraction g@Game{ state = Playing { level = level, player = player }} fallBack@Game{ state = Playing{ player = firstPlayer }} = newGame triedInteraction + where newGame g@Game{ state = s@ActionSelection{ continue = c@Playing{ player = player}}} = g{ state = s{ continue = c{ player = playerWithRestorePos }}} + newGame g = g + playerWithRestorePos = (newPlayer triedInteraction){ position = position firstPlayer } + newPlayer Game{ state = ActionSelection{ continue = Playing{ player = player }}} = player + triedInteraction | hasEntity (hasAt pos level) = interact g + | otherwise = g + pos = position player + hasEntity (Just (Right entity)) = True + hasEntity _ = False +tryForceInteraction g _ = g{ state = Error "something went wrong while trying to force interaction"} + +-- If there is an interaction at the current position, go to +-- actionSelection state. Otherwise just continue the game. checkForInteraction :: Game -> Game checkForInteraction g@Game{ state = Playing{ level = level, player = player }} = newGame where newGame | canInteract = interact g From 0a6e1d7ffb77683bf58bb4dce337fa4438c5b82e Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Fri, 23 Dec 2022 21:39:56 +0100 Subject: [PATCH 33/34] #11 Write report --- .gitignore | 1 + .vscode/tasks.json | 3 +- README.md | 305 ++++++++++++++----------- assets/environment/void.png | Bin 1548 -> 905 bytes extra/walkthrough/5-1-01.png | Bin 0 -> 16801 bytes extra/walkthrough/5-2-01.png | Bin 0 -> 17608 bytes extra/walkthrough/5-2-02.png | Bin 0 -> 17601 bytes extra/walkthrough/5-2-03.png | Bin 0 -> 17794 bytes extra/walkthrough/5-2-04.png | Bin 0 -> 17371 bytes extra/walkthrough/5-2-05.png | Bin 0 -> 17706 bytes extra/walkthrough/5-2-06.png | Bin 0 -> 17284 bytes extra/walkthrough/5-3-01.png | Bin 0 -> 17639 bytes extra/walkthrough/5-3-02.png | Bin 0 -> 19564 bytes extra/walkthrough/5-3-03.png | Bin 0 -> 17909 bytes extra/walkthrough/5-3-04.png | Bin 0 -> 16406 bytes extra/walkthrough/5-3-05.png | Bin 0 -> 22383 bytes extra/walkthrough/5-3-06.png | Bin 0 -> 16238 bytes extra/walkthrough/5-3-07.png | Bin 0 -> 16549 bytes extra/walkthrough/5-3-08.png | Bin 0 -> 16548 bytes extra/walkthrough/5-3-09.png | Bin 0 -> 16143 bytes extra/walkthrough/selection.png | Bin 0 -> 9071 bytes extra/walkthrough/you-win.png | Bin 0 -> 5004 bytes levels/level5.txt | 144 ++++++++++++ lib/RPGEngine/Input/ActionSelection.hs | 1 + lib/RPGEngine/Input/Playing.hs | 1 + lib/RPGEngine/Render/Playing.hs | 7 +- verslag.pdf | Bin 50213 -> 306826 bytes 27 files changed, 328 insertions(+), 134 deletions(-) create mode 100644 extra/walkthrough/5-1-01.png create mode 100644 extra/walkthrough/5-2-01.png create mode 100644 extra/walkthrough/5-2-02.png create mode 100644 extra/walkthrough/5-2-03.png create mode 100644 extra/walkthrough/5-2-04.png create mode 100644 extra/walkthrough/5-2-05.png create mode 100644 extra/walkthrough/5-2-06.png create mode 100644 extra/walkthrough/5-3-01.png create mode 100644 extra/walkthrough/5-3-02.png create mode 100644 extra/walkthrough/5-3-03.png create mode 100644 extra/walkthrough/5-3-04.png create mode 100644 extra/walkthrough/5-3-05.png create mode 100644 extra/walkthrough/5-3-06.png create mode 100644 extra/walkthrough/5-3-07.png create mode 100644 extra/walkthrough/5-3-08.png create mode 100644 extra/walkthrough/5-3-09.png create mode 100644 extra/walkthrough/selection.png create mode 100644 extra/walkthrough/you-win.png create mode 100644 levels/level5.txt diff --git a/.gitignore b/.gitignore index e9a934f..43be4f3 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ extra/ *.dll stack.yaml.lock +.vscode/settings.json diff --git a/.vscode/tasks.json b/.vscode/tasks.json index c27db8b..3cf0327 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -49,9 +49,10 @@ "args": [ "-s", "-o", "verslag.pdf", - "-f", "markdown+smart+header_attributes+yaml_metadata_block+auto_identifiers", + "-f", "markdown+smart+header_attributes+yaml_metadata_block+auto_identifiers+implicit_figures", "--pdf-engine", "lualatex", "--template", "eisvogel", + "--dpi=300", "header.yaml", "README.md" ], diff --git a/README.md b/README.md index 51913b2..39a363e 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,9 @@ @@ -45,40 +14,193 @@ RPG-Engine is a game engine for playing and creating your own RPG games. If you are interested in the development side of things, [development notes can be found here](#Development-notes). -This README serves as both documentation and project report, so excuse the details that might not be important for the average user. +This README serves as both documentation and project report, so excuse the details that might not be important for the average reader. ## Playing the game -These are the keybinds *in* the game. All other keybinds in the menus should be straightforward. +These are the keybinds while *in* game. All other keybinds in menus etc. should be straightforward. -| Action | Primary | Secondary | -| -------------- | ------------- | ----------- | -| Move up | `Arrow Up` | `w` | -| Move left | `Arrow Left` | `a` | -| Move down | `Arrow Down` | `s` | -| Move right | `Arrow Right` | `d` | -| Interaction | `Space` | `f` | -| Show inventory | `i` | `Tab` | -| Restart level | `r` | | -| Quit game | `Esc` | | +| Action | Primary | Secondary | +| -------------- | ------------- | ------------ | +| Move up | `Arrow Up` | `w` | +| Move left | `Arrow Left` | `a` | +| Move down | `Arrow Down` | `s` | +| Move right | `Arrow Right` | `d` | +| Interaction | `Space` | `f`, `Enter` | +| Show inventory | `i` | `Tab` | +| Restart level | `r` | | +| Quit game | `Esc` | | ### Example playthrough -TODO +![Select level 5](./extra/walkthrough/selection.png){ width=600 } +![Move to the first exit](./extra/walkthrough/5-1-01.png){ width=600 } +![Move to the first key](./extra/walkthrough/5-2-02.png){ width=600 } +![Pick up key](./extra/walkthrough/5-2-03.png){ width=600 } +![Move to door](./extra/walkthrough/5-2-04.png){ width=600 } +![Open door with key](./extra/walkthrough/5-2-05.png){ width=600 } +![Move to exit](./extra/walkthrough/5-2-06.png){ width=600 } +![Move to devil](./extra/walkthrough/5-3-01.png){ width=600 } +![Try to attack with dagger](./extra/walkthrough/5-3-02.png){ width=600 } +![Go pick up sword](./extra/walkthrough/5-3-03.png){ width=600 } +![Attack devil using sword](./extra/walkthrough/5-3-05.png){ width=600 } +![Pick up key](./extra/walkthrough/5-3-06.png){ width=600 } +![Open door](./extra/walkthrough/5-3-08.png){ width=600 } +![Move to exit](./extra/walkthrough/5-3-09.png){ width=600 } +![You win](./extra/walkthrough/you-win.png){ width=600 } -- An example playthrough, with pictures and explanations +## Development notes + +### Engine architecture + +`RPGEngine` is the main module. It contains the `playRPGEngine` function which bootstraps the whole game. It is also + the game loop. From here, `RPGEngine` talks to its submodules. + +These submodules are `Config`, `Data`, `Input`, `Parse` & `Render`. They are all responsible for their own part. However, + each of these submodules has their own submodules to divide the work. They are conveniently named after the state of + the game that they work with, e.g. the main menu has a module & when the game is playing is a different module. A special + one is `Core`, which is kind of like a library for every piece. It contains functions that are regularly used by the other modules. + +- `Config`: Configuration values, should ultimately be moved into parsing from a file. +- `Data`: Data containers and accessors of information. +- `Input`: Anything that handles input or changes the state of the game. +- `Parse`: Parsing +- `Render`: Rendering of the game and everything below that. + +#### Monads/Monad stack + +Monads: + +- Extensive use of `Maybe` for integers or infinity and `do` in parser implementation. +- `IO` to handle external information +- ... + +Monad transformer: ?? + +I am afraid I did not write any monad transformers in this project. I think I could (and should) have focused more on + writing monads and monad transformers. In hindsight, I can see where I could and should have used them. I can think of + plenty of ways to make the current implementation simpler. This is unfortunate. However, I want to believe that my next + time writing a more complex Haskell program, I will remember using monad transformers. Sadly, I forgot this time. + +An example of where I would use a monad transformer - in hindsight: + +1. Interactions in game: something along the lines of ... + +```haskell +newtype StateT m a = StateT { runStateT :: m a } + +instance Monad m => Monad (StateT m) where + return = lift . return + x >>= f = StateT $ do + v <- runStateT x + case v of + Playing level -> runStateT ( f level ) + Paused continue -> runStateT ( continue >>= f ) + -- etc + +class MonadTransformer r where + lift :: Monad m => m a -> (r m) a +instance MonadTransformer StateT where + lift = StateT +``` + +2. Interaction with the outside world should also be done with Monad(transformers) instead of using `unsafePerformIO`. + +### Tests + +Overall, only parsing is tested using Hspec. However, parsing is tested *thoroughly* and I am quite sure that there aren't + a lot of edge cases that I did not catch. This makes for a relaxing environment where you can quickly check if a change + you made breaks anything. + +`Spec` is the main module. It does not contain any tests, but functions as the 'discover' module to find the other tests + in its folder. + +`Parser.StructureSpec` tests functionality of `RPGEngine.Parse.TextToStructure`, `Parser.GameSpec` tests functionality + of `RPGEngine.Parse.StructureToGame`. + +Known issues: + +- [ ] Rendering is still not centered, I am sorry for those with small screens. +- [ ] Config files cannot end with an empty line. I could not get that to work and I decided that it was more important + to implement other functionality first. Unfortunately, I was not able to get back to it yet. +- [ ] The parser is unable to parse layouts with trailing whitespace. + +## Conclusion + +Parsing was way harder than I initially expected. I believe over half my time on this project was spent trying to write the + parser. I am still not absolutely sure that it will work with *everything*, but it gets the job done at the moment. I don't + know if parsing into a structure before transforming the structure into a game was a good move. It might have saved me some + time if I did it straight to `Game`. I want to say that I have a parser-to-structure module now, but even so, there are some + links between `TextToStructure` and `Game` that make it almost useless to any other project (without changing anything). + +Player-object interaction was easier than previous projects. I believe this is both because I am getting used to it by now + and because I spent a lot of time beforehand structuring everything. I also like to think that structuring the project is + what I did right. There is a clear hierarchy and you can find what you are looking for fairly easy, without having to search + for a function in file contents or having to scavenge multiple different files before finding what you want. However, I + absolutely wasted a lot of time restructuring the project multiple times, mostly because I was running into dependency cycles. + +Overall, I believe the project was a success. I am proud of the end result. Though, please note my comments on monad transformers. + +### Assets & dependencies + +The following assets were used (and modified if specified): + +- Kyrise's Free 16x16 RPG Icon Pack[[1]](#1) + +- 2D Pixel Dungeon Asset Pack by Pixel_Poem[[2]](#2) + + Every needed asset was taken and put into its own `.png`, instead of in the overview. + +RPG-Engine makes use of the following libraries: + +- [directory](https://hackage.haskell.org/package/directory) for listing levels in a directory +- [gloss](https://hackage.haskell.org/package/gloss) for game rendering +- [gloss-juicy](https://hackage.haskell.org/package/gloss-juicy) for rendering images +- [hspec](https://hackage.haskell.org/package/hspec) for testing +- [hspec-discover](https://hackage.haskell.org/package/hspec-discover) for allowing to split test files in multiple files +- [parsec](https://hackage.haskell.org/package/parsec) for parsing configuration files
\pagebreak
-## Writing your own stages +## References -A stage description file, conventionally named `.txt` is a file with a JSON-like format. It is used to describe - everything inside a single stage of your game, including anything related to the player, the levels your game contains +
[1] [Kyrise's Free 16x16 RPG Icon Pack](https://kyrise.itch.io/kyrises-free-16x16-rpg-icon-pack) © 2018 + by [Kyrise](https://kyrise.itch.io/) is licensed under [CC BY 4.0](http://creativecommons.org/licenses/by/4.0/?ref=chooser-v1) + +[2] [2D Pixel Dungeon Asset Pack](https://pixel-poem.itch.io/dungeon-assetpuck) by [Pixel_Poem](https://pixel-poem.itch.io/) + is not licensed + +
\pagebreak
+ +## Appendix A: Future development ideas + +The following ideas could (or should) be implemented in the future of this project. + +- [ ] **Entity system:** With en ES, you can implement moving entities and repeated input. It also resembles the typical + game loop more closely which can make it easier to implement other ideas in the future. +- [ ] **Game music:** Ambient game music and sound effects can improve the gaming experience I think. +- [ ] **Expand configuration file:** Implement the same methods for parsing stage description files to a configuration file, + containing keybinds, dimension sizes, even window titles, making this a truly customizable engine. +- [ ] **Camera follows player:** The camera should follow the player, making it always center. This allows for larger levels + increases the immersion of the game. + +Changes in the backend: + +- [ ] **Make inventory a state** At the moment, there is a boolean for inventory rendering. This should be turned into a state, + so it makes more sense to call it from other places as well. +- [ ] **Direction of entities** Change the rendering based on the direction of an entity. +- [ ] **Inventory with more details** The inventory should show more details of items, e.g. name, value, remaining use + times and description. + +
\pagebreak
+ +## Appendix B: Writing your own worlds + +A world description file, conventionally named `.txt` is a file with a JSON-like format. It is used to describe + everything inside a single world of your game, including anything related to the player, the levels your game contains and what happens in that level. It is essentially the raw representation of the initial state of a single game. -> Note: At the moment, every game has a single stage description file. Chaining several files together is not possible yet. - -A stage description file consists of several elements. +A world description file consists of several elements. | Element | Short description | | --------------- | --------------------------------------------------------------------------------------------------------- | @@ -159,7 +281,7 @@ levels: [ ``` -This stage description file consists of a single `Block`. A stage description file always does. This top level `Block` +This world description file consists of a single `Block`. A world description file always does. This top level `Block` contains two `Value`s `player` and `levels`, not separated by commas. `player` describes a `Block` that represents the player of the game. Its `Entry`s are `hp` (a traditional value) and @@ -252,79 +374,4 @@ If we look at the example, all the objects are length = 1 Condition ('inventoryContains(key)') Entry = empty ConditionList + Action ('leave()') -``` - -
\pagebreak
- -## Development notes - -### Engine architecture - -TODO - -`RPGEngine` is the main module. It contains the `playRPGEngine` function which bootstraps the whole game. It is also - the game loop. From here, `RPGEngine` talks to its submodules. - -These submodules are `Config`, `Data`, `Input`, `Parse` & `Render`. They are all responsible for their own part, either - containing the program configuration, data containers, everything needed to handle input, everything needed to parse a - source file & everything needed to render the game. However, each of these submodules has their own submodules to - divide the work. They are conveniently named after the state of the game that they work with, e.g. the main menu has a - module & when the game is playing is a different module. A special one is `Core`, which is kind of like a library for - every piece. It contains functions that are regularly used by the other modules. - -#### Monads/Monad stack - -TODO - -### Tests - -TODO - -### Assets & dependencies - -The following assets were used (and modified if specified): - -- Kyrise's Free 16x16 RPG Icon Pack[[1]](#1) - -- 2D Pixel Dungeon Asset Pack by Pixel_Poem[[2]](#2) - - Every needed asset was taken and put into its own `.png`, instead of in the overview. - -RPG-Engine makes use of the following libraries: - -- [directory](https://hackage.haskell.org/package/directory) for listing levels in a directory -- [gloss](https://hackage.haskell.org/package/gloss) for game rendering -- [gloss-juicy](https://hackage.haskell.org/package/gloss-juicy) for rendering images -- [hspec](https://hackage.haskell.org/package/hspec) for testing -- [hspec-discover](https://hackage.haskell.org/package/hspec-discover) for allowing to split test files in multiple files -- [parsec](https://hackage.haskell.org/package/parsec) for parsing configuration files - -### Future development ideas - -The following ideas could (or should) be implemented in the future of this project. - -- [ ] **Entity system:** With en ES, you can implement moving entities and repeated input. It also resembles the typical - game loop more closely which can make it easier to implement other ideas in the future. -- [ ] **Game music:** Ambient game music and sound effects can improve the gaming experience I think. -- [ ] **Expand configuration file:** Implement the same methods for parsing stage description files to a configuration file, - containing keybinds, dimension sizes, even window titles, making this a truly customizable engine. -- [ ] **Camera follows player:** The camera should follow the player, making it always center. This allows for larger levels - increases the immersion of the game. - -
\pagebreak
- -## Conclusion - -Parsing was way harder than I initially expected. About half of my time on this project was spent writing the parser. - -TODO - -
\pagebreak
- -## References - -[1] [Kyrise's Free 16x16 RPG Icon Pack](https://kyrise.itch.io/kyrises-free-16x16-rpg-icon-pack) © 2018 - by [Kyrise](https://kyrise.itch.io/) is licensed under [CC BY 4.0](http://creativecommons.org/licenses/by/4.0/?ref=chooser-v1) - -[2] [2D Pixel Dungeon Asset Pack](https://pixel-poem.itch.io/dungeon-assetpuck) by [Pixel_Poem](https://pixel-poem.itch.io/) - is not licensed \ No newline at end of file +``` \ No newline at end of file diff --git a/assets/environment/void.png b/assets/environment/void.png index be70f441373a27553b8a6deb9204682038abd4d9..9eb2e3f891c1efbd8b4ed0bb9f84c90bbd3b88dc 100644 GIT binary patch delta 868 zcmV-q1DpJe42cJjB!6#dLqkw$V`BgSD3NuMJxjw-6o#KxMbQ=qMMNAj)Q`b}D2k)j z!KFy46|B0nY10Z#3`vWKtKi}wIEXHO4gLdN1y=<@Py`Vte}G?$NIcgH6=`p{IS=o> z@8RSP_@f2gbnC~#w2DrADx%FL=e5RX0(20hkCBw_+7q!@lz*D*;})N)H(}MQ_PwgB zC6je^@HGjK>b6r9mT8Et6m7+u!tHuKl@UG=9<p;Y+2b%lxD`SLQdxP9i=De$`XU z{o(W<*7J@j+%Ft53nl$`9`z-GtTj6$wM%w4E^(%appmA;5(SEcrCO3%b>HBRZ<=NC z=<>E%#Sur2ynlEHL@i0ztW-nvtULu)Rqp@ubdBMma`!+48Xi6GwFerv;cLtDJ`X+b z>j>%|;M}VCmnEmMchR>hT4*0y*WvV{qNR7>Yy&#)?Ua-Hk?Lgo4Fg_IAee-#0^j-ne-4R9*Te~TZnI58SZdSS@%emqcz=28);j`#&gYOa-&ZSU{9!x< z@qou@RRM|#OZ^oO7q$&HDu&kF%RwqmI! zbi@=PW?!{nX8ebA0yR<#vI~EWayPb%NE?F_~!A4t;(Y~@U0 zStavl2OgC7DlVW8Kn`PHvO}gQY+=rmk8<+G3FIK0!4MSCTu7gS#8MT7m&zUBnIi?G z+Omn%b#e48pO&k!kdUK*)!UKui#43tKRKcKsq5OO;4HNVGA*8f#Z6tZ>ZY{R{j4k5 zuob88_gVd=HP&6Dwzs>(CFH2+EJQDy{4wW0$wJV2%LF9Rhq*U%*QG*WU$^Mvl1}-9 u9)1te<=c}+T(D5NZbEqUF*A=U0r01LezJ!=ty4cB&gLAwJ?-@^6Y&JJe?;!dZe*KOr}OB zOg3gtX=#Bd%R%;z#giv5>Yh@O7nC9Rxb^VgAMgM2KmR8#@G5m#q^zOCm41p% zm}P$9wNs(j=1ksiFy(Uiw4HvYT`Xc7Svrk^0$e#$Z**K*H#tes^?9NE|BlzwPb{6V zn#HY;HDrNf+205CAFQLb?M+{navg0vCw}~=1oN)4Mz^zt`}$><7c#NTZ`9uVbp4ga zoBo%wb#;qacygG?NMQuI$ga#kh53BRHFXGcd47d%8G=SO_u-u)P1xs46V=AE=CMz(lrAsK!wP7#K#w gU^EOs*FVdQ&MBb@0D*YVPyhe` diff --git a/extra/walkthrough/5-1-01.png b/extra/walkthrough/5-1-01.png new file mode 100644 index 0000000000000000000000000000000000000000..2b40621788c36f258288085818601394fc984e8f GIT binary patch literal 16801 zcmeHv2~<<(+HP!rYb#n>6@o=b)gw4SFa?RqRJDj!1L6P(gj7;R5QxYSkc6~a#gYKE z4j^H01T6w0V1Phk#RLUIkch~TYz%`t~=x z!}C7xv-xqCx7Yl+pUs6pAoI6x`)W4?^8Qr_Vf47#~BqofHU@(|!?b9E>w{tZ->ulD9Y@Da^4V;Cr*J+h&o? zzW87^cFh-;1I{kEvU2X`i`5@WX8*e2z4FZ;Njx{htJf}*%-(I+{9(}DGY4%yJv{q_ zXW-hvy3N#|P3j(dPKumd|*`=x*)|_e>gVzDZBd2mGDE-xcGH(2ye?q+3t!?J9fBg z>u(?SdK6$$B(gMoqT2AoowFb>b0X+5>y;4~w+dLU=`0o{*kU3Zm~YFiNvN)~>Rrtp zK3i4(_})3dnm8w5wXvZTMZZX2?k==@)~2IEM4** zk(grw`SKK{1_IgpDEp7wn^v$Ag*-`!?wcy1p*u!9}BLa&~-boWRf`gBaxOE1%Xt<-yHm~{FP}>5NV%v=vn-^ zL1pBR;pl>ozYgico=>%jfhZ}}8&Q_FsK*hd^qo}-Fk!+M|yu=X# z3ASPfLm;caeKH$zd9(4>DOq_^(Sb=%x>EhvtKF7K&K_=upA?-`ucg zf^Z*Q&bSPwCgS6l^j3Q6I+IccLYY(5;!r#1tVS>Vqr7awxm2aY``j?m@QY|kWBOzs zILYbZjVEHb@MP&R=NRssKThH=hw?0c{5`-W(K%!&r;-|Mw0dy< zkZ^9&U=DAsEU^PoV zjLhk%bd7-+-wf=Wp8WDZE%djO$Rfuju#(uJWB6lNs@G>-oqFy$yb- zpLA*X{j)5#Q?o9EK1i7j+WVeo$W|n4r@=k3n}BV=B7aj$qy4XVbM$Ffh}A!N@QYab zzUUB5XhRontzoQpgwxv#3()Z*s%cjo5S)d(8`Nj{J{h^88F#Fhu9T&30?#z3~-7E(|d350;-H36b5?M#x5SYXe(Vn2O>^OE|rehqvKzAr*22K)dKiN{joi9{0 zXX7tSqGa{ahRI~h>Q&RW7*^E|(zXE#TR#kLq&f7sT%46P0nYomlS=Z1v@s;Gh@JYW3 zb+9O>|CZL9PLb3{n_V-kkB9e|-YtGE=yqyf6hLO0fg5)B%?-O*&&$`Wha~ONCLaKSfN|D3>tbj;(X6E=$E(oQKsTA`;KDVvCoG;9@9C0zaf+6M^1E1jn;*DOL z88YEbH~wq}{&i$s>$-4cO~6WPGIsSuI~exPJGe7l&q&rY$0a{yJYVltG4_v^-Gn3Y zi?Gzxspy@+?cXBN3gY_)6ENZr@KQJW_P!W%(=+RPv<{5be39%-^bj_mzt;03-0v*Te)lzA;Sm+P+Jxvxgn`}ze!z1uhIC`v z-uf0afI~&2X;KQSOWe0PCI;>)DZ!gJTc`;&7&l5xzE5*@m6G24{@S0N9X8=S0SglY z^1F$s zA8td!Dn*PMIPfj)5%L3b+_8&5pC4W-vOH~bR3&JbD_%24}( z^Z2n5-WxD;dh$_Rezs`cDc9*ipQi6>5nWQ^3v-2J@fOS zN>yZjDT8O#xPD-)&lv~D-QZH2S=499{_U!Pn_{dX;(~EVhD!RGv0+&KVj<`OU4Q2R zt)mUFYMSmB*N4l*He6}v0$weH9D~4Nmw?t8{32psQee#2jXxm^4N>Bw=>T_XdhZQh zfE3YbE#SEEZ+ceiXuqidLs~a*N@yeg&;h^R9%~`pc+6X>G;e-HA4)R|2g3of2sHE+ zPmN}n1{zCN-|Cr;v2zU`6HKZaR>v(peW<|lp*Ra6UOhq`O6D6=fGBy=I4Kbw^Y;+c zDS2{yM?bcEB(*ZSXArwOZLF7Y*faLX(}6kncHQ0p<+?Q=X&KW!&o!(QbT*@vf%wLN z7Wl#I-VTagDHhK$_5)B@QtIRWABEquE;U1)xjyTf_o(v{7WXFeS8p|}uTY#~oZv8h z1L$^&YdH)kX$`nx2mewdOoyW6A~)rcS{`Xs!EcYD@XVEAgUU-}t+1CtE`Jeh4dXAx zhfbiZ2bl9*3vLu+=`0xX`E+Guhare%YCLswC$4q3-RuH>REsuVg!$O8D7~{l8_29jXuhG+h-N1jjSf zj&6~-GUmq5+C-o7TN|sJ!geeQ3$!~FL}@11Il8vA$t9cI1F`pZ`m5dRi&K^88@Q;P zYX~%P+^-8YjrrePS?@swUnqh=Ue>OCvm#2!Er=Y=P<}$@@t!_UvwF2TCW2L&n#4)E zfIo-hc3U*oDO~#k>Y`&NkCsy;m7?tinA!H`1F#3gGb{$I0ZqV?GCutK>QxuN>yB!< zu~L?_D$zo^#w<0!DLhtrbB9Ow0Bs%SI(}bnYWZhvd#ozMm4VY%^w+aF%b?P~1ar8> z-_~IXnesDGWpG+jxntc)ckzb>NrJd4%iX~PD~1Snx7I8~-(Cg;Uf)`T>HsS(sOpf) zy%2XD{D3&$7}AX=k^UAmK*QTQ%?1~ftANL+gu>u7VUXDFB0{b_#yoD(>soLTaYTyG zn)A&+qC!px(u~kmhVlh#P3L4BU!c+O00UF;8@W<$E9VQV zKpN<659mzwIEvHmOFH8zK;;istSF2pLVH66?De2^*4%0TWuj7eK4U3vrvV;;l#e06 znedEzZ}5WXHasp59C!FF&+2T3)iHGuPfS=S!KWePM+4bw2jcs32c-5F&*Gn&t!V?p z0mv_a;2W&qKr=K06Q{%DeKQ?nh5tCCS{Ko-5N)2vaYvp39I%3Y&yi0RwZ;_CrWiI( zstwOHTHD){ec6^e%zb=dz7!=1}Eh$m{1C1NB&i_9`bKdZp!r%T4J z(Xe)lfI6}WTI*`C3&n#%%+SP;q0Tg+rd)XtrtedP-qtC^C@NAec*|4{%g-9JSyeWM z(1)HxD|h{TZ`D8&EwsD=^8q|zy|;qWF<#kh0gU(|hUqn-NX2rD|7b&SGG>Yw`pOnu zprKf)rN1KTUuSw)^)9Z`>gvO#Y=JsNQv!k9zi{wR0&5ysjOEGu%0%A!ascn&i11!> z?>jIVq-q*dGNrXK<682xb}X8o7vf&h88KVN zQL)$aX-5;)3Ch+gWYwo-;>jY=!$L^NS}{tkEyhu9%%3skyvV;@4Ewjyd|G#520L!D zCMX;UCY>TRV{#nxaH_^5h#l}fO@N|K%_f$)fM!-kw-hJ}wYqlv1DpQ@LvtOa;T78Y z0^ii)Ci6WAaJKLwT?N_)QI=lrj;s@B3F`JUNw}`M@|mFiv(nmgy(Yi_#y3cN+W|7n?;Rh3KcEVfu zzhwuPQ{ovB*A-`Am5Cyb13QtKw1GXxH1rn(vzE*^pr`m^u|n)btgxLah;T_*}sx-kycvl5%8 zg8_KG&nod~)K+8?u7~G9J@m@La2*YbiOd;b>Ym}8+-HF(hD)Z9b68fpD4+b&% zPznokvDL8s?B)?mkH&gMhptQ13*Zx9IC1pH^(oTp+&F!wSfc{8a`DFWFl_~(oSe8) z_@l>yHLemWwNTlTnL^OAtkn#RpiEBy9C$<%@F1t%KHp{Fz56M7$P4)!aUZ4#dq?Kz zU&fZP=5$~ITa22++YYTR2Xk%YU>!H+T19~oRgVX&pQ;@BlR(JS1qb4-x+Oa^ zOCZpr8lTA06rLJ+8KJmiVW#C;GrEWJ@95oXa@8HGp%Gy?^uiF$rGizkjg6f?1by?e zIhOE&Qt{?B@=qWwh9yE)KTF8;&OR)iWnrj*pIr$+Irdq-irzNIcL|*LG2x$aX%ykd z-_0@g+rZu{FpcqCWHy(T{2bYs-BeJ&OqLF>5|h+ekLOOD#*3nmivv&PQBs}<--*LK zuHYAjyH|Ye@XU#0o0*YQsEp$|q4wm8UO)8mb}N4nes;&0nvn?8ONp4scBEcaqJJ~7 z+*e0x8<|aBvl4pEogj=_e=!3bj7#e0f}@qw@zlE7J0W`m%HX-Q($~qEI#O$gPyDO` z!Wfb%y#{lD&0e2s>^J;x)`l!A2<$k54}U%CC^~c=8nP|y_0D7$z+R%V4618|r*-7H z?>nrD8Zwl2MVP}-p9(Q)s>utqW~GmItHI{EX$8HyJTI@!w+s4iORY0Q5)UZlC)@F) z(O%>1)L0mUo*E8)e<)xVVKnX?p1)0Ll2-dy~Q8C3``rupO`uu>`pc6FO+- zVcMEeSy-(kwudP}0g`8N*aF{R?RX|pqt`uoJf}9S#G}a`Hapq^ zB7gkZcp6pHh`l|%SvigdbWxPUs5Jp2+o&XJr$@W2 z9UaVJ90_9%7N@MF>rV4T@=-g7myM~Kb4Z{o7 zf=Ym?+6o;q1V%V~T%^w^kO(o&WWZ^l&C=a^_wS$asyDN6P$&1@MQhPAIUkumK1?IhAHXt*p6?JEm2? zcYoheUWz`Xe+J6zS&Oy(9H7Yy6?p^JRvUv^vggw+=b#tx$^1}GP2%|ekl1Kxjuc*& z%u}T_bdX||t)6blgPj(D`*9ccW(kL$4dS^h;P`U32T9$G@OycZD_8?NF*YV)ekShF zqjZ^o{>sFamrOOZEX#E4u{A~^klHHFipK*rYE7qyvfiRtuTAC+q}RVqMntF&1Wdy~ zE1N9av^nNsaLfx=k9IqsF?`oSia5U5VzT~DCIMY;T5*AS=G75w;t82?N)DQE6{%B{ z-T>3yDzJyuPNckA=&=~<`&wt>&=^pVT093K2ytJCCBn-A<`Hyl8S$kM+ zVK7xOu%OpR^AgC2DaHw!xZ2?|)|85=)mo6LPttI3!9EGchXbe0MSGda>g$qQ=IT52 zvI16hvsJhcYPMeK_tJpY{uMXzIUoLS^2--mE`&=z0n$ZO&=_lRuU^Z?CaxU8k<)K{ z+xq%pA*1~5g|_)am_OT#qFwzCoktvU>f4W`yoJQ-eg9 zr%X|GeiT?v-Q(ML+L1opH^eg190##qOLH>cQYVfDK@&(WOg)?_R3#yfikk_b7rnwFUUBmo(|2fgyCgq@xm-HpZY>&$A%AyvlwF4Blh_OrDIB z-#Sxy*?-mk&+@7A18jouZXs&roRv;KU9La+%GTpUkFOzGt~ zJPZJLlIn~btuOm~K?|rCSZLElfUuVp?C`ado1d;*-ZNkHd0NB4uJ0XM8ipp8N%N9w zH_j7X8f4kTIujGOmV3;E%Yz@VQ0#xa7_*Z^(_m<5C=B(6@`Vm%1jg& zG)^sNYD5+$$T470GeXk=Ok|W`Mo~pXqt{41F+UNSbTTopoL2XUCtZdq;PBeh{C~6Vbf# zrQg1@K)p9DYY3)IRBn4s2cxyYG;o9!c_;jSKLfFO*?b73Y8SSjuV7i%Ck{LJnp#u?KpYA3&>z7+5FK2!c zzvFqB?l76O_};OkGI}G)xu_t*09W-yh7lFQTW6vY6B25F&V?qcmkd_edp943hMW9z zNUA>NqxqKNq1xl&@@d%_VSdHc_grW9Ct>~DXxjc;>YMITum0s~a2D!kx=uobgVWeQ zhZn7X6#?*^K%TvyK%OsL6B^kzaF_9TQcU+^o!!Bz*=6>k_tNg8?U9q17ciXwX9t^` zwwxy)(jG_WXMpfZF>{B?NRD)ecwCR~Wvnk;cG|{aHIh?8c*ycgTQ?}I2}|PC@PQmD zYD7IBqv!N*fliL*g<%Qmp)m^+7E&ClIt9-(5b*VP{>oC`eElyVX%4Z^pm;O3qWp>e z?&q&Zr6mbU0!Ht&`HmiaGGW3G&q6RdNi&6R_wMNc(UBr6eb5$H~Xu-wf z8`Qd2Ffh(KMyP*-zV%ZYa%+$Ebtp&(J_5L(mNk(Yo-+;e}!RZT zBN(>WVL20 z2fGd-c#tkM;6L#XDAj*Jcd(5*RfO`n%J?-770dx;|`29?E>TjZi2zQ_5 z;!x^*p)irMFYDq7(=1x(l8J!{7k&(dHu0k?k5Gg4 z49HQl9+1BBubC;NF`$imAGY>+_VYt%0kQD>pFv?Uu1a&YjS1fv)qd=1R-QWd=> h;;%y9R=ugX@ZP~w!R{oD+vbAQViOJ-Q@sNRv&UP`-?E_wFT`ybnW?`7P& z{_87oCPbb3E1z8c-s!`V5$#Rb&s@GE9a;NWx-ne6;d|tb%ry|m^aZF6K;0jGv0n{L<1G7Z#rXXkbELa)fF_XH62ncAe!#Bok6U5WlJG@%xU=~xSam};X; z!SZe&eGk(5ndUInT10k-qJ`fDC1d`{aaZCWhR&Is^0A1p)GsEG;(T1Mb--GsVsV_< zW-~Z7nvnHUj~nG+>Wg#^SpqrrFU_T`dv$1^fg9uA)X6(a2t`L=^y&mj_ROOlu;&=o zOT8>Ilp7`EYTm1j3SV8sU#>^`Lcv4%K6J|s9X<-rxpT&lv&X2)s1%O)`a9PU(;}Nj z|CU*PN16oO+ddY90s8=l>bppWj)BXHTp5Yf;j;$)gUD&c=+KwcW|5nowM zUON9EG*4sxCFmv@x>EYq#cHnmN93cQ(O%KURG;Lq1lo5~IU5dN%QUfiauhGTER*Ou z3KbdojOXjwBahm@thb6C)>T+HnZdZR>J`zI#1*%}=I>-apr|4q@AMlFU9;Y|=I&9} zi>55o4T?RAygf!rziLWZTiST(c0>~9A}pZ{hqW;*fTra7I_HV+^VyDx7`MBj z=x+as`A}V^d8T9P2da6=K(bbu{R09~Jh`a_`Q|*K5(3%zRC6eA-Z?QADExdan^0D= z(c{Kpql^7C|CU%15gXdmP|+Ag=WU=Vl6>P&ioh*^`_RI@dGfVZ^VzT?8q9*}IJ9JZ*tbAU)|powd6b1n>5`4P4?q299Qg#>Pbf)#L< zaodNy2n?(n^)4Ohsr`7Lob(TyD`et`zuUaOK z`&J!AuScvnZcr(&N`2#zE_^}xEqf7sDl#F&fk^t%qvaBf9@;?jA^1gC@Z^4O*O>VX z9UYOYobP1y`~rC^k61FlGSu(pk*P_ZgPkV78w|4inI zo9sk+&?M2gv1lI%y%%uEtWD)Z4-pw{20Z(x2e% zpP&=v9Kla#uNXS+gN{h@ed&tFZ?~tDi>)sQC6p&9NR`%2+jNW#53z16;}Wyf*S~<$ z3!k^(>_|os$hg*9;EH`&149+YxQz6V_`%)1yMiAQg3KpS9vHunO5KOQS?8y<6}RXy z{%e?>mQ>6f2^~qxVXUG(?R807CVl#8d2<4 z?;=gYlz*!X>>ly-!F`I8#;Lb;0y#Nz{=ItW-8j-1;#fn0haJlcb%S}aQQMlPgWzR% zs<-n*OE=!w#S!)-kVg9ifz7U(xU#Xqp;}?o05x=6BjXZ_%CM)yX9|}k zZ*E#zx-0`;>W9C-Kcl}$BGP5KnR>sNN9h%RW=sIzr)~+l5deHV++N#Xn!^`J9z*b4 zf6j-3Hg)jcU`}IMH5YkI$2uOP>C#4pn^>DWSKyy!35maK=62g7hLZJoRc~HY526q)rvW>bI?tUlD4@xBHrus(s zhVR(l+t4X`zCIx$GVI|GnPXmaN4N*7`QZr}IOPvL_8ppZpxz0u@On&dSSHb*`VInl zVD=_-++!VN6!JG2dONl3GqQTJ*W#gfR}E>D^t8IsITuGDlBA@TViG!H`jmP6!d`px zMQ$Y3S}{^^GuiK#3Zny$XK#jVy>is+BQLbjDshi-RaqsLbdc4{iQko;?Rd1M z)+L{Dz*E2wK2*mG0KGr)ngGx+gL#ka8?6U{RFu7S2guPCL$2^-Z?~|E3pU%nDSq{Ht#6@v;Og8fh9(;rh=xFpzcICPT-wFo{7xl6WvFN<0oa* z$HLDy%l2HYB092k)o2Y!;{xhwj$4X~-klf>g0y;e3O+4@S#Zi4LDC^qC_OMF59-b7 znCimJoHg8LOI#2%Gh1Nr@Kev10I#?5G%JHLR)5P9c}_mM$ycx#n5NK^UW%6JmSRo! zx{jN4MA(H_Iuv1dQr4p^v{5NKDMj{-QeJ2z+pqgX%SCPa79vfaqg(qLLDKq@S^)mL zNM3+N6k`%OOjqV-2FIq8d?PMqusNF<;&DA%f-Wq=s*+BmW#tF|F8o|#AMr1;#TdAL zlo~SP#0#IVwqfe3--A3RfBgmtz10}QjM_08d1)`xK4Y}>tBWSiG~IccZJr$Kl-E?- zv{j!+f);=elcsG3+rP^fP@9fLM$K$``($AzKPhad63p#yEaC=rcM&wrO$*C1OIQrb@5XeB=Yt_R7 zv31MD$MYx66u6L}Plg}3&N2P)>=Gol#Et4=o(z-0D+4F@7$mZmTkod_%2!ePTv$tD zI&|EEPa!A^;L_#w-+=VRyQ?A}!po!=`H)tODw`ZLEy_a^63M@ip^rM>TC#;|!znY6 zwA(9}u`!8vln{%3^t}bc+k72#CtcVwdGh+B_##f$u1zBG_~}G=dyVpbQ^5BPMDh5r zb6a4E8tRABVU*RwzHmf*@e&O4AjV*|oL}80ZN=j?b)lTv<@I0Z?A(@5G_bixRQG%s zn&i&!2^qj4t8p#}O3XCNNKNI3Z%f`yKYpaR$jP`?g$3<-=Z%#L;F#O>{^?B(} z+-i)i#3WTEGg;uT#lMibo=YINR=mXt%sRO@uc)&_RsGeo#XWcR2bX!lY^ptScLU&- zortUYG(9<^+Nkt}8dvovdnS@mq;{K6hx@Cc4*@yosA#{=vtJwTf^a&S@KVIx!h#Oo{l0RkNe7{M4U& z`T`kh;I_z)uo+rg=EvOZg${1ROzuj>~62p3K2=*Yc3s9n?K3opw=&ZivI8S-Kba;I9+p<1Ws{Ejg{dA?#pe6dST z_CS^_QMWT9M5YmRgum;}mZhDBK55*hAMccuCy5K{x-Bt*&z!63=M?L4KTC1)q7WZ( zkrBK4x0ThxwlyhCg|}VVSF`o;%R!MisJEsThnyWc05w;MqE4x5G_|AkK1D*eYdTRnu1*`;A@qs8aIX~Y7kZ6OqHjHD__F%7ZPxkI~!funS%xsf^ zP&mW8WUHcn4=6jf^mfPsB<<3&rd5YIoF~jvT^Dc!t*ETyI@3_o@wkinJ@g_C9>lc5E-G2r!!hG}{vq`?i!n4|h!_>c-Vdmj7fs+v# zjT_HD#w!aXZEHAE*UH|@F08&DU!j|wZ;{-YF|vz;$N0BsaIa{tsi;Uhy&ctQ5IZFq z?nD_k>7Y?FaaYXxA|AqDlo1{8R-?;{b)x?#Qk?0mSyU)~BXzpZy{|y~{brTSK)qxs zI&5skr<2i;?v^a_W*;Bag#9$*AQ90SuVLl0I(%!^k5dB{fq++x3oOyeq&~^Hd zX8$?5CiBc|RA9@$TU5A*7-PigH1N(v)&Y}3Yzqpr+#%!6&yQxa?$ z^+ZO7XF*$TRWjmeW!%fZlsP2PXtKBR8HWQkA7~+RTh5Or4tK1anE!IM9Qxmr_DW1I zcLq{C3_l9AksZXn9ORqusV@9|{*E#cZesfQtkR$-x=5`4{+N!%(!F=e$~)mkCLd!G z{8G=`nY^!)M_O&&82gN9AIUC1=66&Uo)h@eZ{V#{gXT z8@;#dr$PpF4Pf&1=VJ8-y&vv>yMnSbv%xr@ynhYsY^z&+TA_JSo2b>??+ecF!0zrg zpNq+y!FXfmYNIWvcd%3UtlE?^+lg7_N(PJ})tB48L#>?`KoOY|NpVc?jfbq?FAPZr zu1-Gewx%A%M?T!gksaLZw;@w3%Mzb*PBE0ScQbq@PKnnpLk*Ox1YE)3C?4Tz(LSLP(tyR%dzL{fiCBk z38&Ts=j`o1VQ^%nrpU`+-j?Wx#vH4O9}E5?1GA%O_9qQz`6?@_6MCnzX|KhP0W*o^ z&mU)Mc>cVsj1n!%^XF?d?#>nDQm9@XJDgCz-Q<14=Ayca79tI8?C4FpuFZUz%RUmj z%HbF{oKWHc%C=fS1*U$_WlX^1`}ZwtBnajGW%jD3JnpgE`eY|3Cgz%Ua}V_%@tN&9 zvecgl+g3!XRdt(jG6i`^>{couV%ifFXANF1V=h)jl1JJt1B4F32wetX>`smuPdAFc z5PVovy}D?cn?P_oYG6*wM5Sw~rI@8*;d!l-2UZ}e7Rcs+)o=*9E!RA`ZAe=3#T{PaK|( zjY+yz?)0cY)H{|Z@Jrm7(?Y*cv+DVUSvvkWdgr*hBv7lSn0>5~;J5oInw|L{6^#Q))=W#D1?3KBjzB*^ZP+B)3Z=$9qsvGF}-;*ee?fSWbB^ zdk&)x^^YkoSQT}|q=tzupOa3QPY=;CG2*!5R_U{gte491y)2!%fx}R91ajhR#<@4! zL-ueb=zr03d=0TWc|N!;`+HY2Nbb;H@9eDcvj>HGo;(he{LrJW!k$$^R19k0nEh9c zw2d=X6mnJEkI5FO%PUlr2I<2>h$@i@_^I5sSvuR#Qk@`Zp9&!N3|dD_YcGL7l9S$S zmPr6rjXD<--7O%-Cg{ern+zzMcuE@HQCJn!?ZM3Ngod3r6y8&w9M~Tj#)zL`Q?umn zV--F7v|2S(tQ&;t=GxbXT3{PFQ2GA#Md}F3Lc>?Bh7<;@`zP6d}T)(IWmdrbM zu--WH0JPuRuEHfi_7Rq-@{EI40$AW(Ta&aW!7%-^hhA1=E-IMrcdN~tQjW8`_1DAG#-Hp|aooW# z#P$~cWOOCFP$v7WYayIpjwkF+i$N;uiW2SSX5+E`5&wV;T$_iH!qH&YKFU>vOdIr= zs&WyE?n2(2Tl^98KfC}9xtaVbK0;Af6k5oA53+GyZZ!zng(?1_yfm?QT#mi&Ug;Eg zw1q$`6mz3=XyW=##W_~rcGd|P4fa8jwnd|1@QZ$ImK@Z@{=S;J_?*A5toakGalN8p zrjijYpJkm*a%WDKLWaM)Jci_w3^Jeo};2mI7FTT(iXlTxOR)8B(MKOoZJ z-Dk|DzJ2mKU7zm_G9nRjWjPj{qA317NODGACCSPmL?2caZ7-b@c+J#zdgVDTt3??> z&VFV99d-Dlr5C)iZ(v=Jx}nEr7UENn-G%YQ_4U{!a?}flbI{+=7FYo_j)UrJh?@!S z`l)*gT_QlJCXll&=$Y~zQ?+uqssTPZM49XBMTW65rsFOOEd0F^qBFB%W^rUk*8I&& z+k4IGyOh|6w219?yD_z$LB7#*bjoZrD|%XsGNp$`d?GzpU8tycRHm|WwIEp6Fpc3r zeG?gTjhXq{Hg)Hnv8sgY=MS5XGAUXG<3JcR3DRJ_ga$HjruO5Tpm%$55yv<@tEZ3{M?kV_A@I_RQ~*1nnB>$!82duXQbYb+t`Xy0JcP zAHh?RmxIz_$t;(Qy6~?INzXaWY^McgRAbK?I`5bpDr^XRLCkXyNJwf@N&EnhbQ zsbbDBb54R#gtFRfNP<|EJcO<))*inrz$J@7F_zZA;wVfxnLro_6hXytLEyVy>ea~W z&wb=3QsZvpDgm$qDV_wH3UU;kKGCC}cb*f(b_P*q{VB3*sv?eHE(fhgzU7`VTZ)vL zppmV(*4YD<5+|SvhwMuJpg;2*FUzyj=Ii12AO*p@k}<6E=m{9cQ@VqqFsnjT{nv3+Y z4tjW}nPi~TT6kxC)+{`#3OC6c_Qy+MxL&Qyh+YEVuB7lCFuo1=||9C+hsx z%^G`d^Wy9<*+eWsT9*MAP)R^KD=ZmDTeayO(#|E*aj;EaDLtND4HXYy>%)#44!g9W3BH+;mZSBj*P!~{TR4ZF4R>40&v!!dJsLT%=6Si+$Pvf)pfw)5L%ak{;S z<#<)5B2`!QqS+t1mLj<@tScRgjULc}ugjv)2)YTS4R{hRljRT9Kr9H{L-eH zYo`nzcEV}fS61`RM+^15mDdEbvCy?vBu@SeOGQ+DMitE5K9|t#INOGZD?YLXbJz1` zK;T#1b}=2&)(t&sR0xLtZy9c6Rljd{cK=ApkEE>)M@D>USI;J9V!iP;)C47YGw)oP ztwm*kn&EztUfRZS!dLD`k^t1b9BY@iGWCi-hl9Eprpk^sqh_l5%RXm@sx~!zVWUW3 zO_Oz_mCi8~*jB^f8N$wr`;Q0zNl*)=2jq)~zeH=YL8ec#2 z^Jd=C+%QEHeC@E2N?49kb~Q)qqAaLI6|y=8&~^oXi$bGY|EBWwZz=TNO8?_AWR*X| z!pM0pOJz>wM<4J_ToFA*nPGFhja!PIcAWQ#qjUV6F$vnmTYT>(>JQ#YbAyh1d4+fZUrZk39P;@lflX0}vA{jUCN;ZR ztUn#V{M0VKVWwx|X4w5c+hb`;dbAC@Xy3BD)sonVim2FX=+&w$AHziQPAZGDgpIEY zKHxLOWknUk%Qgx+6#T4ZbF()2vePl5yYFs-%j>u6e?N5$KJ2~SK4kJ<_JDU{@~@jC zu0@pYFH&}FE7)de#*j|SPzQavdw|!7Z_04`qEn>cKM?3i4`CfGJp&OpKEjBgJF>8v zEfzhOtm@Q_6{vbB9}h2EhTd*ui-S6eq%T)5M6~Mx zE+)BBJaSRnPRcy4aMxNrsXv_%-*M?SuZr|7>Foy9sTj~}n++zESNVFAtcL!XdR`R5 zlEy*ja#ih!*eQWBu9S6cS|1^nqVh3M*jYah+-Q;=%;G1?o;qh!{+V@yQXD>9CqR7EOh|tN;hvAelP)=WYxAddPYT^(U*Pj zFjJl#y?!_zD`>@;*BYX_YW7ye-cGYPk`eG2G*i4}$rOE5Wp`e9T@BJzX5emTD#AY$k2WiewGY$zyoFyWUN6*bA(vrNbb5^crLfUqD1cNIU zynDp#7Q{KkB!clonI`4D{@BxZM)V8e!R) z?Y^}!zBiiL0rI33Q`Hzo|K}J1^@x;hcKOh4-MMG%q;&6@>Ez(^Zd18|Ua?cl9_?;C zGlN?0{Bz7!X~MgH~;D;n6)X`y?r@4mp5aS7nuOUO2xwT6VBL zMag2tAu!;y)vubhF^nzl`-d_FraDeQOloBkig%YqN59F?|IF(P-T6&s(}lu5E1H^Q zGlj5VNL}~^O74gaNCiG@$L`cMqqoywK5px{v;>@y42i}NUc8s%@jkLPj)$<~V zbBWG&r03>MiolSMdM58XQN-P5^)Aa1iuowA%js{C=BWL2=Y=7x z0d;4liPMc?sL+xZF>_lYvuKowR&di+24bJL% z5Ul5FLCM_^ci5}4ZgAk0O=ohsY9WXG{RX7uDfoyE<~A*8z`w{LAod9(*)uwbYu z2h08&e#=DEHt(0hJHefvjdmV{Lmg=}e@9_z2&H-#XR-EXOT5}hoYB4D;1S!D4#F%E za;gi-9ry33>`vLFBCy_?n1P%0%ukZs{xab3K~Z4+nb^duCAH8-^tpn}hM5k~iU+YC z$d5t%IK5|@c_1^7(qhquVR7}i)dJwaQj4a7xw*|K>^J_J8m}}OXnF5&?@>B(Sy@&< z4P|+x)$Ps?T*7ho;vlD^u6M=()8@&VtHpZCf7!8NOC8%viTFA*m_fS^WPqd(1VZ~o zbgVaRn!+A*#G~;Kon?)iM>|p0je@8W2Rr|KAys_(`cQg(5CLbvP9}=$fgEtP(5pau zqUQFBAdM0V{5xvDpcFYa&wCbcWDZULeT)6kz)!81cYO^h*TWLZxlx>Ev-+77IvsW9 zaXO4~GV->7%z*u|ls~Zc5R1aPFm+l>-sNM%@360~B4RJ?wb)oN{-F*VSQe$iRM8iL zO~~|VXrPcOvTP8N+1**LJqKCGS)(>V;&1{xsd`%NHv#Io4QP>OEEOnvCTdfQ!gf0@ za)U@?#<#1{>>Z|?7(186yM1JOEMjZ;b)6I#QQU+sX!}h#wH=4fL{QHq!B1Fd5A(O{ ze-&&)5v){@gUsrscNy&IJ@?OI&Nd*`QczP0Im(W8V+T&!a*w=MBsvQHZ8341ZK;1) zZ3{nG-nGi~7!g}$yWF!AIJnuC^$}jbwruu)k^Yh-?(+opPN2EHY2J|ljUJdj!D<2> zbfrI?<-mBf|1w~G6%rq+m5B%_^9W>pTIAG$jm875JCaOR-iw`n{4cY0N-&0H0>hUG15G?5)o?>hw z+4~Hjw~x41;ws=S&V7@Dof1AvDEHu0TY{1+H%Z(Jm3OzuF_HStmM|`T?u^=8B4AGdhg=^|n9tyHYPhCO`};ZeEt3zXyL`j(V7%JY^KPkU!SVBNfBElF`t=bq3((-m zyxR2{`Ac)p9*;n>8RT(9b($z~k*aRLmlpYYe5$U`>m5ZVA4mJ6nZnG34papk~N zqjryHkfGpNnX7ln%l@(IW>De11v&SS1)cL9!p`%wPuah8FLli9-bs1!C7s_Xk078; zbf9Bkl<1_KuNgzjQk&Fs1c5tS|8IJBG^Q0Wey3?XYhG%BU|R9k#V(9nz0v#wa+vZ= z_o_l+g5$HIhVbhw>525K9a}b+7i`+fbluR-Zu!0zyuB>=~`>o4&-a z2Jp`+SF1g!MKD=<)8}hJ+$}gzQ(M|{w{eaecW|={rG7Fu(2)f5hicsW<7Fex5q*SJ zN?GyFF%&b=2UF^forzVgy9@hm!>Q-7YemlvsV5M?=w`DR7o(o;(wOGV#m#M{iO~Ig)Sk~_C}aC3+}v0s#I}v|)k`OJTO2qD_JVnn!_UO(Q5<0E z7stK3JwIRK#Lw)~if!M9_*Y<<5v)5=k9~~&tYZ9!OfshUAQD^SC~Y}Ky<8oj>Pa~D!07z(a?WB7=lF{E15%}zJBeu_JtLZ$7GG%PWr{kesP*i8 zv#1r@yRvRbG%Eu*tY*~pa0iTCy4WZs<(cmRF|dgOqwgK#>9su4Mj)U#gGNYP@gjw% z5AHBw5P13j*lWQ0s&Q$6-L_bJR6q;^rvo{-ibu`1(ry z42P@0U}OEgqwM4C8&^Y_H`UWUnt{n{WZxXknyZI^H}I=VAAvj=@%Cv`r2SVSfL*tm zw9%S1R7Q|&D%!}|nSRy9mPswi-^bH?2_Ovso$^S{ZZi@?n$I$Uz@8Hf^U zRXM48#Sn(JNwAGad|8cF1M5w3jdK3If6cqa`-T2;sapY~YHFL|VW&s&qT6fw6J0Y6 zb#MRGwh7`&z~$*%SJgc>>^9Qa{UA zqei;)BEcn{_aHyxj=wc2eKIvH6ejyPHrUp!dzER^{WZ0G_AU+>tsz@Zp!!B?IH6bB zI9jP)X~aBKTzKo`vHFWoEpA5FlMZUtdAh#GZ%8}flrKCq8JWPkzrov~x9kT9+^SQ>j-+~RO-sxUrk(o(F>evAQ_$PLQqc*A7R$`+7e^lUIbKl$1 zAJc(?#VFZ9x^R@a3O^9aD5Zs75t06p?$~k9>bcJvS(EWDwQU=W1RZ2y)RSKGuIfE3 zW1GbWrr-yFOJ%|87;L&%*5(gZBTp$l9b`uJgPWk*I7Nlae)@93>!<|?CKv)}h1DNa z=N>RcyN~ts-BxIg1~0yZEPdcoAY95lSst9=`FBhYa;lnZsHfuo0B~@87QcwzA-?3^ zniG*3UejoiYwxwi)VGe!sTyW)r_c~OV`F8;IO)Cgm)`;G1^}*E888^9 z8KDAr3y)ePEx+EoZ}LVLw^%))f!VPvS-UPRLYGE*iVaB3@GN2&Inp+Tm}50we^|Y) z!7H)^#_iM_Ld^v17TXe(#@L!XYiWz{gQ*84<<_(vVq*j%CC69YCa-AhZNxD>415x` zlC=mN#7nWW)h*DdyIvFLZGxk&s4fmcN3?fh1F%19%>ul zF$b;-C(aLq;r#FLA(5UkmugV&3$P*=Izs3U0z8ZdcH(o`_5wXf9}> zWv~N-d&y@cz?gQW42%>35_(r)p()EY>;+L05^S*u^qdloA9idYxfFQkN71T#Kf{-w z5f+w^FOwCv>V3YSMFSIt>UYn1N5$JkRb?&{X=s(#ZdssLB!sT#d~n{|n)si!R`;Xn5I44hMgl1hRjx!}o>X{q*bq0$WL}tpET3 literal 0 HcmV?d00001 diff --git a/extra/walkthrough/5-2-02.png b/extra/walkthrough/5-2-02.png new file mode 100644 index 0000000000000000000000000000000000000000..001f1f24704cccc505e776f0a628025bed0f555b GIT binary patch literal 17601 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 literal 0 HcmV?d00001 diff --git a/extra/walkthrough/5-2-03.png b/extra/walkthrough/5-2-03.png new file mode 100644 index 0000000000000000000000000000000000000000..63bbcf2e09e115bfc8ac53b3d7eb8fd830854608 GIT binary patch literal 17794 zcmeHvcT|(v*KX8NM-d&yf=W|RKxq~vbgUpEU<0H^We^Y{QbGs;Vg(cdrA4WuGyx$a z#ZUrR=n+r?L`o7VAwqyaLI@?0dxB0=`M!0(d+)mc{MORsM9ll1_ndw9e)hAU&D~RG zMxv{?u7*G$qQ=LMo`FD?J%vD)j{dw9{EH4!*BiX8x^~=!w7{Hy~7Hjz#qD@U^R?TCU(2; z#S8KcDgMvTkDd8xg~%$k|yKE&r`GmfPuqs>fd+0Wl)XO(iN3BA8vb{@dH`41>+ zb?}DU7_gxe&RO>mGGX+5Av{_rvC zL8^PbZ5%s}K1g2$ThA&-%+>{_FEvIb3E51fqUCK?HK}R^i{nCN`@vcPo3gtwq@@du3vA zij(xcyoX5d>3!_cJA{pF4^@~Uwfq78Ftw3Rtg{_c7l&M5;VPWZ1C76i?>owMkVJnm zg;3`DH%H_{*WCu+l$Js%_3LS~E$h%e{kb3Q9n8P^)|U%3p3&Q~XL;hLSI3Fx(l{CA zat?UaaIHu`dz1;gMpUoPc01g9QWkDZZ3!jkM&p&y5Am%^-#v^=+=^|6e9?05`ZivK zi}tEqe!vSw6BORk;B+7>RJgML#qhhUeOaZ;(VB-6AAv}FWVR&KsiXwpZM{5T@|y+E!IkkrSA)$ zv^0v1R+qCw%9*KV*7qNiv~Va1`=jTQSY|Hq65WliLJaVU1Cd}S zN@lu?PP;%eK{Dha=^1+;E?unV=2>g26uab%InUoUDTX%Vves4J%}p6jyTF>BuvP%C z`B8Y~^+MB&BE;sKQKNorDVJS+q2Z$$2U`3^my^>=2Gs8p?7W|7me_4l63ZBu(gFJw zNB@N#?A3UuKkz8^6wwweM#uwbjGJn!&0?D$=lb}b`hMNc&}-7G^HThz*O_su2i-Wm zTbw>1IGgw63bwP(_b;rfsFQV_5c#bQKX4o$`pFbEd2h zfztbSUjLjTVY|%$lzsDnT8BlYM)?+eXsub(VL=i(BMjG(p75MX+RBdW7tMw#2{$JO zubjgx3?}SE-5Me{*lL#$o-Bo2mtXJ+|2%{7R__gC20|EZB?G$m?IiRaw5IlehhhiY zP!H>WJ5)$s>``ZyZZ6M=B%K~zV2ijXVw$B0SwTV}AmRFLcy8L9n3Q>Du?HX(! zAicpn|NNXma9RR^xW6A*3VFZHT@|?F7UA%B>#Y;^H68uxhwQLx8%edO%_YxE!|1ZD zVIvCIXk7=X-6*u(ajK2b9D&mVrpWBijb+D~DKBQ?uy*F)4BiggZ~T> z|M7$WikScLga7e^|M7$WKk%Sv|J=K?DUAs)4Do6W_{lXO)i6A&V*3BZw z1o019YX~^#D;StClBoA0m}~cc7e@YTtT-9Rn>>p$k?{;>8$Pp~*j#Lv>i;A%C;=bS zKhihFZpZ8yLK_w0Q)n}H!C(j6x}mISVd%4Ak)W@hP_?R8y&k;@`VM=s!Y^DiFtI5y zD2XLoxys5?$;im{9V#9t(U4Af5+iPQW?pOcR{nG&89)*f0H9TfsdY@(zoO|s18awD zo{guswi3~4>qu_IZUtAv;!(pyJG1EeFwJa&m-xlE)v{)`*y;v=8USX@K1)o9L%SXx z5rv>4VJfVwIer5G*NSw}2z@vjQ?LwjU5My6{R7-L$~${%XswmYJWY3UFp;~zJTpH} zzeLw`w(p>4X?S7pvHVa*a+2u+a&(%$7IZppT@$*>VD&mi5&Su!R+X+-lmZhzM7^!$ z2%Y<5EUer(n(={BXlR3b@~KpvoEZEN>!!Lv0ZY8J^No6i;{rg{Jpk?f^}t|k*<_+0 zl@sDrR3n_dM8g5`D4rPVNZ^CTmfkkQX?M%JF&DWvD8=HSUX-q8qj)B+G0ZvU7`f5$ zRpERH!O>Npb0i)O(wHj8FO!5o+CO}UZzTIi zN{5Qae-R?dI9I6%A0J&SgfsN+EM3&XdQCKLJ+WVroov8=rVK~miCzwyOaUZIZw`G&eJjT&3ViU!F*Ygb17u_`a*)>(n#ud z3X)Sh&xx|aQz6U-(nQg~az|1?N&XQwcXAJ#@U)z%d8C|pz zzKq;J;F+NmxaFL+3yWa|l9in(VtzTnt<&!MTv?t{xtINV{>Krl1cvkJP|0|HuAl~5 z>De~8^!=kg%*`c|*+?pem&BvZ5xejL_T)%Hv!0cHLpi%-aKf832<9;}lxvHvHF#;E zVzG8Aq;k5eNM#>H;c$Y8>aKN$>!U1WE~Fx~C9w zpH+(LX4*9kI?RvWK{hpWMh};}FTbud>*h`3j}f%Vi6IZ&-qtFulv#lyvNv;*YoUG_ z12hC#mIQTqI;EVW}F4t&Vs>*UK;&E&h zfX|V*f)bhj^!B|HYrfO&(}vcRzU07BZ!5v97fXRvi786wQ$*~^|@(3IcLy-WS{TyA%Fg6y8@@eiHnEMbJ)QX@u zTUGR6Nq9;m2CS1Hsktm~wy}Y&!BW{|PoBf(F-ui*2gg3DSV>Ft?5;E8=!=!>)6ULL z$AaWpG@Wo~k`uQa0x3KMRz92vn~%jWebwi;Ty!&OKQv{ibW|aQ|B_iMU<@>>MyT4Y z-E9Y6p?PDktE-(*sBD#hz69D-6TUJUzx3KA-Gwx)K>c&s{epVeo5iY9@tKjFJzmxb zabofH=1a5n^c|5_At$KP;@U-oLDA$)_#JZK<$`P33DCFk7ZKv)?wOIzXgilsAIi*b zYi`&11YU=8@*tQ6tFlXP^zmpmTWd5dMRk=YD$0WFu0|oCd_7aLy7dEJsxBPSU&Ja#?CI_n&Qx_1bT{lRSnzmuNZ#k(T~ptEM{}Kt>o$kCl&-v?$F8dvS)R zT4YDJMJBqf)qyiQFjEr7E>iAQYLZ+vaJDYJJl*8qskZ#k5i+0m~!zBP;$#FjloYU;J zjymDl0?T6DSy5C(anJAkwDkS%0wI}IN3y$saR?YSn-iQ|dHsc7ypZN%le%>`B$A8k zW|b^2-%fRsG#vHp4QeSwY_({@g0wv|4yv-ZNC#bZibC~pgo|f)b-53Z97beLa+KiP zSRTNMPUo8|eg`1F&_hk5loH$au)@}(*3XY0d0l&)x*Byu;d5(H5TJp0pCykkl4;T{ z-pen`xQ_Mg)>JwBuqk0O!jDp_KA_u+psO5sz}s&gr~&V6_t_fset;8}f3oHEkyf>s zL|7ujR1T#{eGGr+K{xFxJwVNh=;h3f^{s2jpH2a%XZ8f}i0NWDTjZnfgJFR>WN$D0 zb8gDB3gl6eu?w9McI+?UQivzuQyOe%Lifi&4;MYHeMR5eCK5Q&b;_(M@_O1Fsmlq)tQpCFZmY~doqaOh49yZ;Xa(#W+@NxShC2?p z3#8k^@UtS@e_>1h!8|SuT{P~luX2szDSJFpdmAvm)pJHONo!7|p1a)MWn%qZ%B%H0V>j5}78MWEbhqE61Sl8lfz%u`6}Zi4`l>)GB(z1x zxmhqvQzK%g_XIl2)a+Y@X3)m4bbRV$q%fUtxVogV0bJsWby>_k840LzU zO;s-SkY1L-HRzWz;h@xde^o7z*l~(TE1}9t8hKJOV?>gn5WL`^4 z?JLRFDKqX?gFkDy^e7pYe zBvLTpYaK+NFL0W^MK(|-P9)p)a#ZLk3E%?b99t~FcSiv=dz0XV`oYOtp#6M&Sxppz z`wmw_O;H8&-m6~Peqz}63ymZE*h^e?z!(JLR`-R<4bI6!Lni zQP%D0(3U6}0{4)0&>$0I6Nd}rv55KD86_8EP72(go(G%&;Jl*nJ6J{4_Z;7$Z)32_ zAg{FlK|B6q=oUnJvkynvlxP9V&%umb{Z)JTn68G3Ar?@OtflR0=PK)etIHY!r(sZ$ zoxz)cQ-(dCq>d%51m0#Iy{ZL015HMiD$D3k6)TgdJEZt4d6nnm>Pac0mFYc=9$ zgXfI(PPgzG39Xn-Tc@T?h1dE*W=7AL${#Va5On%4;vgH@!AZuPw;at=iI;`3+I`B8 z{^{*DxV5w+b)u!sXY!=A#vhxn;-H05SdWlO?I25Z$1W4KXLB5;fhy4UlMcX=GQh(ftYTnC!5rm!w)okHr%)wU{)fYVT9)BWd`g7Hwz5x;`ZrF3xl%nf5x{ zgs0n!pW9y?K`7&_sIJrhOyEA z;8wRb>-6Rvo<7HT(txgwZsPacTItlmMC6Ilv}PNWDy`xS{Ft}$Pdb~?r*Dmjdzl#W zUoy^0)i4sC^SjmOIb~i_D4B-~m4S01f1{p%llapc3qj;I4CN3!O>J!C8fB40&GnSN zs<|{*rh0awni8u<=%O!OK~DNUSvJn(j9WTNT!7Oi%k7_y3fEe+pCStI2yAlIVrl2s zl9lsqP_lAWoZ4J(yPajcCI9xvy(dIUJg+bKxYel0rW^e~PRLFK9SU(RK z2}O~-wwXRN4JD%aS5W4SS%DR7X#8_nJEANrq(>AI_9n& zu%*lVsgr5+c0PZ=)CiQozK|F5SE?3!n)D8edTB!Yapr*ycG!V<(jGRbd-e8vSwBDe zs0KLc{j2{Y+n56y*;?5DWZ_K za5tYzDmXhEKnJDQU~Q1p7<1+U(uv;ClUf*miQo7uTRO=B6}@aa z@aHms@^AzraDa7-2-ZbbuDHsrG0=0s1VLW)XPxVdmD_MucZRn7E8l0C*aX0}(HY8- zp0fHX8jmjXK}~m(og6RRBoGtUrGS)&EkDAwLX-nIlLAuT^G4s5=^e^BXP$A!Jl44? z`n_1QtWB7iJCJ1UkiT}Zj!8C-9{4rMa_V?+{|I-5d0-IVILqib&(1*eA@7$~xi$6Z zD*z|P3dbO)L5TM270q9Hw|E>{hxKj~`1^ORi$BwGNS^80vrU zL#0$~tf_Y{7%@k9ij7Y9arWHP@4XY{Ls5U6EZ=KH0KV6&N@ORO@YP(r>fFJNl@C01wiXO0*;%r|(#MXx$bIGqw+6jpik)n9&DLR@0GK zUW7u@^jm;L_H7y0U}yDH!u_c;QM!#1<6X(-h|MYReZB7eXY0!(*l|t^L#f}WKI*DIl{uA%?tjBw)uX;4;J%rPnjNFn@{kT@>NNGISI5_;RkDA`nf~v_o+uD> z@&h&#S9Cw3G$feSU(#w$(7dcL&v<-Kt$gYn^_<^`cx7XP;l5r3Je*fMt2jURHM0aB zFsS9*Lc6-jvR~xpgQ*L&w|dfT%b<$$G4xOroE#j{es90_LKb1{a+d!VFUkyYS)*V!p;ZDqLlqkVeiJ0K_(}W@2t;z%x5qHvwD%{^_i=x;~o^AIMNY>V>Bo{d1 zoR;zaHwY*Q5b-A+s!r2m%YK$n-Q?L>s%%-|TSaiydLTdX=ocCG){cS{REBa2x^yxr zK+q?RjBSP8?@QwkV`YN#mD{yyZn<_%zl~&Pys>gv1)rNAm<)4zOkFkKn>#}+ZmTZK zmaf=5-(U(~gBfpEYuKmKhwYB1>?WR_Q)4vg7$?2M95eUnr~cH!!w&woo^`fS=0l$*BJ4SU!$tek=eQ_-?FfG)-+wGf z5&1b2M_0K6V~8HeoMf@Up^+QNol{u_ff$|m))oFY_17|mSl(tzsL7*5VSH^N?&V_3 zgP;E%N0mGw?yp*5Gu~4f+m;153jkWf;~ZnyEt@gPN-eXYT&vrpoKiXeGh{u(BlB~> zMP@H5l5n!gkZoKOsUe#fa+9f$Y(?%l4oeVkRt(EasJ-qw7b(VZsM@K2*=P#(%dR)N zs{8jJo!eQk-Q;9jGye_^qXF+d+gO>U0V`m$EeI+7BGzlwk5Jm8LfQEd#Nxh!8iCDt34Lg z+GwYc+uX{uf6Pdx&g<0``nAb)3oco=i;E8jS)+BwI8wvPJQgHlTYQ0+0l zmAHJ(?AgICk7;!x?S$W~<~k=XcgsraXo<`r9THS=YS>8+_;IRZK+%3nlI`%uE?ynzreeoJVhz47j z^@O7OFE?Ja(zMW%`+G8H-(t|G;<&Ll0GGnuuK?g6&tR>=(`G&USzGyBi8U%(;}}fz zB!E*6sK~KqAYgo+`t7|7{!4uh;f>M09q{;HQed}58i|)E{Hf?D*&ST88E#lIb%pHM z(KKY!CRU|=9K3wa(mI3JHUv-8 zM67LUZaG#rU@w1(nc5Nz9e5V@N_d-tn0^rkW1d^Nbw3MbPJf#g)b4D+15rv6X*GmXO)(I00=bk_xFF$GwU{we`%K5FrB}Qow4(zuQT^>j_u^O za>?j8w-fXm$61M}$`EENEQ_EQn5|rfnRB%cR8NU>^3NHj2^^+6Bi}S#rGM&t9F3X# zgh#FSu_q&d1)|?@dn)NFjvW*PZ!+=|fH=r2b8Qa@MFv1PdoAq;TZ zSYPEfq}O3jjchh(Nbq$OPRO^XJsc3k^Ly+HZ;(sQ95g$DL(KY@Q#Qt>*f=9s9(g;- ziM)WX72!tNM%%IKM`v<&$iy>WeG4_d1S!X@}qV8|bRFTMrJsYj@nVnk3SxB-0#CR1gfzSfSVR3 z;|`c)T24jzqxnNwG(#Lg<{XT+bv&ij;S06?$8BfMTwCZ|+P8v87ma@CM0=b0IhxMX z!5xKcPW4k|qj%`pEJAvuW~#T`IVJYN=3EIBF(X@i=GlzWSjq+Jh=tk?z<}w4k}}e^ zO{Cryq-NZjbil`>nYbZ^Y}S`7FnqFUTjns17N7#(&1YCu#sDHAOk|FmfV20goRN_m zLNuv(NJBoxYhVwl+T#7gUe@n*bN7lejX=q2l)NyFSOU?=Y;-b7z&ly3JByn~swH49 z&K(v^x2bXcr%K!9oy$fqTeiWaA)np8sWTFNWV| z%hVzCXNNPncLF0>FU#MnUiDlT;?03bKI>-uBsys|U3y3lXZ8N`hz!kt#GoSIX}$0C z@dNPkng<>@@|~A|Oj6sWPrYC!th|2q_r~;;?Rp++M>?xaavFPw;-t0&aUib_cG~%G zPOqh;ktK6XCFwhIhVWLjvL~h$CXj=7G_a=OEqxxBVl>9J%RFR|{@@D(m&1y$R5!X# zQACL9owiaIW-a%xQNlG#66K@CJNzz5SkgK0&X7vk>czImakizjAnb7ja^e$acA3BRto_#KMpXH_*-%2QIn~Gv2quJ3 z{v}W0R^Ox*PT+zK2PcOD>lUzK<@2cogd@862;_pMyQ;*Nz)Ke|;82fzUO}#D7M8rf zWw;3v0iI!5K3+b*-nkxf&{ab#tf1a=vsUmxiW+Ua0uV+8ez-60X%@ zzlk;L=_JI_9}w0t#GR%;5sdhrdYYI=(D0rfK5`s0)g*!8x0DxqClYdy6{Swa%%;aL zVf4V~D;dtTxj`mOu#U%xFmK~|jFcwGc(_vugLOa@!~0lodqw{X!V+b@evJqM7ebL9 z&dy+p4g4KvC@B@m&?OM_Pz`MObN$A0L_sa)YEr0Y!ez*Xp9@M7^N1@;QOpB|mI?u$r(RDMbWZFM2s@o8Y>O zGT(JoTdfh!e?+e#x8FSq&?C}-ThLQT#*=gE0K?P9@sB4OGY{Yav=?(%eLnt?mbn5V z36_>QS8G)<5eYAk!;ijNk9w)yeymn|9c0hyg#;j81u${0m;DOGdUQ9~8H^K3*BZ@q z68sFUeJHvsPBJm~AMs>W$M@>I5!^g3Hqf?35XTN0;qWSXYB&SmeLYt$JxM2E%@~|= z;YnQ}8-Qj;{VzC{eA^&skQ%h6X(ALz(J8r1yHIN9b&_rhs^4=%=3omJO z5*l}8R&~iMu@3_+Z_g)r1om``32=h#jE>RMqs36zwCWNFVsWi<f*NHocAr&q|w)t{cQLI|}gx{c|0q8SuUc{f1mdZkdC0CN$Km>F2| zyJ)A~OHXhoyi4DVFWeRy7O3V!AG&7{zzCmJBP1euF9ZoIAtwq5OEgG4K|BjF7MpX~ z^DS-!F_tIAZCv*11!*9^T(qm$G$N~*|E5U68vSbEC9_V(O#od7vHOz|;=3-hNTU4%UZ<(ALu&6iEp=js9SViCt5l(@3G- zC@hi77ChQ1>`XW!1w=ky{%;neZJ?&qXfe?FJ|3p{@|_!gxifdLZ-|`nu;-Yruh*s- z7D2<_0j4@ap*|qgYI2enBrY5Jl#s&93;8jjOanbUgetg#YjMCFP=r5&Kz?8Kk0MN` zUClTprTif{Zn|1O!j#52Q74_7W9WO$sxmFkx7^3{d>}9}_@=n$TXhwJm{BnH?`jEsqka^~;BQZTt&|MZhRinJ zT8Sd9ICEPGD>}LkGd?hd`;=8o#+Kh%`S`6d$o8Hzrwb?88E#3B~_B{KLaC#*C8_nsfo6HcL%jZ+mM`g`On1G7W!z|xs zeNrEvY)n>J*$<}_2OdN*HRe6ev3*x^rz1eI4Jh*gl?T-D`9V*%g98==g`R8VS`EIJ z{KFhD>(;DDSO7Pq(r-U}IYjfqzjhvUF2sn>nl3J6k9dTvIz17P%{fIc?=yCuAB!)l zQVsW$m_ElI4F-ha{qKeVzYNO9vv8Jqq<=O0u-`Ae%8&_~tVs#zaiEsIz)W=jho3p$ z|9He)r;5p{2o!+gBsb=qKB*yZbjG}Zy%VxW8~ECh!k)6LcN7 zEo;_0Kc{YjMMsWHh`id{;O-&4y2;J1e{Q3zYq)w?w@TkhpUCyR=!{&_Gp~x_>jc+T z(G=A19qBU9OiNsGY5M3ays`u6zE~BV2z%rl*+F`2E~VU)TS*>5E7UupyKgYMyI+Zj z^4UsQMpmNANM}EZ&^lg7cvV=?A*lNX|HNNx2S4Ugg5$pu}h=D zNciOs1}7lJ>L$0!!!+d*s{GW zNHA$YU+nBQbRo~Q^hs~<7J=MX#g|0upM7Nv(k9}${=vTUe@RzT zu2O3~hAr7^Pd9f}FKeMfX=rEW+JRD+$fAo>Q|)Yjsvf3-+_8rxFP*>}lDgSOjKPWD zk43sq+B3?%WK&ohLHm%08TH!C;SK3gYZIUWP-8{jh z&l25jqHPaxNzlN?*^~Rcu2cP3N$BRv7$8-9`Hy~y{N`EJKP4?}t48FsiPc}47NQ}i zNxzYs#`Ybrv0NYXkb}*Ww)eYARRXh~&YY>OoSBxX81s5;Ak_`0=LS$NkW*N(wF4@# zm0Qran|Y*y^q=g-&xFVFhKHY9QGJrc*bWv>FQP6yC)hVPTM50B{MV*5mu>KQ=&zk) zeXu!x7zp6_ygj85NN&qO{yLn#pd|s#Zxl#O7Gr%%Lw>JyL_j{?*TAlDr}jCt3@$y0 z1^^bJP1y*a@_=0H|AM&(pDSbkJ0VTiNott6LG7)^*>rJ-AdZ!RphZLt>DXV+{z$OG zWTWZ1l@HsYaO+WRxU2u<=N2wuBb>l%q9YyOwl3pTjF0N@MhQM8nO0PX6gVZ=SWWbu z?IvNnkqHW_0I$9as%F@^X>iTT$($5Mt}V(D8eLI4f09S66imy^v0?1?WDS0`gEVjH zwNHg5!)=O5qrrijZ9nqSXrRV8txYkPp)Y+^2Y%Frb?}?6U8YTGoc&`&a&{SI`%Dl# z1X&8N$gEv{!K$!CcL}WfB`L6v5)}B+H=>k8@%MNF2kzi_=Ij8PMd%dVgZ%NNBhBw( zy6qN2keU+-afX{k%$)3hgy^02qi`BhCR79eo>&>dP_FPjuS=cfSoXAbpnT?RIf5#T zZJu*Xmz?$iQSN1w4D$sFXGbsR%Sa5A4-c|vC0%{XRAp(E-aIbY(sg05-j^Dzwv7sM z^f`O$O=L#kRcoY63X9c{e;$Z*oc?O^i%zYRpx(L_IFl;wFr5i2Z*w1W^H9UaogeW; zX3vL-ukC1qOmxDBEy^AvCniGF2z*QsmqQdhDBkB7j-Anh#!>>c*{jdSDN#!)Ak zmy8v>hSSJWN~Rv&vGN7BGW$}M3yADa1+!%#6Cc_d(*&U_uS3Xn^w=X=VHF_2Qt+K@CjUj<|y*C+v!Q6Wt&I7yA*xSMUwLTYHNX;Q6miqE2TM zN~x3BWzkG=0CkWBZHM(Hc7ts;%~FPK`9**D9ci*?tu+XB#A4H1A*1^cs`-Y17@_B; zSbq@}?fOfulo|JzM)V(Q3I8%4Xk1$lk$hxr(|hSCcT0~k!9(BADN1WYpa%-MB)U_I z2o6p|*=kXeSiGrwNNqWNzxk$&)@vE;B>UY z(0y66-RH1NuM$Z_6_Y85ryX z9h8=oYA>;&>d9!Lu{3UNEJ06o2Rt_h=&L}ZHfX{{u-i|dOcZ{&Ct!OD&Sl={RbSEV z7zD&ig*Jw}6+CYjlb?CnC+9`{A9QOw1kR@uM*MGC5}&m%GwW{Nx9%9ZuB;?r3yOCb zNFbqDH93|8Xpm(Mu$}rRCe3)7gd z0sKe9@2>WRBP=jZ+-4R{aQ1u~uw@{O97?Jkd^bp*R%It#b1L%kiK$7nX8oVAGqH|Ik zjN`@f%ET0~q~F-G5xa5S$39+r5lU)zdciINCZ}I|0xT^MeQg6CN7yO5(8|4d?`^$x zqG`n2I@fck$K;lu9bacX&oNguC(Q#ToQGVl-$Uiz5@%-;i0nJy>CdBu#||tp(BuEv z%3c3s`m{j3EWr>-+Ui;!e3RK?=e^a?A+O|Aq8(tyekM&tBncZSynaR<0j!Pi7YIB9 zXT~<}NS&~~=8DQ%oTE-u&v4eEb<;(vVNe|+Nq+$a9a e4}KP`^f~z8&gLxyB~YG)7#}k`ns@lZ&Hn{Un_vt8 literal 0 HcmV?d00001 diff --git a/extra/walkthrough/5-2-04.png b/extra/walkthrough/5-2-04.png new file mode 100644 index 0000000000000000000000000000000000000000..10dc2fc686ead0ad44a032b77b373384452738cf GIT binary patch literal 17371 zcmeHvdpwls`~Ph1cGz~ayX{g5#YSa}lBF`(4pJf2R>*0iAvsLWrorrP2Wdi*nKT)- zNF_AL7&AsE#26D26J`oC&L%S$V-9}zjM{B0``Yj4^ZWkw-9I!`ujhI0`?>DxdSCDN z`x3urx9yjoFZ~<>fqc1h$F_YC$n4)CkXgg?W`V!4h=hlNkNL-UxPZ^E!j)g2T;8;y z4FXvX*|}}={wR;34)>b~zY{v+a#BRh)hqqPzLInOk{_G8hLe9(KezYw&v)j1p8y)$udi5-OEx*% zV4q|~%6VlC5 z-m8&NUP3ml#{l^~5?WoO$=Hc9aQhAdS$Nt^1MfBX;oR!NdLseyw+VS6v#3_6FVtSDe5gC!<%({wNYcef zPdM{4$hFNc+#rypS$}?OijhdvT3TSYbs9^9Gsw4AHC3aU&ao2^c+bn%&TqtrvP73d z$rbB-r`IT{(OiJ?rhqTyiX}f>XlZ)yg3rbteKo-A>`V}>-2~t3;cOK=@lai|U(`0d#A+bC2CXIkbX3UMAK6(`I2(`_HmqRmABcro8z3= zGiIuMvwoqUK#pEey=2w5dz^0kBIuxNGMG@NKK|FvK=ut?9i#OR4_HkM_D4Wj!f@7^ z;(OcKl)Q`_;e$z2oO=+@ylvD+xd$MG5G#?(r$dRPx}{JkJaJ_Kni`AhUPlmbBq4k9e&FYZ6$_f&E$bswIhzRS zK}`+5L0Rl0)A!O^@$QYl+B`fs^9^P}e%X5cgI$YA_6aAW7GVo*52bw#tIhweOS@-X zPdL$mPU-ccFhf~eVT^D_Lke%r^fsvr6ED2GV_Tef!B&(`X}Qz)T;JKUQ&3EYs(e zPaq(WmoHT3N-;Q#M|qyJNn66%oZ)*aV*_$i*^+&f+O1Dfz6qNaxo=NKLVwZ) zTLX?Bp+*V=Cq+-SQIRWP9>wWLKT+EPy(6`%xS&o= zwTVDnck>)S&j|Ed0et^v?SHfOTiE?Sz}jpNjzH3@_6!f}AI_taqD7-jXLTNvy13#g5A+3< z(p2#RPkA(yFeSGvsVGna-iG84@ol(#?c)=a(x@(r^9vm>Y2X}}(eK(=HN(Zj)gtSZXhU`GcUZyla+F4w#tELkxApg^oDILws>h{H(;7KjUs!srA0@f|f?4HP)Wo!rAI1)NT_Lyy;B>j0L9_ zeFC|E{e%86h`OVwtc@0L!`92Io2P+jITxHHx-8nn(&xH`k=pr`o)vgr8Lzj-BoC@; z5QVL9w9p>PxJVsehT^nvH{^4<-csonIE%S-2$vlZpNHv9n1*}Tb~t@ z8h1@pzcgHblbR(f=nbep+rpjL1r@M7&hs9{k&gXJBY|xz9goFSPow?JIWw-#h&uTK z-G(D^8-DFP9JYPpdB}GRE1lsMKkJN2_`NO>tira}ACR_Wk&ycLy` zWzHRLETcGh93j^&n9Xpg7dh-c#56!c6XD3xK$?n-qwo06gfFrGDp+s;okv0XFW zRDxx7B!%P6?_WOhWc|Y=^8sh#oyIGPW%ssgAE7sAw?(;)7q3dG%dF)uL4>$)L>479 z#AmG1LSo;Q#YuaHQ8MOfTFZl|Qz0x>K2fnRqyQ^HB@&QM`9^<|B-1tX3PyHP>{afZ zRTa4r@mpy?5{Z*_{bfpXg7ZRDh$N5c0!4Nwn3vhGo4tN3vI z0i75_k7e=dHUx5f)dvl{VYqeHP7aEr=AA6Yt?I8|L^a{M7SOoLbF88w6@LC2#syK6-(0%Xx8zkz5M6wk7tu<{v?Qn(<2 zTG&?l=Z8fJ*HhIEi+6CkPjr907gKifo@d9(1l}$^I1&BZ+{#n)JN>;Z>{1wjM4BbY z@RrI`mHp8!PRz(h*^{R-UXQh#C-r9Rj;^X0K%J-;!EeF|9Zy`{?Dth&Wl(xhb(A*l z$={dNvialoM%D)!`rB=%KqMiN3) zQBgfEP6eH-`6l59NAOy{&KYN84Gm;;T*Iq);X`5kBaM!ix}f@59>R|pE0MRlZM1j9D9x&l!m#< z;!g8VAdnW^pG46#XB18@8?`L!@*I~QjZtR=Gh}QVtI#xKs{wNoG=*BXma@{UTfXvX^U-!M|x6oUDH-CX3u&wt_n zFeS27g@H?rrYfh+K#ExOH9o{Ptr*fKEtz!A7jeEpIAVFxTbgKf>mRkDu2|92$8Sr>&M$Hs*Uik9-qFqt%~OZ zfabqtst|`_d>apoOW&s!#ZC4M+_jtZH zuW!KN#o5jw(;wjk<9#}Czg4BZqCKzSY9>f>Uma5Hw_g&LV%5fLbA~oK;N)wygBlP1 zNEU_j902VwlvjOA_Tvl7*K04$Q*-qUJ$SxgqlGX73Bpv#t|-q*;jgqlkUXBwc|V=? zn{9n@!z)dk?L4?v&uz5LR8RI8)zrYePFwl()=IhPwO^bykSI5&U&;4Dn%%>ILneEK+Nn%#<8oc=Zao?B%hD^42z zg5|r;acF|Z5Y%g|DT9A%8BZfYudvWl6b)}w&+c8%9>QGIn&y}#wfHq3V6VTkEbu-_ z9<3}#3jnG1(`w{Xj~O;p#lAfKiSBc_Lo7L6+?oe1G+e-&EcA7Y$h6CFM>Z0Wk$xTR z`o@K>{L{bk^m`=w26_Axb9GxHdBfvuu}%DIo>Rp)3?Xy2jimGRxapM0=*nAI3-~r@ zl2Jj#2w@iuQ8` z8`JHV9?&M&iPv3P83{Pj#MZ8T*GGOz(CM(*DAMqddO?$-ZC+?k6(waHll8)?FmzSn z2XYHI#IS|BGm&i_v@i5}9&oJiSjnjllQP7WS=uFS&h=yjJt|-^N(1p?ljlJX6e~Ai zT{tR#aLYEQ@eFw_%yjr|W^LC)r3@s0piE552_ifZv*P&xJqeNSS})yqM}3k0*@E~X zr~{xfpP(Nn25J;5!z3nN-1fFJSt+1LeU#BZst}5F@(bMeTW1A#(F1<^8tbCHpU4dA zHiGG|_O9o(i{SkBA!MBdZ8bT2EE`*f9teVR;W6uRHYgFS$~X)KK-fquPyT-O8wM0G zw7dN7$O3ob$tTj&1+i8*rq~HRXkht1(Jyk0?v1^J$70Rt1F=7O?rJox)MXunMlSAK zwSJi>`nqe!8XOY&oYcM&nZ}eqwylkR@&L{A9~-!;hf;H|kt$miHnDmjjsz{DfEu^} zrIt?HD?(wWlB_it2F1@khe#Q;f27U0rO&GO)fVFAL|$;035BM0#<_kwQkWIJ{xw{y zBVrM~J!+rFOL*X!v+!W=#s8gs-C()|;t+8~WwiSxo9L@0_C>Xyy zLu7R9#Lpo+#ucL<`}BHK>d|(WERArY`3@oz))&YgzsZRR&LMC5E?cQpM{`%S6C$m( z&rBXQkkzE%zJ`Pp6+l%+`kQO6faIiwlAy(Vz~-Vn8+C4TxrC3+RfuS4~^A z`_qpev>{z)e~?ID9g$RQqUUzW-LT5C-n)JPPKXX|s0<?Uv4mhSx9LP)Ew{HHD8Hd^A?vaipy5 zG_ghGzTed&4x=ucZCI^p&2$QyZYLD_I&Unq?INVOr|~iL)3mv^oc+Gc9wWP9<$B%U zG^8+2{A~L%w)JF(u(o1M=((l{#?s}Gu_308(m#|T6pR0wOc6k!6o>P-bUXR!YIGZ{g64i20MsD z9sZe8gQC~$%$4Z#j1~s_`R>(?mNXX^7eyDHqRsW)$MM1owoc?(%jyOlGrBlZEM}yw zV5+sI2Ha90;AX_+_?A^jDlv*j8M5n0yyGO(SRtBh3>f#&kt8CCr^PyLEGN!MJ!L6I zHbi6Yy)2B|{niUm9V=WN2)o~mF32Bk$|=r9 zO*sb4Z5k;;kTi#<%e^i=z|7f42IN{aieTEf2qW#0)>w;@jTHk0^45$g#>i2$H6Ttz zP!Ln)iYgI(yvmRiK(Bb=Yn^8IOZs{1YIj9yg>Yy}Uf+O}jUzGA)D1Zn1ZM1jnst{H zytB1MN|5rT$?fMUkBQTsH;v2V3^FDZw%H4}pZQ9cONQL9Q)=Z`o&^dy-?(!i-T(_O zZu>OAOjCINwa*S;E0^rF*M&M&`f4WJLs^-RF4WUNH_e_QOk%`#=w` zAb!MV2am{OBJTA27R0*?hkLE1eHlci54U2Mzm^YXQ;daFl99pmnQF_lEn*LfpZ8f0 zf|t!~Y68JV5%$xyB;bwi;0*l8x!E2XRPaJjFy89;wmG=};4Wb{q{nyu(z}j==+9cY zQ;%36Rx@H15*KavDBnG=(i={TmpVQit;d;2OM7LP~P<4hTsw)BJ2y+a1o-?$r_V^eKg-t5IP*o$~+`=O3 z1x&cZRKLI9w=qc?Yih$=P(R^JzOJZl11%Yvwr3qtY(gtK@HmAP#zkF`V%4;xrKuCk z#OZORg0XGm@gZ9OvN>O`!6DD=XhGvv|QF zkupKn%y;?>Xy}K{G)kdep=E+7;l|<8oFMt0#KelJbX&^U0mzk?ez4;|Z>J*?O)Ax9j#c!?Z6(R)^ug#lfdZ&aE!#Usj_nN71d8x)KT&W)*vTB$K*$O7iZ1|0~Y`)+c&yrt#9`v2Ph> ziok^yc(z7GO0#YW=Ds{Wly_?`Qsy2PX#AA@wYko#O-I!M1WMr^$A9p(TIX7_mI8N*xE|~jq4~$hir)>TtXPD`(nth3c*c(ac{6N=+F*+5lW2IA z4@;e&Q+PDv%46#e|2A=@wI1lVBGjv^`E9VH_`Jd2=G=$n`(k(>RpPG&5vzYgcIWBq z_r;k8wc`WVc&%fctfjh>-x^x*)mI}a!+%Fccu5S9v3j^KC!Tho$FjYJQS!U=Vgs}+ zGo;%(bEk~lSuS&$8m{iQdw@|?&<}aONU`(~bBg@NWUz&uT&{8Sm`Ig9ysi7*f5!gF z!)M1rf=8+%7i$q$8ACHRL?6aR-$o60Aj5N=>E$lfA$VG)fz>|#R(;7qC>@aUn@=d= z;%IG4BZfUPpHU$R#gOS?L-NKPj2Uj_&!V=KmkzfpK~=jA8wCO+y<#aNDaL7O5PEFN zZuT+W(Gfk&fKGKtBkN|jyhcY(cUGK6lf7J`$2D_E@U!Mq&kAB;xDHGrrW zXTWssX|w2&7DjW~b&UZbOIC4kfop^f09^4-vhstfg-}m%zz$dcOX+Qss5Lrwnyd_` zy*+$);al0@)cg^5Tyo5mIMxK0F*#gJ+BITnMhOUkBU+&qa zA@9zxllHbxs3|~OrbsH*=$If_iw2CVL)xP_gKHz#;t-+GxChjZ!CEmUH<_?v}0g*32GSi1VIVfc%slgr4L=# zM)P$f#`6Y{kNa2Gc3vK-^q>x2J9uzm>VlXx#(5Lv)@PeZXeDv9)9{e~YQ~duMN<-M zhq1`)gwS)7{MEZWmWCoQ#De&p7$W1uiqB=>K&{eVLrbuAI;< zaxb~O)LP`I=oL5PCw`*dX`oECV@a~Qf++zB8Y^VUo1J3=!jfIq^)b65Y8Ye6w%%Qs zIHiR~*@YVm%4`(^q)2{X-Rc?Gld|{37-ZhpSatx;;H>%9HBP8`n*?|E`u;9@DjKXZ zSqV=58!@Kw_p5P>B5wL|!4nqV4!BtOBmn;t$+$wc$a*njYNA5sOOnN5dO8goW4n>^ zE@xcoaeU4o%hWHgE{87d99YiaAHaxu6hY8xbB(L=s@8t{4M!F7gckz^RSvZ?$bUii z2K)U5fv#1twsX(LH7t4M$|#<9OGRq|^v)vqhDkakTWs)g*9?}hTyYicl$3Ao-Ce+V zl<84F7L|T*-6hMo+J-sywNh15uXL36_jtJ+%*vZ;p3-FK)GT;5op=%jAx`+~gLid@ znz?cf5)@m0F6WNCcGszqbV-Cu=t13YQ)1~~0yejr|71!YNUvBEQSHV%5pdRHuU+)qFBih{RNXPDDRk3ks?74o*eA|8{+__Vg= zvrEPv6O|bfO%gQcMW!%)PM7Dzo=a$AR)s%QtwrXxqt3vtc zFFsP4-l$oBh)V!qYp;T#AE$oVwc8xXxcA&UbQ5+U<3{;j_v|x>riHwCkoj=vmHp?T3naW<)&Y~k_%g=2G^5hsm7JyB+j~5SbMC|rvmZdAbWLWBc}p% zjPxmV6H+XYKhW^rNMHXt>h}=-@1M%jt#?Z?vHjF0Zi*Y=c*+~fIB{>}J3mkwNH*cTGDpd5nWlHHc*C0MBY}~0CRG>8Hc#@6uU*iQ(z+lvC zsY6%JxhJZ3A+@bHY5#h(@mRd4R0!a}c^&z59J$^oPj1Og;5Lt$DsqDGKlpa}@-N2# zuy9MU{gA07n|9{?1nw#IdGT4EL~$%h-hC<;c<4Tqw)3BbU)vyvBRxUeeTzV!hcAM= z_H>w>aan_m=-aKIuqMfsFI`-Fk*XzBM=!I{v2LDdaKE%3^XgtPx~X)eJUu@?Bn3C< z=Ik=HSN&XqA>&D{!X7b@8N%O3S7e82I+f_=1)w8DKTX9#F%v(LuE>wy_Nf@{%Shmf zF~~#9L?deOa|qOWH+z>CVCTO>aOsw1w1MG~j7k;KuABg36Mz!y{tH?%A_K)x*T?q& zHZmWYxVr6!RN<9X{`GeRl!g5Y`<6!&7i*I+FEdG3^stB-ew+qa66~d>!JzYt=^L5UEiUox?0hbX}!m@>?V~wTqR^S0T1aVmGXwS zIkkb}P4h%myl!Sy7plT2>;1bQRm7`xBuyjd4ggJdUbz8mp67TA z^|TOYyottPxvrJMA?79=wcDtOk}JXTL3Cbhdg%9p>%cuKep;hhgYMnM;ZAk2 zDuwg&#*~bzaqJ=>_6ELVBAs@HN9NjbGc6Q|ilpbhq-9PyzlhS}WQYo1RC_6av~eqN zMGXY_YVH?sM_Y2oDPwcuB@M{SLM_1X+07swgd;wm;0#eo?Qg&+pTrF*uAAp1d0CX( zMuIb1EHxXSFO4By9`3mneb)+j@LeTLvvSb?R!L{B6uh!S zsbKj`_cL%`+HR%QIQ%u?dJ=CF;P1zao$C!unGE=PT6#`jR~sPSBx|&Wq+w1;BKK5; zS58~lXqu*<8MvjIc8np;b8>PG?#rmxs~m1<2(>o>B?yqi$+WoqZp#vI)KLak=lwA- z@y-T@x7l22?%+E>Yqk9l+e=qb51vO~t-jiVGL!Is$MVcrmTDAei;=A(&d($!2}KYO z)~qK%?S@|fimC1GS7rrZn zv|IIn>jPC7knNROGl^{PDS5Pe+o3*1F2v=WFK)TfQ5(B4Nv8rFo?b3F|IjSfvN_^kAXK+^(;IlVk&`;N-|W9`SFR zWYejMxq`|Qq#X%@klIg!+1lRaz<6blt4Uf0%D2+|&T+mG2z~2l8F~#IEkMoyZWhzs zvEuqQxD;9%*%~!=&-5IrA>ai%pEEa_n=pe1cOP`c!&18_Zf-9A*2U8D(c%(t6xt1rwR zzDREES~WRwt14;u_p6cx1u^SzmD)&e+5RqDFm3^6ML1Qee+1g;-?LHwx!Hvs!#8rh z>_#-CJ}ot*Cj3Vqppl*lvtN_EGfUMIv-%)*{-Vb%k0WsUcmityKha;4c*pFtaKPsLG`R{FAD=s%#pGHq#VL|L!sZ>7Q4&Y;wKN+tT*t zUem6Ww>JW7!{Z|##sd|8&(Zg6BYVj1SuP!coX5WpCLi=Ug)Q{2EUiY>q0)U*kgzf^ zu+lENzzFFG89hQd2c8*R>huQ_Xbi8U8FmcNofIi>wtdp+vSilq4KR>2idq%b$~R1P zfcp4_yfLF3f;PFQ%GW;Mx~X{x_N6BSz?j(ScdnYDQwr+cmK8g}i(dkPl$Ly8Tx;u| z->K}2_WGQOdYYXCEzfdHAwK4QXDWBI(_=sqDFL;YkVpn^px zhrm=5PGGodtWxw=HFuPC% zzX4v6`YKhO0W)Umj>;7PgrMAdkV%!R0FeqtQ)d!VML#1mq#OisxwaD@ho{7O@Vi7d znYs51L>JB+PDg{~G*cX6iGj}J$lk}$75 z?hNRwqrg6kaf``)<|H=|@C%DfDQ-&Rhf_q~V&UUra6vFu2IBsr_~G>6rSivREGoGe z;Odz7kJD3Gr4q%1*)y^h6!4lq#Lw!YbG-C^MEAeiVHA(t!X1Z&VcRad*Z4Md_{ zh~*6Xd{ogWGJbrbLrl68%8(M4$5sT0?iAAEX>r??+h>R>0HLNFgzeDyxD3wpj8!}u zyNB&pN(DWEYidV>ImKQP-fW(R{;GF89ICvbI(Fc_46EJ^`02|J?F;{A?SHfOw1x2h zMAnv!dL043Js@29dja0JjuHRO;QwZD(!Uw}uT;|iFWfykHM?wG{n}$k+b)9N)&bdR MvwK_qmY;w7e+&fGdH?_b literal 0 HcmV?d00001 diff --git a/extra/walkthrough/5-2-05.png b/extra/walkthrough/5-2-05.png new file mode 100644 index 0000000000000000000000000000000000000000..15be37fd4aae9982769c8774fc10d8bcc0ec6db5 GIT binary patch literal 17706 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 literal 0 HcmV?d00001 diff --git a/extra/walkthrough/5-2-06.png b/extra/walkthrough/5-2-06.png new file mode 100644 index 0000000000000000000000000000000000000000..b1cfadc1366837d6089b605796403e4615536fb4 GIT binary patch literal 17284 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 literal 0 HcmV?d00001 diff --git a/extra/walkthrough/5-3-02.png b/extra/walkthrough/5-3-02.png new file mode 100644 index 0000000000000000000000000000000000000000..e590fd11a51006bfd6793a5c63e661d23eecd5ec GIT binary patch literal 19564 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 literal 0 HcmV?d00001 diff --git a/extra/walkthrough/5-3-04.png b/extra/walkthrough/5-3-04.png new file mode 100644 index 0000000000000000000000000000000000000000..9562083b7f3bcb8b20856de9f8dd3dce6149eadc GIT binary patch literal 16406 zcmeHuc~}zI+cxE-GvkyqEox~|wph7Mx!`V_nU$I?E~z+WNoIRdL(j@4^;I%jQ-$+(n3I==K~k?0&Qwf! z`B|pD8QxSw^>ZTo4SJT%opb9_J6~nDmX24AG39rUURq7`-eU>V$n!MTSatU>y%Qs! zfQ^R5vtRDjbEHb~;JaG{p+X(Y7o4ce1UqzlD*irx*pix?h$nT^3BSoHY>0eMzOQaU z$ZNjj_?F%|`aS^qH0o`2X4_$EG~U)ylck4)CB9@2cAKj9Y(@U{m25pPP=G*^9=5K4 zJls!lgg`cAE+57Y6tTA?WlGw4zVS= z=c(ar_YK*I8IuF2ezo_r2*o4JsE^f~G5$2Qv6GtU_7BgOL4H}|p$?9wV`w?#_2z|# zxU)Co{k2+?VR4KrdCDB)eER$JK!%ac_yu&zv*R?iCIv+`7AMN&E*`Lo-ZI^tMu%~BRE2PD zcD$4%X8^vZF=Vfrq<+5>!p2pOkCb1WaP;ErwPSccKoubnoi~5a__N6;MXJlCMwyus zlX*v51*zGk)yJ?$4|7eKo$c8x*c|p*w977}YJPj!b7OS7_b&HnrN9UNQK|&{1JqKm z`E(-``{lDv{BXJi1k!cs>N3csdpdHE>ymEbhp&ChOWoj#ryP!y`cJwYi89PDQfc{# zwvyeD^QZ>jPFx3x86}n&Hm-xXX*QQ-?+53AnWaT!iFanIdmN18x-ib?LyolCf_>SL zu#tsN$?J8$ZAtOoZLV;F8_hIuUTdXiuHZ};=HBt9N6-p&9jRr@AF3D1=>Ek0b~KS8 zr_4^f8So9-#a@}UE7$BEEZz?hyVUlF2*wccCK1FqEy1|7cCn{b+*pv!hs+xttFy`; zk3M#6--cAh+{gaxNbN_TIG&zvi6V41m`=VTpy8@=@?H(Z{zx1iQ5nY|Q#QTya=^ZIS@on4CYniUHtpfN(>Ge;IK?J2o8z0WN8LwmXPs+OAYQ0zY{EwKex^lS+&kbxbJz$g z##TWfT1S$lA^Sqh{(hJ(QS{c{oOk@#E%r6H@2udgQ!y14N8NAWOuZ}GBLw$h@n}0V zHooGHUP{Kw)2OmWUCKT`GtAxp>km`=s9z1X4Myl0U8uoRbk*UU+5_P3>6@`_nxr9$ zzb`RCex1LpTYgSn*|;mdad(QAXTa~i{l5Nn-VCrf4ff%jL==>L82t0JqJED?bzO!d zzYM|1t5+F21|MV6T8B$|&s}b?7=z`dZ@dRW429q-bhfVo$I?<=IvX*DkOD_a@3R*k z=9Wr;gw;di{vFDnhoEmTzX6X{LUIKGq_Tc<`^c8==5QZL?+waMh7M~v;jpGb*^@WL z$EC6u%pm!7(Y&SEFHeG5itiJ)nek;7S-6uAM{9_DyqhZ<#5P{%My0OT9t{Mv@%%(7 zPr0GXJ={0gBlC{diN4*V(fATa%=hTxCWU4NVxV6fejQsj3%^4A3y+wkVyiQ-&pc_= z!Ez@&*1<7zqee7ZPx_jE(u=(ohb>RVk@mS^?w;=IM5q%LRM0Mo;w-flt#VPEpi97h z+{Tym?pP|6O8Mb>)!{2kqlk&b61(&u!`;=x>@nZTIrg&n0E?SN$2du(0tEUP8-5>~ zOJ7Ngpc~WFT2@LlV@#=!<6)ILOAVZ#?n?jRy%s^Y^{2LE#EZuaS9QBu&Tk zC%tCWcUxuM#oKJUUK&l07>nu*1A75>`tjXcc=lnkQT*3qt0?}VS_c?u2U4#c8}Lja zVVKz5$nPSz%2Ta3<*2SfOS2^xhPl%a%+|e_*yL~z1~LX)_KfOuOye#hEt4Ja<%L?f zTbFQ@kn?3arP!TCsoj{+vYdu@Z__FnFX=9{aC(Gk`Zw%NM<#C5Bf#uDEvOPtj7sn& z_jop(uaCBPX|pqk>%0c7->`>jC@r^q!hP2F8B4$GD2!eiKAx9up|Wh4UM`-jolUx% z1UNM5&h{1%7{@@W3(5wY%{=mti?JBc7S+Tj&(V9?+H26ov~a6~0W^C*_|?eG$a@;b zD;Jg@4{MT3mlQ{>HBkY)q@F*t)U_x_HC#J=e6=OUd@4yP#UFp9LsGuR6`CVxBTB@S zO&(4>XC0}6d^`L|ToZ}<3uCq@G*ugc8M{=`mbel4WWtPhEqVtlp6xsMcubbwOVbLi zRCh{3K!=-~O2*V>6U-B6eR1L_Er`L|fQ479h)1+1|1%wYTygiBv7v0)I6TZop&()_EACX=5|{T) zOjs1B-wu}&xIu9xfMTrrNK8}}b^gkqUqgDncg`UnS9rMFB1thF_0bWO?S=V63aGo{Z_ z-4YXS(~#?@-O?)8)kQped?&A17qGfFM>!n%&IX)pA-HvoL}|zm zjb8^!BuoOst>b7Sy2{tpW0{rrz<6aT{V5B$O z;@$R~NLrpMd9>$L*0Z8oA5cX=BBptvP12lRutSH9-lyM+LXQ!ar+v7`| zxdnnF=k6-2U?YlEaXVj9JBv8&-!8_ze7b}x_w28y6S~%AHSA-@(`tN@(Cu$(e8MZs zXhlW|MgAAC0#enXG+I)y`i<{pMT?5>f@YS2JJK~>|XDA+mhxl6Qe|5C!Vs* zCf@{>uV}Vd6yWFIhZQkdoJU21Qo@Xnu`inLL9bCGe!D{r0!ex7j_FRG>vj(0Qy577 zU^y|;Ud7DjRayt;^!W8v6RsgeXk7n%HGxo?%H$K`Bq5bOV-e%eNUdfph!_;Dx|GbT ze3PRbBjqfhSk=yRP0$~HNoK)wd6lT{|20lGY{WaQuj1p0PsI_act~i(93iE{g?K~3 z8;#%+{bxxzf^NcWe&_Sle#(4(h)`f6igKP06X6CM+hf7L4$xGw5i?iFLK?$a#N-S+ zk!z-(gb{aD7{IDg5#kqI-xSEa%74A`y+VJ3Xa-7zQm;*oRGSD#*~1q@-S;x%+q^1P zK=zSrjF{!jahURyw$sE$?M>^#eLLWmHSr^v8*`$|mYpf?G#RbsuUMw3a91E4PF$ozo5hWDQvqU|xc}|GENx82F^~}A{p_+B% z|0tW=FF_zulWd_Ch=Po$U~q(@KvA%*rIVfIX*)}&N;lje8XD?2qTqFc7`{XZT5JjI zR%DSXDW#;T3p(M$}wljjiY9@_DJ8qU`ZE{u_rXvnL!i&aQ#0yQurh@EXe zN8mhsU~G~qi^YWy{*1|HK6W(vkUVT8Nio|1Q~Op7X{>gCAVNSd2%_^njT} zE2FtLW>Db>SXQ}Rfz`_i98<`j1OF4`%;p7O?>sK)S34?|{`pis_kmKXx&$H_=ut(+8>bNu|ZX6a5YB8r1x*coErw5P31k^lZMnuBQ^R zhgR4`Y-%xe^%KpsiZr2=GeO%^Lg0%ydfS(HZ$k}#%$=A%gf_@IiS}_+?;gc|n}h2` zK)DwWFQA#876)f_FY;TMa!;d(Jr!q{ZR%<+e7}Yt(|5n`?m|deY9fW{N5n^+F0;a| zwOd&ra_*6^iH`_KqW_PxUN!}Qs-viNLA*V6rzQ)6D2 zt?=GXpdyXL?4v%)sYrzmd!+MSNR zWkAryZB@4I`Xh{fo;KSS!&QSqYpqpxwgn4f?1Bc9MH`5;d}jg5RG7h% zqkE0r9z(QS8(9m-_41VS(v8fxhq$G~xoJCvB-Nnu1AXHnbiB+8!rXw+u6Fj8Nz}#J zE4XV=E}mPOGd?G(0i3w|mk1Pe+uO7?BA=*b+E>qLWtyd(A+Mu<%a-K7C%uMK%baKz zW_?uKy-m+j#<8_SSD^hfCi56o!fago3!nOm6na28E-dPf$x&Wcaro+A22HLW6%;7+ z440xv>*vGQeuXxD_$Im77(TD1Za_s>#b%OMrsYrB1R|Z+yV7Q?Iz;F;m@TP24^y_6cE|IpsuQFygx3Xu4kdTn zRT{`Y(@My5m3V*v?9_iwWd`Smhrdq@;o$8cdj|HIZ-5of^_Uf#y4JiUPp{PdaUXWN zJ!SSz8IQn|DLF3ZaJAz8mpOfrjW~=n4xDJP!l&|pg{9hf6bOfbL$ zt21%T7!5*FjN^?}W>mW!Y-Yh<-M5p$y)U;Jl+K_2DxF+p6Dun?W43#l z_(r3qJCxb?ja&50d3P>bq}5Fgz>S|3j_rfa>p*XN?qMHoSh?VazY zO#@x_HzgH&OlmoyCP6NZJFf7LYC~(D8~4GBZ}>l*?AC#w@E!@1(v_w-4&+XcdSkkC zEF8}wWyw*BP4hA`*LCD{)Ak4SdIYpSqP*De`K)OE8n@JeGKBr+wT_3n*S659${zVl z3w-mVp2kpP!@AVHHK?kz9fh%p3C9p!vLDt;sSnY|((MX@rsp$f0>YLm?*7N66mS*G zk?H$I>`|y_qQ_*0m=ls>N)bm^(+FKAqcxNjw@o!i$`d6=x;Z}`v0o@%|p`A^2R+gcW)VCT1lA_lJWrY3|!NC z1~JQMT|x0K+wrU!)eMP9{NM8wGK|n+=09KqSmm(h>UkQ&Y#K!} zB2Av5x(9s-oGJ`KGmeRd+>E`S1p6akMsxEzL#<(LDWFXnW&$uEds{-W35*aglUk)A zOwI{~W%ti6+-Co*))kTGgdWULK?Gh+{`FG}&AN>UPxZRUcnx398-*X{$K&?6_8VyMoj8E@I{TWs~7Y|87m4D-LfBM!@6}J{Yoz9rPN<(M$`DQHD zrNJ^Du+@jCH}TKke1y{@@H=!>S*hu{jM%Zw@#u8p0AXReXlgWrjB2a45LAxmz~DAm zd_6eI3OWlB^m;~_YRsAq@_#DYIIEv3!WgdtiIY|3t_{FTU5T1OU!bo+4+@E?)uRSK zJgCb6SMn!tC1nkRre;@pIQBC=hFwco`&9COBJh}{IZ^QT9D(k9y@b}#<&Ncbd$>X? zPK`R;UOU_&xPsge=HCTU5u>cCuCV)AM9)yt9l8{ZdSP%yPfy`iLf0g8)5G;ksH5mJ zN2->RM=3>`2MZZiwKLs0rQvh1yXUE_xy6vc7XX^xHF^aCf#StpfG@$&F#Y@M%~N!{w88JMmk}+`l1?-WQc-C^ zqdXv2H@4SK39SCup5r-;@7pL4__Na_7OG=AjJ4ia_jNoA&lwL#8jj*TyGMsekf$+j z#*kj4gW3s1Pi}8&-?593B5=t;0+Vzbuf5u>&KpA!W?sf{kTeDTESY~$rOAB;DhbEb`iO;H55NiXw!1?V0qSN+Eb&m{#W@ZX`!SPYc>#RYI zljs;-?&a_aXDY_a;UK+tM)B3XvM#lPqeDwRzge@idCF0Kqb%a6WB!pp~1^byp(DT&Ns81+7@9h( ze=5ONur?-&b2ClrN^zK-LQbXMZdyEY&YR_quWc`!t z_@o`{dw$MTTX2?L@A)tRH4&8dheSttg6b9=FXJ>@nt&oq7*hjS2rH;@n;9#*rN7K4 zubN+M&;Pa#@BDUX?WHR~(aJ5oxqUFy0wLk}CL1n|(`FhW`{p{Er?O6m$9tg1YT z*xsk9wytH6Zj{aD-zh6Ub5{DT+MUuM&t8zdU)8r%mzSM+_0G}Yb-9$x<)9bbt$>*A z?r|aun+IXlMGVjRarRBt{7m=QaKx;!xsOLw^{^2ZJG>7Q9C#6(-qP3c#A7~*Sc(*m zPpE5E$8Cumy%gnLc4I7ZTHZ_{m-*KZVVeL*8hBbX!Jp>!rPtJ4Y{yMK?w;U3*{HY^ zS${_oB~5fL?$>;J9|R4*yUIZr(}+}0&Xah9;g-*xVtyb8&VHD5U>TkQ4}=_jxW4g0 za-8dgh&J*p5%8vH6`;Ucx{9)uq zB=Cr!8plkO`U84O-kh+iw1^_^S3_vbs6cM($jR6JgzF)VyI69bKt{kiI?|4znXdUc zjVp97QXC&Ln3vOWGDb}EJ;-HtSQoH}uD4kiVS{74Q*dJ89zUC-`jJgiLi*D13oqCZ&Fmum1J`LUBb5#Qr`nu`a#1oZ-VT&csl95FQevp0X}1RCaQ;!BM) zVDp^r!=h1`ur>zZ^=G@Y8&?c;0jEg7>%p`F<>K1uaUqIhJ+KO-oh$C`w*nxFr2mEu zK>fH}INFxwjlw)~usUsG@X(#J>5skE+imG@uaBWwO(LUWu|)RKD6k;m*SVIIJE@Op zv`cvTfQ+F~K&b5;U7lj>p}wy*AgVf(v_jXL;&@%5@9m(^&9oX3Gsf1LXm-QkN&vU7 z&us|@!IZts{YJISZzl7bs^{L6=O0_h2|6*TfJ86cDj6k;JO&MU3DXPI@pPp!g#jPRT(m;{lMCK-SJudd%jA zL~5Ac?WxRNBa>%0R%(J(cTXEJqb=nIz5yp7`H*Ch#`l0=Jpx!h#>k{ZLyer>#TJ{@ zS2?~Sb~l#T3C4xyC0vS9s-J`$Eas_SV`za2-a$o@uo~tO<P@R-uU$l)l{h9bXYS!>$}m0y&GW7|S9R_wQ z&u?9hx&b1_g@~;(!B=%VO`kLK^s;TkktWnJE$$&D#Ns^ppDE9tJLyzwz&jFbWMZh2 z%X$wbOZT3Ylb;~{tR{Hsj$jxE%~Z}q8@hs+@3fuz+NPWR7`q97g4 zQckaY)FYYudT4osUHN|M^h$SbxYIONr6+I85|nB^o8085~PWHhg308MO{ zfb3iI2@!nqP>qZw%l=QTDUBQ3d(N=>;MCa&`bW!(V0a0hxpQ@yDJ4Ovm8Le#x3Tgc zWCy~pok;b>M;Th$?_kT)fUN_VUe<*RY2Gb+-87I+7&HSFkKK#}3M;DVYZe8sD^~Cz zHRy7Aif_5-#U&3{q_v|#DNoYPD!l%QkvBPl*w`dTR0uczj%IX^vWBPt3^jd8c&U?^ z^JteXtiu0Lu)efL0eMtq;N^lmS{^4&m!xTeo-kHpOuq|aedPLh)cjDUp%#}X}v>ID}M4AzwAx$6x z0R|iRh+<*)P#7@dmp+S9&Of;&G%RFtFpzVcszx7uY1*mQQq}Ax>FtEmYBz3+x$n~H zq^cgH{>hc1j4X@=#miPa;^tE4=kWbuD&U)=z(_VX>)p9#3^5 z@wSV0s=qBOIVnq6z{Gviu**RQn)x5vn2C7nLSCh}3Gm=m*BIW#n{5FKQ4g`c3A2`M z)7!el%=BNbF1rA4+gxPjNJYGyx-Is#d~&CJk8Y2|Gw@&fM;2H#tyZau()z<|RR)b* zjdHw62QvlPr61WdP|aZ*yn2Bps5>0632&|siXkxk>_5+u;_d0#_qVFF)%$N%so-yt z%!U9s=Ii3%Fv#!E4ffv$4lj9$^(3`v5FWs+XobPf$fRk5T|{d=1GIJ9X3N3Ma76g| z;7Lhz?}4LF#f9qDg2XQliX=l%v0K$)3ve?;&~^T4nOR(N4N5N^)_OcG;Zz)X$B%Mi zI%#}xkxu$Ez~g>fam4BSl6ek5n1aQ-TWtYJ2=pe2#YJMB>TxVqOc7;xEa@{#DmF0J z3?hEl5DrA4vO!Z|99m{&7Gsy)+~k($vtg%4)cqFR?5m4tm+eSkXcIR*$gF}Gu-D?U z0s!UG4o97kJqT3ExoK~`t9r7G(kQcwJV$t{dYZFw-N|C}*M4sBt4Ul{q&aX$$(G}9 z0B2wr-n8o6X5c?`c*~ajPqF*&RPoxEODX_{t0B1@y+6>LyLP^P1-%JM6JdL-uKvc* zj1x2&1im;Q0k#>t(`8FB-db2PD~>O))3GJ@Y^fu-M)u#MEsB2tbpWUFKeMGCU$=i2 zx4zz>Wia-l5^}#dxwkl+F8R~yYxcgB)1UV|3uAuN7HtJqce`AtWgE&hG01zhI#iwTv{ujP zL2Plvoq2Zdz7x09ZTkQoZv!T)KpfNb*bENs(X9Pq(<(M%-@w25#Oi*g7cOcyZYVu% zU8}>ju1c)9z(vP(n|s@$<9Z=p!Px+1MC`#)yNnJ6FVXE^>RJ>{7s*H6G5^C&<2L-% zucSyha|$GbJU-~#k4FCztwY_ZsoDZ;*0YAOz~uuh)#5Raq9xE+WiVsage8(dDvBd# z(gtg%^_@)fbUfYnI3+p7^+$5nG0ym);d(urzJ~XEfw{FhT3j3a*j-1#!cp7BBQm~{ z4L&MXGd`C;l%GPOVh{=mn!l5NegDrMcsnbiQ__8~3HZJ2mAcE~@*F4FUV=>+$q& zff>-q0Hj+$?-btSjW<;#q)#w9=V7+9m&eG073^up#rS#c&_Fy-2LE@@82 zByiBAS2VQV8MMc8Z?TQ9%{X$w#&@TXEIT?!IkL{}Ax`Y7cBz_P3w*@l2;q5)n87*6`G)#;8J2Nxc0ewFXi Szz6*y=BAd1iVpm8^Zx)u5hn@& literal 0 HcmV?d00001 diff --git a/extra/walkthrough/5-3-05.png b/extra/walkthrough/5-3-05.png new file mode 100644 index 0000000000000000000000000000000000000000..d53340b56d8be96b0499a26a136a9d506e7f216c GIT binary patch literal 22383 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 literal 0 HcmV?d00001 diff --git a/extra/walkthrough/5-3-06.png b/extra/walkthrough/5-3-06.png new file mode 100644 index 0000000000000000000000000000000000000000..3c9e12fc33d3b8935474e7ebf2917b37e1095050 GIT binary patch literal 16238 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 literal 0 HcmV?d00001 diff --git a/extra/walkthrough/5-3-07.png b/extra/walkthrough/5-3-07.png new file mode 100644 index 0000000000000000000000000000000000000000..ab6235cfa93e698d6bf90f2b18b02134f861630d GIT binary patch literal 16549 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 literal 0 HcmV?d00001 diff --git a/extra/walkthrough/5-3-08.png b/extra/walkthrough/5-3-08.png new file mode 100644 index 0000000000000000000000000000000000000000..fd22e1a27fca4d2466f5dd0579eceefe9782681d GIT binary patch literal 16548 zcmeHvd05if`}U`r(iyj=nY7ZhRV%lqMJl(JnXIX_n$+BItZ+#Y(^MA47R@O$D^p8T zvrQBCjTDg5%#>Wp5CwtU69g1RR9xPJHB-~_{k^~UzxTSnf1q9{aL#kid7k^Z@B4W1 zptY6C4@-W4AV_8Jo?j0`(44yvG+U@R8$4qgY!(2%eebu&0eqhyD1Xelxoryzf|fyh zf8B8e4HvN6bB{9ub^0s!)jNBgFr{Da$L=m+Y1~>|{mV(c>_b2AaQ#l@?Oh#l@A%~F z+28;0-HwA>_AafoGkmnihj#rZr%MQgdq}8pjkK~@)KA$GJftnYT3I-1mP%IL+Iw5) z(b@PbjGh!TGLEWmK)cJ=jooV-U$re2FDOQPE=x7T9F8ZE4$jZPVfoe$vms?+|Ad!= zFh{WU>eRZUMueVCMGt8$Wp>sbvmj^@)ghyEh*@@C$Wtv7blj!8s(GR`W<%)Gk>-(p z!A_)p49c5^72feZVqP~FTDBZ%1m^A)FbmqVCrJs~zO!(B3rUg6JgCmcH!Vj8*-cJW z3Bl_i$e6)lrchim5?Zj&%R0P1dl}Oif)-pN-AUeLA6Fd8i)(ncD=E>MpqbQQT)ROL z#n~;}Nm_|=m?!ql3Qc2QBO1)Xz?#b&g)f}qpVDe^L8sK!9t8PJ()AiFjA(fRhl#-O zGUd>$^il{~b^qf)4`_2JuJp>f9uDR~)&}%A0eyd!sHMi~dT@W2VU20fauFtVN5e0U z2E7Q(Hd8~|Fc)>RPOy47Kc#0jboHu<26QI8=%4pYYpvN!@4GP9Gj{w*Na>n=(3z}? z!+EhaMFy?0SX&1i?ystXZEwFi)kH&edS(Hu$-%*>(i6347@61{#X9Vn{UkPcqAS#z zGZ-t~imA?xleTEFq%3b7$4L?yD~bghP9E^Zp9VY@jKPQ(T3@kepn&6a?67FxDGTZv znw|&c!Ue0hAsokZdY?{FKpqPhG+)W4{vbd&;r`fU^KtEF7U8vgri}(Kg%@72^Q;_m z)82;o(w&|~TpI}KGNk?Mp-#g%Tv+He7A}(Zle3zSzM)>3k2iSQBwx#!xA`Pso188n z8!DC*8OGmsdE=B(x4Y#~q~cJ|OA`6sN|Y_5Yw1!9p-flwZn!qd23Oa5EsT1~ic@g( zmx_jY5Y+tYv+!>sC}2oAoZc++7R}qqmWH$@p|yjZCiea^ltZm)kU0JU`M4g+p@)X^ z386k{AvPnvu?%Kq(q-DLH+|VBb+OwB&(Ce_e|_r5o)mrYl`CXhm)PLbUD>=u^1Y^d z(os8FPDP4aJAGC$OplcWcE(YEjcJG6FthgiQ-bh;o*(fh5R_MMpbbG=uvGN;sV)A} zVp6-JLj6oY#G5prx23e+$6RtiI=`&Rax6B7kDsudvSI{sG2%nLw}ct!MEMc&PU$h~ zgg=SfODQv4)cKma>{s-XAQmCaVJGJm*!kT{O)<2?oP2&9tffaf-vTQ>BxUtJCc1`A z@`K97RV z?DB6V!{Y?IO~|)qVZgYxa+A!e4Jm%sFmn1E;i=X~%UNxCTUn<^938HlI=0Pcw}v~X z+i)L7EtQN|3swm?3PO7x=Hrd?;i6t>hD$t$uxA}dk>N9ObdtTRvC=UgdzTa}3>m6V zX5~_~wY#k?q@^{z7QtiGN`gn&h==P$56lvljGr7H=BKA8^p;`+=k%xtop>QX_tkIv z8>ae)ZKk@STo{4u;BrYe?Yw<9?@Ww;>jiL9wUaa0@+<^(*ris z!#2h-?|jvw!JphA*62*RF#>7SN!GMT&$5rFvT~TDaI7TD1EVJy@WrJTF55r9!8mhc z1ML_)WRUh++h0v;fCA~aHr0H-IKB%^c%N8%HfEoI>@?1imV<$K*eyw+xgL3kA2^@G zpYpO0l?fB7PNwHhF{Z~U(^Fha$0Od4a>`h433ryVq?+ot40&^!_7>+D^fO3VvZMPN zAqe}p|5WFzbE^>w^2OS|6CB*Pi`bz8A)6tbn#>KGlv1Z8#tAfk-86n#SYm^*$HKB4 zeOJm7`%%yQ(b0~M&ZsEbklA#s3j=cpw@9ARjGOWb$#ic_Tk#h*n*cLy2*bihM&B+#njTR5yz9jC7t& zO~%HQ<;cQ(xk7l6mf_=&E39l*Pw<-Rk;x0>LB@|AF{^Lb2Ips~&&7hRvp$cYQ1nm( zz5?VnzkX&wS%OjTDXJtJHA$ToP$_=e!5ekegRH<**WYg}#omz>w9Hk3?(pH8(q*MN z{4$}x=3fL%TLDr)u0^uTe`spsIZNSoT!MZKJz9r@jEk{>;0G2H(XRGdXsRMO}2|bGN4lEp&GODv8Q4eBHkMa(BE6$=C=BSC2;6!vqD z(VG$?tA!P@Mj$xO1+Uc&99k^$a-b|(V*B+Z-t~&2fS_bBZWr6%j!63xI^$v*?$S8Y z2kO7sv%2@m=3~x(pR7BKJoTKi*2&)Jr(TRS*_><#Gf8#5ftb%BM~_6DD~u{czr(tY zP3R9EY|l=eTBp6_1+B~o!Ub@8xmL$ddj~njM|G18hN{#sG1=7duGVVA;u3SE(39xR zUlvhN6DF6H+^ka(7G2oM$n@CaYfHbRJx9lTlksqo_6hS_`w3-z8Xi&Tj7I(uicbK0 z{lpu5z-)0m!b1MAN|EkMojmO@MQCc?m^C1ZI7A*3aR?VSv!(={#?`wRvPfds!FboO z(b9qtNzmroW^@hDV|J}fRN=>Aj7>no3KYcnws8J^@D|k8fmP4k9v1a15J=je-Ta~7o zM3!r4)Ny?Ai++9XXbfWUitvSA)j3mv;UgpSqs9{!ntenipH}m#yN^mlM4&`hoG=!Jlp9o@nMklZF^j!LH&!iFA+B zYpMMXkk#JWO}2N*?nBUad4Adbag#LmpEw*?7KKeMT)%*vC5^xNMt1mi1EFqb%5BM# zCE90)Ffpsq)GY){a-o#QtFg52R-GKkM&BQZLch&e${P7i+@p)~6_DK`v-6mJO{v&y z1J|%($rFBRs_nE9&H<(B-G44G6Sv)U;xxVHHhGL*(2ut>)Im|l?uAVZ6(}=*{0F)e zHCx(3(E7t4NB4dUySoL!E(SGNi9C)rh7u z+1XILVYDON(mp=dMB~<63OHjcMY$bU$cX%7;i)PRly&d3P2y>%CJu6;G&tb$U|USV zQm3<%3yR#7Ao6LqX&T)78VIIiJ$+w`)-cy%%HN@U{1$gD?VZQ8zV_l5jPipnQd7~? z9}f7*_8$G$?~j4=_x52+kmH2I{Z9N10|2Caif}*de_cspA@VjS)BSFGA@AklpuWe7 z0S(oN{H*6~mD*xYToHdzAkqAz%J$lq`p-*9t!LKd5 zarq1`#olF2)*h!RUY5ahCj8Dw9BQJvKv~DSu{^p2OrPmpZ_TqXA!EKCf%jd8cS*L> zbIKWgfveXCgybkH3HGTt+(?Mj2=>f zT2d4?f0?4CZsWeur5D1`G6h5_j0ndS8#JVv>C*;yPSX?o0%eq{rTUL})fFLjqH+nl zKslZH{FTJM4!&vPb;Vu0Xw-5Y1RYKLe0k~?6v69x-*@!&7Z+%CK0DoV*{HunLp=M* za?jkYvf*@a+QP)fHnjzmjea;B%dcly)*e}}2c3EhEF67xQE)9%q#QDBP3KKyTq5m0 zdPm*vRk3CCvS(@+nLiHOKgiQY!i^6`In83MhM*I7KL7Y{;*td`zM}}oyj?Twpb2q( zFKt7X+9TgC}_FMJN^+_cU#Ui}`f ze7!OmD32TIWiRwXx}6)PH=S%ln-Hu6WkKG#H{O1NG6co@s*US;h8FdiX;XTV%hUhl z5(x6mqy#feyz7}Bs!WuxXnf>pQ&fSnh36!$94E~d#Uola2*~i<=JM#EaQUIiUHMFB z-x`AOm_wlJ*PFS=V^)-eUp4IRND|Gp{f=x+;bmoZ5Gud-nCjZQc|Avcx2UooGk^Ch zh2er6r6+5`kqy>xP+p{PV}cNF8#p}>R2<yo4+)E;?n+8k%!&;Kc~q~K zZJTayy&+KPV(x1^H;a{m&OG0rTX7&c!93XpEbclPgZjs0YdSsZRc(@! zhw9W|r)$-&h`h&!K17o{kEwm?6^Vs#{?Lb>KDKP^va~1*t`dEcpfDRcbM1@f4Rbn| zDLQc|cdK{q7NuQJH_0>DmB=65XhpMB(Cj9s;Ev^x>XX*7CiI%(0nxpX!6t<^&95%~ zLFaQN(KZzEEwtw!Obz3drTFn;u_!*1;Fy3adZIlE|Y z=%3}zH&#R2?cCP3)SPgP@83(w%%Ye-JLa(T8`)Dm>ZEO}pfAfH!L10UJZ^G;Uxi#- zWcRkl6fd^W6OGo$Zj(D1wK$(E0-2!LT$DXtStH#$3pyjG8!mqYBYp`Wt7-zt05eQc*~S6c+@y^Zvi7iA?o<^+r6y zH|{I@Gj@ez0(xBJ?Q5p@Kdm*kyDpv5)Bl3XeGI6btVQh}b0CQC`57OPNIy?@u@?n2 zYxk?v*QdsJdj+Q(5LC<$U;Wq$#}W;Oy3I|>x&NVv9nfau=Eop;!*-j0GEcTW{`5l` zTZpl;;$-I3m~Io>1s>qM^6GUlARz4B8uA8sXP8N`f&*Y@7?eg$XE7{O{0>hC?L-eL zT55vqFKQ^Xp=9zo7gGW4U z7rHLB-ZG?TlX3&G{w?y^A2lRRFq5zRC?E?Ai(hLF9FIxgc3A{dgym;3Zbyhu9Hy|p z%3uAQaHq9M*P1*^YCzl2c=X1MZ$vUiXugL3 z8>J_U(qW)HcMjCg%om&s3Yyxa-QoWA=HZk^QLLd|oFTv4+b!vm9|vq;22#_n^)^U% z`^22vWEHV*e~TFZ554;QPdQV#jEvC~D9!cI@EAOJ5_ITzaerq)TaPIB^L$ZPvFz2E zdpiYkc-1(3|73S>)Nc;8AWZ{!rMd?Y03dhEe|J^B&7TX&3u1;3H5}w*W-c_JXTw|x^uZW}9 zlDyq~!j|MZ#qGlo4s$f47}+Gelm~{dw_3I_S^)4r( zd*rlTJZ(LbrA1Mzxa`Nu#xAhj&ot(}lEslSRGkfY^f`!pu}8NdZB(d>(d^3p9itD@ zHp=S>*^N~EbA{CJ4cM&g9ucbC1}FsbbbaCz#<|W~SCSQK?yR%>T*4{T&65UOZXgAF zC%n)U?KP^zAA`t>>XK3qiMlR01;`sn`B~7HmqDTdu?F{^$2ISWsTd`%TPib*8O|BnwnkBLicQ#PdDWx&Yl`i<6P4sO$JP+# z7L2KWXGi(X`RMhyhGSceX?ZIQQwJtfk`w4lz8$pHL;c1% zC#Mr_0(l!2{l<|u$`~elE$!qk!`&~WPM5+^yl?e274e31PBti}zF|b8;J04p zUa}yPNoRKOuXGzKVY(^5V1n-lR*U1S_J~tBna!&Xc+ev@^j{A=6si=3DDGd^zgQi9 z;4R3ycO)=BHkbRJ0lxB?O7LMm9cP&va@J>OIc#E16uKeWKobGVIkgiWCD}moi><>s z?&4f0$MvMf5my0v)G2=EnZu@{MF|{*nHqweq|Mne+8z-ir*MZv6^T@;?OX_2`+3E5 zEc_0+-3^cC+-{1glw`Me#=&&v$qPkhJaIlZWYNsJj4vBXq3fd`7BXu6FYCuwxaR?S zpifRXis$`@RbgtD4JSTS+x0YR{K+PtRe!`N8}s=o`+4^KKcuCPjJ{I_soZ4dvu+TV ziRx;cn7XK=Pd8FCj5ZlZ1@dw5_>mQbehmUxZ-5V1bTurD1|$vt&kNorrSs)9Y_rAw zXT%8NbFc9&&aQUssshgZM78jaH!uH;Ai54}o&y6pwV9+dxRC`=_>M zZKDfVbZtkZhU8?rhljaQQM4a>WcI`1nVQn++F}tF_Kl2lFml$|`dM7>ku9nWMihG| z=5Wk>=@JSmiZGXVMhth&&i7x6Nd;(wxEy6$gF_ZSuMNE3yVUQ|K{L98ZIylGPxTWu zv3dwhm;UQ{6D^&OR6AuVc2k_;Q5pZMGSH=cjU9QX+6q^fbLN*^`o0i!n%Lroi?Z(t z3Kbdt^|u_`8WVZgv}B4G>!Fi?Xc;jWe$-BH{G#`2^jSP|zU7z+?D5d=X0NTx zD^C$J?S>bP9=(T2OQ&Ym0!u<>)AyA&VqMT^_x$~6(zdYXy3Q3mFPhB!9O}QP27>$n z+3$8e_^{4RV+}+NUTf?fgbK&|{tp;ib6#lD zLrF);&sk6AK=6mh`MFHl?YTsrnjTjVAQ?H4zu^<^$z3ltv&rR+>~_Spw+IUH*?mqz zhnz05smH3Q#5@@zH!L6znwALQU3CXIZC!PO7em@kVGEhGl97j=Uq}3(LgcZwOyXZk zE8E8mfD*8uI{R-S08mbHUFW6Vu)RkwqWJk;AleKZ|NiT5*t)?7r`~)Ancvlam$MUT zQgLP5uQxqh1Jv)QV$?p2>j3BVHL3moi=P34{O0^`sriha6b;(Re$#%31@Hr4PB-L~ z(BJgD6i~Ex0O@jHc)T*nYP1!Co^1PEKbe8zo1zp>(jII0J($#2Iqp2J5w2)c>5G%p zRxdM{*ncNE<3H2vz}9qNe^rxSkydv*78e@{Hkx`X> zhQ9#pkT1}pQ*$9X7K<28yle~BXZR!>ZQ8mtenBn z9O1h(f9Vc-zv;yT!pZUN8CZiIN^tY+=g}U8bvA*AY@94j3A^JI$iPTN%76(VsHHe> zr}vYR-t{uV)!;+mlK}5=sW=J|X!Lak(cq2brigc`NPC`^?UnRxgv?Q%U&Sp;jQG9* z%LxlX%DzuZ^nk!TPWVS)9-(}`>2BoJP4achm#WQz{7!T%DB9wq6@Q<8o`Q4(g0cgp zk@q8X=DU&RF1e2U39VZpKi^--1?ux~8bH~3JoGENxO9b}wY3TD39~aU!hQ!WJ4@NT z6)4lj`I5_lRi9(GnH}uS<~CqGZ-`m+d0w2 z#zm%pkXa^r{(!zOG(r4Gw-OsjcMz zXAb1I6e;}EmlN+|zMnGArL;|`l^74F>fj$RDjCJT{v~{0cUxJ{EQsl@r$m?}8a+{J`59pjPZt!;nyAB} zCS}GxE#WQ}hoh}>^A8Xi{GFOdaVT$YV;(j6AA1fO)-&Ya3Xq{gGck*pA_;vXpN?VO{6EBNI zRquizTv{Ewk=NAL#4?}i?A)Ww+B64hyWQ%XLJGN~83R8)U_}K+(XZL*7CJp+C)QV7 zCxz6KT`126NIK+L(IMZJsNdyO``-_%6e8pB&~Ko?wpTAOt9$U;+jkc9_D`>XK0yRE zeIDd@u&6X?RO&if%Oy+g%*y5%cenBmYAofML6GK5js&m(crUygOOE;n9YUpLR@TgZOWN`3I-vxiq)CG`VUQwyj;>;!_ z>bB|IX$iFsV}iS3+Fu8=SPh+dou%C+C<7*fkv~y>K|p)7A(WPl?Co8{wV>Y zLmlj4Sx(nYPLDcTCv)5B)P+gw56(_9uw>gGVl@7FDTE`Rqs(R>yK*=8_8 zX^bQ2DVB{N+A1+6k1n+_Q zboz=@F3>C=kF4_M`UNBfb{Q8sZSSq+&w?P z-Kzze3-{NPzZNJH$$!VOIv#d%S%+SL7^gYV}5JeSWboZ=co8SqVe~*WWp) z>37M%i8%PcYA_;cu?t7e(a0@hkv20%v8G;?3TzREvfXgPqyoq(??6^4pS%|=e*t&& z^*x$9aNmJq$F^x2VyBjCs-0>X8BznAe%S>r$1|a2*ej&LMqpIEX_D|`)7#k)<4lfzLlUmT^C_#!@@8i<`Un55 zdDPX>=aHk)>oCFK-ITxmW9Ipnn!YQ1xSF-G*u9@{YCS7=gl^#^quX}NT(>AmMj}PQi7wFL zzmUFKCaU{hSHOQd@HdpXX0E6)SuYd8r($v~(W6Pb7+g9XS3bnTOUF_j^Am+)N9x#0 z{akoih(tUQ9m{RwA^Z2pb_(pz!%?b6)BV~OJVKJzh$C#Plaw`2Dp2E!ESv)>(tL&< zD0_a>UiWW3C+SU{2RR)fIq7-icoJ<{`Op+8saUZ=#@0-2?-BLql-(`pY=3Sd%Ro(8 z{Z5&B%kTri;dslH@}ng?6Wr)&I>a+io39*yqQ4o!IXG- z*yPIe{Em8C#!t5V-?&YIk0ELo~Gt9)wp4&rBh0-lubUI3+7#vq6QK1 zjkm)I*X|m%RjBoUa-K^`N1Kygo@ih#ZI!ZWoDx1rVm?pE`E`i-fR7B5-cu{fQK~aK zJ~&bvmH|PXck9@3G~K@B9Z783+8SCwfwzSRv}iL@=o?ojjEi(SO^FeiqaCL8D)PNz zl847F4?t%olOj^Qp5$bN0t3>V#FQhn{vM!{3H4B(H;sB1=O~lz_skobs%a3HWl(#_iKq0mFOj%YVebG6$Of(i&Kk*4LOm zQhS*|4;fq{89uco;OMRLzx3N{Pj<75*Y}tu?=Q4awp$gnYdF22_-PZW$;`!pW2kJW2fdsdut+d-b_ zcg$paAf8dW4~%YtMVgz%&V%+CcqGZS`)CQ_#^$yrEDr`udM1fIx%BU=?WRv?MIn(puLM+X;x3B6~|&6%jwu z$(`=kNTHJbA6#H}3p%2U#GEb}#nB7)MoKcl<&yP-^N}3=UEMDGIGHzyD?|bEssZG{ zhe$Ct(KK(Cf(Li(-TAzI14bd!9-^vU6U})6N|r6Hjj509& zR_CmIlqP5_B{ZE-J@?GyK2A@NixmBrwe*6yFa_DvhZlJOl>_oOfu>NG1CnVVPYJL z+*;3CD2or|&jkMWm6WSYyy^4Gl+HO}~w z1TKneERC3+6_7kVG?aSUMT)bUI5g)8Ezkfdd_YJqwHzIQx%haF0vA5bea!^Z(bc~0 zAc;%qj|8UnNm!09!fASPcqgI|*V?3za4n|vsi6y=DjG%w(WcK}+KH``Bep6a_|CD_ zl7PC{@pjZZ@lFFDV3A$ zJSV-BFfws^C^PB@*UG%w^wM@6Q#%*)I)RLlWpASl^|#6|_LPSP3xY;%!UuHb(>7~l zaP@>E_5`L@w>F)5( zgMPASL*sRB%`X+h3^q2 z?O)GaF+$nSg0}zgIrG+RutW3UO`Q~2Z(`?qfA3LrcU|QBS#}#f`&`7Ot!((ZsF+v` zd-85`sMdvZ0L*%|pzmofhBq_JZ@tNFP8SCJ;i9+*;C$dTctEyY3xb$5bH3z2RBVR; z4IczuJ^z`6o#wD;*rmC!#_il=L-J;8ooctP0-jNPR3_ZVqEzeIATRSeu{xp)83!iI zz9F|66dd5#twr^{015KzwjI?uRc^|Ut0x9(lPY}|rF5LY(7dUdND@nb8_wd(tc1*ZRQj8!il$*SUZkBHK;udxHB8zW!^4dH%;%E-^p7SShEGNHP4c-9=I9_5XaXi)w>lp*b`-?rR;o!VEd}A%6u#8K8-(>to zbf2klyUTX z`%F#uXZD#`FJPFsPCNu+dYzaBc8#3&24TJjYDd>sJZNMkaiW+3j$eO;%krGJQ38 zjePdQSguDA45P7Zty8l;!3}h8B#on{4eekCvxqg$@qQ>4{?Roh&2U+N!fI zf)u%(b4i}3fxqM5wiwLKZzb5gs;V>0jTL&5z|JIl-CKma4Td74KO4)AV`w|!7}S2w zQZIHs8L>PU_zy%^iiBvFuOGM%_bud(NBb_@W`hFnsn)y=zK5vN6Ne?-Q>ZfREtkhN zJ?7cVQF}SR4}tZU<6A@8r_XEy6Zy|B7Vj&SFTUYoapdd_8FB-sD#q*^Ytqbt?lFc> zE&2ieM&Y4bXEgQ%+QAhJkLs9O)^H$j`JW*A?{nV+5Jc3>mXzOWt96P4H^aaM7}d{{ zw7XklutS4xBFhcX z)yEmElf=vJl&Xs|hv#YU)G;U{_-+p6D!}Fc&WeU#SMPM+n-I-+w581FxSt!{{=q@= z(Mys?B7Z&OIAqwA+?=hd`DZwCT?M9+Fb7UFyu4Kn?EWlx0UulY*W`&Q%h)#9Tk`R>#QO8#UyIvP8^<@VNz z+oBaY;rfKQU`EBwp-ph&x&*P^12RlK7XS-`Zh7XG<1BE>;?e9&WJKTB(~2R_Mc>!P z15v7ck5YH2d)Z4xB0Ezrq4I?IuC^__G^?_?1^lxB3;F*Ah~hn|0+$_tInPobWC7CT4Ycr z0ntDph!CcbL`8z4fCwRsNhLBEh7ceG2ubdjU|UP^o^yKcU3abD@BHB+u*~~?@AN$T z*?T|NtxlM&{A|N#AP{Kf4@bYZ0fClefk2B#{>J_lImlT!y<5`DZ;GwI(T(F85Eca&t#+?c5Hpw|LQP)NGUpidkdxXt4?N z&M9vbz3s5OpXgp(^YE#ODsuI%ufH+f{T_l#al zQQRRKABX41SkoYR`lceNfyk5}Q*@jL(RC82sx1Tkh{>#93i|46j4rSaB0r2z(T1KP z6OC-TBerR`jv6M~>`3%D+1}jR!E>cqXTEa@BtuOxR@ouNfHs1PTwqitk+V$1oN{ZW zmR?*0`laVp-(t{DKk2D~e!N^#0Rny7Vl2PmbpN!q?)H(oeNV!)t4u>&=tE_WsRuXc z6sd==V!l80hF z+{+K9(vFo02su?pDt;C}e*3K{2o!?NL!8Zicrq=idh-d_X7$?abuIvww%1x$U0UBSs;HOgMwChF&bz zwBR4P@cVT6-9h}A*KEU)q>#miy5c)JGd;t9gU<+6xl2Ky&cpMz{M7^XG#xf#S5%Rk z-`^d@ZRy2`@uA0iATXzqF>BxGX`((e9M~l&KL(PqsZiX|-Pcz9T9_a1aCmy;vb5LTj;Dht_eD1ZS1bwKZR+tvS zv)#Ai&~U2=b$o(F3WK;NzxLa^!~cf`-u5P)DL0rV5y-x~EUg-kyt29^qjeF7^j(VqRiy?9s>R|eM zjh0oDQAWY6Dh^$Mt~wa~k?Tj4KaKQFziM4!8(j4ywxr)j?L|x>qMP8iL;OOh?yI@p zJb@jOc`UH|2ZNGqM+lAC3GB94m6Zu_+tJRwdMz5f4PaYq-@6=jc*RN)vz)oYu0y05 zk)3=|@7DU1m#ZdqYxW~wqQl*=s=OROg9mCLkpKLsy7%dKDQeoG5^F6UF;%Cjl$ldE zTZ4h`^##r~%S z`O(=C8_`btdV1cqw_WMb3)0Y_T6Hpg&}cQ!&Nhtq&|!=QH6{i@#GuQHidIlCLwb5a znWZt^Or~y2X;Ej}{4Z%RYse3$QESi);v|U$HXq&lTQ>RN9Yh(Gev-WwuEpo_NNizZ zz>Y~%^P!_lKtCQITk!v{#I<<@qhu;Ww) z+iNWb*^^Ql(`h?TnK(5eX|pUab3cihUh^ujrb5g|8_m0z?#cE2_fH>mbYe@M)aHp) z%|i{Z`o_L7TC2<=h4=#d)ejlL`Bb9}+;&FN;80cPFHFEDS8H!L#?do0$AGc{6w6Q|!)C2K;`8d0e?i?`%`=^@sv+QcIvxdh0CFqN&M%$6Wb7Xce^($|My zkEtE(rpP?Iz`{?0oJS5hP1JO*Ay4qO&{u#!rB~)Bs?(4IPGqY6T6AO2k0m2TMsC!; z_DgQ4{KU)s0S67DAktN!4^|UM&EvTgu*>AI{b@}5Dviv+t4-+( zZW9qNn<;4iZUTT$NgbJX1^4M+b!@Q}cgem3J-WJOps){!QM`BGGB43kjfj1P03>2> zcGfiQ7kB|k^ocT?eE|Ae@IBS~meoY(JVTe$W*q@#gkC0_JW^ABQ({)b=jp2ua_WFM zol`k-cVovQVA^(uC9Q3f&M#&BQH`3lCC^LR&IHW^+x@<07JAfg;Td9qvTaCocjDRf zDno77Oka`;u&ze0mw-+U1EZHP@^C@xhGluT&%o*-DL2C!`Vecxky!M44m`jJd| z-=_<@GQNFRDxV)%)$1HZYf?3QJqB625){%g(uK>0KK)|h{r!hw)W`@ZGO{=UUdVxW z05)4EN_Tn>r^Bs8^gzJ_=*2S=j@HjG(%`X%9|K$lf< z`)?gbZ;XS5P=Mmm+5Y!xO?Q8-1MYGPvN_)*0eBcdZ2w4Kg6@~U=*LqJjA`Nek>pqq=-{Wy;}2A;x|huw{TQ+*D-i7N zh+ztOPr~LY2#4HRv&N2IlQXG9|9;J@ZSQo{yAdyQON<)c{i% zUVItbr~|E~O()4-zY}U5+&@q`g0BiBt!O+ie=fj{Oe{%Yh!9*SEq}I!0YQS+m354e^0#v5KpU(3@YCzG!>*?DnT8axxC zcw+%(SLh%g(ON2}zpw#nC?Nl-A*mchu@^&GC=At|W|$0AMmBpyv#4vX-q3OPhDi)Y zrV3@OX1R1=$cZ7L#`V0}W;~6KjEu~feH(AwjFCL{m2>dZ%QJuvNb;U#3Mi6P;5YT$ zk^^jj)?g*D2SIYVoPD`@OX&+}-BOT$bKlsaa%q4uTnnqk`hwdYH+z&F$m34M)MUuu zpLgqkM$=0wyyJzwv!?98Vy)>LZ?t69^u#E#l)zcV-SB_&_c&%oalIZWyg|k97KO6LjJeOE072Yk`XO}T%q2_9u zI<_Gt=ifX}(oV%r{9P>U{S5SI)!5#oP3zQvN^n|9UNwKWGl?GyZ@V$sHH@kdQb;1# z>}|nvqCIZ5?{txEXpCB(Ej#Vz5yQN7%|K<+oh!ymXMKYOA`5X-a~HBISTOMnEEQ_u zYa^i(0|p~p_t?u*ZVYh+V;aGg49HvikI9F`P%(}h)Y|8BhhrAhFUEaOgOG)X*C~iE zzl6^6WYNfNgaoI;u(mjqVnI0Wx;!iil?v{I(DxW!7rqV&mzl2Dz1=)AX?@=*V;Lyu zI;A4~l%`RVv{xK@Px35VRF30<*AoPiQh@_ouJ2$39ELIB-lT8yu6(9Bu=r8z_}(Oy zHso}xV5D*+i7{)~odIo@J_ZiZguw{#ZG10q0wwg}Fj*s5nqnqDN&y~aD49ZawY`HL za54dXRg_hqR!>N%k`m+ml*oaNcOH@DFId|-zr<>VPTXqdlwd772OLQFZzIM~t({Qn zXCkJ(0}nHYEr4wNklE>ux04L*1UQngyD+TREZSH3dk4dJ-}C(2XuTcc)OzYSBPu3N zh9hR_eg0oR4Xk9$#`xOSYRAwgrKvovMWE8#6a|)k>!GXmj62o9m>nY($kmsFMlUNd zM&^RZB%3#mqMo5Ai96-T^#-%^t=mO7fBGxswEIuo=RaBF0;-_ zylU`>K){fsG_oQu%J~^I2Q9~V6@I1pbiBlu71=PjneEdW2|9|2k*ijGm4{F24xb#d zTcq6)CF9T0gZn0TT_tNb1a{~6pKtP9Mhv2&nwNt13RX3;HI7NEg+!+9yHkL`` z`~3Hj#>(mDiwN!I5{W3Y z06z4HRBC4zAu?lZW*UdfB2oFRRrtxiM&?xVpy^f4Rr;y&YWY`8q{P4IaMhsfZgfJ=P$N!YhMFxjX3N|bwds(&H%m$xqc+jp>3%UP zKU}6AKlADfFM!rD=OXR8T`B$fu=MaC!LjIV-CgrJU?mH_cjM=7UbnOw2(&}Z)4P~I z7&v9!z7ZsN+zFx2h+3ZvW6ld6s}kz*@`6Z{h(WEI34T9eYOPe7kr(#~K+Tq~j+k|7 zWR3+Qg5McWYoUf@Vw9eRq?$3_h>R{8z8F2;t}g1k$LVU(u%lLLv&auim2Gvg?!JBi`rT)NU~o$+u*mx_`IGc#>;lg#su`y zL>rw8g$HRl16nPCz6d;&32d9(DLO$*-pW|d3#dQtnvetSN{mo3Gob+*hCWcCgvC2h ze5q2wt44fIayLuXrzOAOo2!z>jupd;{o@_D>(T~M*5~sC4JQ^ZsH)FL=uLY&HyCK> z6!#A`bnBz4SnE=Lifu4{eA#>MP|AlGQZi5>SopXq=~C&;jH}sAZba>Ng&^pkpLJKRNfSnDeT8Buim%5!g+=5{^mACYJg2G6#23f5+k){*i23UvW} z#x zCfhMBWa9fjpB)?mYMXhu)M$S7IHweOt>_@8-qJ(ftgv96Vv*p;C_fFj1-yD7&m+u^ z_Pz1!b2rp11&*HCW2?~;(OYc%1<$5B0zbQ)z6FQ}aJeos$5p=g9K_Bk9(a$-h{3{D z0=gy|zDlBVm~E7L0-eu^`7YY~&=SyBo0TfshoOCMbdvf^*5Ib3fCk4Sjhaq}!q4yB zfwmA~`75$$+dOi^WGpRdth#)3S}RIe!I&C`$oTacF@7;4vR7{C{Ph)bem#Bz&vJG~ z4drY?X(!oWtVpwA`rz8BFSIRodvTY6KDCt}1C~iOsDQDvEH$t>6w)?ys<15pUO$*N zSu?msH_r7VMbnzrNtkwEZEtKzspnGRZ(K--6bq@&9*(H!n5nPcqFWLLq`KpWB+@d? z_0t9TwaugqaL>sJF+LBfIvaueWj4_fAxkNwZ9?bZCe`qK!BhJf3#!uy+>5&o^l6FN zHJnVlhEbh&;uQ1 zxcq>xTM2ntXZ?kgXy0$R)6Kl3z%SQdNnl&!#$QAhK5c{5b7x8!uN+lnImV7XDM@38 z5M_>FBxbD_sq}{3_}e^d3`-v_Y#`NO08sD~2Rz&7TQk*cJj+H`eU+Eu?R$unUo`A7 z2Zy9~$e@&7&0-%_S8)(RUl3JpycRQD-jTTs1Ty$jQk>b(&v$M!)aDVjYTIBjjPvxqjHE;i;~Q~MrB7&|jDXiVcA$U&U_ ziy)_z7Xh5r9Ozox&CRXuG3>Hi^*<61b>1_EhP6kdS;a612zZu%1Y#<@|0PX!zN-g{fG|RVnNXR$tAk5+8Ln9e?h+FIu6~UFHxU zUevpst4#{kdd{Fhu%tmofNI~B9l@=p7S`_9K}K4YW_8QTLlO>)d@e`qT^MG}^cQju ztUQ93k;{w{?%Mp$zn?RqsU4zzB4W1P`-8B0IsqGe94G<+{yF5JjvjH-(7LdZyigjB zTN|R}6PAL0Twy*>+_#cnSK%I9neCrD5^`meM`O2cq6U&$sm8MzN#a2`2X`gzEEFD2 z;8(``yPHu^*qAavyaXgxg-e84UWWlB&V4S#ERH)HKrYlt$^WsGT59H*Q^qu&oa6hY z5?K7pJOkHw|Hk$z?H-v`35Ca5yB7r7{Xuw~J1rA_Ee;hAf?-T+7;PytTpT8GVDBO#jEd98ZE0s&dbQb9Or}9o79biX1Yv! z;i9v=h|o5Iu>0M&N~2R^3srN@_$2OfnX;$2`0enm9j%8{MfOya+>ryK!gctEk}$Y3 z)!1r%_F7IKluD?j4EV>(Uksya1~A(&OZ}!BW9xj$t!f!B??GVNLbcQJ&g?+M_*RuV z{UvcTJwqt_p0nM2jKbydtNDsKHNiiN$SXMYu8fYb(3VL>3nJ>3ey1h%+UzH}J{{}4 zhDl4u2%Z)up{0(SowY5Y)Zi`5__4}u<0xB;jh(H5-TB}i+Vg7l$cJ$mx=|{mxy!EZ z#b2p(L3P>F5-v%lGp|~Umk0n#I+%vX|MNnw4lm+*Y^XE&OP7fn&l&ArVsb>`e#4Y? z!GX>kJIXHko2>nkmpLfRL!fQ|=K0%Xee#uZjF43*s7ZcATVB(^n8@?<0`4VQED-#+ z_MV$Z9F$7$K0$(~r%9(@g*V=YOyO#+gy_}<&W`Vfu?reBqX`KeMQtaP&bAL9I+1P^ zvX$v+4udKRPoUVDwq*#?sDrbT!G(2Nh5KixVEu|7s>1zauEQ^US!ePXSMeRbXghtn z+o5Pe_NHXYEpklHwD&T>Q6s|L(ngCtJleG@U@-SV!L zcx-C^V$aB2&vkj$ttOnh+)qk2b6MUZ*sUtM51| zC|nThxwmPkh>FBT#6+o{+Ox(Kugl;#F)B6TzFRyB$|QoZ!ieq02p8h+n}nI9EQP%- zrp4)d(K5ac>Ia%786hYbY^)6td0Jav-Qi$X1FGQtP&Al#`%75b@l!xHsf+s9nNns% zPnzrL-6;vnqw0NF_nJI;{rR^q*jMgdD<3-J7(P{JHI$N)m@>rdHD&X+O2)Q!QcnWU zpK}+!zdOKj0<;ZUp+ei}(}2KyHHo%hmuK6g0w)LIN9IhVEXCN8<1)Z?&i70z{!YIg zYM(x+dE!0<=JhVpy67z)0){%bYpBU5Y`kW*`8p4=gnYp67Nhj}L7+6OpbNK|Iae(P z@3N~t)fv|CS`n2-M+PC}DKUFNil@aGFKzWuue+dYek;lAa@_rRb(P7eeXm8tWdGB$ z-pukCp8vfXqT?;D?aD?XOqZmHh)Vw?aFWm|*lasK!ZQ*^$#{3sJ;|y&KvHd}`&dBb zFc|&vFu)Y|XX)}Z#Mo)Av33eaxLdo26gb6uctJC%U*dm!RAB{G>s2S5wmnXEe2Z1Va`U1TKK)t%C9yfa8%6~S;(C3Evhl!MMfT@gx#si$Fd}M=N`Bj z@P}&bvh&@C;wyYg*YqZ9>3|!l;G_GA=QD?!zq?icB$w<`>(r3brlC{xjqIZR`I@>v zdQHo`MC2y&@k7)Aam>tW%a;ot55RJoBgye^d1?cd3L9Gh>bRrV3&O#N7&DKF-q@9Z zCnf+O6NPOoz-7)Wz{U=-B(1dS87og}>xYh{1Puc&DQpB3C%F|Si=kT)? zTBKHUZyK8cV~`C$T5R#^-5Qx8frg`!h-q!)H6f%X(ySMG1#qFB{D=*1As6b8`9OkG zWYsf!{x{sH1pv4NG)cZ5$;gc|_O>_-jym|RZ()Mt&8huPFJ{lVSi~haxIs?TwX3qC zOB6nV-x5>&jMbg@Wv9}Ji=cAC6^W`986(+}{wVf^kvoANV)!H>qUvezBG9AKKY8$W znYz6QR3o5<95(*cYE;_q%{~}5Y7x!)dGuE^>U($A`+9b>P6l603n&8|!{-lbBNMIY zhNtl#f!TbWjaArtuXxy3+^TNO_`|Mx~#GveQ@Vi z>>0op36vziD#9<}0O=P;>YN&OeF2K4=8^FY3G@g(^#vu4bvFSrAl^UrsJe>=vA6u)e2+7A+~zV44uR zf@>~QETLkX=+1H!Huv;-#p)|Dp14Qpg%yz7-zSO_FW6t-EO0L~)^*9TqG@Dmtz2*x znZL@xj6sa$mR#GxT=2D^PgzD~#?ID%@~XCkttm0L&*32Ae-WpF*8^TK!b;EOVKwPg zPZwXV7~=9*%ul@`(SP(lSr;U`@O~?+jsg;@zXqV60s<{jzk`Aet|v?Bk=*U~?i{~Y zv$gmV%^cF8HmpA5|+q=3^bc;rOmV7)Mk3 zTHWPz*0;r;vF&%e0`z|6?DPRE(~r@AdJo*jK$z}OT7q9o9NL>?>U_}XFPc?XH!)>x z`hbu1wDZu;6ksnV37!l`>BCPJW}^Slv*2rxgz_zU8oL+tBWt*0Z3zJ9h$_OT3m?Ea z)3;&LUv{mMskX5egUZ3T{GPf0V_f}@arFQlb?f=8ZRneTQ&-q`5-|xMw z?NL~yjIV}HwL>?e1}b};*@&HH_X3qk{pa87`xeHiqTlV{0iX$`pdR-I&B9(U5YMpr zeqVmj?e)I>=|h*QEw6TK5@5Mnh$4M;E_8obf)7{CJT%gOi$Ku>00NCp*i!3E&)z=( zjx0}Y4<-27G$ayT z?{W|PX`XI>TkL1h?_KgTz4&*Q#td@ykB}N@ zo$91uJO8oLKcnUwUILwju4g^u`d{iO7_7NHK!S@=FTcy-x!b(`d4adxfupe!siE!R zu#>Om6#R-U!*AZUEw~~|r8P4Xy4u3ECaBc&a);H4Wn^z&E902m6EEMy=E*vJbK;zk z0CXt=?O6)BBDjF_zk!|=Kw4qv{EHIB?bl7Pe=JcbdcU?1|8EqE5219)pwX-;WGQC1 zj^3?JLx?CpdUEYl!^F2tbDsYxFbMSa&iqcJYp3U*%)}Db`*ye;V`h6+-sSG zQ2``GS%)4R7mV~t(P%BhC^|5KZsz!tzMlhC&N{^@yn)Sdj>XO=%ilw0AhH|!>ZTJ{ zMQa)2e6q(#GY|b32Zw5=M$W5dV>42*=6r<8TqmRCZHtqQ0Q;6Go)rU;`Og&IT=&7` ziwG@?6DV(uB>50=w$BqM1bVp86#vjQdd~w9?0vptcf$9K62T3+SGTwTj3}U^DEcJd q^UuoqvlM6mGA^ELm>!TXsa)dD?8rf^2ReX3KbV~Oo_zS!#s31fSN1Rf literal 0 HcmV?d00001 diff --git a/extra/walkthrough/selection.png b/extra/walkthrough/selection.png new file mode 100644 index 0000000000000000000000000000000000000000..5c4e9cc53f45861e253185f695566b07f599d51a GIT binary patch literal 9071 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 literal 0 HcmV?d00001 diff --git a/extra/walkthrough/you-win.png b/extra/walkthrough/you-win.png new file mode 100644 index 0000000000000000000000000000000000000000..eb4cc85ccbdf6f75b019c45e9cfefd43b58197f6 GIT binary patch literal 5004 zcmeHL`&ZK07C%yo6CqRH8(BHyZAzVMVPP7+P#epf8f~VBnlM>Dw=N{V>xCv z+SQ>_SM!J(U#Te~iKzLetT9Ey2Z2(GBI>9J0{7b=ao4&(@WWaAoU_haXRp2X{_Oqv zopMZIP%`IR^ZWPj<8deg^LUz2`^!w1LK*pbJ1#w8!(AWsBORIRE!(pzl{~qpBFtaQR zxvGmd4~%zMJc~w(#Hs>q4GqRgdd1tzO6K2O12ZsN>qHtS5{Vf#IDDlj4zuW ziZ>;i-{Pu%N@yvHCNw=Eir~0HVGepaz$~Rp=iw?tBzH zc=b9nB)CyVPR>t>J`|CyEzw_1VYnQ{M|`&ITqi31*~e^BpCUf@PsS{P^Pe#Ui zU6J*(u+C>2t&T=)W+8<2S7iYqY_krRL5^LRrZa)(VH-Pe77-xNBI57hdn)}&kK@K% z(wY|rk+Ywc7wx`jc?s^|Wt>wD#UuLaq zqS|cE+NQRBvpk``seqR+ZNpq@dlbC7$R^og(dyS(L*(We#cMwvzxIYHc_yLcVxvq~ z;ShtjsTX=0=$!PZMH@u*2-gDlZ0KMapTlA^hBL!4hvd3hrzJxk z*VwWbL(6or3a4PI24b931K}Z5s7;>6GHC0`|`R z>q)N-rJV326p3%z{k^EI=CZix(B)CcFX|-}C5G_2PQ23r8Sj2?LpETTDB>%&*D54W zIcm2>cxQ&}z_)+)h*fWPO24ak^&+OI>tf2U0f~hq@+_(Ej_ZH8Af=^~QyV1z{j1t5 zTBVzM-FkUcw5Q$iAP~8XMJh?t>9%F%1)Pdb_jX^*C#$yf%ErOfit2OA@e zg6i!0LefP5q}S~~y&Sjf4JFH9^RprZ3qhDr9hu%hD1(x&rQx-+QtP*m{NNgjseVlEgcP5He1y|V0x&POe^zBIP(o_h*kVM@*kk|eC z-C;l1zvia~bWwQ>5B5rs&Fsw<<>nqdfvf_Cj_V&{zy zBD<{Vw<~tW9!KEiQtQC!)z4w&`=i+l!{TDgqBv<}HWA)hT>5}gOG($Ob$aKF8fvJ< z>kZY}LvDZXURTSH#MV$hv-D6;eRVdev&SDdx3%X3OV~gkF>(bXc`~v|BX2ee2}a4} e|5-Bmn?wsV|Gsdr^b91g0srFxzSYOVvi=Kj9bVu7 literal 0 HcmV?d00001 diff --git a/levels/level5.txt b/levels/level5.txt new file mode 100644 index 0000000..0263dac --- /dev/null +++ b/levels/level5.txt @@ -0,0 +1,144 @@ +player: { + hp: 100, + inventory: [ + { + id: "dagger", + x: 0, + y: 0, + name: "Swiss army knife", + description: "Your trustworthy army knife will never let you down", + useTimes: infinite, + value: 5, + actions: {} + }, + { + id: "potion", + x: 0, + y: 0, + name: "Small healing potion", + description: "Will recover you from small injuries", + useTimes: 5, + value: 5, + actions: {} + } + ] +} + +levels: [ + { + layout: { + | * * * * * * * + | * s . . . e * + | * * * * * * * + }, + items: [], + entities: [] + }, + { + layout: { + | x x * * * x x x x + | x x * . * x x x x + | * * * . * * * * * + | * s . . . . . e * + | * * * * * * * * * + }, + items: [ + { + id: "key", + x: 3, + y: 3, + name: "Secret key", + description: "What if this key opens a secret door?", + useTimes: 1, + value: 0, + actions: { + [not(inventoryFull())] retrieveItem(key), + [] leave() + } + } + ], + entities: [ + { + id: "door", + x: 4, + y: 1, + name: "Secret door", + description: "This door can only be opened with a secret key", + direction: left, + actions: { + [inventoryContains(key)] useItem(key), + [] leave() + } + } + ] + }, + { + layout: { + | * * * * * * * * * * * + | * . . . . . . . . . * + | * * * * * * * * * . * + | * e . . . . . . . s * + | * * * * * * * * * . * + | x x x x x x x x * . * + | * * * * * * * * * . * + | * . . . . . . . . . * + | * * * * * * * * * * * + }, + items: [ + { + id: "key", + x: 1, + y: 1, + name: "Key to sturdy door", + description: "You have proven worthy", + useTimes: 1, + value: 0, + actions: { + [not(inventoryFull())] retrieveItem(key), + [] leave() + } + }, + { + id: "sword", + x: 1, + y: 7, + name: "Mighty sword", + description: "Slayer of evil", + useTimes: 3, + value: 100, + actions: { + [not(inventoryFull())] retrieveItem(sword), + [] leave() + } + } + ], + entities: [ + { + id: "door", + x: 8, + y: 5, + name: "Sturdy door", + description: "I wonder what's behind it?", + direction: right, + actions: { + [inventoryContains(key)] useItem(key), + [] leave() + } + }, + { + id: "devil", + x: 6, + y: 1, + name: "Evil powers", + description: "Certainly from hell", + hp: 55, + value: 10, + actions: { + [inventoryContains(dagger)] decreaseHp(devil, dagger), + [inventoryContains(sword)] decreaseHp(devil, sword), + [] leave() + } + } + ] + } +] \ No newline at end of file diff --git a/lib/RPGEngine/Input/ActionSelection.hs b/lib/RPGEngine/Input/ActionSelection.hs index d0ed414..321a796 100644 --- a/lib/RPGEngine/Input/ActionSelection.hs +++ b/lib/RPGEngine/Input/ActionSelection.hs @@ -16,6 +16,7 @@ import Data.Foldable (find) handleInputActionSelection :: InputHandler Game handleInputActionSelection = composeInputHandlers [ handleKey (SpecialKey KeySpace) Down selectAction, + handleKey (SpecialKey KeyEnter) Down selectAction, handleKey (SpecialKey KeyUp) Down $ moveSelector North, handleKey (SpecialKey KeyDown) Down $ moveSelector South diff --git a/lib/RPGEngine/Input/Playing.hs b/lib/RPGEngine/Input/Playing.hs index 8025611..6f1c9fa 100644 --- a/lib/RPGEngine/Input/Playing.hs +++ b/lib/RPGEngine/Input/Playing.hs @@ -36,6 +36,7 @@ handleInputPlaying = composeInputHandlers [ -- Interaction with entities and items handleKey (SpecialKey KeySpace) Down checkForInteraction, + handleKey (SpecialKey KeyEnter) Down checkForInteraction, handleKey (Char 'f') Down checkForInteraction, handleKey (Char 'i') Down $ toggleInventoryShown True, diff --git a/lib/RPGEngine/Render/Playing.hs b/lib/RPGEngine/Render/Playing.hs index 98252c2..9a661bd 100644 --- a/lib/RPGEngine/Render/Playing.hs +++ b/lib/RPGEngine/Render/Playing.hs @@ -42,8 +42,7 @@ focusPlayer _ = id renderLevel :: Renderer Level renderLevel Level{ layout = l, items = i, entities = e } = level where level = pictures [void, layout, items, entities] - -- void = createVoid - void = blank + void = createVoid layout = renderLayout l items = renderItems i entities = renderEntities e @@ -92,12 +91,12 @@ renderInventory :: Player -> Picture renderInventory Player{ showInventory = False } = blank renderInventory Player{ inventory = list } = pictures [overlay, title, items] where title = translate 0 (offset (-1)) $ scale uizoom uizoom $ color white $ text "Inventory" - items = pictures $ map move $ zip [0::Int ..] (map (getRender . itemId) list) + items = pictures $ zipWith (curry move) [0::Int ..] (map (getRender . itemId) list) move (i, pic) = translate 0 (offset i) pic offset i = negate (zoom * resolution * fromIntegral i) withHealthBar :: HP -> Picture -> Picture -withHealthBar (Nothing) renderedEntity = renderedEntity +withHealthBar Nothing renderedEntity = renderedEntity withHealthBar (Just hp) renderedEntity = pictures [renderedEntity, positionedBar] where positionedBar = scale smaller smaller $ translate left up renderedBar renderedBar = pictures [heart, counter] diff --git a/verslag.pdf b/verslag.pdf index d607e19ef4b64e0453b857b332f480b7f4627c32..03999f64678eabf908f2a6a93325d65fdf433b57 100644 GIT binary patch literal 306826 zcmeFYcUV)~wl*GYsHoT|iWn>uMS>IwJvKx|KtTa%QHp?+Py(R^upmm68Xyz_Y0^YM zN+7sVKzfOYln^?E79l_gf#h4kefHjW@9nwg-tYX%ABQ~8%p!}-HRqUPyyG43oCx2% zd__VEa&D(^bJs6lYQMFAs@r)^=&TUJ?BPyjWg*EcHg-r$XW-rLKGO2Cr8&aFQb_WK zrGpjHT1Z+!K}c0q=+~c}E=Xs~`}RBCdlPlb9X_7jMO^1ud-O&bx#^ML>kuxa?NHw2 z?UQlRvMgBCyRozvPd1eti#%RSTM^?Q+Ol18H`6j?i`&us*|9A&9ZGtjN`UI8hBr>B zZ8wYKJK`zNqV_%V+jMx>j!i}*2_L%4PwfyNSlN62M&KR@r@S13HG}9r7iMpEPj;Zl zd@>X|BbR#Z#Ve(c_1nTP^Bps5<_^x2=R*Sf=O|j`Ws))Qvhe{K$ZqQY|Z-MV^B_dMyxFtmj zD85RyQfg^j*SQ>LKWpfiT~0wHy&3>b;p8qZnAz=&{yLWcf$b~a%s(qb*nY%Tw~W8$ zrf641(&|9LTS`3G^5E;rot6$3zgi8rVw+r6=3lhEl)Rku@5cY#_OnI}4o9GStJ?Gz zxnsw5`u#3!CdAKy@WLG}-y2?_U+kF|u1+4-TF!_Wz5M>{-fdNdJtfMjdOW9k(X-aH zCYv%?RdVhmzi`Yj9K{;@^nJ&yL8eEh#NPb*xl6S98>bGem`CXlsz;)R1>cB`@%p+q z981#mewcgEYUh(r$=CCGMw5n}W)zeHTpYfy9&Rfrv~<`0lr{q+`pSpUGOLYx#8Pg! zC4;rVu%Q;%cUnr?Wdp+c2v!9tbf~f5`&z*qjZh$ZxT{sY=C$;Sx7*rsNuTGUk@R5- z|6Y#3HhYoXg^z`?x2&QyikrX4U>8HXTOrc1E6=5`WtCWChpiphzqM%m&G zvXAciHY|uQ`>kQ)O@F>L)#C^hHe?6lP5}Oedn)C+d?&ROG2Yki%-S}?b>zd)Gv>o zEWMP!qnIR(;?VB6lJ3Ugo3CyF8QcycT}->TTnd=8H1* zm%rXKx7cUh@pzs~gBtUSr|F*L(IxMb=0uoEn9|)FYCV3fAf8F=7flum*~@q zkUnTo!Zxn6vBaJi;$|1}{MV8$B459d(s|+gwOXcspnsuGBxBnSqqhxDpInQQRvf7l zzC3*7k@PD4Y0F*F@p|thT}G^Eof6%2lTefx=azoZ{ONYL+8o=M z2ChDNt+TmrAGf||#T6lTQLmqU7|!w0lJ83I-VD2Av7ZmP4?%;K1x|$Y-RF0cNz!8rdQ|qazzz>+)H{5%GRx*0TPDn{n@{S}?xJ+vH%$D&BH*$2A3Xtk4vFG;Pba+emDcmZ! zJ|(MK(vYzuy5nT3&Y5K=Oun6tn85Me>-BEBH=B3wzrgow6lwqVQ-t$Ux^;xu&|rE| zL)W`A7KMW^Rvtc#Ivse56I)rJvgveU6VL3OgfmO!`&AA~6^19J$R9rT;d4@-U$gCN z+rD)DpaJ@e7qcESm6JzOGFlbD+0V+`AzK?ux)0%B#m@xo2=tT4==N|@ctGj>#d+2P z;_+Sf;KDSQXYN!*x9QYaKmYjY+Zxw14P?T$Kcb!UB_|!}(-J5uKs1{voFS{A#kJM%&CmM1!Eu1ZQ_M6Jj%Y zcKhKGy;7TPi*8v{(L*DNr`-_~IrF*~l$|aZuFzNVjLn+A&K$f9PL;8%4>@^QaG5?d zdQ={KC?sFB^W9uuG7Ls-71M!B9?CrUrV7RM)$i0$!sf2KQy-V z^qq!upRlKF^#Nn1oq3t7Q+rTVWNghuRb<0OYE@)F@aEv{rprzt!de<@nELc&n@&GN zNp44p?Z?eJB*L~;UAb}GBim1HOlhg_=H}St>8;o7Lc^+-(J(A;(&SNtA?~%?CTeG9Cdr{i-o

W$ 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∨PohS2PiG;r<*?MxyrsM&}2+d|C+|mUFQ&f`28}V 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 delta 39285 zcmZttQ*fZc+O`eH$;6)6wryu(+qR86n2BvpY}>Y-Ol;duzO~-9_1E+7banSe?{wAG zM;+JM5u2z*Vc@l~pow};2#Hys_+XsesdIi17{H~Di~}AgeD`%tnxLT?(itQc_=Tqh z+`;m43sFhqSdjo*>er_-YEk@A9j|)^7c=It@m7MA|C>|<%e8<<=ldfQAAvu&&(e=+ zzLB1*+Xdkrw5{;z@o8_v`Mu4Ot55!8kzEDHX+40FdxUqxk%9l~Mj0&I#rLdJ{TUA` z8W2p}#yq}3k2PT&^5aQGtWj|F&*xn5h=^(bzInM~5+L}p8X3m^+5Z0G>iG@o@D<+U zfp0P1akc%jrUI?K^MdgAd-K!j<+4}$!r4>=H+05ggfXY1O0vhIXiOeN#qdDaVvV^* zY#AF#n>)dLy6Z;Ez8%p!-XzJ9nTMx%8sMNK3p_3IFP(NCAwx&_jUpX60zHkPN*a&-lVwK)#d#io70xB6`v=Sqg+__ZzLQ%z;{$Gs z9fPmSXa4s&$##}*dOmIhR=P=)ZXDec`1Cof6cr-ldoRG*aBd z&@>=@shKBns3_zk=z$^V5b2d8U(-%r1<8a^dC~DF4MmIBJ7*rIm=+XSYrCLZz@gQR~ z;>-rMT~gzUy1Xi$-paJf>vr%I;9jH#_|i~6N?}FJVUx#b@fjgzI$JdIDd66qVl<#6 zP3*B^e(n>J!S37kg%l@IrD;8o%hnCDh)!p~>X}-;z~CF%P$>lz^lsIc=Kv7A2zZ8A z45*P$y+ZBO6>FLeyMO4ZY$6s5oYVO8#G{hbvSnQ9!#uChTR5Q~3U&&cKGd=_M}DWP zzq>k24kB?2kJGAq{7PT%^lKQks&&NR#;Sj_8e=uxjSyLW*-vMrc$np>$*Mt++!pt* z*tj1QXY!&|bQB$~AS!5nR09kH%q0{?Ypt|6$^Xe5D~axs&mknm7Su#^!`I~(b?)wE z?&mW<=cHbMJdRb#yo5qLF@T`}Fqdx<0&}|2HTr9@&&s#9O+9 zoaFZLcEw01VP>l;lLjP;E9gk6`o>fjmPxD+O~cPLR6#cAdcgWc)tZk7(5^Zu_Va?N zrrFl%`3A?nBt4&665ip@4&wQBx>H z#o0xcJZ>m^Y%C>BAU|37P@W!-QFY~))RBZxWl_-RRHhfQBk8l zI^p3Uit;-?PRTsH_D=kyqZL@t7AO76F4PuuSL5cBmX@%)bF~F>RGAQJhaPws8ICVR zd9~!bev9w6F(EiO7G2K&q}cv#Y>5wH^%0`%xjkO98N08;$$aiNU^Cab=b827mgJ?@ zw@2c~Fk<4pdj-&LfwJJL(n1$aA1$BTVuJswItnIj?$XT{{p+|!FR)n`OFhKOQeF9H zBFs+5i>cpIR1bS7gbQpv)UM2(OI1Gke zZKPa6T4kzSf|lsLTtY_@cn~%B;q5;9Z!>2bDcjL|>#hWbD&w#)@rDF+ZxUG&mGs}@ zXt>eua%ezW^`s6?^I+{(I{$z?hDu}6CijEZLZ;}k*Fpu6q**@9$&!0`KvXZ+3qMIA zQ9fd{hcwHzkq&D1wc<`ZSzG%Iv5c`(P`DlzO@lQ_ieLU@`@i8t?QkMbmPxM?t~Dkb zw?tMt(eM65T-|zw%I1nuO3@}8qShbvfBtcM7NrA9-BwC!C+U=IZZIlvUbff{Ic#>@ zsNwSoXY4=R)~F#VPBSK!$n%@B{Yxl<8gNf1<*C=Y52uNJhE$W+W%gt&C7LIfJP^w4 zc&pK(P&^r3Y2=x!p1J?|+PYuSMs*Z=Ef0T2ewV}$H>VW7KV?mAZ}McC(WbI$dsn9}7t{web|5Y94`8Mn zO?PxbQe@Y{2x;tecbZb{W5J`LDeWNl>FG(G!flt85py@lQa6|O~Irkj^ z5pz*M7c%vSk}k5y-M0|rKcfYfwoBD9&7}sj(4&vKqZaQ~f|Tg>Dc`! zjL)+#Cww}C?tgTF&eQ(Ct3mqzxf>3a#CT9ja29r^|MAQa?pXCf^X*r+h~Nztrq)J) z1bEk2fJ6G^)D3mG-x%ISBp^8C7r*G;1<{wA(=J{2q=zH$DR6sQ5_|s~+cPCE-T26DVXAP9Nf*w234KNso`E4$5osac^I8Ov-|ZF^X_w}SD(F{Kj#SAZwr2%SnWpzXMDZx@247A-5NZ+ zdhN;b@P1<0zcc57G*uF(4Qc1#wOuH1+4+=ci`FboTcoWF z#0rSFqLxH}c(T`mbEJMdF-_4ZvW${pP`G9N%=fzv@;&03`4J>o5NpV52TL`Kg>D62 zT6*{TBCXNc){Di&fKvh8H#=@DR^r(sw8VrCjG59&GrsGRXttmzVd! zCssSOJMR7xC)vXw_A}kmP1;ocxwqEl$>f$kgKO|BcJos9;8ww&+s6UY*9g^iw29dGKJ8}7Y2xS zeQ$WNhbR(#27UIejo&tYSA$HU%VFXi%SmN+`Y?VZVQL#Bwm&w#(7vzPVGECpuA-qq zn&qd%q>B-WFZvgF@24%4mwtIe{6)zL-Nr}JT3bDNql;%Xbk2OKAg8Z3<=-7$)^5MJ zWoKv)qHP6&YInb!Jd6y;g;LrBwScOo^x^ZI9t9npZGUZ525sdw;-(tikBnTpJ&Mz` zBloxa?F9$BygT%MUz`(ISG!shAr8d=%kAF5a`lITfveJ-=a=y zWP#66wn4l_mnC$&brxt2)RrG3M6OaE!umL zw}!Q45=ZO1^%09oSR$pw_wQ(9OY@!t9n|KvA)s|b@E5XamKh|=Bg=lktaXAUr)_6OnA^_T^O<*>y8j|A|n^OjHGtS%+1R+=ViQPf_7M-000dXbT>1T-T z47}ih{A=}6_xCF*=q_sL#mO}50+zAeB+a=FJ1i-gscjgln|A9`fL5-Ab!^c1adnZu zjl^w=%w(E!2d5t)uys3|W{j<0D6f<1&a6go+oM=PzwM%dKC9q^9Yij=b^Vr0iU2xv zqda4HzjyWBNlh)bW1_V&V-<4WEr3nZNaNLxMdmb#+D_8mA~~zg@iS}9qz%`_t+j$l zYeLTL#2kf5Gl$FpxEUy9<7g+4G1ng!%;ddK2;dNhk!Mll>P@v*8l}C$Vn>f1_cSdC zoj+zy81~anHlC&rxRF8MVEEx52JX!jb3c6nU5Qjks?4Oe-52=kcK}w3=MKlTMOAxA zvmVn@T*io_n>h~kk380n?N$jCuA;qiE%j976aQJvyiE24@Jf-7Y5tS)JNTtmq(Ba# zx1@JM{+NIgFMSe^OrV;pL<=oxjJxZ#DSeI7+_k_dn<0rbi)uCjBMI z6aW464A(fT4XAI~`Ecnbjg-YXTcr?;r4M26rUhnH63jZgCnKJ#d&1ps zR9P6bJ+wPi=rUJ5Fr@O@i;Bsd+3`~85ihMzYn1OY1H79^wmT^hqmi8|@Ye^}Wfqr5fk7f@ z2=kq6|6)$xu5|;lg80=9M)SA+!iY9C>T-vxs0L~Jy#12w3M;fFvp)xqgmT8-qlXTYS)>uRx#MnM;jZ6g|nGmx$=>VvoVB7uie&cwu z*yO9=6o4MriqB-f76ny2mJ!;)=tmeYemT%H&!~w>lo#JUdS~WK8?E{xIK=!|y>>=$ z5@Htr1Ja~CC0Ro|K(+BDZwBNBkJZ8pSJ<@Y$wxtjQHXibSnR2@&kQ?J4swZtSmw1J zkbXoqxZ5;!2OreH<2Ycd*p%U` zSy&ea#@?d~NMsm^65d@N{hxn^5T`*lbB^Llvxr>$jR%I`w`;F#HAy znWrcEYjU47{+uRcUv~TtX5LG~*goVA_?0H?cB!=5_Bi8lA4y5VsqsO+Ss+v#t>yR( zwW-4Mlijqb#;>feydq{g$ieN5FjLsJvHiv89Uu>|S-(O7l zI)l>y=zfB@)c-|u4sIeQqW>~FA0H8;gq3Y#4k$J_3o}3@-kjdz%TRVVPgGu zVtu3{70vO8`%BK?%NzV7M1e_*S*s?k*7N!O!EJWL8I*fSJ&`p*cQAZ+rM z%g3@8y@xJ7yFNt=5MuL!X~5_M+VP^#_2DajJyT}(ku6)}{r#S*;E>Ab@ZDJNG%TzD z_yX^5yjve=H411J^Bt!>ZL81HRzj}`-QPZMmzOJ?e?z9y`=JiEd$uW@k6qNG)A{Zt zu+(gzl1$a}(bt$`Yn1r!4$OsfP78^wr=nPC2Tg)CwQ(nasQgbCr|_zr(-rUH&vFB# zn%F_`1snoW?~k(KG9yD&W4{s+bX}IIOY6oDGi*r9;waDD%MyufdJ*d4YyvA3h&s_> zTlRhAhQ}z;$z@jfI?WlbL0f(gl!>rj*3f8j3z4_^e|*>FBCL^h(L`RtP^kP;dxC$F zGkJ4X@^?J}yrX~%4dN(+)Saw?aT)#H^uw43qOMMqr2uUB#==MGy(0ZE+XU>JithKN zp*gW+y?(`U@0DL}G@CsSyfzE#a!Y2CwmKT+6ZBo?1Qh#pX#CA#pIpbPn%txQ-ahvOY8A{XkQwb1pFi+ zdYvEuFD7fwi_Cl&>lDWQ-^Hs5QGTs#pCWdV#e7Usoo1^L=P;NE2V&; zRv;AAeEmN=a{t}A@Cj&C?k7R`jVHtnqYNNaWg9K85lV5d5fCUL&cBgmW^hA;MzdFz zr@-l#8YMPZ(_wk##oWK_u3gsA)8Mn#OW^yL-{i!j)U`G2)}!Bs)n{-iXgcm6BO-w= z78yLw8fj~_+}kFz%TV(^;82@JdWr%y5)x_*-cugdVkPO#5hU7M__}3L;16Nastglg z%8%!D!g%>3m~*;sk5Z<1xsvd!o!X2y@wL-V_Pw2)C;S3A4K(6nImbK+=dV21f@xmt zfGQbNLUJwoB-xC2Z&uEI8cm$}(vw;&BSU=ik&5Ooa&q?GNMpD)LHV zE>}4|B>Ny;*1q%=lnwG0g0?`vT+XJs9UK29jfcyexORz1N-^di@;{ZDuKlLEVl;xR z?^^9g)5{l}QcjbhOFWm>0#e^MuHR;zh2M=z_!pg8-LXoJ`Z7nWPohyx>(1UYLTyc1 zp5d-#C1xp_0lC~YrVMo*zw~^u!37u5tyT0fNE+&J?>Ndn$o?LM|H=m{tmWbzY+=yf zQDeM1+NSOY(Ji1$`}Qht8(BdrU?jBI;~^qY)e#XQu=8MwiustSv{h{r8Y9p(jeqD| z^H-#rC^%a2n8Lx8-3<6YUPwNQEt?2TDZMTaqHRpCh2OKCdvO^vli!DlvSNh2(-Qq5BI;dq?28tMW}{Eeyo#duE0QSh2-($yL)@L}DW8g$~lNC#tblChS~>n~6;m}%pA`4BY?2biaRbWO|C z>_cEpW%T3PWg7r3<_nIGT9%hW(|qMxS8&-;(_kozz>KOe6D@?ef3GU!sJl;XWO=jY zjI$cegk3!>*TSRwA%_+Mq;?Z%i=2AjowtY8eY!|*sp4f8GEoxVY<4;17eu{)e=k?- z2dyC{I3frXp3>is;#aiJaD}^o?WsUJ0bQo|pnt&;Gf6<9dW%dr|0I=N>-4|4tF-BL zS90y^4FH(EZ-3E0d_?vEoP9mVC6#MEX5^j(!Udmc(~>urT^YDFP*4uqi^+t+r?aLuvR=*QBgZlX91U@&0 z#ylWty#+GaABLr$njDFTAbJq>M+0TQTA+?Zq{y>eQ6)L=xPIDIBafz1nG{0FlD**` z=JU#FSOr!_<03+640ZS#&KO<&{$K^icXB{FaAj?ZMnp1ADk?-STOyWn{NEI73EDNm za8z=fqO8sey2z@*FR}ET<(;8VVnoRsXD-Tov1mgrWv#0Ia2d(ad%4%3N9Hf4q-~~%+^^4ix|xyf)K(NB7DQ6k zbrDSt2m7JlcrUwzgsBdM23Lqk|8-d3H<#OlLOnO&cu`zfl<77t_>q*-D+d%u!!-loBqV5dtI;^F|OzcWJ2c6o%qFngIno41MJkcolt z0l0<8iwkkl88e9vX>P2MMpb-s$iJoMzp2jC4)ZC;8&2CE#d<}q3!VIB2H&oqsg*>W zC6P5coju$7h^wm7)PS%6GBQLK^(I^3D4(~6%b^;6swecexQR4G*Xm}|OFU%6+KR+cM`pj1i*|h*M8vyF`hQt3w(vRHy)jcd)bJ z5kw18iPZ2Sg-*s12_ip@DUCWwx;T70187W%ycHRrHu1Z7ASvHh#Iq@!)qqZCH*rATTZbXK}<0=34#mErzfZIFJRyICh`OfFfs?>sr&e zZ@a7SNVke>Hz`)pZBJP@mKo(L zL0rsh^5^Dr*gp-Prt<>8v|JT5uD&TCsYwN1d*4L9p15so(r+0FaSpL!nCM*mKZ+(d zsDhmyci&-ut1AFqCEQE}L9?7Cte5$RL{~~KMeK;HY5(KkFqAO1kmlfqN0k^ zYPOS=z%NZkWF;nH76PO3`>N3uUAe_-wnwUw^B>*v?Jh3?mSU(a(<}a0t22e(PTC$Q zoSIZh+e%O`CHFd!am;7VH`kZVE?D`!nYym9G5uVcSKGRe`0z0;{niz{{U(VLWpN0o0Qy63*~7Lx*q;+H(m zVudP(^SlRv^ToGT^RQgCDTCOy)D;~2vUp-nH+Ljw%c!cSkmzEa8-k?{gce3rJ8>eC zV0LT%Rj2nlD;A7H*?hhR4q3;h6*sqXR1srk|@)G*k0v6%l%U;}Ml zFD~RLN4t zefYU%c!5A|X?9Nb)0vcGw^0`=%=4fin8!Nsevn`xs1dLH2dOH5)7Us6Pcq*4tPCok z4{IimXz1X5i*rH`Ug6!Sr?8Bd@c~%4@>2SfWId2*$Y#1(kL1YI6UA**LIZm#@ceAa zF$m%)5!4#YPCv%SzkKUAr&%*)z1If!|NU0Se0xLqqh2su%f^w2Mh;WXWX8Vit@MNb z#pPG2PJs_lOOSN8!$e;FxEq{hqGJU_fgdFedr9gmYXjyGyNy@aMd_SLn0ar=&2AM$ zRq{?8mt248iuT2{a|su3cP^M?|4`OZRm>@N*1(?X-74(G5ew$&7l^;M>(S6qRcI|& zxUUA(Ko37#8q~@*jPWrkF6|LE=_~~jyIH=Xn$GK_78$d-PnkuHc!;3(qM-p8g&Y$j zmGZDI)s(4GjmD);JDwg?)UyV?UMN%1GT z?c#3Gn(CUGcCC@XhnYWTB+kk|#hvjFP35zs@ULc$u}hnLde-dwB4h1uFTU||+k#St zp`z*Z_iKAjVpH;F88`2@KN~_kFUY9&(v@NUR0!1T_DutuxfxgJdxNw>H_j8K0(@^` zR!I*)$BwuA7sR(L{?Y#hH?IGJ8x=S+Gk59^7z8@7sWld}&w}bW%JAISq8UFgbC$A zqwAwh6h9D_Rv|goc+}?F=J635L<<*8s5A#g$fp-naj_VXp65NW3`@xoep1hzXsElC zc=Q6wf8>OEz`lK_ZN*w0)y6C=EN|;~U&u3d07spHpp8p}H61nEjrSeRj2Kukd2cN_ zPuk()huf#`+e_CS+wO6O1MmWRza4_Zqp$^BZ;ZzbEt$MDhkN+@Z z!MAhO-r7cbHu!lsCV8d2jXh%G*m{F=!3T_E5h8PO6x8wv(X9@@GYcqwnwnft3FiS` zxD`^y|C|o9IP;gg5NbB=bv$KgSseoWRapme|HTJU8hTu4R6DP|62C=r$vK5EOS7q1 zvou9gF-T+Wq@Dcr)`kn$VN$m})Rb)`-j|ADlIYz!`KkFYd%4Vr$+~jkhUM6%dz^QF zOapN=%$D*O!uYkS_XZMiSB`L*8QM0$7)=aX+gDa`SUQg4Li_$+2$M$jn#MiWX*TF5 zW}oo2!x;k&`2x_pV> zKPyVrZI&wZ_e!(drQZ}lmY4il(Q`-g; zEofanh8$oUL#Sz`!&^D3!KvJ1iSZRag}&ddtg`hsiFdxDaM~3bQ}Z6Nk-@UM?Qo>crv$ndN0yvy67R+c6p@ z3;L#H*=ooc1;+RUPnDzGw58d%)mdW@T>h=VMD9XzCcb%pVc*q|vL+LA6dubg8G zHHN-OTR#0GlnU#|hl)*(WFk@?l<+lQu2o&0BFeMQXGKWbZ%MoUrwN14u7H$3_RozD3BK!DPaCFQPx@Aw{4bgPg zf7vzB0eQM6kqe97^SWKG-PHkHz_WQ8n@T^iMj$K3d9do#Aq zlXeq9%LVaRus@^%?*En{ZqENH@YH`Q5X2BHEUYa5D@R@0V=<(hs9h@>C(s!KJMu!v zL~cb;H&c$=@Hw`Vy2VH?8QVEKsHSmkR7)IDLAjNaQY_0q;m8@kyGJJk1qrqVzHX!5 zz?8+pfmDa@HxOSEM#tN;y#kc+{mtuCgBsAQ)N3iqqKKD1Ah~P>H7CUK zQ7t2M2#6)M_AD-W>DoZoooIr6`L^i#s!TfM*Xy~wY;h3uqx;YKymu6TAbegw-$!bC zcBcey?ia*i@dplp^o#c)O`$J*{qN(uiIEw?`UraH+a*XLF`K@by>+5|C{%+rx~vfy zgcd0<&yS7Ud#e5eA87;yRNiloo-)TNkG!rnz_ic27*>)ON=ewW07ug2jPdL%Lk;%T zyU#|i3=7FJ;zKvjs`28X753O}TUyA|ije`zsLDRZ*kQcTL8}qUj9qu=Qu?S94QFF* z$-StOR^InV*;#&WQ-I}h@;$7~e0-I&Mc1f1rRvmS_Pn0PH3cpB>6pc5I=Q)pLUU^( zFtyp#>bbn%9|%gQBHUSZDFnzb9x=;pmfLY=2VN!xfrl$nUhC)|%L-EmdCc zwME3F!o!)SjEJ}pL{rsdiV|k-NV?VsAo6Z>=Ze(8GqG5b;onvHMaxADh@r1lw1b)< z7&dGev_)ag4OC_jx})47+5B;})^~TjCo9=+mnoi>=tOG2L=^H!-7{NNYX)bWBWp5? zLuG#Z5!*N-bm};$+5)f6)1;Cgf>?U{#-rS)gCd%R6(2$jrh2>7T->G#Td61wtot^t z6l*94xUeEM!`{hmQ$BHofeiO_c#L;q9(74SNvhr64}KAIJ9>J2Ba6XUyF(C>a!7i% z^{8um`U+fqD~`O|RQ(Rt@}KPz$Tfz+KVwHaStd@?g2ra=-`Qzl{NdV|ZXIw|&l=)& zck%bB!kA!EDHV1&|vF_WO`aH`tJ)MqT=Ey+tn<5 zWAk}%QzrTVVIG!#Nijc~>2`>HU?33wND+!FNx0fHj+T9shg?Wyw7LdV~-o)Ec!#gY|J2aDj6;fh5m;&GF+!Gz^jg!e3`o!SS_z?JAc~2 z56fS!#R($6gefpf&)O?*PE~PxRa?VF3a35IL^;M|iC~XXPA5$;s!QrcxMWee(4hY# zb}Y(yPeXS}Lk{mH2gmz&NEn7>q(O6lHKOu2u6j`Ro$p3+RaYJGYo?ypohs6bovakJ zFCpF8n(AmxN}zT%W?rv8_^NRdC^NRBb!guY6YTY&;J$c{0h7bEBN`7jPic|YG<#lC zN3=r5x9K8!qmk{RamtT6MINS2qfgPb=Cwy(35} zK%Kznw8?!0&j|(OycY6OV@BDG;ln?x1G~);f2|- zZ#);a;39^5HR+Z!LszRW?@mS+epQ+aFY!JZkp+C2lQqlv6}uK&pUItYomr}|4EeMk zVzD1UO*bFNc#OB4O|PEm&Cq&nCg3WZEB;E5Z;o%gUh)SjNVN249#<%#64T6UWvmm8 z+g!;HFFq$}*B-k3mpg6|uM>{_JnXT4^uai6PEJm<-9>2*2llGRR2}%`Y@s%=b$ePE zTeHD1oiXkxNg_{s$VwYZG$~Yt7#b%PUs(U0M0-MjG`i4iWp4H-FO|T^FkETCS{8p= z9$)aSd=3Ug^VUhuk{!DsHeq|F4C|4-dg?KV)3FUL+2nr&&Lki4v2=%aY%=!~1T#px zDZ!#GAomgbt4(R0wpbM?>c=)J`_oUSFPu^40m4%yht~aS$yt;j^nAX{qDzxyRqc)v-nU ziB>cV{_b3rI*x{8)dON(FY48k5fBqQ0l4Cq;a*QHLVTnn)xmiy<)0#!(JQTjafbyC zjK=`EwUwab49oBI^;L#*KM=$Lx2FH30RL*?b}weYf+APbM>a$SvZJUMSnt#I^5xXX zW9W$XS9Medx=D5)=Y)KJW+Wb$YWypg9lxOLb0fP-4=^%kL>V({A&rrF=Z<8{3bcdU zf-@gL%1yO{ruoeI5dIsxSW+3cm7}^Xr27FxAoiuy9c?X6pVivyiO;ZB3ImePqeNi8 z6P@7)Go8uYosp@>soKyc?k8649p&-ttzaeoHl&aS64Myj@loDL>4JHb+Uk$qR+B^SD$H z%*VLIMd4nfrWo2ws{&8k|i6=4gsbF|W1sGx<8&+hqYAB_ zPT%$m#?+qoe?kFl3xqYPGX|D73U7WHLOT~Fz+U5uG_LfmPWv~}Xa|lH_?Cv8c@IKvA6$BoeH5?1|6=UKzyJ7>)2xhSiHleLmh6@&IE(7wx}&{L zvRhK>Q15cxb$@?iYf?X(ht~wBM9~83gHpSdA8QYUjCf6|vXy4bbTr>fec0X#Rk$v) zV-u!%DCyQLq$OMKku^++{xVm%lOL*$qsWhH79#Wp-lwoEikjUb_)qdH4x6i%DEb1tEpSXuA$!L`%kn8h$Q&6mP464fP)i>N*U_ecg2adc%&QR6EJ> zY1NsrwK;o`B1Nl-;JEU1m-%{IbI_;6%=n(#>uffQ&dQxVb45#Z*`fz01g^)~i&d;Z za{}RiqGr#8*vec?iSzy3T-%orDR)&Lf&-0tGHuRqW7{G4bfwvIyFC~!%-H~F$ZN#v z|DQU>^8d2yU~HVJj6M*UK(eY_O#uXJom$5 zkAc?LKQiz?s8RT@L*CcYh-o=clPyMV2nNju@Ii7!v2CANitfF+KTJi8WrXuR?m0kn z69Ykzk14_8V4^^hOh;**^nx#j4>ln>J+Vzh_ftH~^ey_!Dyhfcfu`yj46SY#x2OB} zOD|1_fooFE+JG(JjnEd4AO5Mgw!Q7>Q-psu5q!|U3Mzs0m0hy{8eGJ?F`~5t{*VS= zLObVw2cnps)Fe*H+#s#8K2>{e3S-R>u}9judSDS8Om2XadFpY^8Kgo-!;)!TXoFMC z(W0XbVn6dqE9BTCus>f7cIFCd%d0r9G_K4@uck1KF@b?w4!RM~6q_D`Cn3YgmO{qr zOy8=wqlVg+G{;?d*rUqW;-9rn-Y+4`KO7Sd4VJ7QV~v(_V#ZiGrna%hEaM@y`xW?A zFp@KD6J2iHWW7Le68+o#HxZgvO!d;*A+8Jm+gc?2`*>-R7OGZ6^zj!JFbGO8CZ<#rN(jQ#yj$>}Kv%k+gAONZ z$n_(w=PD))Qs{x>sJJ4LU0ic7gCd!pp$E7@YfN0L6TN7CA7??)>OTyIdPXd%+#Sn1|39M#$A zNVC!Ad#Eq41rXgdGk<8GI!;sONp3&QvA@y+OzCvhvYnH&veaeG1&(R!%u5g!;y@CV z$EqKQjol#sur)*0B%~a@SkvUSfmNGU|2zMH-Pc6bsMXnZ;A`o92BK-pwa%0=Eh5V2 zFqfAO2jan68TGon(Po#h(L?LB(?e6WdHFl6 z6b5Uq0!DHma1V;X2J|8o?&Tnhh*vQDPAwt|f{4QCOwFYlq!KAbANXb{>-&={A_Eb} zU_4|Ol*fi1SM9H2U)W>i)%T!7ge>-kFwWsd!H*jLi*%z^csC4Fgi4b{3P#-`od}a; zwlP>~DydZnVqqCmA*X#e8Mj;Kv(b6LB61Y0Ak?d6fbm=y4_HT9k zfexM?-M(OD8b>+-PPAu*kIE<8+QF?)y3-{N4G3qG>M%oV+;$H zlP8|(|3(j#v*<>~eq~h-{%;tcLx7ef80=q~N!`B}RG8WB*v(ven{xha*MXNDcpg9+ zXsI#tJ?YMIos#t>TBMJNO-mg#eK3?8vv|Qr0A{i}(98^MU(Qa?K4^2gFIXLAP9@RG zj(nV(;{43K5fXbP)ZAQ1tc>nR3D%Uu-M^k#~@AB>m_a7P~mk3~RpR1<0NyTz9;(iIk~k ze`p1=M8hJt7+*!arRI6Uq|HgP_$~D`DLN90z{UMei=+O|Ixj#4>rMRV;*+B7lJN5cLcQbgLaN(@aj|Ak74haImRCvLQ z-1o`f;TW5dR(EpeL%tK;sf(R*yMa|7uXinnt7m%}))|yh?He0~3vf-AgtYAEP5wV$ z(I7YJ@s_L*Ag0{2eu=a+;{VGYv+Y_ByjW#5bQKe5$Z7^(l`#I>$8>lW-4D<*aR3x# zQy|Kjb1Qth$lqF!+ttxN&o9S**AMR0NlRClR@kH*i7(OX;`!7p?*Z_J*O6gSRlOp|sAJALff3>y50|~V3#}b?(AxLf z8LDeu$?sVMh@_W|qPox!mYP#t^=s`-E=f5dUwAHgAtU?=B{5mK{PPPd9Il zT&HR)9tjOZJT%5p%J;+VG61Rt687c6ztlbaI(8iIxwwmz&dG{()@nSm_sa^u5QUz{ z7&S^L-)A*xvemv-EFhSh@2w#sng>!5c{6SG8d-C z&RPz=2^*VU=3@<1(nKJ>=#%~KySelo>?bxnk~kVzEm?VkNA?Kl{DEsQEy2gcsq_6n zvS*_8z_movEF&coEO1BOTEfP2_!`zrn^`_^O*4WKTc{Ci{bMqLQ9L5{tf^aa#oc1; z?y&mT5bQqIzY-Xkpt3_N4=o<@B+oPy@!3A!@pq#~WluffR~4kGo;BpDtu6`-PUb59 zxG(9y8xUTABSB<)SoHt%(UO_<|7D?4fopIaV9I`-3BLQC`T=Z7>Q^Hf5pvw%Q@~cl zk@~};aR*&!_S0K|za6DcBnh&VwCc;J6x9F6);k7i7Hr$1UAAr8wr$(C&97|Rw!3U~ z*|zQKva4S2eeQkn;>Gzff8<)3D`G`v%*Y&L%#lwjf$#h|^ncpOFhdJK#uW7YVJUeE z6uFe?HRa^h>o1Of1&D(K2gDTS2geN7Z zzo|Y{0@Tx7%EaU9(?^YZVf$Xz1VI|TyWkn>8(--Ul>*rZss%c(s%VS%2*`d)^TNx4 zsO{F~DMBkiSUPpyuiO~h7vdqY+>cNjsojLF0R2#B~F2=P*HIJDzG+ouGotVzc+ND-$v>fTOq|vC-wloKhgQ27FyMehP zMG10S-eAS%d$}6jchu9rJeR9}u`dj=_1AHnpiwFTCDOQigGi-;&eBG64{sf-$lQ?q z7;_LC^}jicvG+aDe8s~--Tuxq=rx>q7TfBzJ!~nwlR^0SbBk$hR61tu-rHwZhgdFa zX!6JVuoF^oT8n*TjE*^Nt!32If$9s4Z5>nhf7$+WrWaC!Ql?|FgW}-t9mwAN`URBn z>*s&%sKgE`1j5A0@;}FYKUEu9R1MTTqf9K?!rsNJ^YfrmNp~}`>oXecWZcKO(wQj$x~|gcsGJlspY2XI z5tNo;xf(ScNy})-=?Ua4L(g=wM4wjAvX2*eYb2qBdcN9v{YABBF5d`zv-C3$j;<6# z$4@aI@SB*}ABxE+(@_+~M^LeJOl4jB@l$1Lu@2oS*U8ePvl_TCQUk@Qr}S}YL3LfC zaXRl$;|gJ9m@j<5*9oiUapW4M?}n&rB4|e%Y*OwKfs%uGZd?|-t#uY)sR}qCE@EnRW{NhO zPvlQ^lgQR^7hZg&i0jAwGb}V-_plW#OXz(?Vtq#v)h!{I`$kvCkbJDEvw#pj+a%)% z+f7q&pN~r3G@{M5z9aJ^x&_8^2JQ84lg#yW!r#1v8BpqyEIVHE=upU^ zq&A}YUjpGB`@dEA44|_Klqa5(a&_JwKffa*2flr36CDfWF!M_2JDq3iPL+_O+KOFM z;A`-3gYHaQ%z;mI#{SJPs?nOc7v%KAK-sm(VniT-!*a5|tuUc$1?l{f#%^+!Ar%^i zhq?}myRS?T7OK>c#Zow<3yJI9slEXOlKKjUJF0sfTHyO4QLh2__vT1bn4o7vM-}c{v=7-s+_*DvN5m4Eh||epadw_{{@qXI4${Iu-QRvaqC% z=MU6yRG8nR0$*U_s|2=bMj=u+2I06UHtzv7I4YE@(^9ht?+?ZhsKf&yjaaMzL$VTWCtMZjme%?a zUl5r_eGS+kqZ|3#(7eg_@(njzkZ;x3frg0OGamEo@~Xldh6)}5QbC@^LLunig&CeXr(Fki|VZMm?kgNghF>z!~AZ?O``DxZzp$g{Ph;z7{=( zAPXSrfei-?U%e02d0E7cNa%u1EfC4KIv2yd-eA%3?wx6y@ffpso9TUthi0p{*TT3VA+ z>-fAkm`z;vy#F#LseTzeMnISdkQw1+*xxAtl)vb! zo?MiNh4)#NJ?_uqxDyKVKFH0tMZd;-qZ z(o3qsTKS9}L6wiVEVmXNCoW0>_J|9J!j+ZQ?~6~Y{9Uc(=tk&VCW}$7dO9?ZEqn!< zN{Gzi%VBl81a=e>!l^%EBvq%(AcEM^|bNL=2;S1+e~zs=(1poNmkR3 z{mn)PRM8J9aEp2+9j5Q&Lo~E$TwO7Bg9xEt9i$cDw^Y6DkxYdNvA;d3*H2ek3Tbxk z0%9cC=Pt!RL3Zjt2mY6N<6`=c(kGpO7?c(GpW4TfA5^d{l^C=E7=($7CB1MPgtE<% z3^Wz!KQI3?!@x|e=}Z)$*m!%6zkdBv|0VUmeUmfYi62w|gq@M=e{3a8Obv~U3{6c_ zAnyBO~Xy7R)R-b5r#<{)}Uklf!NnS+4_QPop5xDTf2=1rgcxB;v zrvTR96=RNL-&IDbW`>3}#}J8nK{{S30kCDF_-KfIkrJtaxsid1LP0`dg<<<#e>lQ+ zW2QK-SQc2A10twQ(P;pr)G4TBWhE!%=w>uWOR1l+3-w zBZCqH3kQnB#sw#k^Y^i_$jHFd-`|gqkG&%a&Ai9oI^MImq5qNoWLVHbmBW={W`bt+ zZ(d~3{*fJ(&=-gd6_-Li!1wvUbHRo@yei5M(#$f{SOJ2eLdjT|%!2(>{ZnI@>;89k zFNp-3ag238PVrtnYw?(^x5ff&nm@m+Mt5mMAN0uq7^YwP1Llu9b1Lai9%HwgMGk#3 z2JihFv#rv0n+^c0m0g;bh~C#kuWJv!OOM4}x1zY+%ic}DdW++a9v7kY_^SKc?tIKX z8b1?#GWX!?>3C{&o>mnCQ4KnsZga8moWY!+qMB~8Wtro*jlihf+2wSYuY@i#U!aoL z&lirGQH3k*RweJ-!J>+)xQ$j}ufAOfO^3$ZPyZ;l!A3wYS9D38*-i5!f^;-|x{U?O z4&xhiH3tp>B@onS!if?HIT*NvVk0G5RsMIe_50`fX8CPCKaynQ*G%qA9QgS4;H zGT&ofay>$cYDY+voavRxA$4k$ zE5@*J#pr$IvSw&zO~aVgdrWreYA(J6X5A*5LaEb={bvI^u`u|CHMdl|13yxQF0N%r z-A(5)3Zk?g`3L{Z{*Gp&j-LZ?yQ_pALA#39QM@F!)zvzIC-v2b7!k@t8^T0jNn`ei z3GO$;#g}2j#TL5TV0_0#He`3xv#^j?JU9bk5lkdWwuKd1O<6(hFe9J(hj`iN4>?K% zhp75Yp6~^Jw1#?-+PmPZyZC~sn->23DbP$3DCvzAt6TFFOvqY&zO=D*D$n-i{>6Hp zbcj)ZFnI$bay2uCrHh{+dm*wXa55MklnC9=-)^VtXE;(JA0At*yJQ^!i&s$>P5?d` zP%Jj90=A0c+goqdBzIA3nmd$VSU-Q&f@ftnYpF*li*NOF-<@TPVPC%Mk<5RvK~$js zCK0OtmzzeRTGT4>3+#T8%a#DVlQgr`>j&JJn#S&c} zoBO-bYy7BUKJIM(KTADrwz1s=Z&aw0wJ`}k_ur@rA(qPZTbA8F3PrYdVLjnC!X|9r zS)93m0`@_JHc=F`l1xNQw1VY!hS=Ce@geIn3HrDNF^Tif!{)%OIY~L)2=zlGEP%DI zb3BQlFg_M1t8{rJF$eebLB;DBr(Om7in+1sqq3f*qhyZxE90h77SRzV{}n;TrX_hu zxXlSBl`)%3^KWfEU?Bgwg^J#=vt1#6K1CcX4D7ZPVgi(?LQ_;3Csc(@Wph;uX?eH8 zP8HzgU}eR$B?(oBDumK9QE8Ee2V`-QQ zNkyV8D9X4f)4VKfu?agfI@qx^eKq~U$$xNkBWxsU7(^FLMXU{#tIMRY2kApqj{=6c%} zW!`b_t7vc14}pr#;!|1M^m~RKGmES9*`pnWh{eNJ8uUlvH!SH}VPBkvDR7a)+P#kxO_HZ7I9`k>%z+n54I3>hWPQF)5Ca4V08smDSmZ_d^7;T= zKjFp{x64-!>a&4ZwQ4@qX3!Q4wE3Pv+Ju~o4puyq6PE_H;Q)@I8(g1&-TO6$5SUfL zDgmBtZq?s(Pc7=e|2XkpdHxjl^6(aiMJ5y-zP=ym3XWmmK-kFkuhH-J7`i!b0)py6 zr!_P*%ow9NIJ$m$R)B#9$#qTPEyut*LFPvqV-`q!n<2b5kJ1}^wdi}g`3Z(_ z{c!*oTKgf(GXN@lJc$i-(A%9ceBIJeBY2-rM1&Yl8U#dIX1o*sRuViMp1`&`kf7>3 zT~6FgUz>e^eUGcb+aGSp@W~458DlN(^U#DRz*aw`!?zoo>sKoE(G*2V#n$se;_I+Z%O>Qh_t83z zXb)TW2xun(=X$~9Q~-VP^u;reUf7$zX6Jp*JH9^S>;m4 zBu}JI>9sH{K4yUjmJ(Wb`uc14xkj44Hv5k>Gj?ED9Nj*Laqx*F6s{UXCN0N~_VFN^ z{Cl%~u6lvTMwW^kWoQ>AJXT-Jr|qc!8HaMf4p6WNWvJT6h0rr+H)i00xJGtxtK;!$ zkjz&p^iDeE_v?wkL57$T(WXL;?cRTq>}iwUze)8fY-Awe#zzGFt3~7EoJ8v z%2I?eibSuytr!N-%J_~k_>OvH*PB4@xCJkMyk!18GsCv!9?c3^$eML|2|vKJa6 z0PyuBs*FO3;>fp%)DHzJXSj?~Ggzh;?sh-=h)NjA=ksB2Qhd5P5`~+{&6P|nTawJR zKIVgOJ5T^M~Tk!+nF+Xn?ZX1@!(-Vqd%kZjCP6*Fw#5l4+1v28nt zF9Bo$5>et!=*Kud_ zSO6H6WkbL9<{vmJQ3`dP6cSc=tWevYE(cOEvTA9nr3>jN7NP*#FJ>I@AFI%{Fl!>qHK7* zPy7c8n@cH{yPmG>4R&i`R4ZJuqH~9T;SaN}fQjzl9!?l(${7N$=>`;BfE}Z+UVdQ< z)Q2AFWZyu=Vp9%qf^l}+`57&V&|bX%}uP!__DT zxUsXS14Y<=T>B%hbWlz>MXKr~WzTLrd+FsU8h643eTU#y=01sT=Try}6 zR+<;nTCNK3iEYm3)OsYNZ&LzOo-L-jJkT_Iwtjn%MQf{o(XCV&ibjU&n-h9$>H{he zUBY4qeYpF>EfC(5Y7G~hz+1`jmtJ2eKH5FV$Im6gX5rdWA?d;DMZ!Q7{i8w6;ony^ zD5E4V5MW{>mLN@TqoOV`fQq!LZ)r9J5>!K-EnsIQ>B|E9{22iHAs;2>UjrVIrTMxm z3<~L9-g0F90J+WczI`cFDd~`w$VVKIhp>#fWRL{}BkDRM0vAj;3q$<&v4 zX~ON)l#zH+s1+>Cac(iCw2%62G2=lsb}m|Dv6Kn9Y!wFS(sna!fUKp$USc2XWg9}T zv{ZAu?XO84L#wlSopz5*NuTN5?KY0fhKnt&moJdXLdb?{g&{yq>`WI&k z(IN9rnCLhc5U(}a;>J%sij`Wt_Pl{nz-+r(ycCJHt|wKUB$#=L3s=CC=|YF-{yFWb zV_#^A7gQ|GV~wK=H3ZT1KR668R@(D1U7ZY?BX--`G#r2ja3D2DjAYzXAP$vb4B*`7 zd^vEwz5`#r)3c%q7bCLq;G@yq#z*?iq-n-8<5QEaw9$%2$`)EHPN6D|p86O~F6m%g z5X33et+(t+!bor(7mv9k zH|Km&lX`a4TT33De-P6KJ5=4_V2;4FsC1~}hG8#AdlrJd8zP6_FBVG?WtT76#8TNh zbwRm*PBV6TogbAVI|Vyou49EKrX2M{iM=LxJVQnR+%;n11)elRRPy)1@O@I`z*LxF ztI0k@-*Zw%`qd{#abKmSYDgpovvbdSY=x2-GQ*_G)AOWgg|JLtK5I;FyPY{G=8dCW z6*$FL8acMS8ke9MT5)%r&7<>Z8+3GAY1fdwk&=CS_d`tTA^V(iaPYNmvQK_u6SpT8 z4bVyedF*KV?E~T~f!U!rDi~$&wZ4FsB15hY?h|l{wYcf9HE9iLnGESkh%LPYGu#Qz zjKpmlkTwcx7b$U7m@d{RvziZ}P&*aG>&(Uqg_S3@Uo zkyI}|@aBT~D)~LXW6~ONcNN%k;G!6yOAqfBCH8|MbxZx5{R(;luPo$!MkW8=G#*2j zQ?ag71^bM7dK#rx%;K;s#H4h!U24%!f_sYJd%@uG#n3Mb=d*vPeP*D9y27@QY#k4r zmPk%02&3b4u2%#r+}rMGC8Xv~2gf*Shqr9}roZG=EnhMV30L2t&HOYdkb@fFEsXH| z)XbWkas^dX#O3HQR#uH)BH?mNX21Z0>=A?m^Y-)#ksfedUbI$Iv_&wXs_>LY5~h%2 z!ZYzO<^*8s57tvvzJ|bsB8e#_g1+J|(r|tJyY4a&jzJF#|C9w_ZH{-0(WY%ujt5{)g5A#Huf)A0T{w7zy- z^@$DGcnbc}F2EcMT8)aiVq-?#$SYf(^4ibA4;56 zh)Q5MQ^!t%!PDDs>dK1vqJi9TY65W$V{Wx#1w}K3f6UhQM!l6DHeQ3BybAs$$YyFk zMBk7utX&Ht^+~^O3sDD*hZ^e9Js-V~-u98}Z>wq-ccbU?km>aV*30c?`^fbv1Ejp{ zZoktn7K}CE>C7cGBUM^ExoNeWSEvEmxxw4Zb^NF8 z>6uI{LtHd^x+yHrw3Tv1mm4W5A!ICN_V=!8GZE3}_0Por>~nmW;dmlQJoY@IaQY|| z_ND?8x6V9VS5+^NK^6m)5N#v^a3xTW2*tpw^tKxpIn8gJlU`xkkI%wP=xHCoy2d37 z|9qT@fZy4V(1-!hJO^R6m z6dA!>cu@t@h`?qQo$^9y$)nDB>3Q~oLa7U8;>-`2u{0sR`9;Gw#?JWk$~cdPhxNHG zU3%MCm*lMXC(cdoDOWe^%h8$R6|39I*QNCef#;%$FZB&w$EF?V^?uQd}q9Qv1Q6G z3%I%1J2BTXJ58qRlCW{fHYmOh_O!3Ewc&Bfwol7$JnQW`lFP)K?dUQ8;;}M95PQp9 zctL6d)IZb78tnMfYV5B-EKs-IxV{=AoF3Le6@Q=E%;Rk*{*0m1i}|J!`wkeiT18j+ zBHrntirvJkWtJ~ja%_PY3pHcv*PqDIbZgz#RdalCyOIo<3GQJ{u_? z)EV-_V{Y^tbse5xyTS7+$(v=A2=?3tkZzHHO&2`^I|gad0b6qCE{~_}Eqo6O8u2ey z_Ks-u?c>SczWBa9OX`xdObaaO8!KH?>|OVU!Id}?tSE~uA5*|OCxBrR`JP*$bjO~h zckBuOTWkZ#PGKe1IEy?~(n6+^0#IxbY-vIeD@$OkGyE(;mot3nLa^o#Wd)}zsz^dW z3K?azQd$A6xK>ajrWw?P@qZqimpkDsDj=|F%qhCSDXEAfvXM=&vXP-IwWpr_zu^Lz zi+zjyFOr}x$c8|lppApP;plj=%1E6|9sh5XIGQ^C|Dv0u^t_xn!k;?qth^h5Zgy6V zDk{MCp9Z>{f@~e3F>~l#(FtO50=O}9lE2Fd)feb8aBgINrgS(wise~Av8(3{`rO|f zC9>o1Ornu57Lgbp^SMlT?e3X7&|7C9B{4c8*DBPaDD7!an=hS>zP+!NcTMW`4y!Hk zyXt9&7=I1a(yujtH?^1YPlVO_=}Yn3b1S@0AiV7yf&YQ_xcfSOE}u zF%Yss&xW(kcUwA%Egb+`qbZ^~0fD9ZMIwTxEK7{gA}r ziXnDyi`a2@mnRz91C3}^~hi2RLE8nQIzxyusd4J!YzV5Te6l*RES zgxMiYo=j1?|0PzzYaL9>t7wjc*eg%uvfe0F3As(`0{MUt9|eGnPohBsYTRSYWR@h- zK|!ziIw=|o&)}sswDwG)#TAH5dJA`eR}lxJVVBdw#Q#l3?dg}GY|eCjX%&BnuxHTp z6Y~~ASjucXvs6i)Y8|EPV;2SRJX?xPHmfo;OPJF7Ero;@TTcg%pfZ7bTnN4XDugzo z@KzF~E`_z?EC>h_2_Tlms@W3WONSqz00os&3`v+HdyF6o;WrYDh$tLl986#&wlwA( zKqL>zz>Kt`B-0cQqvHyDjbbDlK|YMQ8v->ZW{PAaAxAkGLN%sliha#$AS^>+iOA%3 z=Y9^72?7=>ERb~6QlzCsN`amLJr05%kQtyEm<`$mB>+efjVBl+87m9Nky4{V$3Bc}rZrTVg0-0$;l{Hdq8?e@>X zPAM`7IiRN{C^ExU@jR0nf~&;xj#QwooQ_gISNqjXBgkr^Ky@|9AYYPQ7YgtMx-GJPK6Fhf+N=hBsrq@Rjx!Gm@nchy3581D4LU$012NBcbl zq0Rvag3DktnY~K{P@3~ZZaf}!xf)Ly2(!zw4qu^J)ioYu=X5Vo+VnzyPQIX<1{zQ4 zV0)ajI{4G+YKa*jYR2+ugk0tshYFr3K}E+PYc%I$hsaVS6N3|Kai_YY=71+I@ezlea zV}&&>1YPb_>=sruqW+zWFr^7gQAvdNix1XrJd{~PTGg<;=SP2Z*d9vgx?Y)KPUl93 z`-yG#%Ovw_^^r-l1q#$}br`KEqo8NztN!7P)EVSLFo<7LwS}5=I_i%o=sJt=nbd$< z^X1?qn*!|R$LCD#BH0+0#U$jyiRLqYm1PRcS17WqXu1#G;ouE@{?}?O#*Sx{v&Ni! zv%W4z-Gm?E+5YAz$I9rS<}Oc~3K?SVJ0JKxg}`6Epc;=szEXwPTcny0Qk=wdR!ed> zf%de&PRhhMx(|l{hkVyZ;%@tE*C7Ca5{HNTZC%w|y;r0yA zX$^8Q!0jnB4$1hZC;mx&@{qSzwxCtrmM)4;?9X1$IHvR?_MGBu3aFW=3qt+F76_A7mobl=&u@tB8D*H z&Xy&=SqrXG%^6?T_I-Qcwg3R+2f-Z?2#(^r z*j0?XWMXy$#N85XpvJxR(k5HQT=II{na=sCdzjC#Q9O;L&{EBFb z1OpO-$3u39r<8i=zvDOCx#l+7t1|IM3i|X@^F1NXccSc7P9(Y0r1PWQ^}avHY)1$Iy;VY}{G|x}6BA#_KbZV?G$w_3v&AIfDA03Rc&s{7egFaLL zaEDIL!MGcEv2Iy!8l)P9mKmw;0XpJ2DZ;%{2e_Ck8y!*!>#3P`p?^T)(X3);i7%t~6JE%=Vg!iSuK!mp_IBy0)Ed_h(+7HMh&(=_lRnU@o4 zYUE@!UnxBPz{`Whtl8m6PxCIlJ$26hApN_XWqE4)vK%^U{Yhhdj1iC;KQvg0_exlC z>*;*%tE)<`qbX(%@nKGzOeivx9c1Eh!k`jhB-RB8Zvk8(;V$`pY9cH6l3pD`bpk&d z;iRf*n4KQm*r6t0$u2m_`RS{3+CA$ecK8Qc(IXEL2q0-SxPoOZv!RJDETNmHS+LCL z_}lqd2<^1?3!{_4_F2ypI);Kv)H3mHGYXLu^%nC)Fi2>nYV3X9G-k$lJI3?_G zp%JMDC)R&Oa{&7NdUgbRCaK7v+ot5K&E!_8rQBIMzMTAun@FyfNJSMVS%fUvH%-B? zHWa>V91Tu*-nyh43Vr%}p79tQblGQzo!Uz0!4RMXQ**htBIhx_$5Ue3f3^H|ijc!V zoK(tFpQR$BR^G@Qk1NABF_O>SblH5AUdM9_W5PhK@VjrR*b8;rKDpa$(SU>$g*H7# z-))D6CrJ*`c(5YlFWlu&6iQNUpPl>idhw|cZD(tn=t0dwqfUxNl$$|>-n@GZj>Sjd8|?( z5Tf{W0gIGKU;mJ93>v%1JPtVzA035`mo?xI`0)uG(fR!P+9MogbEU%>p%}`U>Q zWr|m>#!J}oea-tr2+i^(pGn)*ZhhKF4WS3FK+|PTi2p}-{VTvxhnwR_?$r4L9OlwbEzAyooEZK+i*mg_Sp(^{McY%5$+{>E$+HwIQeEn71`4$it zGPIp!cSmKh#y?o~Log_Y_1&V3l!MI}i%oGN7=s|%1-@cKC@I1Lzfsv(viB$^b#1}p zhp3*W%bm+m$@RkX$BRA2;Ji%pkiJ&^?Q}!(EKcp_vgd1!kJH7YHNmxxk8I;vfQ*Xm zF;~DPJf*h_wCR4Ok$S0dVxXqgcLv}(HZtyyje-2t*B6j!NaYjwnD~7b==*I4ebzJ$ zOp_is)Hh#v-eVLD8gXlE6S;^>7azvH+ZT|WFEeh4XLP>GQD=H=V2o0hY`NAXP)S7z zspD-QuI+XaUp2q1EwWqa?Myv`CM}P zIjpTEtF~V_OUYXP7?4Sj{pl}{_dBzK&h>HPk znw)~-_%9aw3Hn>D^<*Ah(sC6{o2Dd0vN-RWEXu-kbmg@rwv<5g2pFIWt`v&mn7ezl zveq7E?j%Af9Gnw$aG}86)-#EHR5giG!SnYp@gQZAYT4kX9G;dfIyC4kr9~XJ*-ir~6R~iRi<__R_-J3a*6gY*2&{=FUaI(2z?2%Y5wMZ|?lYC`Y%hFaTvxWatYjq%K zBg`-S?p52s&(g+wC_4DeO0}J#EwM0*AZ8~CpBUp}z#pf_zSjFr$0YL?DasjQBxg1k z5#7?3`>Z`s9}-3pTTH_;n39Hqj7%j;VmbGBw#Trf?l*uL(~|^y9ukwPWDZY%CAVa(pC8tAVs^uXRZMbqEO3nUT{##j1 z=(9B1_+3m1z`dxIt>zrsgY8LH<*MV|H2xlZufngTHvNXcjDJ2;tjdm)UZ>M_5VeTc zC%)ZUjR#mIh~(j`pGjQEUBtaE_rcT2KA%`%Nb{`K;X6Rr-;^Z(t)^&FV+Op`cgWC_{s)4gEIQb@#@IYwiU^ zO}QXBE7Fs`V@1)W7t>55t#sBXEjr=|Yw-^CN3YL;(EWRCTbH9dBH?#Qncgu$M2tgrKD&bR;07 zO%svz!-k}gA$HgFDDk%FKX;P3UL~3fF3WbEp~aKDII~;6V3&Pak92i7ze;*RENli! zGd27P{M%>42@12%JCCf*&d(ct>m)#6qb(dTRLxMOljdPgRwb|z2Ozt^hdkVN_5e4t zK@JN&Y2`^lz{?}B*LD6>V_#dwQ0{FvpeaE|^&>$<)oNMQHiv;vBK(>-0wwSRFCB)^ zCk^T^(mzX~bJRo8J-IkpsPXO>#-c5{_O(hz;kSH{R&M7-`NGI6E}9CX)e<(+IWEm{ zXS8F~_4cSgjqPZ6a;IFfy2?jt=zyZ!n>=niN9R`Lcpqz+e?BuZ1^JozEtDU5?XB&5 z>N15N(Dicu{?JrkO9*;KC$*gm&MbX3%;8^D4Lr83YXj4x>fDAV>DptqjMVK2Qro+( zMjr6{>P$Vyt2U*qt9ttZVR)t*?_FZ!WRKxqKx){}PJJ98e}rBzB!S+JJOE>sSF$vu z!X(;wg5_1*)r|pS{7|jYeQB={Ia5)lJ$h1NH>nNex?2}Zf)6(VKm@Q(@e{ns=>$&X zq@={kNEuj}!KXc)w}R;q3)uc7S+kEvs_cBotD+}wVIdCs^<%1Lw+hl*Z3dm}YpYz+ z++(n<-vT0@AG^3peS800=K*=&xe!-u-z4@xi^>Te4}q^`%%gzAle2<^7&?pOh7{6D z8PfzG$h3RDkktohqA&x5Del0u z&Rp`^gf);%VjiZ(YQZw>?l?dh=!5w;i)lC>wd8Mz{ww3d;sBathbl%{3MR!^GLR^zSw$~cpUwqOXnRdZ9kMftp8p#IdClc z-iv4se(x9pIkAamKROhnq{v)w=xY^Gou>J#U4+dsa5JJqy!TiP-`}K;-26PB#BIfZ zmub1*J4vBCYS?mOZaOy|wTb6f!@SHCOFuEurVFk(k6_H6hO#elXjVVj0x`*$F*ss~ zA#~r2NZ6GKE==JH`1Jg`Zm$rZQiXGW27fpghb_Dp5`M1=V4sm;pK-v?pK;)`GS1;L zzXnyCe8pZR&DXvq$-I2vEhx*L{TAJN!LB6nq>6POa>1)F~69ClBO`4fE-)50lD|?d` zKwBVkwMw?F73n2<+F9jjI=HKwYGIMB>8loCkhN)|@2hWh8u5|)#kO{nHk*ALTr044 z6TJaYL)f{x^WM-1ZlK#fo*qyA9JSl8i>R-I-2Gc#d(lmsC;EsPR`_A4W*U9JgEH*Nh^m#xj`#NRc$H)m-PYFm`&7<}GI(4RcKfK?zAAh52NBkw zYD(S93H8MQ$_=A!b@JF$M~N7=OeJwW`0^Iu?6Z?!i`i;r%j}WG(Z*o%M!U65{|~K4 zT3BRmdNRx3ur|Q8V{~!~QDM^RT4ikzdtUXDT>b!Z#dtS}y^3Gh{g~iLs#C%onO{l{ zB1IK}mM*|tPz1)36d@TCG_eGoE?{B}H=hH}5}~n#sx3?{rwmz9Jfeak!X@L3a!dj2 zQ4T5v6+=ou#SyDgRK(3?7O)6s-gwg&)=_Y~t)k>+ zm2PVXiiPC>eg6IR1@UJOm>N+!An&hxuV_8GpwIWi^t(8EF*+Cg4Vh6~e?c0At6L(#)y0#&zTACt#y`daHQ#Otf z2Q}gd!bWd8u+qs^y*eaQYo)-vP%_|N(l_A z;}S{YW%A18Kfj~>!;4HiS{J@<+NGM+k0tdpp_f&q?pr1zl{FQaCju7~xt8ivB){Tg zz6IbqFSoYEOE>=ghq4W`=*Uo+D0Wiw`}Bqw(|d(xtwGv6YPRtby+Z5?0B7MbOj5-* zhMh>wCMtdzyo6lx=TlZxGOc{1{Q^##4~xmN_(?}uE-XZ|_=dz!JB^5ley7CE!)Nw- zbj$CE>E%b|2HAnRblSyjdTMG(OnT9E{vCDooTVibs?78HUDKq7DEMEMZFyF6HHnIG z%SIQHxMhlKwg2{!;>{@UcZEkSS_g zGapOJ6_zP4Qd&%eX36)+Oc$6daZjW=7w;5rQ6Wq$HvYL!IYyaWWfg;Q9%OlwD=(9Y z`A|Y*hRLkIa%fa(yrnbOxoT-ATb;m|xTM9J{55I65e6^Bof1dVt#+;Eul~VBVLvHhYqW3nFUzXauDI7I{iXM*DeFu7kCyrTaC!4^KN#1bC!s)k zjrSdI*}8g-r5D1xZKW*~2-(yYqf(FkpFN2OGhZno@GJ0kN{N28 znA~RsJHFBcj#MM57{IRV0YNq!*7m~LGvb&VqfMC_?6d~=5oN%h5zBruwL82a6^af- z1*=V`&+-IlZ$^2+&4})W$#Bi-tLa+3dmff8Etw5M>ndDr1JoKA!Z%qyzf2xsKu=Iu zI3rm4g;qfDojr|CZnmx8FS;3TF}Rtys7Vq$bo_9x1pN+YzIKu~RkA{T`WDJL-kSSn z54k+C@xxUdu`QY49zaiYnPO0K4Rd6j-BFYzK4HZh@X2|@=@J4dKEp)7y2Cixol{V9 ztx*{qBn&lb06Lfll#JuULotH%;8&zzroTB6t{lmbF2+?9z@LM+BXEp?q5kA2WX1tz zNT2zEKr+n;2bq~CQJ65G7#qXDUX&agP*|On>`(mPdP}<`0vyw$k1yW-@v8KPluTf?P8D103AKFkz}A0Zw$h0pm#^+GBzm&VqRWF_j?j zhSm7?0RI4d!2v|iM9c|;%-~}hJV$U@e=-Zi- z6{&u*V7eNRO%7iA@XX?0$>B)dAS2ZxHVlx?TcpGzlrH*WDGYV zf@q5j0pmJgy+5i6ZV-Xc7V{05MiiWq1~xYxk840XeOd}#gqSzx);X`4Lew4IDhvO`w}p^F0y!~uOiJysP7BV(11)ly^KSq+{Oklq zl36Ou?d$~)U^sR0*$NgU6m5Wo9)5qCdF_DxULK`y7ZI`TSO=?g)B6A)|(24ibO2E^MR z+!4?-iPq1-axISbp3&YOeN@LPkpaWc;&NsKE5(Dg zqplApvCGw7H3O|Ice=bUFIWX=8Nw_LP0XU`OBe3Q9!&$OoVu1L^7)lER!ID*xbUTc zel203KM-e4Vy-I6#;F1V%U0a}28KR=0Ux_v0kvf?n)Hm74|m0JGdGdc-{&(3G8E*SM1t#FVy`16@}P9094Rk5+FjbLYD`Dp-cz!cDH zDZ^h?+t^fkPxd?rbd~`n7BG8f4ESce%ahq+{%S%xd7v_*PDWd{!qC)HXF%e9 z9ARKS#7p!Cmu}A5&n{C03`6Gp6>R{5`>Ve<$iJGjfHgTloZ$CyU+96;ineAE{Geob zGO+O{|C+~Bkg}$7Lv0{Tb8qGob5JkL?>P- zDWp!3DeH6xuatWJq=ilF*~>U(`A^hN*t$h994P$fyR$(S_Vi!@8~pw9a&zNiF*tZ( zksZcVn96^;Himb>+tc7}-?{Ki@%To)7a_NfK~@TyrH&X!=GDkB=!*HQ9}r18WX5yv zOn_%oRcE=+6L0DX(+;D%p9DM-24^xs!Aj)RWPYo_IyK4-792iyKR6Y>SBM`n>)<9Rq(NWjgQqsn`ufL>6OBi-0DP|g2$Mu_9(GyQ8vVp-@ zJ|ntJYQ3M{ldea`wZ9257|o~rwAr>{fB6}4vLXyEke`KPcLAVec| zfv|j!ms*UBH!$1Uzot0Yf8Vh77jn9QsAxXh06z~Aj}DcnmH9;m8&Ey3!~SC(?>M0z zqx~Lr_NAe}j^dEHoq&i}O4LccbHV&g<}@Jl{}gsrQEhPDvRH7J;!vanhoGUjyA@A? z;_eiOAcY3^;x56xIHf>wmqPJEky13c^Yi`ReYyALKJ2y5IcwhcnzQ%J{IyiGj++ar zWZ`+aV_rF!-f!=C@mi_J9#DoxZqvpUYw6K96^@OIR4t3>QNg(9eGkbtl)>pLJ@qJGsBPKRww%3;*LI~{BGKRX zw_m=_4C{!F!#H@9PnT?r^>1tm${cYy(J&!X_%qg}(fEM~EnO(NQJS0HkWRG=UELkV zLIOQ$aefSNQJV}kGSf}{+O$?sTG)AExg2+{H~xceZysYkqF+>x%RpdspDxR}6gw;f z?ygN818GqhaAb4Bovk`4migM69Q3?9{DMzunXNAIf&N2inDvqBT?ooWC?U|#W@3JI z`Fecs$1dv3LG?EPBr$o!M~*DF?LA9u>H@T`JJhr4hDWb&E2C#kF3Ro#t)VD+J!Jru z&->7GHlyXs%BMN^B%&IaPVX^E)-0&u_syTG3MypJL_c4pkikp85Y;|Z(lBWqn-S%H zjjdl!=;9cn{nF)wRRe}Q3EBDC^yPy%TR7>H*V3QGriUaAc=uwIeo5Iw4%tzb0Ta-f zUc8f{MXEdhE6?NSMrC_9e~i6OHy`(X=3VZN}qm! zcmpKQau?e9)JXv(XFC`@US`5gkbU?7cy!g((@o(Q zAFuj>jkB|7xSi`eJ0>BRui#J_trzkD)6nS7adjMqPhCntflgcf!jrq`my<||eYv~c zW*7brzte8*zdi+vPtUn`X3>czb`GGh^Tw{$MKrYn_^$hWPST8#O~I^XX{|jIZ)Ier z;m9Z!3@FGV4CrY0vD@84KWh0Kk|U%@F-y1X?~BH-m>%Uer>s;o zmmOf}l$hB=nCM>voh06u=xQZO-lrqR{QHE%T zy5(K1>gz?1()Pae6n2PX+d5ilK8hE7f|idBEcOYXl>{a2{gv;o|C8b23^lM9a8aMI zc^Ok$M{13H1EeS-WUxRm$3#sbxo+pXH1@o>cGzfF_ITL zy#xe(5wF{jNfoRVZaGZMW`GWa4S!@^THf^1JKm1`IfQFg;@EV=9S=wN+WXr@Vonmu z>ct{h{q}-Tb5L)%zwpHzz3C9!?e=E>jn2n zX}+G)kj@B(L}ntdZewFOZ?j`OZsDLp-SI*}m;^F=0 zE7SImMe>f#$HE&-4ZcB6^DwrA> z1ZoK=MMWU7@__ky`N8ZwyevFCEUfh0YA%+tW}a5`Kv@wG4+zBj?3@2DB<*t$#uIYw(H+e>)7LT4D?-$g_()I%x3E8Z} zvgDv1+%>{vA{V=iCY{6d8N6c{h15khn0ABHU<+8AK`@d7BcRw|DD6=!OvT`IBUB{t z4>8|{s%WVqlcMX(l`zSN#O`2W*70Wl)pF|nBMVe)JfHeyMFZroV3cB}hw+o!k2A4^ z3@JKCW>^r#z$l0ad5KjcqzUzzNQ9~Siy0;c=r<&tC2JU!zu`pO0=A3m31AX_C=p~^ zaemmmGlo1(5#jGNsXl#dVCj|TAhCSSlAsv{b^I=^1c-Pg%_>t`9?Y@HL^Ow~AfPEH ztP?I`{#`vR%vI5VRb%L@nQQ=K9O?k(rLJ_?Yi-qV%N9kx^GF;E9(VFp0!CXi+vjfv zycwpKsvdvYmK>}OFad9~VD>n(b3#88%t(M6=~|k^eJ(?)e@fj#9VtPhU}lm=q6!gf zr~tWg$VaUW%&ItA0-^-hY!1M|HE)QrIghiWDoYA zOQBiYQc2ojEph(~B0f3Kd=`n}&K9~mHFUFuFH!ePEqA;&iHJH(=;fs0iQtX4*SX8F zAST-;bMEf1)T&YB0Uc8KXz@_hitP~uu@ z)%#$d(8ddqGtnZWlH{u(wtSrDDC~|3rXs!LisJ<_8ZkOD(a}@6q;9y^y@(HCC(cA- zkETg-yT@O6m@+E8*jvbWBltJ@I6t8h7jU@mi1P+ z=@`-8cV|OHY*Q2n9ODSZ7R|XdEi#5pHy5i_=Y%?8j>pg8jf|f$am`K+i*|I4&-ZhT zruDGpusDzlU%S36qriX6V4-{B?H+Dm7>4KdFL>rh0Pt<7C<`&P)n?{bMLS%>wdtOqtYH7earWhAa%gsiTFfs;BY zcD_IF6|56{;~`W-(O5jmQuWv8xMuawN*q#3^ig?mBFojj*qWT8yg$q{h~nBqM9@l1 zTPVlV?bVkKCehkusi|u~FEd;un(i;ep_6`c@#(bQG7-5?dGFl((#~5E?zjD;pRX?y z@WccTzEXZwF^$L;thPt-%_59_^TXZG^89D>$g1f3&$gy{-kh~f+t7H!qX?q2pd9YU z$`XgD=+8DQ4QGKIw1T+=cS}bGMQ=-2lKmFgW{02LKh6=_`Yqi|K}8Kmzxsft8Os6% zQ=aVbOBV^hso_~1Ax*&uNlcp@C0fdLDw-})d>Sb+^~Q8 zIuv>WyQ$XkRux)Xse4A3Yu<9sGPbiQ3_MkBYd5Kmip^~}-QLJ8d+||+_w&4QIdnZB z7$W2mrSmhJ4EQY~2E_vHB`5?auInulc4p(}c&}B|J5?{89iyXu+|2zjwtro(w#{hm z!np>4<-rYv<5}zGC$ejMnjghN#p2LjzPS~&Yqy+xO$gK-7IE-ftiY)Eoo%Hfn4c{CnoWQ9W;#Zqhbyg%3De$bsU=_pl_~XaDN%_3NkdG%g&2feA9IZGUFz`R zu{a3tu`H5g)0+Flw_O=XFOa%AVQMl(CMUf5QNC~O3#6lhfExO4vQe(xMEWl>W%2gv zIof5STjJc|G4$BCP?O@18uJ%*1*LTgj$7Id zwUY^Gk!`h`{x?7F1vu|xbxhJLBr?)c>jfTuE{0!Nb zLAo%CSDVnu!XaARtmx><%F5AWlUMYH-4T~Jw;$fEsX`0-y-0Vj-Yd8=FY+@@0oB)y z&tHhgWg!JjE#irvhg(sLpGOvi;TLX*2QYv?YB1$uiQ^l-N0mvW?9n6%~i8 zSVLmHC6hdD{EXwor)y^yCqK7rx@P=%o453;X7hs-9-^TWU3p}hUv|A)IM92u6Q8MP zdqURIPZ7#cfs|A#&2Qjdh|VmO9m&vw!hENr+1|2$M_$fuh=iWBfSwt9_BO}bOYNW9o(Cm?D5rIp(6 z2~VhH3DRq{GTZ8h9WC==_;F=%w9fpK5_+T`3u6l8zQjcXD)OY6F4W6&%8#{Xfi?oe zvtt%GtfV;{|MEdx?Q13DA)MhTQ2D(I@nX@V>{g)0Ou766T$=btcuIW=W<5x2KdnKbFL zNsW-g||etoN}oVvC-C zO`6bm8o)F3LcpFTcx7m!7-?{KY)q10uvp%%RM@9&8>PL28Yp(*|dr}OrWQ_uF zH?l-;4vu}#V~w>S3tCw+bERv#kE$#Yp1zhJ(~f+?WlV5CR7a8*eWnqRkTs5Xu8!1Y z>{OjQ1zpl}i~SNPP2;8E7JyDv779AYh#Zvlzq+#*pZb0&TK2ZFH%vEBRP{%V;#$<4 zGq8bBJDQ&n%VAk-KWxs-^jW!!(5B%APgIuD{wPLi7Qt{V0rb(MmpaTDOhV@oNRZXK6069a z;z;3(w5_sf+1%SK5Andn0Cl~^2)*__5%%QGl)X~6EREU`VQvk>l5?iZUH1N_R!AD+ zC5gfZ@ah?`G}kP8N`_`_lxpklp2Klz^3BhxN$x`DW;Cd8=h*M@@pbDYl1d4< z>X744%$r0bWH7u+3#=jjI(TCR-_hVIwI)FKcSr=ONJk~pFNmqtergZbh2sMIN9K2T zH)pzjS)XyuKgHHuNv`Vl8C@i5*G_3JTza~~gIL5k(C%&rOlxE0>mQS5*_n?wI&u=C zl#5oiXY%7{{Y#+5AuHR_h}tWPF3=49N-CO`zZF5TcZp1lUGc#Rm8MsBwz}1q6>fN+ z4$V;-N&+r{*HJ^6$$m~WqjIc%!Ig5nzEm9Ow=UCYtll)DJ8Gcvo8}pUEnPl(nhj*+ zlnr{UJLNK6->t$9j`q9b>50+3rKp>DN`*7ukiJd}$TWNSQmSOBRAV_OeuSR?HLvL+ z!f%0f|3+R};7A4Eqn-ZC!|F;`?5%IiFGh%9$C_8|%8~W*cDcKH`#hx9Wb`9medu~r zv0aRSL)Ujz@rON%ep3VfiY&jX)uadf+i?${f2X6MG(-g|Bb1Ma-k6^It(~70{WDji z=hml((DTxRp6mHp+r`C`9t8R)3Qe1yTglnl<@qN3KR4NDq^O{vkQ`VREXOa;!zV1w zCnzQRTtafP!XPOjK|vX5@&5+`_%FsdnHQ55Spf8}O%~~liZ-zp$tQ$I<|k1S*)SN4 zKvNUdR~8ioi!u@@OCKf0zadb7rGP_ouTtr1YbaNbLYDuT-PPGpeKq)vE5|y;{Y*}l z=hfkzG2dODfw_3skj-Y9mrB2HXvOz)jK=icJJz`ZT3p-jcG*(InQmm9!k^9*3rJVX z+@7efzCwcqkHF&yh9x&ln${TgpsXz)G~+TkZfE+iH!z2nCpg@iy&_(%pMC8T&Kwl| zSJ!>4@oN|c?m9OhF&Do@0ItM+XwT`OY)p0eN1)fIzPsH8HGhO=13S zz5l7y6(9eE09+S0OnvGTV=yU{j_3(#-NDQ?hZ3T3m{G6;H^F)cA_t%F1(@KNF#?YY zc<1x_NZ%}G7nten*UU1|TsNM6bM@hCC8faoKsm^?UX#vd8#m%jj-1W3!I(xp-5-uz zPNV2~bFRXcKb(}Xox-+s_#ZB#XkT`}S)(SRE;$#3dSRO_*P7OJ&dz3zoOiv=cS;~G z^fpp}@J`Z`WL3`eBIJUIQ%uwq}vkvu>#%i>4%=hu)06ENV$A`!S7~N7sMI+8V15u7i Date: Fri, 23 Dec 2022 23:00:55 +0100 Subject: [PATCH 34/34] Delete 'levels/level_more_levels.txt' --- levels/level_more_levels.txt | 134 ----------------------------------- 1 file changed, 134 deletions(-) delete mode 100644 levels/level_more_levels.txt diff --git a/levels/level_more_levels.txt b/levels/level_more_levels.txt deleted file mode 100644 index 3dd557e..0000000 --- a/levels/level_more_levels.txt +++ /dev/null @@ -1,134 +0,0 @@ -player: { - hp: 50, - inventory: [ - { - id: "dagger", - x: 0, - y: 0, - name: "Dolk", - description: "Basis schade tegen monsters", - useTimes: infinite, - value: 10, - - actions: {} - } - ] -} - -levels: [ - { - layout: { - | * * * * * * - | * s . . e * - | * * * * * * - }, - - items: [], - - entities: [] - }, - { - layout: { - | * * * - | * e * - | * . * - | * . * - | * . * - | * . * - | * s * - | * * * - }, - - items: [ - { - id: "key", - x: 1, - y: 2, - name: "Sleutel", - description: "Deze sleutel kan een deur openen", - useTimes: 1, - value: 0, - actions: { - [not(inventoryFull())] retrieveItem(key), - [] leave() - } - } - ], - - entities: [ - { - id: "door", - x: 1, - y: 4, - name: "Deur", - description: "Deze deur kan geopend worden met een sleutel", - direction: up, - - actions: { - [inventoryContains(key)] useItem(key), - [] leave() - } - } - ] - }, - { - layout: { - | * * * * * * * * - | * . . . . . . * - | * s . . . . . * - | * . . . . . e * - | * . . . . . . * - | * * * * * * * * - }, - - items: [ - { - id: "sword", - x: 2, - y: 3, - name: "Zwaard", - description: "Meer schade tegen monsters", - useTimes: infinite, - value: 25, - - actions: { - [not(inventoryFull())] retrieveItem(sword), - [] leave() - } - }, - { - id: "potion", - x: 3, - y: 1, - name: "Levensbrouwsel", - description: "Geeft een aantal levenspunten terug", - useTimes: 1, - value: 50, - - actions: { - [not(inventoryFull())] retrieveItem(potion), - [] leave() - } - } - ], - - entities: [ - { - id: "devil", - x: 4, - y: 3, - name: "Duivel", - description: "Een monster uit de hel", - hp: 50, - value: 5, - - actions: { - [inventoryContains(potion)] increasePlayerHp(potion), - [inventoryContains(sword)] decreaseHp(m1, sword), - [] decreaseHp(m1, dagger), - [] leave() - } - } - ] - } -] \ No newline at end of file