Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411593 Posts in 69386 Topics- by 58444 Members - Latest Member: FightingFoxGame

May 07, 2024, 11:14:51 AM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsCommunityDevLogsPatterna (now available!)
Pages: [1]
Print
Author Topic: Patterna (now available!)  (Read 1142 times)
sschoener
Level 0
*


I survived 2079.


View Profile
« on: April 16, 2016, 04:21:01 AM »


Hi there,

allow me to give you an impression of my current project: Patterna, a logic puzzle game inspired by HexCells. If you are not familiar with the latter, think about what a version of Minesweeper would look like that was actually fun to play. (That's probably not a good way to advertise my game, but I never claimed to be any good at this.)

So what's the point of the game?
Each level is a network (or graph) of nodes, connected by edges. Each node is in one of three states: Unknown, marked as pattern, or marked as non-pattern (think minesweeper: either the node is cleared, marked as a mine, or still pending). The user has to figure out which nodes are pattern nodes and which nodes are non-pattern. Some of the nodes may contain information that helps you achieve that goal, and each level in the game is provably solvable without any guessing. That sounds so exciting, I know.

Why would anyone want to play this?
Well, you might happen to like logic puzzles (Sudoku, anyone?). Mostly thanks to the music created by my composer and friend Alex Cottrell, the game also has very relaxing vibe to it.
Here's a video from an earlier build that hopefully conveys that general feeling:




Anything noteworthy?
That depends really depends on what you are after, but here are some of my favorites: The game features procedurally generated levels and has a level editor. This hopefully means that your thirst for puzzles will be quenched Smiley.



The game is already finished in many areas, but I'm still polishing it and adding features. The itch.io page also features a demo, if you like to give it a try. In that respect, I'm a bit late to the party, I guess. I have been working on the game since some time around last Christmas, but only in my limited free time (so at best a few hours every evening, plus weekends). I'm doing this in parallel to finishing my degree in Computer Science, so I probably will not be able to update this thread with a progress report every day, but I'll try to write something at least every weekend (starting today).
« Last Edit: May 15, 2016, 02:34:39 AM by sschoener » Logged
sschoener
Level 0
*


I survived 2079.


View Profile
« Reply #1 on: April 17, 2016, 11:45:11 AM »

I figured that before I can tell you anything about what I changed about the game, I'll probably have to explain the game to you. Since the game is rather abstract, this is usually the first barrier. I have considered theming the game around something, but my approaches weren't really successful. Let's dive into it:

How to play the game:
Each level of the game consists of nodes. Each node is in one of three states: Good, bad, or unknown. Think Minesweeper: Good nodes are clear, bad nodes contain mines, unknown nodes are still to be classified.

Good nodes (pattern nodes)

Bad nodes (non-pattern nodes)

Unknown nodes


The goal is to classify all unknown nodes and reveal the pattern (hence the name).



Nodes Form a Graph
The nodes are not isolated from each other, but form a network (a graph):
Here are a four nodes. The grey lines are connections (edges) between the nodes. This allows us to talk about the distance of nodes between each other, and about reachability: A node is reachable from another node if there is a sequence of connections that leads from one node to another. The smallest such number of connections defines their distance.




Information on Nodes
There are three different kinds of information that help the player to classify nodes:

Radius Information

Radius information specifies how many of the unknown nodes reachable in a given number of steps from the node with the information are part of the pattern (that is, are pattern nodes). For example, this node shows two blue rings, which means it refers to all nodes reachable from that node in at most 2 steps, and has a white two on it, which means that of the unknown nodes it is referring to, 2 are in the pattern.

Color Information

Color information tells you how many unknown nodes of a given color are in the pattern. Note that unknown nodes have color orbs that light up if the node is in the region associated to that specific color. In this case, the green three tells you that of the unknown nodes with a green light, exactly 3 are part of the pattern.

Component Information

Component information tells you how many nodes this node still needs to connect to: A node is connected to this node if it is reachable from this node, with the restriction that your path may only use pattern nodes. Connected nodes form components. Here are two examples:

The two nodes with an 8 are connected to each other. The node to the right is not currently known to be connected to them, but might be: There are nine more nodes in its component, so its component has 10 nodes in total. This is also true for the nodes on the left: There are eight more nodes in their component, so their component also has size 10. Thus the two groups could in theory belong to the same component.

The situation is different here: The top node has a nine on it, the bottom node a one. This means that their components have size 10 and 2, respectively, so they cannot connect. The implication here is then that the node right between them must not be a pattern node. More subtly, there is more that can be derived from the above situation: Focusing on the node with the number one on it, we know that there has to be exactly one other pattern node connected to it. Since it is not the node above it, it has to be one of the two other nodes (to the left or to the bottom of it). Both of these nodes share a common neighbor (the bottom left node), so no matter which of them actually is the pattern node, the bottom left node must not be a pattern node, since otherwise the component size of the node with the 1 on it would be at least 3.

At any time, the game will also give you the total number of pattern nodes that are still unknown, so this is another source of information.



Directed Edges
I have skipped over two more advanced features: Directed edges and a variant of radius information (I'll leave the latter out for now). Directed edges can only be used in one direction and have little arrows on them:

Here you can see how directed edges can interact with component information: The top left node has a component of size 6 (five plus itself), and the lower right has a component of size 5 (four plus itself), but the top right node can still be a pattern node: The edge between the top row nodes can only be used left-to-right, so it the lower right node cannot reach the upper left node.


That's it for today, and I think I have covered most of the important concepts. Next time, I might talk about the random level generator, or maybe something else entirely. The game is still on Steam Greenlight, your vote is very much appreciated. Thanks for reading!
« Last Edit: April 18, 2016, 01:17:23 PM by sschoener » Logged
sschoener
Level 0
*


I survived 2079.


View Profile
« Reply #2 on: April 19, 2016, 08:46:22 AM »

Random Level Generation
In this entry, I will give some insight into how the game's random level generator works. The game is still on Steam Greenlight, your vote is very much appreciated.

(Remember that a graph is simply a collection of nodes with connections between them.)

When selecting the random level option in the main menu, the user is taken to this screen:


The left side contains a list of modes (these correspond to specific presets) and the right side has a list of additional options that the user can set. This allows players to disable features they don't enjoy, or to ask the generator for a very specific level; maybe a level a friend played that they now want to play, too. This last usage is facilitated by so-called level descriptors that combine the seed with a few other parameters to make it possible to re-generate a specific level.

The level generation process can be roughly divided into three stages:
  • generating the underlying graph (that is, the network of nodes)
  • distributing information on that graph
  • improving the level until it is solvable
Let's go over the steps in order.

Generating Graphs
Each of the modes corresponds to a specific way to generate the underlying graph. Currently, none of the modes generates a truly random graph (but there is nothing stopping you from creating your own mode that does just that -- modes can be loaded from external .NET/Mono assemblies). Instead, each node corresponds to a specific form of graph that can be generated given a requested approximate size for the level. Sometimes, a bit of randomness is then added to the graph by omitting edges. Here are some examples:

This is the Full Grids mode. Unsurprisingly, it generates square grids without omitting any edges (thus full). Square grids like this have an interesting property, namely that there are no triangles in the graph: No two neighboring nodes share a neighbor. This has certain implications for the levels. Last time, I said that I skipped a variant of radius information. This variant concerns connectivity of the nodes, that is, it tells you whether the pattern nodes within that radius are all connected or not. Since the neighbors of a single node are never all connected in this kind of graph, this information is meaningless for radius 1 information in this setting.

This is the Circles mode. As you can see, the generated level contains some directed edges, placed in a very regular fashion. The Circles mode may decide that a specific set of edges can only be used in one direction. For aexample, it could decide that all edge that go counter-clockwise cannot be used in this level.

This is the Random Hex Grids mode. In this mode, the nodes are arranged in a hexagonal fashion. Modes with the prefix random decide independently for each edge of the hex grid whether it should be included or not. This leads to graphs like this one, where some times both directions of an edge exist, sometimes only one direction is present, yet othertimes an edge that should be there for a true hex grid is missing.

This is the Triangle-Free mode. The graphs generated by this node share the amazing property that they contain neither triangles nor rectangles -- no two neighboring nodes share a neighbor, and any two nodes (neighboring or not) share at most two neighbors. Furthermore, each node has exactly 3 edges going out, making this a very symmetric family of graphs. This makes levels generated in this mode often more difficult than average.

Generating Information
At this point, the layout of the level has been decided upon. The current implementation of the random level generator will now first assign colors to the nodes. Depending on the specific settings used, this can be done either complete randomly or based on the position of a node. For example, in the triangle-free mode screenshot above, only nodes in the lower half of the graph are violet.

When that is done, the level generator randomly distributes information on the graph, again according to the settings specified by the player (she could for example deactivate the user of color information). Additionally, a set of nodes that will be revealed initially is selected. The random level generator tries to make sure that no redundant information is added to the level, but does not completely forbid that either (since that would be quite hard to check in general). It will also try not to place information on the board that is completely trivial (for example, the lower left node in the triangle-free screenshot has two neighbors and states that both of them are pattern nodes. No thinking involved). Again, this is hard to avaoid in general.

You may have noticed that the options also contain a slider labeled Difficulty. The difficulty settings impacts this stage (and the next) of the generation in three ways:
  • On a lower difficulty level, trivial information is less likely to be dropped
  • A higher difficulty levels skews the distribution of the distance used for radius information towards higher numbers. The idea here is that a larger radius means that it is less likely that you can make any inferences from a single node alone. You will more likely have to combine multiple nodes to deduce anything.
  • On a high difficulty level, information may be degraded by inserting inequalities instead of specifying the exact number of pattern nodes. For example, a node could state that there are at most 2 pattern nodes among its neighbors.

Improve Until Solvable
This is the hardest part. To start off, we have to check whether the puzzle at hand is even solvable at all. It turns out that this is a quite difficult problem to deal with (in fact, it is NP-hard:

). Loosely speaking, this means that the best known algorithms to solve these problems have exponential running time in the worst case -- adding a single node to the graph means (roughly) twice the running time. This is bad news. Basically, there are two ways to tackle this problem:
  • Make sure that the puzzles are easy enough for the solver: If the runtime is only bad for the worst case, we could try to avoid the worst case by adding more restrictions to the way the level is constructed.
  • Bite the bullet and try to make the best of the situation.
I went with the second option, and one of the main reasons is that I want the game to surprise me. If I only allowed certain rules in the random level generation, there would hardly be anything new for me to learn. Having to solve a difficult problem to solve in this case just implies that we should focus on small levels (<= 50 nodes). The largest randomly generated levels have 49 nodes, and this is actually plenty of room to hide some clever puzzles in. I felt that HexCells' random level generator went a bit overboard with the size anyway, so this is not that bad a restriction.

To solve a level, the game uses a so called constraint solver. This is just what it sounds like: An algorithm that produces a valid assignment to variables given a set of constraints for these variables. In our case, each node is a variable that could be either 0 or 1. The constraints are given by the information that is currently known. For example, the information that a given node has exactly two neighbors translates into the constraint that the sum over the variables associated to its neighbors is equal to 2. To find a solution to such a set of constraints, the solver will (essentially) try out all possible assignments for the variables. In doing so, it tries to be clever and use the contraints to deduce that certain partial assignments cannot lead to a valid solution (so it can exit early). For example, if there are 10 nodes there are 1024 different assignments that we have to try. If we can figure out that setting the first variable to 0 can never produce a valid solution, this cuts the number of different assignments down to 512 immediately!

Unfortunately, since one of the game's main mechanics is revealing new information, this is not enough yet. In the beginning, the nodes are almost all unknown, and for a lot of nodes there will be no information on them at all -- this translates into no constraints, and no constraints a very easy so satisfy (they're always satisfied). So what we actually want to find is not a solution to the level that is consistent with the current constraints, but solvable with all currently known constraints. Since the whole point of the level generator is to generate the information, we have to take an iterative approach. In essence, the following procedure is applied:

  • find all nodes that are completely solvable from the current constraints
  • add these nodes to the known nodes
  • check whether there are any unknown nodes left, if not, exit
  • add more information to the level, repeat

There are of course many subtleties to this (e.g., where will we add new information when required?), but we will ignore them for now and focus on the first point. How do we find nodes that are completely solvable? With a bit of thought, we can use the constraint solver: For each unknown node, try to find a solution (that is consistent with our current information) where this node is a pattern node, and a solution where this node is a non-pattern node. If we can find both, then we cannot deduce anything about the state of the node from the information we currently have. If there is no solution that has this node as a non-pattern node, it must be a pattern node, and vice-versa. Clearly, we do not have to go over all possible solutions: We can simply add the constraint that the node under consideration must be a pattern/non-pattern node before starting the search.

(And once again, model theory saves the day!)

I hope this post about the random level generation process was interesting, at least in parts. Let me know if you have any questions and/or feedback.

PS: Here's a question that I have not yet been able to find a satisfying answer for: I mentioned that the constraints are used to deduce that certain partial assignments cannot lead to a solution. This is called constraint propagation. Is there an efficient way to use propagation for component information? (A good answer could speed up the level solver massively for some levels.)
« Last Edit: April 19, 2016, 09:50:52 AM by sschoener » Logged
Jasmine
Level 5
*****

Boop


View Profile WWW
« Reply #3 on: April 19, 2016, 09:37:49 AM »

This game is all the things!

You have a tester, right here!
Logged

sschoener
Level 0
*


I survived 2079.


View Profile
« Reply #4 on: April 19, 2016, 10:01:02 AM »

Thank you for your interest! You can add me on Steam, if you'd like to play the full version of the game (I organize most of the playtesting via a Steam group). My steam profile is linked on the Greenlight page.

I somehow managed to forget to point out that there is a demo available on the itch.io page, so that's also a way to give it a shot.

If you have any thoughts, feedback, etc., let me know Smiley. Thanks again!
Logged
sschoener
Level 0
*


I survived 2079.


View Profile
« Reply #5 on: April 26, 2016, 01:19:28 AM »

Porting to Android
I have spent the last few days tinkering with an Android port of the game. It seems like a natural fit for a tablet: No 3D, everything's on a single plane, immediate feedback, etc.

To start off, I'm sure most of you have already heard that there is no one-click solution to port a game to a mobile platform. Well, it seems like I got incredibly lucky -- because simply changing Unity's build target to Android already produced a playable version with only minor quirks. Actually, it took me more time to install the Android SDK and the most recent JDK than it took me to get my first build on my tablet.
One reason why this worked so well is because Unity is using touch input to simulate a mouse pointer. This meant that everything that used only one mouse button worked right away. This of course also meant that there was no right-clicking, which unfortunately breaks large parts of the game.

Here are some thoughts about the porting process:
  • When I first launched the game, it ran incredibly slow. This is because I use a quite sophisticated shader to procedurally generate a star field in the background of the game. Replacing this with the version I cooked up for slower systems some time ago immediately resolved the issue. One of the key points that you hear over and over again is that mobile GPUs are often fill-rate bound -- that is, drawing a pixel more than once quickly makes the framerate drop. When are pixels drawn more than once? Whenever you use transparency. Unity actually has a very nice view mode in the editor that highlights where overdraw occurs. Using that, I was quickly able fix some of the over-drawing issues and get the framerate to a stable 30 (which is what I limit it to, it could render faster).
  • During runtime, the game reads a lot of its game data from disk. This data is encoded as Json and is then deserialized using FullSerializer. Since that uses reflection in some places, I was not sure whether it would simply work on Android. Fortunately, that was no problem at all. Since I did not want to spend hours reading about the Android's file system and organization, I decided to bake all the necessary files into the game's APK by using Unity's Resource system. Performance-wise, parsing and deserializing the Json data is not as bad as I initially feared -- mainly because I already spent some time optimizing that part of the code a few weeks ago.
  • Since all of a sudden the game must run on all kinds of weird resolutions (my tablet uses 2560x1920 or something like that), the UI has to scale properly. I have a difficult relation to Unity's UI system, but it scored some points when it just worked by setting the canvas to scale with the resolution.
  • A problem that I did not anticipate was that all text rendered in bold font suddenly looked very weird. This actually is a known problem whenever your font uses a dynamic character set. I am using Unity's inbuilt text component and sticked to Arial from the beginning. Unity supports importing multiple versions of a font at compile-time, this fixed all issues: Basically, I just took the Arial font file from the Windows fonts directory and dropped it into Unity. That comes with TTF files for bold, italic, and their combinations. Updating all text components to use that new font asset was a bit of work, but fixed all issues.
  • Probably the biggest issue was input. With Unity's new UI, you are driven to use their event system: Each element of the screen knows how to react to certain events (such as being clicked, or have the mouse pointer hover over it). This of course works well when there is only one pointer (the mouse) active at a time, but I did not find any resources on how this system works with, say, two fingers touching the screen at once (as when zooming). Alternatively, Unity provides direct access to the user's touches on the screen. In the end, I used a combination of both systems (events for UI elements, manual control for game elements).
  • Another problem with input (aside from the technical problem) is to decide how the game is controlled on touch screens. On the desktop, the game requires two mouse buttons: Left-click to mark something as pattern, right-click for non-pattern. To overcome this problem, I decided to have two different input modes: One for marking pattern nodes, and one for marking non-pattern nodes. Switching between the two input modes is done by tapping on any empty space on the screen. This system works surprisingly well. The user can also interact with nodes carrying information (either deactivating the information or highlighting all nodes affected by the information), and on non-touchscreen platforms that was also handled by distinguishing between left-clicking and right-clicking. In the mobile version, tapping such a node cycles between the three states (normal, highlight, hidden), skipping the highlight state if there is nothing to highlight (i.e., the node's information is not needed anymore).
  • Tooltips. The tooltips are a very useful feature of the game and some people prefer to dive right in without playing the tutorial, learning only through tooltips. Looking at HexCells, I have often seen the request that tooltips should be added. Since the desktop version of Patterna already has tooltips, I merely needed to adapt them to touch input. But how would that work? What does it mean to hover over something on a touchscreen? The aforementioned mouse emulation could have worked, but it did not feel natural to me. Right now, a tooltip is shown to you whenever you hold your finger down on some element with a tooltip.

After all of this, I now have a playable Android version. To my own surprise, this took me only 3-4 days. There are of course plenty of things left to do (adjust tooltips, tutorial, and instructions to reflect the new input method, optimize level generation etc.), but it already feels great on my tablet.

The game is still on Steam Greenlight, your vote is very much appreciated.
Logged
sschoener
Level 0
*


I survived 2079.


View Profile
« Reply #6 on: May 15, 2016, 02:34:21 AM »

Just a quick update to inform you that the full game is now available on its itch.io page.
Logged
Pages: [1]
Print
Jump to:  

Theme orange-lt created by panic