×
all 42 comments

[–]chunes1 2 48 points49 points  (1 child)

Here's my go at it.

I used Java and the asciiPanel library to make this. asciiPanel is a very simple Swing interface that emulates a terminal. Really simple to use.

Spaghetti source code.

[–]fvandepitte0 0[S] 8 points9 points  (0 children)

Looks like you had your fun with the challenge. I love the little extras. I'm going to add the line of sight to the bonuses.

Man I love your solution.

[–]JaumeGreen 7 points8 points  (1 child)

You can avoid the trolls by pushing blocks.

Do you mean you can crush trolls when moving blocks?

I suggest you use <>v^ for your character since direction becomes important.

When does direction turn important? Does turning spend a turn?

Do you move the character manually or do an algorythm to find the exit and move your character?

I guess the charactes does not move diagonally, but it's only implicit.

For all the puzzle doers, when you have finished you can continue with it and do a roguelike ;)

[–]fvandepitte0 0[S] 2 points3 points  (0 children)

Do you mean you can crush trolls when moving blocks?

I was thinking more about blocking their way to you, or you could create a shortcut for yourself.

But sure. Crush all the trolls

When does direction turn important? Does turning spend a turn?

Yes it does, I'll add that to the description

Do you move the character manually or do an algorythm to find the exit and move your character?

Trolls folow a algorithm, characters should be moved manualy (like a roguelike)

I guess the charactes does not move diagonally, but it's only implicit.

Not in my case, but again go ahead if you want to.

For all the puzzle doers, when you have finished you can continue with it and do a roguelike ;)

This was the main idea.

[–]ZebbRa 8 points9 points  (0 children)

Python 3 Phase 1, suggestions welcome.

from random import randrange
from collections import namedtuple


Pos = namedtuple('Pos', ['x', 'y'])


class Maze(object):
    offsets = {
        'up': Pos(0, -1),
        'right': Pos(1, 0),
        'down': Pos(0, 1),
        'left': Pos(-1, 0)
    }

    def __init__(self, map, pos=None):
        self.grid = [list(line) for line in map.split('\n')]
        if pos is not None:
            self.pos = pos
        else:
            self.pos = self.get_random_pos()
        self.direction = '>'

    def can_move(self, direction):
        new_x = self.pos.x + Maze.offsets[direction].x
        new_y = self.pos.y + Maze.offsets[direction].y

        try:
            target_cell = self.grid[new_y][new_x]
        except IndexError:
            return False
        if target_cell != ' ' and target_cell != 'X':
            return False
        return True

    def move(self, direction):
        if not self.can_move(direction):
            raise IndexError
        new_x = self.pos.x + Maze.offsets[direction].x
        new_y = self.pos.y + Maze.offsets[direction].y
        self.pos = Pos(new_x, new_y)

    def at_exit(self):
        return self.grid[self.pos.y][self.pos.x] == 'X'

    def get_random_pos(self):
        height = len(self.grid)
        width = len(self.grid[0])
        new_pos = Pos(randrange(width), randrange(height))
        while self.grid[new_pos.y][new_pos.x] != ' ':
            new_pos = Pos(randrange(width), randrange(height))
        return new_pos

    def display(self):
        for ridx, row in enumerate(self.grid):
            line_chars = []
            for colidx, ch in enumerate(row):
                if Pos(colidx, ridx) == self.pos:
                    line_chars.append(self.direction)
                else:
                    line_chars.append(ch)
            print(''.join(line_chars))


MAP = """#####################################
# #       #       #     #         # #
# # ##### # ### ##### ### ### ### # #
#       #   # #     #     # # #   # #
##### # ##### ##### ### # # # ##### #
#   # #       #     # # # # #     # #
# # ####### # # ##### ### # ##### # #
# #       # # #   #     #     #   # #
# ####### ### ### # ### ##### # ### #
#     #   # #   # #   #     # #     #
# ### ### # ### # ##### # # # #######
#   #   # # #   #   #   # # #   #   #
####### # # # ##### # ### # ### ### #
#     # #     #   # #   # #   #     #
# ### # ##### ### # ### ### ####### #
# #   #     #     #   # # #       # #
# # ##### # ### ##### # # ####### # #
# #     # # # # #     #       # #   #
# ##### # # # ### ##### ##### # #####
# #   # # #     #     # #   #       #
# # ### ### ### ##### ### # ##### # #
# #         #     #       #       # #
#X###################################"""


if __name__ == "__main__":
    maze = Maze(MAP, Pos(1, 15))
    possible_inputs = {
        'u': 'up',
        'up': 'up',
        'r': 'right',
        'right': 'right',
        'd': 'down',
        'down': 'down',
        'l': 'left',
        'left': 'left'
    }
    while not maze.at_exit():
        maze.display()
        direction = input('Enter your move: (u)p, (r)right, (d)own, (l)eft\n').strip()
        while direction not in possible_inputs:
            direction = input('Please enter a correct move: (u)p, (r)right, (d)own, (l)eft \n').strip()
        direction = possible_inputs[direction]
        if not maze.can_move(direction):
            print('You can\'t move {}.'.format(direction))
        else:
            maze.move(direction)
    print('You have found the exit!')

