Skip to content
Matthew W edited this page May 24, 2024 · 6 revisions

Client API

The client API is used by the turtles themselves in order to actually display the visuals requested by the server.

Note

Many methods are still private, so they will not be documented here yet. I will be making more of them available as I refactor some things.

Public Fields

Here is a brief explanation of most of the public fields, as they are mostly self-explanatory. position and blocks_used require a bit more explanation, and are covered in their own sub-section below.

  • array_style: The style of array the turtles are in, either "horizontal" or "vertical".
  • font: The font the turtles are using to display characters, loaded from the disk drive. This is a 2-layer table that maps [y][x] to a boolean value, where true is foreground and false is background.
  • color_map: The positions within the turtle's inventory each color of block should be in.
  • current_color: The current color the turtle has placed (or nil, if none).
  • color_want: The color the turtle wants to place, but hasn't yet (i.e: the turtle is currently working on placing another color, but the server told it to draw a new color instead). This color will be drawn on the next redraw tick.
  • guideblock_top: In horizontal Turmitors, this is the block that the turtles will look for and treat as the "top" side of the array. Defaults to minecraft:polished_andesite.
  • guideblock_left: In horizontal Turmitors, this is the block that the turtles will look for and treat as the "left" side of the array. Defaults to minecraft:polished_diorite.
  • control_channel: The channel that this specific turtle is listening on for control messages from the server.
  • is_bottom_right_corner: A boolean value containing whether or not the turtle is in the bottom right corner of the array. This is only used so that the turtle that is at the bottom right corner of the array can report back its position when the server requests the size of the array, as the bottom-right turtle will have a position that is the size of the array.
  • frozen: A boolean value that, when true, will prevent the turtle from drawing anything. The turtle however can still pick up the block while frozen, it just cannot place anything new (and as such will ignore placement orders, unless the order is specifically ONLY to pick up the block).

blocks_used

{
  ["block_name"] = colors.x,
  ...
}

The blocks_used field is a table that contains the blocks the turtle will use to draw the screen. The key is the block name, and the value is the color of the block it should be placed as. This is used to determine what block to place when the server tells the turtle to draw a pixel.

position

{
  x: number,
  y: number,
  char_x: number,
  char_y: number,
  inner_x: number,
  inner_y: number
}

The position field is a table that contains the turtle's x and y location, as well as the x and y position of the character they are a representative of, and the position within that character the turtle is. The x and y values are 1-indexed, while character and inner positions are 0-indexed.

For example, a turtle at position x: 3, y: 3 would be the turtle representing the character at the very top left (0, 0), so the char_x and char_y values would be 0. However, within that character, the turtle is at position 3,3, so the inner_x and inner_y values would be 2.

Similarly, a turtle at position x: 11, y: 15 would be the turtle representing the character diagonal down and to the right of the top left character (since the characters are 6x9), so the char_x and char_y values would be 1, 1. To determine the inner_x and inner_y values, you just subtract one then modulo the x and y values by the size of the characters. In this case, they would be 4, 5.

The char_x and char_y values are later used to determine the channels used for controlling the turtle, see the Shared Documentation page for more information on how those are determined.

The inner_x and inner_y values are used when the turtles are told to display a character. The server just sends an offset position to the turtle, and the turtle "looks" at that offset position, then offsets it again by its inner_x and inner_y values to determine what specific pixel to draw.

Placement Example

Let's say we are the turtle at position 3,5, and the server wants the character we are a part of to draw the letter F. The server would send all turtles in the character the following message:

{
  action = "character",
  data = {
    offset_x = 50,
    offset_y = 46,
    fg = colors.white,
    bg = colors.black,
  }
}

The offset values are the offset positions of the top left corner of the character in the font -- in this case, 50 and 46 (See the Server Documentation page for more information on how the server calculates this). The turtle will then add its own inner_x and inner_z offset to this value to determine what pixel to draw. In this case, the turtle would draw the pixel at position 52,50 of the font.

