Jacquard Card Reader
The punch card sequencer: where it is in its deck, the four redstone strengths it gives, and the holes of every card it holds.
The Jacquard Card Reader is the head of Jacquard's loom of 1804, which read a chain of punched cards: the first programmable machine. A shaft turns it from below, and at each quarter turn it reads the next row of its cards and gives four redstone strengths, one on each side. On its own it runs a factory sequence without any computer. A computer connected to it follows that sequence: which card and which row it reads, what each side gives, and the holes of every card it holds.
local reader = peripheral.find("card_reader")
local p = reader.position()
print("card " .. p.card .. " of " .. p.cards .. ", row " .. p.row)reader.position() | Where the reader is in its deck, counted from 1, as the goggles show it: |
reader.outputs() | The redstone strength each side gives now, from the row being read. All four are 0 while the reader is empty or held. |
reader.card(n) | The holes of card n, as a list of 12 numbers, one per row. nil when the reader holds fewer than n cards. A number with decimals stops the program with bad argument #1 to 'card' (number has no integer representation). |
How it works
Cards. A Blank Punch Card becomes a Punch Card in the Card Punch: 12 rows of 16 holes. Right-click the reader with a Punch Card to add it at the end of the chain, up to 9 cards; right-click with an empty hand to take the last one back. The reader reads its cards from the first slot up to the first empty one.
Rows. Each quarter turn of the shaft moves to the next row. After row 12 comes row 1 of the next card, and after the last card, the first one again. The faster the shaft, the faster the sequence: at 16 RPM, a little more than one row a second; at 256 RPM, about 17. The direction of rotation does not matter.
Holding it. A redstone signal from the block above (a lever on top of the reader, or redstone dust on it) holds the reader: it goes back to card 1, row 1, gives 0 on all four sides, and stops reading until the signal goes.
The four sides. The front is the face that looked at you when you placed the reader. Right and left are the reader's own, as for a person facing you: its right side is on your left. Put a Redstone Lamp on each side to check.
The holes. In the Card Punch, each row has four groups of four holes, marked Front, Right, Back and Left. The holes of a group are worth 8, 4, 2 and 1: the strength given on that side is the sum of the punched holes, from 0 (no hole) to 15 (all four). Engineer's Goggles show the card, the row and the four strengths.
Reaching it from a computer. Put the reader against a face of the computer, or, from the Minicomputer on, against a Data Cable of its network, where it is named like card_reader@120,64,-35. It turns and it holds cards, so it is also a kinetic block (reader.speed()) and an inventory of 9 slots, one card each. See Peripherals.
When the reader is gone, every method stops the program with card reader removed; when its chunk is not loaded, with peripheral is not loaded.
Following the sequence
reader.position()
Where the reader is in its deck, counted from 1, as the goggles show it:
- table
{card =, row =, cards =, reset =}
| field | content |
|---|---|
card | the card being read, from 1 to cards; 0 when the reader is empty |
row | the row being read, from 1 to 12 |
cards | how many cards are chained, from 0 to 9 |
reset | true while the redstone from above holds the reader on its first row |
local reader = peripheral.find("card_reader")
local p = reader.position()
if p.cards == 0 then
print("put punch cards in the reader")
elseif p.reset then
print("held on the first row by the redstone on top")
else
print("card " .. p.card .. "/" .. p.cards .. ", row " .. p.row .. "/12")
endSee also card_reader.outputs() card_reader.card()
reader.outputs()
The redstone strength each side gives now, from the row being read. All four are 0 while the reader is empty or held.
- table
{front =, right =, back =, left =}, each a redstone strength from 0 to 15
The sides are the reader's own (see "The four sides" above). This program prints each new row as it comes, the four strengths in that order:
local reader = peripheral.find("card_reader")
local last = ""
while true do
local o = reader.outputs()
local now = o.front .. " " .. o.right .. " " .. o.back .. " " .. o.left
if now ~= last then
print(now)
last = now
end
sleep(0.05)
endWhen two rows that follow each other give the same strengths, only the first one is printed.
See also card_reader.position() card_reader.card()
The cards
reader.card(n)
The holes of card n, as a list of 12 numbers, one per row. nil when the reader holds fewer than n cards. A number with decimals stops the program with bad argument #1 to 'card' (number has no integer representation).
nnumber- the place of the card in the chain, from 1
- table|nil
- the 12 rows of the card, one number each; nil when there is no card n
How a row becomes a number. Each punched hole adds its value. In the Front group, the holes add 8, 4, 2 and 1, as the Card Punch marks them; in the Right group, 16 times as much; in the Back group, 256 times; in the Left group, 4096 times. So a row is
front + 16 × right + 256 × back + 4096 × left
from 0 (a blank row) to 65535 (all 16 holes). Seen as a binary number of 16 bits, bit c (c from 0 to 15) is set when its hole is punched: bits 0 to 3 are the front strength, 4 to 7 the right one, 8 to 11 the back one and 12 to 15 the left one.
Brass has no bit operators: // (division rounded down) and % (the remainder) take a row apart.
local function strengths(row)
return {
front = row % 16,
right = (row // 16) % 16,
back = (row // 256) % 16,
left = (row // 4096) % 16,
}
end
local s = strengths(16138)
print(s.front, s.right, s.back, s.left)16138 is 10 + 256 × 15 + 4096 × 3: this prints 10, 0, 15 and 3. A single hole works the same way: hole c is punched when row // 2 ^ c % 2 is 1, so row // 2 ^ 4 % 2 == 1 tests the hole worth 1 of the Right group.
The whole deck, card after card, with the four strengths of each row:
local reader = peripheral.find("card_reader")
for n = 1, reader.position().cards do
print("card " .. n)
print(" row front right back left")
local rows = reader.card(n)
for r = 1, 12 do
local row = rows[r]
print(string.format("%4d %5d %5d %4d %4d", r, row % 16, (row // 16) % 16, (row // 256) % 16, (row // 4096) % 16))
end
endSee also card_reader.position()
Example: a sequencer dashboard
A screen beside the loom: the step being read, and a bar for each side. The reader runs the factory by itself; the computer only shows what it does.
local reader = peripheral.find("card_reader")
local SIDES = {"front", "right", "back", "left"}
while true do
local p = reader.position()
local o = reader.outputs()
term.clear()
term.set_cursor(1, 1)
print("JACQUARD READER")
if p.cards == 0 then
print("no cards")
elseif p.reset then
print("held on the first row")
else
print("card " .. p.card .. "/" .. p.cards .. ", row " .. p.row .. "/12")
end
for _, side in ipairs(SIDES) do
print(string.format("%-6s %2d %s", side, o[side], string.rep("#", o[side])))
end
sleep(0.1)
end