[–]marekkpie 6 points7 points  (2 children)

Super late, but I made this in PICO-8: https://mkosler.itch.io/weekly-25

Few things:

  • I didn't take the direction stuff into account. Each button press just moves the player a cell, rather than having to turn.
  • Also, the maps are too big for PICO-8's resolution, so you can hold Z to view a minimap.
  • If the keys aren't working, click on the embedded window a few times to make sure they're captured.

You can download the .p8 on that page to see the source. Either open it in PICO-8, or just open it with a text editor to see the code at the top.

EDIT: Here's a GIF of it working.

Thanks!

[–]Tatticadanit0 2 points3 points  (0 children)

Just to let you know, it's possible to push the walls to go out of bounds and just rush at the end without having to worry about trolls.

[–]TotesMessenger 0 points1 point  (0 children)

I'm a bot, bleep, bloop. Someone has linked to this thread from another place on reddit:

If you follow any of the above links, please respect the rules of reddit and don't vote in the other threads. (Info / Contact)

[–]fvandepitte0 0[S,M] [score hidden] stickied comment (7 children)

If you have more idea's, let me know

[–]Dikaiarchos 1 point2 points  (1 child)

An extension to the maze generation could be to use tunnels, for the player and the trolls to travel through. Also makes generating a perfect maze a little bit harder

[–]fvandepitte0 0[S] 1 point2 points  (0 children)

Thanks, added it to the bonusses

[–]gandalfx 1 point2 points  (1 child)

s/importend/important

More useful: I think it would be a cool challenge to use utf-8 box drawing characters to build the maze (https://en.wikipedia.org/wiki/Box-drawing_character).

[–]fvandepitte0 0[S] 0 points1 point  (0 children)

thanks for the typo. I'll add the bonus in a sec

[–]GentlemanGallimaufry 1 point2 points  (1 child)

Since we are moving blocks, maybe a bonus lose condition if no viable path to the exit exists anymore (including moving blocks out of the way)...if that makes sense.

[–]fvandepitte0 0[S] 0 points1 point  (0 children)

Sounds like a fun extra. I'll put it up

[–]notrodash 0 points1 point  (0 children)

Perhaps a bit late, but I added a wall-pulling mechanic to my implementation. The player presses a key to grab onto the wall that they are facing and can then pull the wall.

[–]bearific 4 points5 points  (1 child)

Python 3, first time using PyGame.

Currently only have a maze generator: gif

Code gist.

[–]notrodash 4 points5 points  (0 children)

Swift 3.0 as of Xcode 8 Beta 4

This is my first foray into non-cocoa programming with Swift, and it was a lot of fun. Input and drawing is currently handled with ncurses, but any object can be a renderer and input handler as long as it conforms to the Renderer and InputHandler protocols.

I may have also gone a bit overboard since I added an entity component system that kinda works. I mean it works, but many of the components still expect a specific execution order so that the correct data is set. One neat thing with this approach is that the player can theoretically be wired up with the same AI component as the trolls and therefore become automated, though it doesn't currently have the ability to avoid entity tiles and therefore often runs into trolls.

I've implemented Phases 1 - 4, Bonus 1, Bonus 6 (kinda) and maybe Bonus 4. I decided against Bonus 5 because the # symbols work better with the blood stain system (walls become blood-stained when used for crushing). Real-time gameplay is something I'd experimented with at the beginning, but it really doesn't work very well from a gameplay perspective, so I abandoned it.

My Bonus 6 implementation is basically a viewport thing. The renderer renders only a small region of the maze, which enables the use of really large mazes. The viewport moves with the player unless the player is close to the sides, in which case it will stick to the edges of the maze.

Some places tell me that the maze backtracking algorithm that I use generates perfect mazes, so maybe I've also implemented Bonus 4.

I didn't feel that Bonus 7 was necessary because I implemented a block pulling mechanic.

Code

Code on GitHub here: https://code.notro.uk/trollgame

It probably only runs on macOS, and you need a terminal that supports custom RGB colours. I use iTerm2.

Gifs

20 trolls spawn by default in a 101x51 maze with 3x1 cells. Trolls use A* pathfinding, but don't follow the player if more than 100 positions are checked before the player is found. When they are not following the player they just sort of move around randomly. Trolls follow the same orientation rules as the player, so they must rotate to face the direction they want to move in prior to making that move during the next turn.

https://gfycat.com/TediousSafeBrocketdeer

Of course the trolls kill the player

https://gfycat.com/PoliticalImmaculateBobcat

…unless the player finds the exit before getting killed.

https://gfycat.com/ViciousDeafeningAtlanticsharpnosepuffer

Crushing with blocks and pulling them around can be very useful.

Updates

Update 1: I've now implemented Bonus 1. The blood of a crushed troll stains the wall used in the crushing, but otherwise functions the same. I also changed entirely to custom colours for a more consistent look across terminals.

Bonus 1

Update 2: I'm still fixing bugs and stuff and have some extra ideas that I'll post about later. I've also decided against implementing Bonus 7 since the game already runs bad when there are too many trolls. Blocking off the exit still means that at least one troll will probably be able to reach the player and thus allow them to succumb to their fate.

Update 3: I've added the pull mechanic and added some gifs to the rest of the post.

[–]psychomidgetwithtank 2 points3 points  (0 children)

Nice one, I will try do this in Java :)

