Dungeons - simple procedural map generator

Based on how many times I refreshed https://lootdungeons.vercel.app/ , and how much I enjoyed checking them all out, I’d say they all feel unique and special! I think you’ve struck a good balance in terms of the amount of doors and POIs. Some felt right on the edge of too much (which was a good thing, right up to the line but not crossing it) and the rooms with no doors and no POIs felt like a little bare. Could definitely be up to the future devs, but personally if I minted an empty room, I’d feel a little disappointed. Having just one POI or door gives you so much more to imagine/room for creativity, and makes it feel like more than just a random 2D room shape.

Love what you’re doing with this project! If you need any help (especially with community management / documentation stuff), lmk!

3 Likes

Hm good point about the empty rooms… let me think about this. Perhaps each dungeon should have at least one ‘thing’ to it, even if it’s a tiny loot room:

I’m revisiting the API for dungoens and have been planning on amending the calls.

Here’s my current thinking.

Primary Audience:

  • Game Developers
  • Dungeon Masters
  • Artists or Writers who want to add metadata on top of the dungeon (e.g. environmental art, how to represent them as 3d, encounters and monsters, lore behind the dungeon)

Function Calls:
See updated post below with more detail and a consistent example.

  1. getDungeon() - Returns a 2D array representing all tiles in the dungeon as ascii characters. For example:
[
  ['X', 'X', 'X', 'X', 'X'],
  ['X', ' ', ' ', 'X', ' '],
  ['X', ' ', 'X', 'D', ' '],
  ['X', 'D', ' ', ' ', 'X'],
  ['X', ' ', 'i', ' ', 'X'],
  ['X', 'X', ' ', 'X', 'X']
] 
  1. numDoors(), numPoints(), numFloors(), numWalls(), numTiles() - Returns the number of individual spaces present (so you could say… query for dungeons with >3 doors)
  2. getRooms(), getHallways() - Returns an array of arrays of rooms and hallways (in case someone wants to place an encounter only in a room):
