• No results found

Minecraft to C for the Raspberry Pi

N/A
N/A
Protected

Academic year: 2021

Share "Minecraft to C for the Raspberry Pi"

Copied!
42
0
0

Bezig met laden.... (Bekijk nu de volledige tekst)

Hele tekst

(1)

Bachelor Informatica

Minecraft to C

for the Raspberry Pi

Tom Peerdeman

Student nr. 10266186

July 1, 2014

Supervisor(s): Raphael ’kena’ Poss (UvA)

Inf

orma

tica

Universiteit

v

an

Ams

terd

am

(2)
(3)

Abstract

In modern day education, studying logic and digital circuits is a essential part of beta studies. Minecraft appears to be an ideal tool for learning these subjects. Minecraft has redstone, a feature that enables the creation of circuits very similar to traditional digital logic. The downside is that executing the redstone circuits is very slow. In this thesis we will speed up the simulation of redstone by pulling it out of the Minecraft environment. We will then transform these circuits to logic equivalents to be able to simulate them. As an extension we will add a real life input/output device that can also interact with our logic circuits.

(4)

Contents

1 Introduction 5

1.1 Motivation . . . 6

1.2 The project . . . 7

2 Overview of the redstone components 8 2.1 Input components . . . 8

2.2 Output components . . . 9

2.3 Components from the Raspberry Pi . . . 9

2.4 Remaining components . . . 10

2.5 Powering other blocks . . . 12

2.6 Ignored components and functions . . . 12

3 Detecting redstone circuits 14 3.1 Defining the detection area . . . 15

3.2 First attempt: path finding . . . 15

3.3 Iterative neighbour algorithm . . . 16

3.4 Prioritized neighbour algorithm . . . 19

3.5 Complexity of the prioritized neighbour algorithm . . . 20

4 Transforming redstone components to logic components 23 4.1 Transforming redstone wires . . . 23

4.2 Transforming powered blocks . . . 25

4.3 Cleaning unused components . . . 25

4.4 Complexity of the transformation . . . 26

5 Implementing the simulator 27 5.1 Intermediate representation . . . 28

5.2 Implementation . . . 29

5.2.1 Programming language . . . 29

5.2.2 Interacting with the simulator . . . 29

5.2.3 Implementation of the simulator itself . . . 31

5.3 Testing the simulator . . . 32

6 Integrating the Raspberry Pi 34 7 Open problems 37 7.1 Visualizing the graphs . . . 37

7.2 Detecting the circuits . . . 38

7.3 Transforming the circuits . . . 39

(5)

CHAPTER 1

Introduction

Minecraft is a popular open world 3-D game, with over 15 million users, and is increasing with over 15 thousand new copies bought every day [4]. In Minecraft the player is placed into a world composed uniquely of textured cubes. These cubes are called blocks in the Minecraft world. An example of terrain generation using these blocks is given in figure 1.1a. The player can interact with this world by breaking and placing these blocks, and thus removing them from or inserting them into the world. A lot of different types of blocks exist. All these types have different textures and different properties, like the time the player has to click on the block to break it. When the player breaks a block, depending on the block type, often an item version of the block can be picked up by the player. Using these items the player can craft new items. Some of these items can be used as armour, tools or light sources, others can be placed again as blocks. A couple of game modes exist in Minecraft: First we have the creative game mode, in this game type a player can spawn in blocks, and instantly break them. The next game mode is survival. In this game mode the player has to survive hordes of enemies that spawn during the Minecraft night. The player can hide from them by building a safe area [17, 18]. For example see figure 1.1b.

(a) Terrain generation using blocks. (b) The player can protect itself by building shelter.

Figure 1.1: Ingame screenshots of Minecraft.

Some research has been done to utilize Minecraft in education. For example in an article of Daniel Short, from the university of Liverpool, he states that the Minecraft world resembles the real world in a couple of aspects. These aspects could help the teacher, by using Minecraft as a

(6)

addition to the usual teaching methods [14].

Another article from Jeffrey Brand and Shelley Kinash from the Bond university of Australia state that Minecraft can be used in education in two ways. The first way is to provide an alter-native informal learning environment. The second way is as a simulation environment, similar to the article of Daniel Short described [6].

Although mentioned in passing in the work of Brand and Kinash, a Minecraft topic is yet underserved by previous work: redstone. Redstone is the name of a Minecraft material that can conduct power, and other blocks can be used to generate and use redstone power to form circuits. For example a light block exists. When it is powered by redstone current, it lights up. Some components can modify the redstone current. For example if a redstone torch, shown in figure 1.2, is powered by current, it outputs no power. If the torch is not powered, it does emit current.

Figure 1.2: Redstone torch placed on a white wool block.

1.1

Motivation

The behaviour of the torch closely resembles a boolean NOT gate. Almost every redstone com-ponent in Minecraft has a logic counterpart. All the comcom-ponents are listed in chapter 2. In present day education, the studying of logic and digital circuits is an important component of beta studies. Since interacting with the redstone is very easy, Minecraft could be perfectly used as a learning tool for logic.

The downside of Minecraft is that it is not only a logic simulator, but a whole world simu-lator. This means that the developers had to make a few decisions regarding execution speed of the redstone. The minecraft engine runs on ticks. Every tick the game applies updates to the blocks that require updates. For example this can be growing crops. This engine does about 20 ticks per second, or 50 ms/tick. Every second tick a redstone update is done. The redstone thus runs at 10 ticks per second, or 100ms/tick. This is good enough for small circuits, but large circuits are slowed down a lot. For example 10 redstone torches in a row, it would take about 1 second for the signal to propagate trough the chain.

A couple solutions for this problem exists. The first solution seems simple: turn the tick rate up. By increasing the tick rate, the time per tick would decrease, and thus allow for faster execution. A couple problems rise using this solution. First of all Minecraft is not open source. Changing the tick rate would require a decompilation of the distributed Minecraft’s jar files (as Minecraft is written in Java). Second increasing the tick rate would increase the resource usage a lot. Minecraft as is uses the CPU quite a lot. We could therefore max out CPU usage very fast, and not even get get a significant increase in the tick rate.

(7)

1.2

The project

The solution of the speed problem we will be using is similar to increasing the tick rate. If we would pull the redstone simulation out of minecraft, we could create a much more efficient simulator. This simulator does not have to simulate the whole Minecraft game-play, and can thus run on a much higher tick-rate.

Why stop there? As said before, redstone resembles a lot to logic circuits. Simulating logic circuits is a lot easier. Also these logic circuits can be used for things outside the scope of this project. For example, these circuits could be transformed to VHDL [20].

Our main goal is education, so how can make the simulation more interesting? If we would be able to extend our simulation by using real life components, like switches and lights, it would be much more interesting. So now two questions remains: How can we efficiently make use of Minecraft’s redstone circuitry to define logic circuits? And how can we extend this simulation to utilize real life components?

To answer the first question we divide it into two sub questions: How can we detect the redstone circuits in the Minecraft world, and how can we transform these circuits to logic variants? The final part of this project will be implementing a simulator for these circuits. The components of the project are given in figure 1.3. We can see that some components are clustered together. This clustering means that those components run in the same environment.