Public Methods

set_position(x: number, y: number)

Sets the position of the turtle in the grid. This calculates the char_x, char_y, inner_x, and inner_y values based on the x and y values provided, as well as determines the control channel for the turtle based on the char_x and char_y values.

char_x = math.floor((x - 1) / 6)
char_y = math.floor((y - 1) / 9)
inner_x = (x - 1) % 6
inner_y = (y - 1) % 9

control_channel = TurmitorChannels.get_client_channel(
  char_x,
  char_y
)

Note that this method does not actually open or close the channels, it just sets the values.

get_position(): x: number, y: number

Returns the current position of the turtle in the grid.

determine_position()

Determines the turtle's position using one of the below algorithms:

Vertical Turmitors

The algorithm for vertical Turmitors is simple, the only rules are as follows:

  1. If there is no turtle above and to the right of us, we are at 1,1.
  2. If there is no turtle above us, but there is one to the left of us, then we are at position ?,1. We determine what x position we are at by waiting until the turtle to our right knows its position, then we are at x + 1, 1.
  3. If there is no turtle to the left of us, but there is one above us, then we are at position 1,?. We determine what y position we are at by waiting until the turtle above us knows its position, then we are at 1, y + 1.
  4. If there is a turtle above and to the right of us, then we wait until either of the turtles know what position they are at. Then, if the turtle to our right knows its position, we are at x + 1, y. If the turtle above us knows its position, we are at x, y + 1.

Horizontal Turmitors

The algorithm for horizontal Turmitors is a bit more complex, as the turtles in the center of the array have no concept of up, down, left, or right. Thus, the algorithm is as follows:

  1. If a top guideblock is found, and a left guideblock is found, then we are at position 1,1.
  2. If a top guideblock is found, but there is a turtle to its left, then we are at position ?,1. We determine what x position we are at by waiting until the turtle to the guideblock's left knows its position, then we are at x + 1, 1.
  3. If a left guideblock is found, but there is a turtle to its right, then we are at position 1,?. We determine what y position we are at by waiting until the turtle to the right of the guideblock knows its position, then we are at 1, y + 1.
  4. Here is where it gets a bit complicated. If no guideblocks are found, then we must wait until any turtle knows its position. From there, we must wait until either the turtle to its left or the turtle to its right knows its position. From there, we can take the largest value from each turtle and our position will be largest_x, largest_y. The reason this works is due to the way the positions will propagate through the array.

queue_block(color: number)

Sets the color_want value to the color provided, and kickstarts the redraw process if it isn't already running.

run()

Runs the Turmitor client, handling loading data, collecting blocks, determining position, listening for messages, and drawing the characters.

Actions

The turtles listens for specific actions from the server and acts on them, as follows:

Character-specific actions
  • character: The turtle will draw the pixel at the offset provided by the server.
  • place-batch: The turtle will check if its position is within the batch provided by the server, and if so, it will place the color provided by the server. Useful for sprites and other large images.
  • place: The turtle will check if its position is that of the position provided by the server, and if so, it will place the color provided by the server. Useful for single-pixel changes.
Array-wide actions
  • clear: All turtles place the provided color of block.
  • reset: All turtles delete their data and shut down.
  • get-size: If the turtle is at the bottom right corner of the array, it will send back its position to the server.
  • pickup: All turtles pick up the block in front of them.
  • freeze: All turtles freeze, preventing them from placing any blocks, they can still act on the pickup command, however, making this a suitable series of commands to use if you want to reset the turtles (freeze -> pickup -> reset).
  • thaw: All turtles thaw, allowing them to place blocks again.

Redraw Process

The redraw process is very simple, and is as follows:

  1. When we receive an update, resume the process by queueing the event "turmitorclient-queue-block"
  2. While we have a color we want to place, destroy the block in front and place the new block. Since this takes about a quarter-second, we can receive another color to place before we finish placing the current color, hence why we loop here instead of waiting for another resume.