module BBS::Petscii
The Commodore 64’s character set on the telnet line: a 40×25 screen, colours and cursor moves as single control bytes, and the lowercase character set (letters swap case against ASCII).
A session switched with session.petscii! sends everything it writes through an Encoder, so the code above it keeps writing UTF-8 text with ANSI colours and cursor moves (a Flow, a FakeOS, a door), and reads the C64’s keys as the usual key symbols. PETSCII control codes can be written directly too: they are the C0 and C1 code points of a UTF-8 string, so "#{BBS::Petscii::YELLOW}READY." is yellow on the C64.
Constants
- ANSI_BRIGHT
-
The PETSCII colours for the bright ANSI colours 0-7 (SGR 90-97, or 30-37 with bold); black is dark grey here too.
- ANSI_DARK
-
The PETSCII colours for the normal ANSI colours 0-7 (SGR 30-37). Black is dark grey: the C64’s terminal screen is black, and a black-on-grey door must stay readable.
- ASCII_FORMS
-
The typography of a modern board in ASCII, for the characters the C64 has no glyph for.
- ASCII_LOWER
-
The codes of a-z in ASCII, and of shifted (capital) letters some C64 terminal programs send.
- ASCII_UPPER
-
The codes of A-Z in ASCII, and of the unshifted (lowercase) letters in the C64’s lowercase character set.
- BELL
-
Control code: rings the bell, where the terminal program supports it.
- BLACK
-
Control code: text colour black.
- BLUE
-
Control code: text colour blue.
- BROWN
-
Control code: text colour brown.
- C1_CONTROLS
-
The C1 control range, never printed as a character.
- CASE_OFFSET
-
The distance between a letter’s lowercase and uppercase ASCII codes.
- CLEAR
-
Control code: clears the screen and homes the cursor.
- COLORS
-
All sixteen colour control codes; the
Encodertracks the last one sent. - COLS
-
Width of the C64 screen in characters.
- COMBINING_MARK
-
A combining mark, dropped with the accent of a decomposed letter.
- CONTROLS
-
The control bytes that go to the C64 as they are.
- CYAN
-
Control code: text colour cyan.
- DARK_GREY
-
Control code: text colour dark grey.
- DEFAULT
-
The colour of plain text, after an SGR reset.
- DEL
-
Control code: deletes the character left of the cursor (the INST/DEL key).
- DOWN
-
Control code: moves the cursor down.
- ESCAPE_FINAL
-
The final byte of an escape sequence the
Encoderacts on. - GLYPHS
-
What the
Encoderwrites for a character of the lowercase set: the PETSCII code point of its graphic (GRAPHICS,LOWERCASE_GRAPHICS,LOOK_ALIKES), or its ASCII form. - GRAPHICS
-
Graphics both character sets have at the same code: the box drawing with square corners, the blocks, £ ↑ ←.
- GREEN
-
Control code: text colour green.
- GREY
-
Control code: text colour medium grey.
- HOME
-
Control code: moves the cursor to the top left.
- KEYS
-
The keys the C64 sends that are not letters or signs, by byte: the key symbols
BBS::Telnet#readkeyreturns.DEL, and the ASCII backspace and delete some terminal programs send, are:backspace; RUN/STOP and the left arrow key are:escape; Shift-F1 is F2 and so on; the pound sign, the up arrow and Shift-SPACE are the characters they show. - LEFT
-
Control code: moves the cursor left.
- LEFT_BRACKET
-
The left bracket, the same in PETSCII and ASCII.
- LIGHT_BLUE
-
Control code: text colour light blue.
- LIGHT_GREEN
-
Control code: text colour light green.
- LIGHT_GREY
-
Control code: text colour light grey.
- LIGHT_RED
-
Control code: text colour light red.
- LOCK_CASE
-
Control code: locks the character set, so C= + Shift no longer switches it. Its byte is the backspace of ASCII, so the
Encodermoves the cursor left on it: send it raw (Telnet#petscii!does). - LOOK_ALIKES
-
Characters the lowercase set shows as the graphic of a look-alike: double and heavy lines as single ones, rounded corners as square ones.
- LOWERCASE
-
Control code: switches to the lowercase character set.
- LOWERCASE_GRAPHICS
-
The graphics only the lowercase set has: the tick.
- NATIVE
-
The characters the C64 has keys and glyphs of its own for.
GLYPHSmaps them to their PETSCII codes, which happen to be in the ASCII range;::plainkeeps them as they are, so theEncoderstill finds them. - ORANGE
-
Control code: text colour orange.
- PLAIN_CELL
-
A character the C64 shows as it is in one cell: printable ASCII and £.
- PURPLE
-
Control code: text colour purple.
- QUOTE_ROOM
-
The columns a closing quote needs: the quote, a cursor left and a space.
- RED
-
Control code: text colour red.
- RIGHT
-
Control code: moves the cursor right.
- RIGHT_BRACKET
-
The right bracket, the same in PETSCII and ASCII.
- ROUNDED_CORNERS
-
The rounded corners, which only the uppercase/graphics set has; the lowercase set draws them as the square ones (
LOOK_ALIKES). - ROWS
-
Height of the C64 screen in lines.
- RVS_OFF
-
Control code: turns reverse video off.
- RVS_ON
-
Control code: turns reverse video on.
- SAME_AS_ASCII
-
Codes that are the same in PETSCII and ASCII: space, digits, punctuation and
@. - SHIFTED_LETTERS
-
The codes of the shifted (capital) letters the C64 keyboard sends.
- SHIFT_OFFSET
-
The distance between ASCII A-Z and the C64’s shifted letters.
- SPELLED
-
Letters and signs the C64 has no glyph for, spelled in ASCII by
::plainwhere theEncoderwould show a question mark. - UNPLAIN
-
A character
::plainconverts: anything but printable ASCII and ESC, so ANSI colours pass through. - UP
-
Control code: moves the cursor up.
- UPPERCASE
-
Control code: switches to the uppercase/graphics character set.
- UPPERCASE_GRAPHICS
-
The graphics only the uppercase/graphics set has: the rounded corners, the balls, the card suits, the diagonals.
- WHITE
-
Control code: text colour white.
- YELLOW
-
Control code: text colour yellow.
Public Class Methods
Source
# File lib/bbs/petscii.rb, line 238 def self.ascii_letter?(text) = text.length == 1 && text.ascii_only?
Whether text is one ASCII character.
Source
# File lib/bbs/petscii.rb, line 215 def self.base(char) = char.unicode_normalize(:nfkd).gsub(COMBINING_MARK, '')
char without its accent: the base letter of a decomposed one, or char itself.
Source
# File lib/bbs/petscii.rb, line 224 def self.bytes(codes) = codes.each_char.map { |c| c.ord.chr }.join.b
Returns the binary String of bytes for codes, a String of PETSCII code points (such as the constants of this module).
Source
# File lib/bbs/petscii.rb, line 229 def self.cell(char) return char if char.match?(PLAIN_CELL) return char if GLYPHS[char]&.length == 1 ascii_letter?(base(char)) ? char : '?' end
Source
# File lib/bbs/petscii.rb, line 204 def self.character(byte) case byte when ASCII_UPPER then (byte + CASE_OFFSET).chr when SHIFTED_LETTERS then (byte - SHIFT_OFFSET).chr when ASCII_LOWER then (byte - CASE_OFFSET).chr when SAME_AS_ASCII, LEFT_BRACKET, RIGHT_BRACKET then byte.chr end end
The character of byte, a letter (its case swapped back to ASCII) or a sign; nil for anything else.
Source
# File lib/bbs/petscii.rb, line 220 def self.color_code(sgr) = Encoder.new.encode(sgr).each_byte.find { |b| COLORS.any? { |c| c.ord == b } }
Returns the PETSCII colour code, an Integer, that the SGR colour sequence sgr turns into on a PETSCII line (BBS::Petscii::Colors::YE is YELLOW), or nil when it sets no colour.
Source
# File lib/bbs/petscii.rb, line 200 def self.key(byte) = KEYS.fetch(byte) { character(byte) }
Translates a byte the C64 sent into a key the way BBS::Telnet#readkey returns keys: a Symbol of KEYS (:enter, :backspace, the arrows, :home, :escape, :f1 … :f8), a one-character String with the letter case swapped back to ASCII, or nil for a byte that is not a key.
Source
# File lib/bbs/petscii.rb, line 263 def self.plain(text) = text.to_s.gsub(UNPLAIN) { |c| plain_char(c) }
Returns text in characters the C64 shows one to a cell, so that it wraps where it is counted: the typography of GLYPHS that has an ASCII form becomes it (an ellipsis three dots, a dash a hyphen), the box drawing and block glyphs of the Encoder stay, the letters of SPELLED are spelled out, accented letters lose the accent, and anything else is a question mark. ANSI escape sequences are kept.
BBS::Petscii.plain('Ærø – “café”…') # => 'Aero - "cafe"...'
Source
# File lib/bbs/petscii.rb, line 266 def self.plain_char(char) glyph = GLYPHS[char] return char if NATIVE.include?(char) || (glyph && !glyph.ascii_only?) return glyph if glyph SPELLED.fetch(char) { ascii_letter?(base(char)) ? base(char) : '?' } end
One character as ::plain writes it.