The partial answer to the last question is not that hard. To interact with real life components we will use a Raspberry Pi. In particularly the Raspberry Pi model B is used. The Raspberry Pi has itself some extension pins. With some programming these pins could be used to interact with some real life components. The problem is that doing things wrong with these pins is very easy. Doing things wrong is also destructive, since most of these pins are hard wired to the CPU of the Raspberry Pi. To solve this we make use of an extension board called PiFace (see figure 6.1). This board has clearly marked input and output ports. Also it has push buttons and output leds built into it. The integration of the Raspberry Pi into the simulator is explained in chapter 6.

(8)

CHAPTER 2

Overview of the redstone components

To be able to define logic circuits with redstone, we first have to understand all the redstone components Minecraft has to offer. We can put these components in three categories. We have the input components. These components can provide power, and ignore any power going in. The second category are the output components. These components take power as an input and do something with it. For example these components can provide light, sound or mechanical movement (in the Minecraft world of course). The last components are the remaining compo-nents. These components take power as input, transform it by a function and then output the new power.

As explained in chapter 1 the Minecraft redstone engine runs using ticks. The components from the first two categories however have no tick delay in their behaviour.

Some components from the last category however do have a delay. Most of these components have a delay of one tick. The maximum delay of an component is 4 ticks.

Every redstone component however has a common feature. Every component can use or produce power. This power is represented using a power level. This level is stored in 4 bits, which means the power level can be 0 to a maximum of 15. In the Minecraft world a power level of 0 means no power at all. A power level of 15 corresponds to power of maximum strength. The power level 0 is always present in the world. This means that a component from the last two categories that has no input connected, has an input of power level 0.

2.1

Input components

One of the simplest components has to be the redstone block. This block, despite is simplicity, was added in one of the latest updates dated March 7, 2013. The behaviour of this block is equal to the logical true. This block always outputs a signal value of 15.

The next component is the lever, or switch. This component has two states. In the first, default, state the component does not output any power. This is equal to outputting a power level 0. In the second state, the component outputs the maximum power level 15. The user can switch between these states by clicking on the component in the Minecraft world.

Very similar to the switch is the button. This component also has two states. When the user clicks on the button, the state is flipped to the on state. The user cannot flip the state back. The button flips itself back after 10 to 15 seconds, depending on the type of the button. The next component, the pressure plate, has a few variations. The normal pressure plate’s

(9)

behaviour is quite similar to that of the switch and the button. If an entity, either a player, an item or a monster, is on the pressure plate, the state is on. Otherwise the state is off.

The other two variants do not have the two states. The iron and gold pressure plate output a power level which is linear to the amount of entities on it, capped at the maximum power level. The gold pressure plate increases the power level by one for each entity on it. The iron pressure plate increases the power level by one for each 10 entities on it.

Another component that can output a varying power level is the daylight sensor. The day-light sensor outputs a power level which corresponds to the height of the sun in the Minecraft world. If the sun is at its highest point (midday) the power level outputted is 15. At midnight the power level outputted is 0.

2.2

Output components

The first output component is the redstone lamp. This component will light up, if an input power with a power level greater than 0 is provided. The light this lamp provides cannot influence the daylight sensor.

The next component produces sound. This block is called the note block. The note the block plays can be adjusted by the player. The instrument used for playing the note is determined by the block the note block is placed on. When the power level changes from 0 to any value above 0 the note is played.

The last output component is the piston. In Minecraft the piston can move up to 12 blocks for exactly one block in the direction the piston is facing. When the input power of the piston is greater than 0, the piston is extended. When the powers is equal to 0 the piston is retracted. Since this block can move other blocks, it is possible that the layout, or the power levels of the circuit change, depending on the state of the piston. For example a piston can move a redstone block next to a wire. This changes the state of the circuit, since the wire is now powered. Because this can change the circuits in a lot of ways, this feature is ignored in the detection. The piston is seen purely as output component, that cannot change the state of the circuit.

2.3

Components from the Raspberry Pi

While listing the components of Minecraft’s redstone, we might forget the second component of this project. Since we want to interact with the Raspberry Pi, we have to list two more compo-nents. These components fit perfect in the two above categories.

The first component is the digital input from the Raspberry Pi. This input acts like any other input in Minecraft. When the corresponding gate is powered on the Raspberry Pi, the compo-nent in Minecraft should output the maximum power level. If it is not powered on the board, the outputted power level is 0.

The output component from the Raspberry Pi acts the same as any of the outputs from Minecraft. When in Minecraft the component is powered with a power level greater than 0, the output on the Raspberry Pi is also powered. If the component is not powered, neither is the output on the board. Note that the output on the board is digital, so it is either fully powered or not at all. A variable power output on the board could be scaled to the power level provided in Minecraft. However a different expansion board would be required for this feature. This could also apply to the input of course.

(10)

2.4

Remaining components

The remaining components all take at least one input. The output values are functions of the input values. The best way to describe these functions is using pseudo code. In the next descrip-tions the input values are given using a vector called input.

The first component is the redstone torch. The redstone torch has exactly one input. The input is the block the torch is attached to. The behaviour of this component is very similar to a logic NOT gate. The function describing the exact behaviour is given in listing 2.1. Note that without any input, or an input of power level 0, the output is always the maximum power level. In these cases the redstone torch acts like it is an input component. This component has a tick delay of one tick.

Listing 2.1: Redstone behaviour for the redstone torch. 1 function redstoneTorch(inputs) {

2 if(inputs.size >= 1 && inputs[0] > 0) { 3 return 0;

4 }

5 return 15; 6 }

As said before some components have delays of more than one tick. The redstone repeater is one of them. The redstone repeater has a variable delay of 1 to a maximum of 4 ticks. The name of this component somewhat reveals its behavior. When a signal of level 1 or greater is applied, the components repeats this signal by outputting the maximum signal level, see listing 2.2. This component only does this in one direction, thus making it act like a diode.

The input and output of the repeater are determined by the placing of the block. The repeater always is placed horizontally. This allows for four different placings. With every placing the back is always designated as the input and the front as output.

Listing 2.2: Redstone behaviour for the redstone repeater. 1 function redstoneRepeater(inputs) {

2 if(inputs.size >= 1 && inputs[0] > 0) { 3 return 15;

4 }

5 return 0; 6 }

These two components have common that they output a full signal or no signal at all, the next component can output a varying signal level. The component mentioned is called a redstone comparator. This block is placed in the same way as the repeater. The only difference is that an additional input is defined by power going into the sides. We will call this input input B. We will call the original input from the back input A. Note that the block has two sides, but only one extra input. When both sides are connected, the input value is assumed to be the maximum signal level of both sides.

The separating of the two inputs becomes important when looking at the behaviour. The redstone comparator has two modes. The first mode, called compare, is listed in listing 2.3. The second mode, called subtract, is listed in listing 2.4. In these listings the inputs are not given by the inputs vector, since we want to distinguish input A and B. If one of the inputs were to be not connected, the corresponding input is equal to 0.