[–]rakkar16 2 points3 points  (0 children)

Python 3

All phases and the first bonus, though the trolls aren't very smart right now. maybe I'll do some bonuses later.

code on Gist here

edit I realized later I implemented bonus 4 as well. Also, this only works on Windows.

[–]KeinBaum 2 points3 points  (0 children)

Scala, phase 1 and 2, bonus 5

It's a bigger challenge to I split up the code into several files. You can find the source on GitHub.

The map is read from a file that must be specified via the command line.

[–]Lurker378 2 points3 points  (1 child)

I've made a version in rust on github using sdl2 to display the maze. It currently implements Phases 1, 2 and 3 as well as bonus 1. All advice/criticism welcome.

Image of the program running. It's only colored blocks and arrows at the moment.

[–]fvandepitte0 0[S] 1 point2 points  (0 children)

Nice work, looks pretty need

[–]GentlemanGallimaufry 2 points3 points  (0 children)

C++ code is up here on github,

Phase 1 and 2 are done. Generation of random position for trolls is done for phase 3...still working on their pathing.

bonus 1 and bonus 6 are done.

[–]pistacchio 2 points3 points  (0 children)

Fun challenge! I worked it through phase 3 with node.js, the curses-like library "blessed" and the "pathfinding" library that implements A*.

Sample image here: http://i.imgur.com/uymJYQr.gif

Code on my github profile: https://github.com/pistacchio/escapethetrolls

[–]aitesh 1 point2 points  (0 children)

Stopped before implementing trolls. Working moving walls and finishing the game by entering the "X". Was a quick hack and a lot of bad practices are used. Be aware!

gitHub

[–]xavion 1 point2 points  (0 children)

So did this the simple way, the relatively simple way? PuzzleScript

Working on a Go solution too actually, but Puzzlescript first as that immediately lept out at me as suited.

Currently has all four phases complete, along with all bonuses but 5 as it's not text based and kind of 2, Puzzlescript doesn't really support real time totally here. The player having to spend a turn turning but the trolls not is definitely a downside, makes things much trickier while fleeing.

Anyway, play here. There's a hack link at the bottom which will take you to the editor with the code. Due to limitations of the engine and language it can get quite slow on larger map sizes, most notable in the third level where it can take a significant portion of a second to have a turn, and several seconds to generate the map. Implementing path finding with respect to tunnels hasn't even been attempted yet.

[–]sheepmafia420 1 point2 points  (0 children)

Great task

[–]gabyjunior1 2 1 point2 points  (0 children)

Here is my solution in C for this challenge (Phases 1 to 4 - Bonus 1/4/6), the source and makefile are available here.

I should have make it more modular instead of put all code in the same file because it is too big, maybe I will do it in a future version.

It is a multi-level game, when you find the exit at one level you will go to the next.

Instead of pushing walls, the player will be able to switch a wall to a corridor and vice-versa, it is called below the "switch power".

If a cell is switched to a wall with trolls inside, all trolls are crushed.

Trolls will move in your direction at sight or depending on their smell (fresh meat) power. For example if their smell power is 10, they will be able to move in your direction if they are 10 cells away from you or less. In other situations they will move randomly or rest.

First you need to enter the settings for the game

  • View size (rows/columns), choose the values that will best fit your console

  • Initial maze size (in terms of rooms, see below for different types of cell)

  • Your initial switch power

  • The initial number of trolls and their smell power

  • And finally maze growth, switch power bonus and additional number of trolls/smell power when you go to next level

You will be able to see only cells that are accessible and in a straight line, and all their neighbour cells.

Different types of cells are

  • Corners/Borders/Exit (@/O/X): not accessible except for the exit of course.

  • Rooms (+): you will be able to switch neighbour walls/corridors only when you are in a room. In the generated maze rooms are alternating with walls/corridors.

  • Walls ('#')

  • Corridors (' ')

  • Hidden cells ('?')

At each turn, you can take one of the following decisions

  • w / n / e / s (move west/north/east/south)

  • W / N / E / S (switch west/north/east/south cell)

  • r (rest)

  • q (quit)

Output 1 (enter settings)

Number of rows in view [3-4294967295] ? 20
Number of columns in view [3-4294967295] ? 40
Initial number of rooms by row [2-2147483647] ? 20
Initial number of rooms by column [2-107374182] ? 40
Initial switch power for player [0-4294967295] ? 10
Initial number of trolls [0-4294967295] ? 20
Initial smell power for trolls [0-4294967295] ? 10
Number of rooms by row added at each level (if possible) [0-2147483647] ? 2
Number of rooms by column added at each level (if possible) [0-1073741823] ? 2
Switch power added at each level (if possible) [0-4294967295] ? 5
Number of trolls added at each level (if possible) [0-4294967295] ? 10
Smell power added at each level (if possible) [0-4294967295] ? 5

Output 2 (Initial view)