[ [3, 4], [2, 6], [1, 2], [0, 1], [3, 5], [6, 8] ]
  1. getDoors(), getPoints(), getFloors(), getWalls() - Returns a 1D array of each tile type. This will allow you to say, query for dungeons with >=2 doors or make every point a trap.
  2. getTile(x, y) - returns the value of a specific tile (so you could query from another product to see 'show me all dungeons with ___ at position x, y).
  3. getName(), getEnvironment - returns the name of the dungeon (string) or the environment (int) (so you could say 'get me all dungeons with environment=5 so i can build out fiery 3d land environments for all of them`

Would love to hear thoughts on this. Is this excessive? Am I missing anything? I tried to think through the ways I’d want to use and query these objects. @dom if you have a few minutes, would love your input here.

#3.1 – Sorry I’m a little confused. Is the array of arrays snippet meant to be an example output for the first #3.1 if run on the getDungeon call above it? The instance of [6,8] appearing out of bounds makes me think not but I might be daft. If not, maybe include a sample call to the example dungeon to be clear.

How are you defining a room? Is it a contiguous block of empty spaces? Is it the tile adjacent to the door? Does the definition of a room change when there are two entries into it? Is a hallway just a room of a specified width or smaller? A sample call for each function using the getDungeon call as sample data would help me here a ton.

#3.2 – 2. and the second 3. seem very similar. I’m terribly unfamiliar with Solidity though. Are you returning an array of pointers to their locations in the second 3?

I don’t think this is excessive. As an API user, I want to access all the raw dungeon data and one or two of the more high-use composite data components (like rooms, halls) as you defined.

#3.1 – Sorry I’m a little confused. Is the array of arrays snippet meant to be an example output for the first #3.1 if run on the getDungeon call above it? The instance of [6,8] appearing out of bounds makes me think not but I might be daft. If not, maybe include a sample call to the example dungeon to be clear.

Ah that is confusing. The examples are not related. I’ll edit so that they reference the same dungeon.

How are you defining a room? Is it a contiguous block of empty spaces? Is it the tile adjacent to the door? Does the definition of a room change when there are two entries into it? Is a hallway just a room of a specified width or smaller? A sample call for each function using the getDungeon call as sample data would help me here a ton.

Currently, rooms are large rectangular objects between 2x2->15x15 that are drawn initially and then hallways are placed. Rooms currently cannot intersect. Both rooms and hallways are ‘walkable’ tiles which is why they are both marked with ’ ’ in the dungeon but I’ve found it helpful (e.g. place a door only in hallways) to have a delineation in building the dungeons. I’ll write up sample API calls today for each of these using a consistent example so it’s clear.

#3.2 – 2. and the second 3. seem very similar. I’m terribly unfamiliar with Solidity though. Are you returning an array of pointers to their locations in the second 3?

Fixed numbering in original post so the ‘second number 3’ is now 4 :smiley:

The set of calls in #2 (e.g. numDoors) will return an integer that tells you how many discrete doors exist (e.g. 2). The set of calls in #4 will give you an array w/ the locations of all of the objects (e.g. if there is a door at (4, 6) and (9, 10) you will get [ [4, 6], [9, 10] ]. I could just provide the array of positions and a developer could infer the count from that (e.g. something like array.length) but I’m not sure if that’s an easy thing to do when querying the smart contract.

If not, maybe include a sample call to the example dungeon to be clear.... A sample call for each function using the getDungeon call as sample data would help me here a ton.

Here’s the API written out with a single example used throughout. I’ve decided to remove the getRooms() and getHallways() calls for now because I don’t think there’s an easy way to make them deterministic (e.g. always return the top left room first) given the code length constraints of the smart contract. May revisit this if there’s alot of demand but it’s fairly trivial for developers to identify rooms from the getDungeon() call.

API

The dungeon contract exposes the following endpoints to allow developers to use and query dungeons in their games and applications.

We’ll use this dungeon for all examples below:

dev-example

Dungeon Geometry

getDungeon() (array) - Returns a 2D array representing all tiles in the dungeon as ascii characters. The array uses a zero-based index and the top left corner of the dungeon starts at (0, 0). We’ll use this dungeon as an example for subsequent calls:

[
['X', 'X', 'X', 'X', 'X', 'X', 'X', 'X'],
['X', 'X', 'X', 'X', ' ', 'i', ' ', 'X'],
['X', ' ', ' ', 'X', ' ', ' ', ' ', 'X'],
['X', ' ', ' ', 'X', ' ', ' ', ' ', 'X'],
['X', ' ', ' ', 'X', ' ', ' ', ' ', 'X'],
['X', ' ', ' ', ' ', 'D', ' ', 'X', 'X'],
['X', ' ', ' ', 'X', 'X', 'X', 'X', 'X'],
['X', 'X', 'X', 'X', 'X', 'X', 'X', 'X'],
['X', 'X', 'X', 'X', 'X', 'X', 'X', 'X']
]

Dungeon Attributes

getName() (string) - Returns the name of the dungeon. In our example: Den of the Twins.

getEnvironment() (int) - Returns the environment of the dungeon. There are six total environments and each affects the color of the original SVG. Developers can choose to embrace or ignore these environments. In our example: 0

numDoors() (int) - Returns the number of doors present in the dungeon. In our example: 1.
Useful for: Querying for dungeons with a specific number of doors.

numPoints() (int) - Returns the number of points of interest present in the dungeon. In our example: 1.
Useful for: Querying for dungeons with a specific number of points.

numFloors() (int) - Returns the number of accessible floor tiles in the dungeon. This includes doors and points of interest. In our example: 26
Useful for: Determining probabilities of objects based on the amount of floor space. Making sure a given dungeon is suitable for a given party size.

numTiles() (int) - Returns the overall size of the dungeon. In this case, we have an 8x8 dungeon, so in our example: 64
Useful for: Querying for dungeon based on size

Specific Locations

getDoors() (array) - Returns a list of coordinates of doors. Coordinates are represented as an array of [x, y]. In our example: [ [4, 5] ]

Useful for: Querying for dungeons that have doors in a specific location

getPoints() (array) - Returns a list of coordinates of points of interest. Coordinates are represented as an array of [x, y]. In our example: [ [5, 2] ]

getTile(x, y) (char) - Returns the character at a specific tile. Characters used are as follows:

'X': wall
' ': floor tile
'D': door
'i': point of interest

I’m also considering converting getDungeon() from an array to a uint256 or string. The int would contain integers to represent tiles (similar to the array) or the string would contain characters to allow us to scale to more use cases (e.g. decorative art).

Reconstructing the dungeon: Because all dungeons are square, you can deduce the size of the dungeon by taking the sqrt() of the string’s length. You can treat the first character as the top left (0, 0). This allows you to create a 2d array in a few lines of code.

For example the above example dungeon would become:
00000000000013100110111001101110011011001112100011000000000000000000000

'0': wall
'1': floor tile
'2': door
'3': point of interest

This is a little bit harder to process but dramatically compresses the amount of data sent over the wire. Because dungeons are always square, it becomes really simple to reconstruct the dungeon as an array from this int.

1 Like

Initial thoughts on drop mechanics here. Would love to hear peoples’ thoughts on introducing an inflationary mechanic that produces new dungeons over time (like a drip feed of content).

Drop Structure

There are 9000 Dungeons. 8000 are gated to loot holders at 0.02ETH. 1000 are available to the public at 0.1ETH (to allow for lower entry). Loot holders have 72h to claim their dungeon before the drop opens to the public.

After this, one dungeon will release per week to a random Loot holder. The loot holder will need to pay 0.1ETH to mint this new dungeon.There are 9000 Dungeons. 8000 are gated to loot holders at 0.02ETH. 1000 are available to the public at 0.1ETH (to allow for lower entry). Loot holders have 72h to claim their dungeon before the drop opens to the public.

After this, one dungeon will release per week to a random Loot holder. The loot holder will need to pay 0.1ETH to mint this new dungeon. If the dungeon goes unclaimed for 72h, it becomes available to mint by anyone for 1ETH.

I hope that this will create a cadence of new content that gets released. The dungeon will be made available for minting with a seed based on the tokenId. This means that future dungeons can be predicted, just not claimed until they are released.

I hope that this will create a cadence of new content that gets released. The dungeon will be made available for minting with a seed based on the tokenId. This means that future dungeons can be predicted, just not claimed until they are released.

Love this idea! Once Dungeons is shipped, what do you think of collaborating on a new entity that uses both Dungeons and Monsters (for Adventurers) to create ‘quests’?

Each Monster is randomly generated and is weak to a single weapon type (e.g. Books, Long Swords, etc.) There are a getWeakness(), traitsOf(), and canSlay() functions you can use to determine if dungeoneers can slay a Monster. Monsters (for Adventurers)

Monster stats can be generated from a combination of the Dungeon attributes and Monster traits. The canSlay() result can give adventurers a bonus or Critical against that monster.

Happy to collaborate on a future proposal that combines Dungeons and Monsters.

Once again, very cool stuff!

1 Like

I think instead of using a string of ints something like comma separated ints or another longer ID may make more sense. With ASCII you may run out of characters if you want to add future primitives or “space types” to the dungeon format in the future. Assuming I’m understanding what you’re asking here.

Great point. I didn’t think about the fact that others might want to expand this spec and add more types.

I suspect we could use a string for this. I’ll look into memory limitations (e.g. for 35x35 dungeons).

I’ve updated the above spec accordingly.

1 Like

I like your use of color in defining this new archetype, it adds flavor. You’re right that there’s certainly a higher-order structure which combines Dungeons and some manner of foe into a larger experience.

Your description of a Monster functionally beckons other supporting systems, like a Damage Model with immunities/weaknesses, a Poison / DoT Module, and racial traits.

In the interest of interoperability It may be helpful to think in terms of defining the elemental primitives that are exclusive to a Monster in the same way that threepwave has defined the elemental primitives that are exclusive to a Dungeon.

Just caught up on the thread! Chiming in to once again voice support for the direction you’re taking this, with such emphasis on interoperability.

I also think the drop mechanics are fair! The .1 eth for non loothodlers to claim seems right to me. Low barrier to enter for those who are early.

For the weekly drops, might I suggest dropping two per week? The first claimable for the initial lootholder, @ .1eth, but then the second available for new entrants @ 1 eth? Once the value of dungeons rise above .1, I don’t see a world where the lootholder fails to mint at .1.

Perhaps you could even consider releasing them with a bonding curve? Like how moonshot bots launched?

By using a bonding curve, mint price over time could reflect general market price as it rises! (Also could allow a portion of mint price to flow to an organization/Dao to fund/seed projects that are built on top of dungeons!)

Feel free to message me on twitter if you want to chat more! @russmatthews32

1 Like

Great idea on the drop mechanics and good point that we should offer drops to non-loot holders from the get go. I like the idea of two per week and wonder how we can keep it simple. Bonding curves become challenging over time. Would love to kick off a dutch auction every week but I want to keep this incredibly simple.

Let me think about how to make that fair.

I was thinking that a dutch auction would make sense, but couldn’t figure out how you might be able to write that into the smart contract!

I also am not that advanced w/ solidity :smiley:

I’ve created separate thread with a ‘final’ API proposal to signal boost and get more feedback: Input Needed: API Design for Dungeons (which could inspire future lands, town, etc)

The document is more comprehensive and also has an updated project overview. I’ve also published this to a github repo where i’ll also share the source code once the contract has been published: https://github.com/threepwave/loot-dungeons

1 Like

Migrating @IraFudmore’s response here:


**Mint Allocation Details**
There’s likely to be a gas rush. You could go the contributory meritocratic route and do a pseudo-timestamp model for who contributed before Dungeons were cool. Dungeons Hipsters, as it were.

You could also keep a few pre-emptively set aside for a mini-grant that you use to sell for liquidity to incent other modules to integrate the project.

**Acknowledgment**

* Acknowledging OG Loot holders
  * For inclusion consider mLooters get some middle pricing – like 0.05 or something
* That .1 Eth is brutal for normies… But also not insignificant for VC posers, so maybe I like it…

I know some don’t like or even dislike the mLooters but by giving them a middle tier you acknowledge them while still acknowledging they’re not OGs. It’s your choice ultimately, I’m not married to the idea.

**Inflation / Deflation**
You create a constant expectation of inflation. I really had to think about that.

That makes me think the asset price wont balloon because users know their item loses value
daily.

However, it also makes me think that you have a content factory constantly cranking out new stuff. The content production pipeline is the lifeblood of live operations; staying ahead of player behavior is what passes or fails live games. Players always find a way to stay ahead – but it’s a motherfucking boolean for Dungeons lol.

Provided that we get people onboard integrating the Dungeons into some form of consumable content I have 0 concerns. Hell, maybe if someone “beats” a dungeon (however that manifests) it gets retired from the list somehow. Narratively: “the dungeon is cleared and no longer a threat”. Functionally: we get persistent consistent inflation plus a straightforward potential deflationary mechanism.

It’s edgy, and requires work, but I like it.

**Inflation / Deflation: Part Deux**

> After this, two dungeons will release per week:
>
>
>
> One to a random wallet that holds OG Loot. The loot holder will need to pay 0.1ETH to mint this new dungeon. If the dungeon goes unclaimed for 24 hours, it becomes available for mint by anyone for 1ETH.
>
>
>
> One open to anyone for 1ETH. This will allow new users to have a chance at owning a dungeon.
Like the required activity / lotto; and it functionally encourages holding. This synergizes with the inflation well, since it removes liquidity without encouraging sells.

Is 2/10000 daily odds good for a lottery?

I want to say so but interested in hearing others’ thoughts. My only gambling is crypto and honestly if you read a lot the odds are pretty good.

BIG ALSO: this is new code outside of the template.
I don’t know solidity, but in my experience this is the kind of thing someone would find an exploit for to give themselves all the things and fuck the project.
For anything non-standard I would request for an audit early, possibly pre-mint.

How are you surfacing that someone has the golden ticket and wins the weekly prize?
A readContract function call would be the simplest way I can think of.

Again the pricing! 1 Eth for the newbies HOLY. I get it though. Bloody moneygrubbing VC moochers (whom are admittedly some of my irl friends but still). An intermediary element here would be a mid-tier for alt-loot projects at .66 ish.

**Your Alt-Loot Thoughts**
…How do you actually feel about xLoot/mLoot/SynthLoot/etc. ? I view them as a second-class citizens in a first world nation; they deserve the worst of the best. Non-holders on the other hand are foreigners and should pay hefty taxes to live in OUR GREAT NATION. This becomes more pronounced as time passes and the alt-loots progress to a price floor similar to the current Loot price floor.

Migrating thoughts from @matto-matto

I am probably an outlier in that I would distribute heavily to non loot holders and make everything much cheaper. There’s a big rich get richer dynamic and all the loot is getting into hands of speculators not builders or players. If everything just gets air dropped to the rich few this community dries up imo. I think we need new things to bring new people into the community, not reward the very small group who either got in early or could drop 30k worth of eth on secondary.

Migrating comment from @bobolo333

I was thinking something similar about the inflationary/deflationary dynamic–if the use of Dungeons is expanding, adding new dungeons consistently seems great (kind of like Nouns project–the number that will be added is consistent and known by the community), but also I was curious about adding a “deflationary” element, where old dungeons are retired/burned so the supply doesn’t get too big. (but also having new elements to play with is just great.)

Also, I am a total noobie without Loot, and I agree that even the initial .1 entry will actually still be high for a lot of people. I wonder if there are ways to make Dungeons accessible to individuals who just missed the Loot boat but are interested in the community while avoiding gas wars/VC/flippers…though that seems like an issue all projects are facing right now

Migrating my response to @bololo333

I am viewing dungeons a bit like ‘Land’ in that hopefully collecting them will generate value over time (e.g. perhaps you earn AGLD when people visit your dungeon?) If this is the case, even 0.1ETH should be trivial compared to the monthly earnings possible from a dungeon.
I’m trying to learn from what I’ve seen with Art Blocks pricing over time - scarce resources quickly become impossible to enter for anyone and even new mints result in insane gas wars because the upside to flip/sell is so high.
2 Likes

I’ve taken another swing at the drop structure based on feedback. Adjusted to focus less on ‘cost of mint’ and more on ‘generate long term value (for users and developer):’

Drop Structure

There are 10000 Dungeons. 7777 are gated to whoever owns OG Loot with no fee beyond gas. 2000 are available to the public at no fee beyond gas (to allow for new entrants). The remaining 233 are reserved for those who worked on the project. Loot holders have 72h to claim their dungeon before the drop opens to the public.

After this, two dungeons will release per week:

  1. One to a random wallet that holds OG Loot. The loot holder will only pay gas to mint this new dungeon. If the dungeon goes unclaimed for 72 hours, it becomeos available for mint by anyone for 0.1ETH.
  2. One open to anyone for 0.1ETH. This will allow new users to have a chance at owning a dungeon.

I hope that this will create a cadence of new, interesting content each week and create incentives for game designers and developers to continue supporting these new dungeons.

We will reserve 233 dungeons and take a 1% fee of aftermarket sales to cover development costs and fund future marketing/development expense.

4 Likes

This is my favorite version so far.

1 Like

Yea! This looks great! Any idea when you’ll likely drop this?

I think it’ll still be a bit (sadly)!

I want to hear more thoughts on the API (@matto-matto has already raised really good points I need to figure out if/how to incorporate). @IraFudmore is helping iron out names/locations.

After that, need to port everything to solidity and solve two technical challenges:

  1. Proper random number generation (being discussed in the builders thread)
  2. How to do timed releases (vs everything available at once)
2 Likes