The compare mode basically compares the signal level from input A with the level from input B. If A exceeds B then A is outputted. Otherwise a power level of 0 is outputted. The subtraction mode does as it says, it outputs a signal level A-B. This component has a one tick delay, no matter what the mode is.

(11)

Listing 2.3: Redstone behaviour for the redstone comparator in compare mode. 1 function redstoneComparator compare(inputA, inputB) {

2 if(inputA > inputB) { 3 return inputA; 4 }

5 return 0; 6 }

Listing 2.4: Redstone behaviour for the redstone comparator in subtract mode. 1 function redstoneComparator subtract(inputA, inputB) {

2 if(inputA > inputB) { 3 return inputA − inputB; 4 }

5 return 0; 6 }

The final component Is probably one of the most important ones. This component is called the redstone wire. This component is used to connect all the other components to each other. The redstone wire, also called redstone dust, is placed horizontally like the repeater and comparator. However the redstone wire has no facing initially. When two wires are placed next to each other, or the wire connects to a component, its facing changes. The wire will bend towards the component it connected to. This creates either a line or a intersection. An example of this behaviour can be seen in figure 3.1.

A special feature of the redstone wire is its connecting behaviour in the vertical diagonal direction. When a redstone wire is on top of a block it can connect to a redstone wire below it and exact 1 in the x or z direction away from it. However this can only happen if no block is blocking the vertical wire. This behaviour is shown in figure 2.1. In this figure we can see the wire is placed on top of the green and white blocks, and are being powered by a redstone block. In sub figure 2.1a we can see that the wires on top of the white blocks connect to the wires on the green blocks. These wires are therefore also receiving power. In sub figure 2.1b the connection is blocked by the yellow block, now the connection is broken the wires on the white blocks are no longer powered. This is visually indicated by the darker red colour of the wire. An exception to this rule is if the block blocking the vertical wire is a non opaque block, like glass or slabs, when this happens the wire can connect.

(a) A wire can connect in the vertical diagonal direction.

(b) A wire cannot connect in the vertical diagonal direction, if it’s blocked by an opaque block. Figure 2.1

The behaviour of the power levels is a bit strange. The behaviour is listed in listing 2.5. This component itself does nothing with the power level. This means that the output power is equal to the input power. It is however possible that multiple components connect as input for the wire. The output signal level is therefore the maximum of the input signal levels. The strange part kicks in when a redstone wire is connected to another redstone wire. When this happens the signal level is lowered by one, to a minimum of 0 on the edge of the wire component to the

(12)

other wire component. For example a redstone wire is connected to another redstone wire and a lamp. When this redstone wire is powered by a signal level of 15, it transmits this signal level to the lamp. However on the edge of the wires the signal level is lowered by one. This means that the other wire gets an input signal of level 14. This means that the output value can be different for each output connected to a wire. So unlike the other components, who have one output value, a wire has to calculate the output value for each output it has.

The redstone wire has no delay tick. This means that a line of redstone can be all activated in one tick.

Listing 2.5: Redstone behaviour for the redstone wire. 1 function redstoneWire(inputs, outputType) {

2 if(inputs.size > 0) { 3 output = max(inputs) 4 } else { 5 output = 0; 6 } 7 if(outputType == Wire) { 8 return output 1; 9 } else { 10 return output; 11 } 12 }

2.5

Powering other blocks

Beside all the listed components there is one more important component. Any opaque block can be powered by redstone. When powered this block also outputs this same signal level. However block are powered using a special mechanism. We introduce the high and low power. These two power types are completely independent of the power level. The high power can have any power level, and so can the low power. A block can be powered by a high and low power. The block will also output this type of power. Depending on the type of power outputted, some components that connect to the block are powered, and some are not. Any component that can be powered by low power can also be powered by high power. A powered block cannot power another block. All the components and which power they can provide and use are listed in table 2.1.

Table 2.1: List of components and what type of power they can produce and use when connected to a opaque block.

Component Can provide Can use Any input High None Any output None Low Redstone torch Low Low Redstone repeater High Low

Redstone comparator High Low (only input A), None from block (only input B) Redstone wire Low High, Low (when directly wired into the block)

2.6

Ignored components and functions

The list of output and input components is however not finished. Minecraft defines a lot more components. However these components provide or use events generated by the minecraft world itself. Since we cannot simulate these events, we do not have the Minecraft world simulated after all, we cannot use them in this project. These components are:

(13)

• Door: Opens on a redstone signal.

• Dispenser: A block that can drop items into the world, this can also use a subset of items. For example a bucket will be emptied into the world.

• Dropper: Similar to the dispenser, however the items are not used. • TNT: Ignites on a redstone signal.

• Hopper: This block can pick up items dropped on top of it. It will also transport the items to other hoppers or a chest. When powered the items will not be moved to another hopper or chest, and will thus stay inside the hopper.

• Command block: This block can execute a command when a redstone signal is applied. • Powered rail: Minecraft has a rail system. This block accelerates any carts on top of it

when powered. It decelerates them when not powered.

• Detector rail: This rail emits a redstone signal when a cart passes over it.

• Rail intersection: A rail that has more than 2 other rails connected to it can act as a switch. When a redstone signal is applied, the switch changes it state.

Note that the last three, the rail components can actually create some interesting circuits. Imple-menting this in the simulator however would require to implement the Minecraft rail simulation. This would require a lot of time, and of course break the fact that we want to define logic circuits. Some components also have some other features that will not be used in this project. An example is the piston’s ability to change the circuit, as described above.

Another example is the redstone comparator. This component can actually interact with the minecraft world. For example if on the side input A on the comparator a chest is placed, the comparator will output a signal level linear to the percentage the chest is filled. The comparator can interact with many blocks this way. Implementing this would, again, require data from the Minecraft world itself. Since we do not have this data, like the percentage a chest is filled, at simulation time, we cannot use these features.

The final feature could actually be implemented in the simulation. When a redstone repeater is powered at one of it non input or output sides by another redstone repeater, it enters a locking state. When this locking state is active the repeater will save the output level it is currently outputting. While the locking state is active this output will stay locked in this saved state. Even when the input signal is changed, the output stays the saved state.

This feature is however quite unknown and underused by the Minecraft users. Implementing this feature would require a lot of extra code, like separation of the inputs like the comparator. Therefore was chosen not to implement this feature.

(14)

CHAPTER 3

Detecting redstone circuits

One of the core components of this project is the actual detection of the circuits in the Minecraft world. We want to achieve this by implementing an not too complicated algorithm. This algo-rithm should also run in reasonable time.

So how should we implement this? The first question would be; in what language should we write the algorithm? Since we want to run the algorithm as fast as possible the answer could be C or C++, however we forgot an important part of the detection. The detection requires access to the Minecraft world data. A program created in C or C++ could read the save files in order to load the Minecraft world. This however poses a problem: The data of Minecraft is saved automatic at an fairly long interval of about 2-3 minutes. This would lead to the player having to wait about 3 minutes after each change. This opposes the criteria of the algorithm running in reasonable time. The solution is integrating the detection with the game itself. Minecraft has a large community, that created modifications for the game. These modifications can enhance Minecraft by adding new items, new blocks, increasing the performance, and many more other features. This community also created libraries to let users implement mods themselves, without a lot of effort. We can implement such a mod to access the world data. Since Minecraft is written in Java, so are these modding libraries. It is possible to create a bridge between a C program and a Java program, however this is not an easy method. It is therefore the best solution to create the detection algorithm in Java.