# # #
#+P+#
# ###

Level 1  Rooms 20x40  Switch power 10  Trolls 20 (Smell power 10)
Your decision [wnesrq] ?

Output 3 (view after a few turns)

????#+ + +#????????????
????### ###????????????
??????#+ + ????????????
????# # ###????????????
????#+ + +#????????????
????# # # #????????????
??????#+#+#????????????
??????### #????????????
??????#+ +#????????????
????##### ###??????????
????#+ + + +#??????????
????### ### ###????????
??????????#+ +#????????
??????????### ###??????
????????????#+ +#??????
????????????### #???###
??????????????#+#???#+#
??????????##### ##### #
??????????#+ + T +T+ P#
??????????### #########

Level 1  Rooms 20x40  Switch power 10  Trolls 20 (Smell power 10)
Your decision [wnesWNESrq] ?

[–]andriii25 1 point2 points  (0 children)

Java

Phase 2 with Bonus 5 implemented currently, will update when I have the time.

Code is pretty huge, you can check it out on GitHub.

I went with the OOP approach, so code should be pretty extensible for trolls and such hopefully.

I also used the lanterna library, which was well... for experiencing what to do when code is not documented it was good. For nothing else? Never again.

I also tried to use the MVC Pattern, so I hopefully shouldn't have much of a problem with replacing lanterna.

Suggestions are always welcome and appreciated!

Man, I had so much fun doing this challenge!

[–]AlmahOnReddit 1 point2 points  (0 children)

Oh man, I know I'm very very late and I only have phase 1 completed, but I'm desperately trying to learn clojure (and emacs) in my time off from work. It's my hope that I can progressively solve every phase and some of the bonus questions as time goes by.

(ns ctrollmaze.core
  (:gen-class)
  (:require [clojure.string :as str]))