The next question is what modding library to use? The Minecraft modding community is mostly split up into two groups. One the first side we have Bukkit [1]. Bukkit is an API, this API is implemented by server implementations. This means that the Bukkit mods (or plugins as they are called in the Bukkit universe) can only rely on the API and not rest of the server environment. The Bukkit API is server side only.

On the other hand we have the Forge modding library [3]. Forge is both server side and client side. Forge extends the original Minecraft client and server to provide its modding capabilities. Since both client and server are modded we can add new block and item types into the game. The Bukkit API cannot do this because the client does not know this new items or blocks. Since we do want to add a new item the latter one is chosen.

Now the input of the algorithm is taken care of, we can focus on the output. We want the output of the algorithm to be an accurate description of the circuit. The simplest way to determine the output is to look at the circuits itself. Circuits are basically components with connections between these components. We can therefore describe a circuit best as a graph. To be precise this graph would be a directed graph, since diodes can be part of the circuit. A diode only transmits power into one direction, this would result in a single directed edge.

(15)

debugging purposes. To visualize these graphs a tool set called Graphviz was used [2]. Graphviz includes tools to generate images of graphs from a text representation of that graph. The mod itself can generate these text files. The Java VM can then call the program from the Grahpviz tool set to generate the graph after the detection (and transformation) is done.

3.1

Defining the detection area

We can imagine that the more blocks are searched the longer it takes to detect all the circuits. The Minecraft world is built using chunks. Chunks are sections of 16x16x128 blocks, where 128 is the height in blocks, which is increased to 256 in the newest versions. In older versions of Minecraft the world can contain about 2,147,483,647 chunks in each direction from the center. This gives about 1.8 × 1019 chunks, which totals to 6.0 × 1023 blocks in the world [12]. While

the limit is decreased to about 30 million blocks in each direction for the newer versions, we can still see that this is an absurd amount [19]. It is therefore not feasible to detect the circuits for the complete world. Instead we can let the user define an area in which his circuit is defined. A common used method for defining an area in Minecraft, is specifying two blocks. These two blocks represent two outer points of an rectangular box. We can then find the absolute coordinates of the starting and ending block, which are not necessary the given two blocks, using the equations given in equation 3.1. In these equations the vectors ~x and ~y are the coordinates of the two given blocks.

~

start = [min(x1, y1) min(x2, y2) min(x3, y3)]

~

end = [max(x1, y1) max(x2, y2) max(x3, y3)]

(3.1) Since we use Forge as the modding library we can simple create a new item to select these two blocks. This item, called Detection area selector, can select blocks by left clicking on them. This item can also start the detection by left clicking on air blocks.

3.2

First attempt: path finding

The first thing a user might notice is that the detection area is not used efficiently. Most of the circuits defined in Minecraft are not built to fit in a small space. This is mostly due to users tending to built their circuits on ground level. This results in the defined detection area containing only a small portion of the actual components we want to detect. An algorithm that searches the complete detection area would therefore be very inefficient.

The solution to this problem is a path finding algorithm. This smart algorithm can use the rules of Minecraft redstone. Since each component in the circuit is connected to other compo-nents in the circuit trough 0 or more other compocompo-nents, we can find the complete circuit when one component of the circuit is found. We can do this by discovering the path to all the con-nected components.

We can detect the first component by iterating trough all the blocks in the detection area. Once one component is found, this iterating can be stopped. The remaining components can then be found by applying the connection rules of redstone to the found component. For example a found component is a redstone repeater. The rule for a redstone repeater is that it only con-nects to other components that lie in the direction the repeater is facing. If we search the direct neighbours of this repeater in the direction the repeater is facing, we can detect the components this repeater is connected to. If we repeat this with the newly found components, we can detect the whole circuit.

(16)

This algorithm is however not suitable for the purpose of this project. Lets say that we de-fine multiple circuits in the detection area. This smart algorithm would stop after finding one circuit. This is not the behaviour intended. If we would extend the path finding algorithm to detect multiple circuits, we would have to restart the iteration over the blocks in the detection area. This iteration would then skip the already detected components. This however would defy the purpose of creating the path finding algorithm, since we did not want to iterate over all the blocks in the first place. This gets even worse when we look at the properties of circuits. By definition a circuit is a directed graph with one or more components. By this definition a single redstone wire would suffice as a circuit. This means that every block in the detection area is a possible circuit. We can therefore conclude that a path finding algorithm will not work. We always have to check every block in the detection area. We can therefore replace the algorithm by a better one that also searches the complete area, but is more efficient.

3.3

Iterative neighbour algorithm

The next algorithm is designed for detecting multiple circuits. The algorithm is based on the idea of quick access to the already detected circuits. This quick access is implemented by a 3-D array of mapping entries. Each mapping, if not empty, entry contains a node an a reference to the graph containing this node. Each block in the detection area corresponds to one of those mappings. The mapping entry can therefore be retrieved using the relative coordinates of the block to the detection area. These relative coordinates can then be used to index an entry in the mapping array. The conversion from block coordinates to relative coordinates is stated in equations 3.2. In these equations the vector ~i represents the index in the 3-D array. The vectorstart represents the starting block in world coordinates, as defined in equations 3.1. The~ coordinates of the block in world coordinates are represented by the vector ~l.

~i = ~l − ~start

~l = ~i + ~start (3.2) The detection itself is implemented by iterating over all the block in the detection area. Each iteration the z coordinate is increased. After a full run of the z, the y coordinate is increased. Same goes for the x coordinate after a full run of the y. The iteration in pseudo code is listed in listing 3.1.

Listing 3.1: Iteration for the iterative neighbour algorithm. 1 for(x = startx; x <= endx + 1; x++) {

2 for(y = starty; y <= endy + 1; y++) {

3 for(z = startz; z <= endz + 1; z++) {

4 detect(x, y, z);

5 }

6 } 7 }

The detection function is implemented by looking at the direct neighbour blocks of the block that is being looked at. Let’s say the block looked at is at position [x, y, z]. The direct neighbours looked at are located at [x-1, y, z], [x, y-1, z] and [x, y, z-1]. While looking at a neighbour block a few situations can occur.

First of all the block we are looking at (the block at [x, y, z], not the neighbour) is not an redstone component. In this case we can skip this block.

Second the neighbour block does have an empty entry in the mapping. Since each neighbour block should be already visited by the iteration, blocks with an empty mapping are not com-ponents of the circuit. Since the neighbour is not an component we can not connect to this

(17)

neighbour.

Third the neighbour block does have a non empty entry in the mapping, and the block itself (the block at [x, y, z]) does have an empty mapping. In this case the neighbour block is a redstone component. Since the component is already visited before, it should have a corresponding node in a graph. Let’s say the current block should connect to the neighbour component following the rules of redstone. In this case we can insert a new node for the current block into the graph of the neighbour. Also we insert this node and reference to the graph into the mapping.

If however the component should not connect, then the current block probably is not part of the circuit of the neighbour. We should therefore create a new graph, containing only a new node for the current block. This graph and node should also be inserted into the mapping entry for this block.

Last if the neighbour block does not have an empty entry, and neither has the current block. In this case the current block is already connected to another neighbour, or created a single node graph. If the neighbour should connect with the current block, following the redstone rules, we should merge the two graphs. This is done by transferring all the nodes of the graph with the least amount of nodes into the other one. After the merge all the mapping entries are updated, so that each occurrence of one of the merged graphs is replaced by the new one. Note that it is possible that the two graphs are actual the same one, since both neighbours can be in the same circuit. In the case of merging a graph with itself, nothing is merged of course. After the merge the mapping entry is filled, using the newly merged graph, and a new node, that is also inserted into this graph.

It can occur that all the direct neighbours are not redstone components. If the current block is a redstone component however, we have to create a new graph. Again the mapping entry is filled using this graph, and the node for this block.

This algorithm is listed in pseudo code in listing 3.2. The first case, the block is not a red-stone component, can be found on line 15. The second case, the neighbor is not a redred-stone component, can be found on line 22. The final two cases can be found from line 27.

This algorithm however does have some problems detecting the redstone wires correctly. First of all, redstone wires can connect to other wires that are 1 block higher, the detection of this connection is however possible using an extended version of this algorithm. Lets imagine the following situation. A redstone wire is located at location [1, 2, 3]. Another wire is located at [0, 3, 3]. The wires should connect following the rules of redstone. If the iteration arrives at location [1, 2, 3] only the direct neighbours are checked, and thus the wire at [0, 3, 3] is not found as a connected component. We cannot extend the definition of direct neighbours with [x-1, y+1, z] since the components at y+1 are not checked yet, and thus contain no mappings. However it does not matter if we detect component B if we check A or component A if we check B, thus we can extend the definition with [x+1, y-1, z] for redstone wires. In the situation defined, the following then happens: The search for block [1, 2, 3] results in no components connected to the wire. Then the iteration continues and finally searches the direct neighbours for the block [0, 3, 3]. Since we extended this definition with the block [x+1, y-1, z], one of the neighbours checked is the redstone wire at [1, 2, 3]. If we extend the definition by the blocks [x-1, y-1, z], [x, y-1, z-1] and [x, y-1, z+1] we can solve this problem for all directions.

(18)

(a) Single wire next to a block does not connect.

(b) A line of wire into a block does connect.

(c) A curved wire does not con-nect to a block.