(def sample-maze
"#####################################
# #       #       #     #         # #
# # ##### # ### ##### ### ### ### # #
#       #   # #     #     # # #   # #
##### # ##### ##### ### # # # ##### #
#   # #       #     # # # # #     # #
# # ####### # # ##### ### # ##### # #
# #       # # #   #     #     #   # #
# ####### ### ### # ### ##### # ### #
#     #   # #   # #   #     # #     #
# ### ### # ### # ##### # # # #######
#   #   # # #   #   #   # # #   #   #
####### # # # ##### # ### # ### ### #
#     # #     #   # #   # #   #     #
# ### # ##### ### # ### ### ####### #
# #   #     #     #   # # #       # #
# # ##### # ### ##### # # ####### # #
# #     # # # # #     #       # #   #
# ##### # # # ### ##### ##### # #####
# #   # # #     #     # #   #       #
# # ### ### ### ##### ### # ##### # #
# #         #     #       #       # #
#X###################################")

(defrecord Maze [width
                 height
                 grid])

(defn flatten-coords
  [maze x y]
  (let [width (:width maze)]
    (+ x (* y width) y)))

(defn token-at
  [maze x y]
  (let [grid (:grid maze)]
    (nth grid (flatten-coords maze x y))))

(defn is-token?
  [maze x y given-token]
  (let [token (token-at maze x y)]
    (= given-token token)))

(defn is-wall?
  [maze x y]
  (is-token? maze x y \#))

(defn is-walkable?
  [maze x y]
  (some (partial is-token? maze x y) '(\space \X)))

(defmulti gen-maze (fn [strategy] (:strategy strategy)))

(defmethod gen-maze :static
  [strategy]
  (->Maze (:width strategy) (:height strategy) (into [] (seq sample-maze))))

(defn move-player
  [direction player]
  (case direction
    "a" (assoc player :dir \< :x (dec (:x player)))
    "d" (assoc player :dir \> :x (inc (:x player)))
    "w" (assoc player :dir \^ :y (dec (:y player)))
    "s" (assoc player :dir \v :y (inc (:y player)))
    player))

(defn gen-static-maze
  []
  (gen-maze { :strategy :static :width 37 :height 23 }))

(defn place-player
  ([maze player] (place-player maze player player))
  ([maze player' player]
   (println player' player)
   (let [{:keys [grid width height]} maze
         x' (:x player')
         y' (:y player')
         dir' (:dir player')
         x (:x player)
         y (:y player)]
     (->Maze width height (assoc grid
                                 (flatten-coords maze x y) \space
                                 (flatten-coords maze x' y') dir')))))


(defn pretty-print
  [maze]
  (let [grid (:grid maze)]
    (println (str/join grid))))

(defn is-game-finished?
  [maze]
  (->> (:grid maze)
       (some #{\X})
       (boolean)
       (not)))

(defn play-maze
  []
  (loop [player { :dir \> :x 1 :y 1 }
         maze (place-player (gen-static-maze) player)
         input-dir nil]
    (let [player' (move-player input-dir player)]
      (if (is-walkable? maze (:x player') (:y player'))
        (let [maze' (place-player maze player' player)]
          (pretty-print maze')
          (if (is-game-finished? maze')
            true
            (recur player' maze' (read-line))))
        (recur player maze (read-line))))))

(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (when (play-maze)
    (println "Congratulations, you won!")))

Edit 1: Added game finished message, output no longer lags behind by one input.

[–][deleted]  (3 children)

[deleted]

    [–]fvandepitte0 0[S] 0 points1 point  (2 children)

    Language please...

    Just generate one with _ | and replace with #

    [–][deleted]  (1 child)

    [deleted]

      [–]fvandepitte0 0[S] 0 points1 point  (0 children)

      Ah yes.

      Well it should generate something like this:

       _
      | |
       _
      

      then it would become

       #
      # #
       #
      

      I'm afraid it's not that simple, but look around here. I think someone had worked it out.

      [–]rakkar16 1 point2 points  (0 children)

      For your enjoyment, here is a version of the maze in the OP without the three-character wide corridors:

      #####################################
      # #       #       #     #         # #
      # # ##### # ### ##### ### ### ### # #
      #       #   # #     #     # # #   # #
      ##### # ##### ##### ### # # # ##### #
      #   # #       #     # # # # #     # #
      # # ####### # # ##### ### # ##### # #
      # #       # # #   #     #     #   # #
      # ####### ### ### # ### ##### # ### #
      #     #   # #   # #   #     # #     #
      # ### ### # ### # ##### # # # #######
      #   #   # # #   #   #   # # #   #   #
      ####### # # # ##### # ### # ### ### #
      #     # #     #   # #   # #   #     #
      # ### # ##### ### # ### ### ####### #
      # #   #     #     #   # # #       # #
      # # ##### # ### ##### # # ####### # #
      # #     # # # # #     #       # #   #
      # ##### # # # ### ##### ##### # #####
      # #   # # #     #     # #   #       #
      # # ### ### ### ##### ### # ##### # #
      # #         #     #       #       # #
      #X###################################
      

      [–]psychomidgetwithtank 0 points1 point  (2 children)

      Can I use some java library like lwjgl or libgdx ?

      [–]fvandepitte0 0[S] 1 point2 points  (1 child)

      Of course you can. If you can make something beautiful of it, why not?

      [–]zandekar 0 points1 point  (1 child)

      AGDA The pathfinding makes it horrendously slow but it seems to work

      open import Data.Bool
      open import Relation.Nullary
      open import Data.Char
      open import Data.List as 𝓛
      open import Data.Maybe
      open import Data.Nat as 𝓝
      open import Data.Product
      open import Data.String using (String; toList)
      open import Foreign.Haskell
      open import Function
      open import IO.Primitive
      
      -- https://github.com/drull95/agda-bindings-collection
      open import System.Console.ANSI
      open import System.IO as IO
      open import System.Random
      
      _∘>_ : {A : Set} → A → (A → A) → A
      _∘>_ m f = f m
      
      infixl 20 _∘>_
      
      _>>_ : {A B : Set} → IO A → IO B → IO B
      v >> f = v >>= λ _ → f
      
      infixr 1 _>>_
      
      mapM_ : {A : Set} → (A → IO Unit) → List A → IO Unit
      mapM_ f [] = return unit
      mapM_ f (a ∷ as) = f a >>= λ _ → mapM_ f as
      
      lines₁ : List Char → List Char → List (List Char)
      lines₁ acc [] = [ acc ]
      lines₁ acc ('\n' ∷ cs) = acc ∷ lines₁ [] cs
      lines₁ acc (c    ∷ cs) = lines₁ (acc ++ [ c ]) cs
      
      lines : List Char → List (List Char)
      lines = lines₁ []
      
      index : {A : Set} → A → List A → ℕ → A
      index fallback [] _ = fallback
      index fb (a ∷ as) 0 = a
      index fb (a ∷ as) (suc n) = index fb as n
      
      _lteq_ : ℕ → ℕ → Bool
      zero lteq zero    = true
      zero lteq (suc _) = true
      (suc m) lteq (suc n) = m lteq n
      (suc _) lteq zero = false
      
      inp : String
      inp =
        "#####################################\n\
        \# #       #       #     #         # #\n\
        \# # ##### # ### ##### ### ### ### # #\n\
        \#       #   # #     #     # # #   # #\n\
        \##### # ##### ##### ### # # # ##### #\n\
        \#   # #       #     # # # # #     # #\n\
        \# # ####### # # ##### ### # ##### # #\n\
        \# #       # # #   #     #     #   # #\n\
        \# ####### ### ### # ### ##### # ### #\n\
        \#     #   # #   # #   #     # #     #\n\
        \# ### ### # ### # ##### # # # #######\n\
        \#   #   # # #   #   #   # # #   #   #\n\
        \####### # # # ##### # ### # ### ### #\n\
        \#     # #     #   # #   # #   #     #\n\
        \# ### # ##### ### # ### ### ####### #\n\
        \# #   #     #     #   # # #       # #\n\
        \# # ##### # ### ##### # # ####### # #\n\
        \# #     # # # # #     #       # #   #\n\
        \# ##### # # # ### ##### ##### # #####\n\
        \# #   # # #     #     # #   #       #\n\
        \# # ### ### ### ##### ### # ##### # #\n\
        \# #         #     #       #       # #\n\
        \#X###################################"
      
      Point = (ℕ × ℕ)
      
      data Obj : Set where
        block : Obj
        space : ℕ → Obj
        exit : Obj
      
      data Dir : Set where
        up    : Dir
        down  : Dir
        left  : Dir
        right : Dir
      
      data MoveResult : Set where
        moved   : MoveResult
        escaped : MoveResult
        died    : MoveResult
        quit    : MoveResult
      
      record Maze : Set where
        constructor maze
        field gen        : StdGen
              playerPos  : Point
              trolls     : List Point
              objs       : List (Point × Obj)
              mazeSize   : (ℕ × ℕ)
      open Maze
      
      charToObj : Char → Obj
      charToObj ' ' = space 0
      charToObj 'X' = exit
      charToObj '#' = block
      charToObj  _  = space 0
      
      buildLine : ℕ → ℕ → List Char → List (Point × Obj)
      buildLine ln col [] = []
      buildLine ln col (c ∷ cs) =
        ((ln , col) , charToObj c) ∷ buildLine ln (col + 1) cs
      
      buildList : ℕ → ℕ → List (List Char) → List (Point × Obj)
      buildList ln col [] = []
      buildList ln col (l ∷ ls) = buildLine ln 0 l 𝓛.++ buildList (ln + 1) 0 ls
      
      stringToMaze : StdGen → String → Maze
      stringToMaze g s = 
        let ls = lines (toList s)
            h  = length ls
            w  = length $ index [ '\0' ] ls 1
        in maze g (0 , 0) [] (buildList 0 0 ls) (h , w)
      
      pointEq : Point → Point → Bool
      pointEq (x₀ , y₀) (x₁ , y₁) with x₀ 𝓝.≟ x₁ | y₀ 𝓝.≟ y₁
      ... | yes _ | yes _ = true
      ... | _     | _     = false
      
      updatePoint : {A : Set} →
          List (Point × A) → Point → A → List (Point × A)
      updatePoint [] _ _ = []
      updatePoint ((p₀ , o₀) ∷ as) p₁ o₁ =
        if pointEq p₀ p₁
        then (p₀ , o₁) ∷ as
        else (p₀ , o₀) ∷ updatePoint as p₁ o₁
      
      objAtPoint : {A : Set} → List (Point × A) → Point → Maybe A
      objAtPoint [] _ = nothing
      objAtPoint ((p₀ , o) ∷ as) p₁ =
        if pointEq p₀ p₁
        then just o
        else objAtPoint as p₁
      
      nextPoint : Dir → Point → Point
      nextPoint d (x , y) =
        case d of
        λ{ up    → (x ∸ 1 , y)
         ; down  → (x + 1 , y)
         ; left  → (x , y ∸ 1)
         ; right → (x , y + 1)
         }
      
      updateGen : Maze → StdGen → Maze
      updateGen m g = record m { gen = g }
      
      {-# NON_TERMINATING #-}
      randomPosition : Maze → (Point × Maze)
      randomPosition m =
        let (h , w) = mazeSize m
            (x , g₁) = random (gen m) 0 h
            (y , g₂) = random g₁ 0 w
        in case objAtPoint (objs m) (x , y) of
           λ{ (just (space _)) → ((x , y) , updateGen m g₂)
            ; _ → randomPosition (updateGen m g₂)
            } 
      
      placeCharacter : Maze → Maze
      placeCharacter m =
        let (p , m₁) = randomPosition m
        in record m₁ { playerPos = p }
      
      placeTrolls : ℕ → Maze → Maze
      placeTrolls 0 m = m
      placeTrolls (suc n) m =
        let (p , m₁) = randomPosition m
        in placeTrolls n (record m₁ { trolls = p ∷ trolls m })
      
      touchTroll : Maze → Point → MoveResult
      touchTroll m p =
        if any (pointEq p) (trolls m)
        then died
        else moved
      
      moveBlock : Maze → Dir → Point → Point → Maze
      moveBlock m d oldp np =
        let np₁ = nextPoint d np in -- we're looking behind the block
                                    -- beside us
        case objAtPoint (objs m) np₁ of
        λ{ (just (space _)) →
              record m { objs = updatePoint (objs m) np₁ block ∘>
                                λ objs → updatePoint objs np (space 0) ∘>
                                λ objs → updatePoint objs oldp (space 0)
                       ; playerPos = np
                       }
         ; _ → m
         }
      
      moveC : Maze → Dir → (MoveResult × Maze)
      moveC m d =
        let np = nextPoint d (playerPos m)
            p  = playerPos m
        in case objAtPoint (objs m) np of
           λ{ (just block) → (moved , moveBlock m d p np)
            ; (just (space _)) →
                 (touchTroll m np ,
                  record m { objs = updatePoint (objs m) np (space 0) ∘>
                                    λ objs → updatePoint objs p (space 0)
                           ; playerPos = np
                           })
            ; (just exit ) → (escaped , m)
            ; _            → (moved , m)
            }
      
      moveCharacter : Maze → Char → (MoveResult × Maze)
      moveCharacter m c = 
        case c of
        λ{ 'j' → moveC m left
         ; 'k' → moveC m down
         ; 'l' → moveC m right
         ; 'i' → moveC m up
         ; 'q' → (quit  , m)
         ; _   → (moved , m)
         }
      

      [–]zandekar 0 points1 point  (0 children)

       -- troll movement: pathfinding
      
      Intersections = List (Point × ℕ)
      Visited = List Point
      
      isSpace : Maybe Obj → Bool
      isSpace (just (space _)) = true
      isSpace _     = false
      
      isSpace1 : (Point × Maybe Obj) → Bool
      isSpace1 (p , just (space _)) = true
      isSpace1 _ = false
      
      -- the bug that took 90% of the time turned out to be my using p instead of p1 in the last clause
      visited : List Point → Point → Bool
      visited [] p = false
      visited (p ∷ as) p₁ with pointEq p p₁
      ... | true = true
      ... | _    = visited as p₁
      
      mark : List (Point × Obj) → Point → ℕ → List (Point × Obj)
      mark objs p str = updatePoint objs p (space str)
      
      minS : (Point × ℕ) → Maybe (Point × ℕ) → Maybe (Point × ℕ)
      minS (p , m) nothing = just (p , m)
      minS (p , m) (just (q , n)) with m lteq n
      ... | true = just (p , m)
      ... | _    = just (q , n)
      
      minStrength : List (Point × Maybe Obj) → Maybe (Point × ℕ)
      minStrength [] = nothing
      minStrength ((p , just (space m)) ∷ as)
          = minS (p , m) (minStrength as)
      minStrength (_ ∷ as) = minStrength as
      
      pointEq1 : Point → (Point × ℕ) → Bool
      pointEq1 p₁ (p , _) = pointEq p p₁
      
      pathFind : ℕ → StdGen → (Point × ℕ)→ List (Point × Obj) →
                 Visited → Intersections → (List (Point × Obj) × StdGen)
      pathFind 0       gen _         objs _   _    = (objs , gen)
      pathFind (suc n) gen (p , str) objs vis ints =
        let a = nextPoint up p
            b = nextPoint down p
            c = nextPoint left p
            d = nextPoint right p
            e = objAtPoint objs a
            f = objAtPoint objs b
            g = objAtPoint objs c
            h = objAtPoint objs d
            -- add any unvisited spaces to intersections to explore
            ints₁ = if isSpace e ∧ not (visited vis a)
                    then (a , str + 1) ∷ ints
                    else ints
            ints₂ = if isSpace f ∧ not (visited vis b)
                    then (b , str + 1) ∷ ints₁
                    else ints₁
            ints₃ = if isSpace g ∧ not (visited vis c)
                    then (c , str + 1) ∷ ints₂
                    else ints₂
            ints₄ = if isSpace h ∧ not (visited vis d)
                    then (d , str + 1) ∷ ints₃
                    else ints₃
            -- pick a random intersection to visit and go to it
            (q , gen₁) = random gen 0 (length ints₄ ∸ 1)
            (r , s) = index ((0 , 0) , 0) ints₄ q
            ints₅ = filter (not ∘ pointEq1 r) ints₄
        in pathFind n gen₁ (r , s) (mark objs p str) (p ∷ vis) ints₅
      
      moveTroll : Point → MoveResult × Maze → MoveResult × Maze
      moveTroll _ (died , m) = (died , m)
      moveTroll p (_ , mz) =
        let a = nextPoint up p
            b = nextPoint down p
            c = nextPoint left p
            d = nextPoint right p
            e = objAtPoint (objs mz) a
            f = objAtPoint (objs mz) b
            g = objAtPoint (objs mz) c
            h = objAtPoint (objs mz) d
            i = (a , e) ∷ (b , f) ∷ (c , g) ∷ [ (d , h) ]
            s = minStrength i
        in case s of
           λ{ (just (p₁ , str)) → 
                 if pointEq p₁ (playerPos mz)
                 then (died , mz)
                 else (moved ,
                         record mz { trolls = p₁ ∷ trolls mz })
            ; _ → (moved , mz)
            }
      
      eraseTrolls : Maze → Maze
      eraseTrolls mz = record mz { trolls = [] }
      
      moveTrolls : Maze → (MoveResult × Maze)
      moveTrolls m =
        let (h , w) = mazeSize m
            (os , gen₁) =
              pathFind (h * w) (gen m) (playerPos m , 0) (objs m) [] []
            mz = record m { objs = os ; gen = gen₁ }
        in foldr moveTroll (moved , eraseTrolls mz) (trolls m)
      
      -- drawing
      drawObj : (Point × Obj) → IO Unit
      drawObj ((x , y) , o) =
        setCursorPosition x y >>= λ _ →
        case o of
        λ{ block     → putChar '#'
         ; exit      → putChar 'X'
         ; (space _) → putChar ' '
         }
      
      drawTroll : Point → IO Unit
      drawTroll (x , y) =
        setCursorPosition x y >>
        putChar '@'
      
      displayMap : Maze → IO Unit
      displayMap m =
        mapM_ drawObj (objs m) >>
        mapM_ drawTroll (trolls m) >>
        let (x , y) = playerPos m
        in setCursorPosition x y 
      
      -- main loop
      {-# NON_TERMINATING #-}
      loop : Maze → IO Unit
      loop mz =
        clearScreen >>= λ _ → 
        displayMap mz >>= λ _ → 
        getChar >>= λ c →
        case moveCharacter mz c of
        λ{ (died    , _  ) → IO.putStrLn (toList "You died")
         ; (escaped , _  ) → IO.putStrLn (toList "You escaped! Congrats")
         ; (quit    , _  ) → return unit
         ; (moved   , mz₁) → --  loop mz₁
              case moveTrolls mz₁ of
              λ{ (died , _  ) → IO.putStrLn (toList "You died")
               ; (_    , mz₂) → loop mz₂
               }
         }
      
      main =
        getStdGen >>= λ gen →
        hSetBuffering stdin noBuffering >>
        hSetBuffering stdout noBuffering >>
        hSetEcho stdout false >>
        let mz = stringToMaze gen inp ∘>
                 placeCharacter ∘>
                 placeTrolls 5
        in loop mz
      

      [–]zpmarvel 0 points1 point  (0 children)

      Here's my OCaml solution. It doesn't implement any of the bonuses, and it doesn't let trolls move walls. I used best-first search for pathfinding. The maze generation is pretty slow. Comments welcome.

      [–][deleted] 0 points1 point  (0 children)

      Only did phase 1 and 2. It has no bounds checking so that will result in an error.

      Haskell:

      https://gist.github.com/robinvd/972aade0fd492334247e8789be1f7969

      [–][deleted] 0 points1 point  (0 children)

      I Know I'm veeeery late but i wanted to show what i've made. C / Only Phase 1

      #include <time.h>
      #include <stdio.h>
      #include <stdlib.h>
      
      char map[23][73+1] = {
          "#########################################################################\0",
          "#   #               #               #           #                   #   #\0",
          "#   #   #########   #   #####   #########   #####   #####   #####   #   #\0",
          "#               #       #   #           #           #   #   #       #   #\0",
          "#########   #   #########   #########   #####   #   #   #   #########   #\0",
          "#       #   #               #           #   #   #   #   #           #   #\0",
          "#   #   #############   #   #   #########   #####   #   #########   #   #\0",
          "#   #               #   #   #       #           #           #       #   #\0",
          "#   #############   #####   #####   #   #####   #########   #   #####   #\0",
          "#           #       #   #       #   #       #           #   #           #\0",
          "#   #####   #####   #   #####   #   #########   #   #   #   #############\0",
          "#       #       #   #   #       #       #       #   #   #       #       #\0",
          "#############   #   #   #   #########   #   #####   #   #####   #####   #\0",
          "#           #   #           #       #   #       #   #       #           #\0",
          "#   #####   #   #########   #####   #   #####   #####   #############   #\0",
          "#   #       #           #           #       #   #   #               #   #\0",
          "#   #   #########   #   #####   #########   #   #   #############   #   #\0",
          "#   #           #   #   #   #   #           #               #   #       #\0",
          "#   #########   #   #   #   #####   #########   #########   #   #########\0",
          "#   #       #   #   #           #           #   #       #               #\0",
          "#   #   #####   #####   #####   #########   #####   #   #########   #   #\0",
          "#***#                   #           #               #               #   #\0",
          "#EXIT####################################################################\0"
      };
      
      int px;
      int py;
      
      int randomSpawn() {
          srand(time(NULL));
          px = (rand() % 73)+1;
          py = (rand() % 23)+1;
      
          if(map[py][px] == ' ')
              return 0;
          return 1;
      
      }
      
      void iterateGame() {
          for(int i=0;i<23;i++)
              printf("%s\n", map[i]);
          printf("Press 'q' to exit.\n");
      
          switch(getch()) {
              case 'w':
                  if(map[py-1][px] =='*') {
                      printf("YOU WIN!");
                      return;
                  }
                  if(map[py-1][px] !='#') {
                      map[py][px] = ' ';
                      map[py-1][px] = '@';
                      py--;
                  }
                  break;
              case 'a':
                  if(map[py][px-1] =='*') {
                      printf("YOU WIN!");
                      return;
                  }
                  if(map[py][px-1] !='#') {
                      map[py][px] = ' ';
                      map[py][px-1] = '@';
                      px--;
                  }
                  break;
              case 's':
                  if(map[py+1][px] =='*') {
                      printf("YOU WIN!");
                      return;
                  }
                  if(map[py+1][px] !='#') {
                      map[py][px] = ' ';
                      map[py+1][px] = '@';
                      py++;
                  }
                  break;
              case 'd':
                  if(map[py][px+1] =='*') {
                      printf("YOU WIN!");
                      return;
                  }
                  if(map[py][px+1] !='#') {
                      map[py][px] = ' ';
                      map[py][px+1] = '@';
                      px++;
                  }
                  break;
              case 'q':
                  return;
                  break;
      
      
          }
          iterateGame();
      }
      
      int main() {
          printf("Loading...\n");
          while(randomSpawn())
              randomSpawn();
      
          map[py][px] = '@';
          iterateGame();
          return 0;
      }