Figure 3.1: Rules for redstone wires connecting to a block. Listing 3.2: Detection function for the iterative neighbour algorithm. 1 function detect(x, y, z) { 2 merge(x, y, z, (x − 1), y, z); 3 merge(x, y, z, x, (y − 1), z); 4 merge(x, y, z, x, y, (z − 1)); 5 6 if(emptyEntry(getEntry(x, y, z)) { 7 g = new graph; 8 n = new node(x, y, z); 9 insert(g, n); 10 setEntry(x, y, z, g, n); 11 } 12 } 13

14 function merge(x, y, z, nx, ny, nz) { 15 if(!redstoneComponent(x, y, z)) { 16 return;

17 } 18

19 nEntry = getEntry(nx, ny, nz); 20 myEntry = getEntry(x, y, z); 21 22 if(emptyEntry(nEntry)) { 23 g = new graph; 24 n = new node(x, y, z); 25 insert(g, n); 26 setEntry(x, y, z, g, n);

27 } else if(canConnect(x, y, z, nx, ny, nz) { 28 g = getGraph(nEntry); 29 n = new node(x, y, z); 30 if(!emptyEntry(myEntry)) { 31 g2 = getGraph(myEntry); 32 g = mergeGraphs(g, g2); 33 } 34 insert(g, n); 35 setEntry(x, y, z, g, n); 36 } 37 }

The next problem with the wire detection is the connection to blocks and output components. The redstone rules for wires connecting to blocks and lamps are shown in figure 3.1. The wire only connects if it goes straight into the block. The wire only goes straight into the block if there is a component on the other side of the wire in direction of the block, and if it does not connect to anything in the other two directions.

(19)

The algorithm cannot detect if the wire at [0, 0, 1] should connect to the block at [0, 0, 0]. First the algorithm has to decide if there is an component on the other side wire in the direction of the block. This is not possible since the algorithm only checks the blocks [-1, 0, 0], [0, -1, 0] and [0, 0, 0]. This also applied for the check if the wire does not connect to any other components in the remaining directions.

This cannot be solved easy. The only way to solve this is to delay the decision if a wire should connect or not to a block after all its neighbours (neighbours in all directions) are checked.

3.4

Prioritized neighbour algorithm

The prioritized neighbour algorithm is an evolved version of the iterative neighbour algorithm. It is designed to fix the problems of the iterative neighbour algorithm. The basic idea is that every component is placed inside a category. Each category has a priority level. A component in a category should connect to any other components in category’s below or equal to its priority level. This priority ensures that we do not have to create a connection rule for every component to every component.

The algorithm itself operates in two phases. The first phase is creating an inventory of the components in the detection area. The second phase is actually connecting these components to each other, to create the circuit graphs.

The first phase works as follows. Each component has a category, which only contains those components. The algorithm iterates over all the blocks. For each component that is found the coordinates of that block are placed in a list. This list is a part of the category. This means that every repeater is placed in the repeater category’s list, etc. The mapping from the iterative neighbour algorithm is reused here. For each item found a new node is created. These node however are not placed in any graph yet. The nodes are placed in the mapping array without the reference to a graph.

The second phase loops over each item in the lists. The order in which the lists are looped over is given by the category’s priority. The priority’s are high to low:

• Comparators • Repeaters • Inputs • Redstone blocks • Redstone torches • Redstone wires • Powered blocks • Outputs

For each item the surrounding area of the block with the coordinates in the item is checked. The category depicts what this surrounding area is. For example a redstone repeater has no use in searching the block below it, because it can never connect to that block following the redstone rules. If a component is found in the surrounding area the found component should connect to the component. This connection should however only happen if the found component can connect following the redstone rules, and the found component fits in a category lower or equal in priority. When the connection is made, a couple of situations can occur:

First of all both components do not have a graph element in the mapping. In this case a new graph is created. The nodes of both components (coming from the mapping) are inserted into

(20)

this graph. The mapping of both components is then updated to contain a reference to this new graph.

The second situation is that one of the components has a graph entry, and the other one does not. In this case the component without a graph entry his node is placed in the graph of the other component. The mapping is then updated for the component without a graph.

The final case is when both components already have a graph entry in their mapping entries. In this case the both graph’s should be merged. This is again done by placing all the nodes of the graph with the least nodes in the other graph. Again the mappings are updated to the new merged graph.

3.5

Complexity of the prioritized neighbour algorithm

One can imagine that detecting larger circuits take longer time. The question is how much longer. Since the algorithm consists of two phases, we can determine the complexity of both phases separate, to determine the complexity of the whole algorithm.

The first phase’s complexity is actually very simple. Lets say n is the number of blocks in the detection area. This phase runs in linear time of this number of blocks. Or in the big O notation: O(n). We can prove this by dissecting the algorithm in its components. The main component is the iterating over all the blocks. The type of a block can be checked by correspond-ing Java class type, and thus in constant time. If a block is actually a redstone component we need to find its corresponding category. This lookup can be (and is) implemented using a hash map. Since the lookup of an component in a hash map is complexity O(1) so is the lookup of the category. The next part is inserting the component into the components list of the category. If we implement the list of components of the category using a linked list we can also achieve constant time for this insertion. This is possible since the insertion of an element at the back of a linked list is complexity O(1). Since the body of the iteration is actually of constant complexity this iteration executes n times a constant complexity. This results in the iteration, and thus the first phase of the algorithm having a complexity of O(n).

The second phase of the algorithm is however not so straightforward. Lets again consider the worst case. In the worst case the first phase of the algorithm has detected that every block in the detection area is a component. This gives us n components to check for neighbour connections. The amount of neighbours checked for each component stays constant, so the actual detection of neighbours is of complexity O(1) and thus not the problem. The first problem is the merging of the graph. Merging two graphs is implemented by inserting all the nodes of the smallest graph into the largest. The worst case complexity for this merging is thus O(1/2n) = O(n). Since this is done for every component, the second phase of the algorithm runs at least in O(n2).

The next problem lies in detecting the graph the neighbouring components are in. Since we have the direct mapping of the component to the node and graph reference one might think that the graph lookup is constant. However this is not the case since we can merge two graphs. The problem is shown in figure 3.2. Entries 1 - 3 contain nodes that are in graph 1, entries 4 - 6 contain nodes that are in graph 2. In the next step is discovered that the node in entry 3 and 4 actually connect. This implies that graph 1 and 2 should be merged. This is done and the nodes of graph 2 are inserted into graph 1. However as can be seen in sub-figure 3.2b, the entries containing this nodes are not updated to the new graph. This also cannot be done easy, since there is no list of entries containing nodes in a specific graph.

The partial solution for this problem can be seen in sub-figure 3.3a. Instead of entries having a pointer to the graph, they get a pointer to a wrapper object. This wrapper object then has a pointer to the graph object. In this case when one entries graph should be changed, this will happen in the wrapper object the entry is pointing to. Then all the entries pointing to this wrapper object have the new graph object. This principle works perfect for small circuits,

(21)

(a) Two graphs each have 3 mappings referenc-ing to them.

(b) The nodes in E3 and E4 connect, E5 and E6 are not updated.

Figure 3.2: Problem updating the graph reference of mapping entries.

however when testing larger circuits this method failed. In sub-figures 3.3b and 3.3c this failure is showed. When two graphs are merged everything is fine, however when a third merge occurs things start to go bad. In this case the nodes in entries 6 and 7 should connect. Lets say that graph 3 has more nodes than shown, and thus the nodes from graph 1 are inserted into graph 3 during merging. The entries 1 - 3 are in this case not updated to the new graph since there is no reference from entries 6 or 7 to the wrapper of entries 1 - 3.

(a) Three graphs each have 3 mappings referencing to them.

(b) The nodes in E3 and E4 connect, W2 is updated to G1.

(c) The nodes in E6 and E7 connect, W2 is updated to G3. W1 is not updated, but should be.

Figure 3.3: Problem updating the graph reference using a wrapper, when merging multiple times. This is solved by extending the wrapper to use a recursive lookup. This process is show in figure 3.4. A wrapper can contain either a graph reference or a reference to another wrapper. When the graph for an entry should be changed, the wrapper chain can be walked recursively until the wrapper containing the graph reference is found. This wrapper is then changed to point to the new wrapper containing the new graph reference, and thus enlarging the recursion chain by one. This recursion is obviously not very efficient, each merge the whole recursion chain is followed to update the graph reference. Worse however is the lookup of the graph reference. If we remember the detection algorithm, we see that every neighbour component of every component is checked to see if its corresponding node is already part of an graph. Lets again consider the worst case, each entry is merged the maximum amount of times, n times when all blocks are components. In this case each neighbour lookup has to walk trough a recursive chain of n elements. Each block can have 26 neighbours so this can count up to a complexity of O(26n), which is equal to O(n). Since this is done for every component, the second phase of the algorithm runs in O(n2).

Since the second phase runs in O(n2) and the first phase in O(n) the algorithm itself runs in

(22)

(a) Three graphs each have 3 mappings referencing to them.

(b) The nodes in E3 and E4 connect, W2 is pointed to W1.

(c) The nodes in E6 and E7 connect, W2 is used to find W1. W1 is then pointed to W3.

Figure 3.4: Solving the updating of entries when merging, by using a recursive wrapper. This method of keeping track of the graph is evolved from the iterative neighbour algorithm. In hindsight a better solution would be moving the graph reference from the mapping entry to the node itself. Since each node is processed anyway when merged, updating the graph refer-ence there would be much more efficient. This would get rid of the second problem completely. This would also make the graph lookup run in O(1). However since the merging itself causes the second phase to run on O(n2) as well, this would not lower the complexity of the algorithm itself.

Important to note here is that O(n2) is the worst case complexity for the second phase. This

worst case assumes that every block in the detection area is actually a component. It is however very difficult to create a circuit that uses every block in the detection area. This difficulty of course increases when n grows. We can therefore conclude that if the first phase was also O(n2)

the performance would be way worse, while it would not matter for the complexity of the whole algorithm.

(23)

CHAPTER 4

Transforming redstone components to

logic components

Now the detection is finished we have a directed graph of redstone components. We could at this point just build an redstone simulator, however we would be just reimplementing Minecraft then. The goal of this project is extracting logic circuits from the Minecraft world. In this chapter we will focus on the logic part.

First of all it is important to note that the logic used is not the commonly used two truth logic. Actually the logic used is a form of multi valued logic. In our case 16 valued logic is used. This also means that the boolean logic operators do not apply to our logic. Some components however do behave like boolean gates. Lets say we map the signal values 1 - 15 to true an 0 to false. The output is mapped back from false to 0 and true to 15. We can now express the behaviour of the redstone torch as the boolean NOT gate. The repeater also acts in a boolean matter. This component corresponds to a diode with a variable propagation time.

Some of the inputs also act in a boolean way. For example the lever has a value of false (power level 0) or true (power level 15). All the output components described in chapter 1 also act in this way. The inputs and outputs are transformed into common input and output node. This basally means that the type of input is lost during the transformation.

The comparator makes use of the different signal levels, thus it cant be transformed into a boolean gate. All the transformation does for the comparator is splitting the two modes into two different components.

4.1

Transforming redstone wires

The basic transformation of redstone wires can be a little difficult due to the strange behaviour of the redstone wire. If we recall, a redstone wire outputs the same power level as its input. However when a edge from wire to wire exists, the power level is decreased by one for that edge. The basic behaviour of the redstone wire corresponds to a simple connection between compo-nents in the transformed logic. However if the behaviour of wire to wire occurs the wire acts like it is a resistor. For the sake of simplicity a wire node is always transformed to a resistor node in the new logic circuit. The behaviour is preserved, so one might see this component as a variable resistor, where the resistance depends on the type of the output component.

A key component of logic is that no gate can have multiple inputs connected to one input port. If this was allowed the power level from the one input could unwanted power the other

(24)

input component. An example is given in figure 4.1. Note that this graph is undirected, this should make no difference since the NOT gate is directed. What happens is the input of the NOT gate gets powered by the true component with a power level of 15. The lamp L1 does not output power, however since it is connected to the same input as the true component it is powered as well. Also what happens if instead of L1 a component is placed that outputs a power level 14. The input value of the NOT gate would be undetermined.

To prevent this behaviour we have to introduce a new gate. In boolean logic we can simply com-bine two values by using the OR gate. The OR gate is however not present in the 16 valued logic. Instead we use a maximum gate for this problem. This solves the problem since the maximum gate has more than one input port. The maximum gate is displayed as an OR gate in the logic circuits.

Figure 4.1: Example of problems when multple components connect to one input port. Before we can fully apply this principle to redstone wires we first have to solve another problem. Let us take a look at figure 4.2a. This looks like a very simple circuit. The behaviour also looks straightforward. When the switch is turned on the light gets a new power level of 14. When the switch is turned off the power level drops to 0. However if we look close at the connection between the two wires we see that there is an edge in both directions. There is a edge in both directions since we do not know on detection time in which direction the power will flow. This causes a bit strange behaviour. Let us say the first wire is transformed into a maximum gate connected to a resistor, since it has two input connections. When the switch is turned on, the behaviour is as expected. The max gate propagates the high signal level. When turned off, the behavior becomes counter-intuitive. The power level from the switch input will drop to 0, however this resistor node has another input. The value of this input is the value of the other resistor node’s power level minus one. The maximum will be this value minus one. Since we have a resistor to resistor connection this power level is dropped by one again when outputting. The second resistor now gets a power level minus two of its power level, and will output this to the max gate minus one. This max gate will decrease it by one again, etc. The input power level of the light will now change a couple of times, this is unwanted behaviour.

(a) The two resistors can interfere each other, and

thus changing the value for the lamp. (b) The resistors cannot interfere any more.

Figure 4.2

This problem is solved by splitting up the resistor nodes. If we split every resistor node up into two resistor nodes, we can chain these resistors in both directions. Any other components on the edges of the chain are connected to both of the nodes. An example of this is show in figure 4.2b. We can see that the resistors now do not influence each other any more when the switch is turned off.

This also simplifies wire intersections. Since we now have nodes that act as input for the intersec-tion and nodes that act as outputs, we can simply wire the inputs into a max gate. The output of this max gate is then wired to all the output nodes. However if the intersection was originally a wire, we have to preserve the lowering of the signal. To do this we instead connect the output of

(25)

the max gate to a new resistor node. This resistor node is then connected to all the output nodes. Another benefit from this splitting is that we can merge resistor chains into one component. Using the old mechanism of resistor nodes connected duplex to each other it was hard to com-bine the resistor chains. The main reason for this is that merged resistors can have multiple inputs and outputs, when merging these nodes the information is lost to which node this input or output was originally connected. The new system does not have this problem. Since intersec-tions are transformed to max gates, there are no input or outputs connected in the middle of a chain. This leaves the edges of the chain. Let’s take the example from figure 4.2b. We can see that the output, while connected, actually has no effect on the lower chain. This also applies for the connection from the chain to the lamp. If we remove all these useless edges, we always get a chain with the inputs connected at the beginning node, and the outputs at the end node.

4.2

Transforming powered blocks

As explained in chapter 2 a block has two internal power types. In the logic circuits no compo-nent is known with such behaviour. To solve this we can transform all the blocks in the redstone circuits into two max gates. One max gate is for the low power, all the low powering input components are connected to this gate as input. All the low powered output components are connected to the output of the max gate. If we also create a high powered max gate we have successfully separated the two power types. However if we recall, all low powered output com-ponents can also be powered by a high power. To solve this we wire the output of the high max gate to an input of the low max gate. This ensures that the low powered output components have the maximum power level of both the high and low power.

4.3

Cleaning unused components

The last task of the transformation phase is cleaning up the circuits. It is obvious that compo-nents that do not contribute to the circuit are unnecessarily increasing the memory usage and the size of the intermediate representation. An example of these components can be seen in figure 4.2b. We can see that the bottom chain of resistor nodes can never be powered, or never power anything, we could therefore remove them.

The pruning process is trivial. The first thing the algorithm does is generate a list of com-ponents to be checked. This is initially the list of nodes in the graph. When iterating this list four situations can occur:

If the component can provide power and it has no output edges, the component is useless. All the input components from chapter 2 can provide power.

If the component can use power, like the output components from chapter 2, and has no incoming edges, it is useless.

Otherwise if the component has no input or no output edges it is useless.

The last case is when the component is not useless, and should thus not be removed.

A special case is the redstone torch, one might initially think it fits in the last situation. However what happens if no input is connected to the torch, it provides power. Since it can provide power, it fits in the first situation.

When a component is removed, it is likely that any other component that was connected has become useless itself. It however is possible that those components are already checked. To solve this we add, while iterating, all the input and output components of a component that is to be removed to the iteration list.

(26)

4.4

Complexity of the transformation

To determine the complexity of the transformation algorithm we again look at the individual subcomponents of the algorithm.

The first component of the algorithm is the transformation from redstone component nodes to logic component nodes. Let’s say that n is the amount of redstone components. The transfor-mation can then run in O(n). The edges are also transformed, since each component can have a maximum of 26 connected components, this also runs in O(n).

The next part is the transformation of the resistor nodes to split nodes. The splitting up is of course O(1) for each resistor. The transformation from the old edges to the component to the new components is again constant, since there are maximum 26 components. The transformation to maximum gates for intersections is also constant for each intersection. If we assume the worst case, that every component is a resistor, the complexity is O(n).

The transformation of blocks is regarding complexity equal to the resistor transformation. Each transformation can be done in constant time. And thus worst case O(n).

The cleaning up of components is a little more complex. Let’s imagine the worst case, a circuit where the last element checked is the only useless component. When removing this element the second to last component becomes useless. When removed the third last component becomes useless, etc. The first initial iteration is O(n). Every removal can be done in O(1). After the initial iteration n − 1 items are removed. The complexity is thus O(n + n − 1). Which is equal to O(n).

Since every component of the transformation runs in O(n), so does the complete transforma-tion.

(27)

CHAPTER 5

Implementing the simulator

Now we have the logic circuit we can start creating a simulator for these circuits. A simple solu-tion would be loading the circuit in an existing simulator like SIM PL [8]. This is unfortunately not possible. The first reason is described in chapter 4. The logic used is not simple to truth valued boolean logic. Actually a 16 valued logic is used, which many simulators do not support. The next reason is that the redstone components in Minecraft do not follow the usual prop-agation delay rules used in simulators. In existing simulators is assumed that every component has a propagation delay. In Minecraft this feature does exist in the form of ticks, however some components do not have a delay. For example a redstone wire can be activated, in the same tick its neighbouring wires are activated.

This feature can influence the behaviour of a circuit quite a lot. Let us take a look at figure 5.1. If we simulated this circuit in a regular tick-based simulator, what would happen? In the first tick both the NOT gates would output true since their inputs are false by default. The next tick the wires output the true value to the not gates. This causes the NOT gates to output false in the next tick, etc. Both NOT gates continuously change their output. Now what would happen if we would run a similar circuit in Minecraft? The output of the torches (NOT gates) would be a maximum power level. In the same tick, remember the wires have no delay, the input value of the torches would then change to this maximum power level. This causes the torches to output a 0 power level. This however only occurs if the torches are simulated at the exact same time. This is of course not likely. Actually the torches are put on a list with components to be simulated. So the torch that is highest on the list is simulated first. This torch immediately disables the other torch with a maximum power level output. So when this torch is simulated eventually it actually outputs a power level of 0. This thus causes a stable situation where the power does not blink on and off. If the order on the simulation list is deterministic, like in order of loading, each simulation of the circuit results in the same stable state.

Figure 5.1: Simple circuit for which the simulated behaviour differs, when using a existing simulator or minecraft.

The final reason is that existing simulators are built to support a wide range of components. Our circuits only consists of a small set of components, so we do not need this complexity. By creating a small simulator we can optimize for the components that actually exist in our circuits.

(28)

Because of these reasons the decision was made to implement a custom simulator.

5.1

Intermediate representation

As can be seen in figure 1.3 the simulator does not run in the environment of the Minecraft server. This means that the logic graph that the Minecraft server has in memory is not accessible by the simulator. We do however want to load the circuits into the simulator to simulate them. To solve this we have to export the circuit in a representation known by the Minecraft server environment and the simulator environment. We do this by using a intermediate representation. Note that if we would have used an existing simulator and intermediate representation would also be required. However now we design the simulator ourselves, we can also design the intermediate representation ourselves.

This intermediate representations used is actually very simple. It is actually nothing more than a text based representation of the logic graph. For simplicity a known text based data structure is used: JSON [15]. Note that a text based solution is actually not the most efficient. A more efficient way (in space and parsing time) would be a binary representation of the graph. However a text based approach is taken for the sake of extensibility. For example if a secondary person wanted to implement a better visualization of the graph. This person could simply open the file in a text editor and see the data structure. In a binary format this would be much more difficult, if not impossible, without any proper documentation.

The JSON document consists of two arrays. The first array contains a list of all the nodes. Each item in this array always has two properties: id and type.

The id property is the internal number in the list of nodes in the graph. This id is used to specify the edges.

The type property, abbreviated as t, represents the type of the node. The types are: • Comparator node, labelled as cmp as type.

• Subtractor node, labelled as sub as type. • Diode node, labelled as diode as type. • Output node, labelled as out as type. • Input node, labelled as in as type. • Resistor node, labelled as res as type.

• Logic true node, labelled as pos (for positive) as type. • Max gate node, labelled as or as type.

• Not gate node, labelled as not as type.

All the items in the array have a optional property called n. This property is the label (or name) of a node. These labels are used further on to retrieve and set the current value of a component. Inputs are always labelled inn where n is the nth input component. The outputs are labelled outn. The label of other components can be set by placing a sign in the world. In the detection phase, the algorithm will check all the signs in the detection area. If the first line of the sign equals [redstone id] (including the brackets) the label of the nearest component within 1 block maximum is set to the second line. This can also override the label of an input or output com-ponent. In such a case the counter for the inn or outn label is not increased, thus no gaps in the label counter are created.

Some components have some extra information that should be saved. For example the diode has a variable delay, which should be saved. The list of extra properties is given in table 5.1.

(29)

Table 5.1: List of components and what extra properties they have in the intermediate represen-tation

Component Property name Optional Description

Input s No Signal level. This is the sig-nal level the input was providing when detected. If for example a switch is set to true this is equal to 15.

Diode d No The delay in ticks. Possible val-ues {1,2,3,4}.

Resistor r Yes (defaults to 1) The resistance of this compo-nent. This is equal to the amount of resistor nodes com-bined.

Output or Input p Yes The pin that should be used on the Raspberry Pi. If this is not set, the component is a normal in or output component.

5.2

Implementation

5.2.1

Programming language

Now the decision is made we want to implement a simulator, we can think about the imple-mentation. The first point in every implementation is the programming language used. For the previous two parts of the project, the detection and transformation, Java was used. Using this again for the simulator would be a smart idea, since we already have a data structure for the graph. However if we look back at the motivation of this project we find that we wanted to speed up the execution of the circuits. This implies that we want to create a simulator that is fast as possible. Java however is not known as the fasted language. Instead C or C++ is often marked as fastest.

One of the aspects of Java that was particularity useful was the object-oriented design. We can see that every component is a different object. So a object oriented programming language simplifies the design process a lot. We previously mentioned C and C++, C++ also has this object-oriented design, and thus making it ideal for the simulator.

To simplify the implementation, we use an external library for JSON parsing The library used is boost. We also use the newer C++11 syntax and standard library to benefit from ranged for loops and hash maps. [7].

Note that the simulator is not bound to the PC. If we refer back to the separated environ-ment from figure 1.3, we can see that by impleenviron-menting a separate program this simulator can run any machine. An example of a different environment is the Raspberry Pi. There is no re-striction on compiling the simulator for the Raspberry Pi. Even the used libraries are available for the ARM environment.

5.2.2

Interacting with the simulator

An important feature of the simulator is actually the user interface. If we would create a simulator that is as fast as possible, but the user could not see the outcome, the simulator would be useless. Creating a fancy graphical user interface would however be overkill for this project.

Referenties

GERELATEERDE DOCUMENTEN

In de praktische weidebouw werden deze verschillen in stikstofbenutting tussen rassen van Engels raaigras in Nederland ook waargenomen; deze hingen in hoge mate samen met de droge

Brown and Chalmers [5] studied practices and behaviors of tourists, in order to provide design implications for novel event sharing tools (related to visits and.. George Square

De reden van deze verschuivingen zijn dat via PPO de afhandeling makkelijker geregeld is en dat vanuit de procesuitvoering ook vaak de afspraken gemaakt worden.  De kosten

Bij de RRT dient opgemerkt te worden dat er hier en daar een verschil geconstateerd kan worden tussen de RRT van de zuivere standaarden en die van de

Nagaan \o7elke chlorobiphenylen in nederlandse aal aantoonbaar zijn; h e t vastleggen van de volgord e van elutie van d eze componenten op een fused silica

In aansluiting op de publikaties no. 3.23 en 3.54 wordt in dit hoofdstuk een beeld gegeven van de te verwachten bedrijfsuit- komsten op grotere weidebedrij ven onder

Governments and organizations to: acknowledge de- creased male fertility as a major public health problem and to recognize the importance of male reproductive health for the survival