---
title: 'Commodore 64'
url: 'https://wiki.teletypegames.org/hu/engines/rubbs/commodore'
markdown: 'https://wiki.teletypegames.org/hu/engines/rubbs/commodore.md'
lang: hu
date: '2026-10-27'
description: 'Egy rubbs-boardot Commodore 64 is hívhat. A session.petscii! PETSCII-re állítja a vonalat (40×25, a C64 színei és billentyűi), miközben a felette lévő kód továbbra is UTF-8-at és ANSI-t ír; a BBS::Terminal megkérdezi a hívót, milyen géppel jött; a kétarcú board pedig ugyanazon a porton ad modern é…'
taxonomy:
  tag:
    - reference
---

# Commodore 64

> Egy [rubbs](https://wiki.teletypegames.org/hu/engines/rubbs)-boardot Commodore 64 is hívhat. A `session.petscii!` PETSCII-re állítja a vonalat (40×25, a C64 színei és billentyűi), miközben a felette lévő kód továbbra is UTF-8-at és ANSI-t ír; a `BBS::Terminal` megkérdezi a hívót, milyen géppel jött; a **kétarcú board** pedig ugyanazon a porton ad modern és C64-es arcot. Ez az oldal mindhármat bemutatja, a Feediát és az Alexandria BBS-t kidolgozott példaként használva, és leírja, hogyan kerül vonalra egy valódi vagy emulált C64. A `BBS::Petscii` a 0.15.0, a `BBS::Terminal` a 0.16.0 óta van meg.

# Hogyan hív egy C64

A Commodore 64-nek nincs hálózati portja. Egy **terminálprogrammal** és egy **modemmel** éri el a telnetes boardot:

| Elem | Amit a hívók használnak |
|---|---|
| Terminálprogram | **CCGMS** (CCGMS Future), **StrikeTerm**, vagy PC-n a **SyncTERM** C64 módban. PETSCII módban kell lennie (ASCII-fordítás nélkül), a kisbetűs karakterkészlettel. |
| Modem | **WiFi-modem** (Zimodem, WiModem232 és társaik) a user porton vagy egy **SwiftLink**/Turbo232 cartridge mögött, SwiftLinken jellemzően 38400 baud, RTS/CTS. `ATDT host:port`-tal tárcsázzák, pl. `ATDT teletypegames.org:2323`. |

Amit ebből a board lát:

- **Sima TCP, telnet nélkül.** A WiFi-modem általában nyers TCP-kapcsolatot nyit. A C64 nem válaszol a board telnet-egyeztetésére, és ablakméretet (NAWS) sem jelent. Az egyeztetés bájtjai néhány kósza karakterként érkeznek meg a C64-re az első képernyő előtt, ez ártalmatlan.
- **PETSCII-bájtok mindkét irányban.** A betűk az ASCII-hoz képest fel vannak cserélve, a színek és a kurzormozgások egybájtos vezérlőkódok, a RETURN egy magányos CR.
- **40×25**, és a C64 a 40. oszlop után magától sort tör.

Ezért tippel a rubbs C64-re, ha nem jön ablakméret, és ezért alakít át a PETSCII-vonal mindent egyes bájtokká escape-szekvenciák helyett.

## Tesztelés VICE-szal

A VICE emulátor (`x64sc`) eljátssza a C64-et, a SwiftLinket és a modemvonalat: a VICE RS-232 eszköze közvetlenül egy TCP-hoszthoz kapcsolódik (a `+rsdev1ip232` nyers TCP-t jelent, IP232-protokoll nélkül), terminálprogramnak pedig egy lemezképről induló CCGMS szolgál.

```sh
# A C64 with a SwiftLink at $DE00 (NMI), its line connected to a board over TCP,
# CCGMS autostarted from a disk image; CCGMS's saved config sets the SwiftLink to 38400.
x64sc -silent +cart \
  -acia1 -acia1base 0xDE00 -acia1irq 1 -acia1mode 1 -myaciadev 0 \
  -rsdev1 127.0.0.1:2323 -rsdev1baud 38400 +rsdev1ip232 \
  -autostart ccgms.d64
```

Az `-rsdev1`-gyel a kapcsolat már a CCGMS indulásakor nyitva van, így nem kell `ATDT`; a board felteszi az első kérdését, és a RETURN elfogadja a tippet (C64). A bájtokat emulátor nélkül is ellenőrizheted: kapcsolódj egy kis szkripttel, ne küldj NAWS-t, és nézd meg, mi jön vissza (lásd a példát a *BBS::Terminal* résznél).

# A PETSCII-vonal

## `session.petscii!` és `session.petscii?`

| Metódus | Mit csinál |
|---|---|
| `session.petscii!` | Mostantól a session egy C64-gyel beszél. A `term_cols` / `term_rows` értéket 40×25-re állítja, végleg kikapcsolja az egérjelentést (az `enable_mouse` ezután nem csinál semmit), nyersen elküldi a `LOWERCASE`, `LOCK_CASE` és `CLEAR` kódot (kisbetűs karakterkészlet, lezárt C= + Shift, üres képernyő), és egy `BBS::Petscii::Encoder`-t tesz a `write` elé. Másodszori hívásra nem csinál semmit. |
| `session.petscii?` | `true`, miután a `petscii!` lefutott. |

A `petscii!` után:

- minden `session.write` az Encoderen megy át, így egy Flow, egy FakeOS, egy Application vagy egy door továbbra is UTF-8 szöveget ír ANSI-színekkel és kurzormozgással;
- a `readkey` és a `readline` a C64 billentyűit dekódolja (lásd *A C64 billentyűi* lent);
- a kliens NAWS-jelentése már nem változtat a méreten: a vonal 40×25 marad.

Egy 40 oszlopra rajzolt Flow, FakeOS vagy door változtatás nélkül fut C64-en; egy 80 oszlopra rajzolt tördelődik.

```ruby
require 'bbs'

BBS.configure do |c|
  c.flow = BBS::Flow.define do
    call do |_ctx, runner|
      session = runner.session
      session.petscii!
      session.write "\e[1;33mREADY.\e[0m\r\n"
      session.write "#{session.term_cols}x#{session.term_rows}, petscii: #{session.petscii?}\r\n"
      nil
    end
  end
end

BBS.start
```

## A `BBS::Petscii` konstansai

### Képernyő- és vezérlőkódok

A konstansok a PETSCII-kódok egykarakteres UTF-8 stringként (a C0/C1 kódpontok), így közvetlenül beírhatók egy stringbe: a `"#{BBS::Petscii::YELLOW}READY."` sárga a C64-en.

| Konstans | Kód | Jelentés |
|---|---|---|
| `COLS`, `ROWS` | 40, 25 | A C64 szöveges képernyője. |
| `CLEAR` | `0x93` | Képernyőtörlés, kurzor a bal felső sarokba. |
| `HOME` | `0x13` | Kurzor a bal felső sarokba. |
| `UP`, `DOWN`, `LEFT`, `RIGHT` | `0x91`, `0x11`, `0x9d`, `0x1d` | Kurzormozgás. |
| `RVS_ON`, `RVS_OFF` | `0x12`, `0x92` | Inverz be és ki. A RETURN is kikapcsolja az inverzet. |
| `DEL` | `0x14` | Törlés (backspace) a C64-en. |
| `BELL` | `0x07` | Csengő. |
| `LOWERCASE`, `UPPERCASE` | `0x0e`, `0x8e` | Váltás a kisbetűs (szöveges) vagy a nagybetűs (grafikus) karakterkészletre. |
| `LOCK_CASE` | `0x08` | Lezárja a karakterkészletet, így a C= + Shift már nem vált. Ez az ASCII backspace-e, és az Encoder a `0x08`-at kurzor-balra alakítja: nyersen küldd (a `petscii!` így tesz). |
| `CONTROLS` | tömb | A vezérlőbájtok, amelyeket az Encoder változatlanul továbbad a C64-nek. |
| `DEFAULT` | `LIGHT_GREY` | A sima szöveg színe (`\e[0m`). |

### Színek

A C64-nek 16 szövegszíne van. Mindegyik írható közvetlenül, a legtöbb ANSI-ból is elérhető.

| Konstans | Kód | Az ANSI, ami ezt adja |
|---|---|---|
| `WHITE` | `0x05` | `\e[1;37m`, `\e[97m` |
| `RED` | `0x1c` | `\e[31m` |
| `GREEN` | `0x1e` | `\e[32m` |
| `BLUE` | `0x1f` | `\e[34m` |
| `ORANGE` | `0x81` | `\e[33m` (sötétsárga) |
| `BLACK` | `0x90` | (nincs: írd a konstanst) |
| `BROWN` | `0x95` | (nincs: írd a konstanst) |
| `LIGHT_RED` | `0x96` | `\e[1;31m`, `\e[91m` |
| `DARK_GREY` | `0x97` | `\e[30m`, `\e[1;30m`, `\e[90m` (a fekete sötétszürke: egy fekete-szürke door is olvasható maradjon a C64 fekete képernyőjén) |
| `GREY` | `0x98` | `\e[2m`, `\e[2;37m` (halvány) |
| `LIGHT_GREEN` | `0x99` | `\e[1;32m`, `\e[92m` |
| `LIGHT_BLUE` | `0x9a` | `\e[1;34m`, `\e[94m` |
| `LIGHT_GREY` | `0x9b` | `\e[37m`, `\e[0m` (az alapértelmezett) |
| `PURPLE` | `0x9c` | `\e[35m`, `\e[1;35m` |
| `YELLOW` | `0x9e` | `\e[1;33m`, `\e[93m` |
| `CYAN` | `0x9f` | `\e[36m`, `\e[1;36m` |

A `COLORS` mind a 16-ot felsorolja; az `ANSI_DARK` és az `ANSI_BRIGHT` az ANSI-leképezés két sora (0–7. index = fekete, piros, zöld, sárga, kék, magenta, cián, fehér). A félkövér (`1`) a világos sort választja. A háttérszínek (`40`–`47`) és a 256 színű / truecolor kódok (`38;5;n`, `38;2;r;g;b`, `48;…`) elvesznek: a C64-terminál hátterét a terminálprogram adja.

## Az Encoder: mi lesz abból, amit írsz

A `BBS::Petscii::Encoder#encode(data)` egy stringet PETSCII-bájtokká alakít (bináris string). A session hívásonként egy Encodert tart, mert az Encodernek állapota van: a kurzor oszlopa, az aktuális szín, az inverz mód, és a két írás közé eső, félbevágott escape-szekvencia.

| Bemenet | Kimenet a C64-en |
|---|---|
| `A`–`Z` / `a`–`z` | PETSCII-módra felcserélve: `A` → `0xc1`, `a` → `0x41`, így a kisbetűs készletben helyes kis- és nagybetűként jelennek meg. |
| Számjegyek, szóköz, `!"#$%&'()*+,-./:;<=>?@[]` | Változatlanul. |
| Keretrajzoló `─│┌┐└┘├┤┬┴┼` (szimpla, dupla, vastag, lekerekített) | A C64 vonalas karakterei. A kisbetűs karakterkészletben nincs lekerekített sarok, ott a `╭╮╰╯` szögletes `┌┐└┘`-ként látszik; a nagybetűs (grafikus) készletben megvan (`Petscii::ROUNDED_CORNERS`, a `Petscii::Canvas` rajzolja). |
| `█` | Inverz szóköz. A `▀ ▐` inverz félblokk, a `▄ ▌ ▁ ▔` a C64 saját karaktere; a `░▒▓` egy sakktábla-minta, a `▖▗▘▝▚` a C64 negyedblokkjai. |
| `£ ← ↑ ✓` | A C64 saját `£`, `←`, `↑` és pipa karaktere. |
| `\ { } ~ ^ _ \\|` és ``` \| `/`, `(`, `)`, `-`, `↑`, aláhúzás-karakter, függőleges vonal, `'`. |
| Tipográfia `– — ‘ ’ “ ” … · • ● ◄ ► → × °` | A legközelebbi ASCII: `-`, `'`, `"`, `...`, `.`, `*`, `<`, `>`, `x`, `o`. |
| Ékezetes betűk `á é ő ű Ä` | A betű ékezet nélkül (Unicode NFKD, a jelek elhagyva): `á` → `a`, `Ő` → `O`. `ß` → `ss`, `π` → `pi`. |
| Minden más (`€`, emoji, CJK) | `?`. |
| `\r\n` | Egy RETURN (`0x0d`). |
| Magában álló `\n` | Egy RETURN. |
| Magában álló `\r` | Vissza ugyanannak a sornak az elejére (RETURN + kurzor fel). |
| `\t` | Két szóköz. |
| `\b` (`0x08`) | Kurzor balra. |
| `\f` (`0x0c`), `\e[2J` | CLR. |
| `\e[H`, `\e[ROW;COLH`, `\e[…f` | HOME, majd annyi DOWN és RIGHT. |
| `\e[nA` … `\e[nD` | n × UP, DOWN, RIGHT, LEFT. |
| `\e[nG` | RETURN, UP, majd n−1 RIGHT (ugyanannak a sornak az n. oszlopa). |
| `\e[…m` | Szín és inverz (`7` / `27`), lásd a színtáblát. |
| Egyéb escape-szekvenciák (`\e[K`, `\e[?25l`, `\e(B`, …) | Elvesznek, akkor is, ha egy írás kettévágja őket. |
| PETSCII-vezérlőkódok (a konstansok) | Átmennek. |
| Egyéb C0/C1 bájtok, `DEL` (`0x7f`) | Elvesznek. |

**A 40 oszlopos tördelés.** Az Encoder számolja az oszlopokat. Egy pontosan 40 karakteres sornál a C64 magától sort tör; az utána jövő `\r\n`-t ilyenkor lenyeli, így nem keletkezik alatta üres sor.

**Az érvénytelen UTF-8** kódolás előtt `?`-re cserélődik.

Valódi bájtok, `ruby -Ilib`-bel a rubbs 1.0.0-n:

```ruby
require 'bbs'

enc = BBS::Petscii::Encoder.new
hex = ->(s) { s.bytes.map { |b| format('%02x', b) }.join(' ') }

puts hex.(enc.encode("Hello, World!\r\n"))
# c8 45 4c 4c 4f 2c 20 d7 4f 52 4c 44 21 0d
puts hex.(enc.encode("\e[1;33mREADY.\e[0m\r\n"))
# 9e d2 c5 c1 c4 d9 2e 9b 0d          (sárga, READY., világosszürke)
puts hex.(enc.encode("┌──┐\r\n│ok│\r\n└──┘\r\n"))
# b0 c0 c0 ae 0d dd 4f 4b dd 0d ad c0 c0 bd 0d
puts hex.(enc.encode("Árvíztűrő — 3…\r\n"))
# c1 52 56 49 5a 54 55 52 4f 20 2d 20 33 2e 2e 2e 0d
puts hex.(enc.encode("\e[2J\e[3;5Hx"))
# 93 13 11 11 1d 1d 1d 1d 58          (CLR, HOME, 2 x DOWN, 4 x RIGHT, x)
puts hex.(enc.encode("€ ☺"))
# 3f 20 3f
puts hex.(BBS::Petscii::Encoder.new.encode('a' * 40 + "\r\nb"))
# 41 negyvenszer, majd 42: a teli sor után nincs RETURN

e2 = BBS::Petscii::Encoder.new
puts hex.(e2.encode("\e[1;3")) + ' | ' + hex.(e2.encode("2mGo"))
#  | 99 c7 4f                          (a szekvencia a második írásban fejeződik be: világoszöld)
```

## `BBS::Petscii.bytes` és `BBS::Petscii.key`

| Metódus | Mit csinál |
|---|---|
| `BBS::Petscii.bytes(codes)` | Egy PETSCII-kódpontokból (pl. a konstansokból) álló string bájtjai, bináris stringként. Ezzel küldhetsz PETSCII-t az Encoder megkerülésével. |
| `BBS::Petscii.key(byte)` | Egy C64-ről jött bájt billentyűként, ahogy a `readkey` adja vissza: Symbol, egykarakteres String, vagy nil, ha a bájt nem billentyű. PETSCII-vonalon a `readkey` és a `readline` ezt használja. A billentyűbájtok egyetlen táblában vannak, a `Petscii::KEYS`-ben (bájt → billentyű); a régi `KEY_*` konstansok az 1.0-ban megszűntek. |

```ruby
BBS::Petscii.bytes("#{BBS::Petscii::LOWERCASE}#{BBS::Petscii::LOCK_CASE}#{BBS::Petscii::CLEAR}")
# => "\x0E\b\x93"   (0e 08 93: what petscii! sends first)

[0x41, 0xc1, 0x0d, 0x14, 0x03, 0x5f, 0x85, 0x8c, 0x5c, 0xa0, 0x93].map { |b| BBS::Petscii.key(b) }
# => ["a", "A", :enter, :backspace, :escape, :escape, :f1, :f8, "£", " ", nil]
```

Átkapcsolt sessionön nyers PETSCII-t (az Encoder kihagyásával) a socketre írva küldhetsz: `session.line.write(BBS::Petscii.bytes(BBS::Petscii::LOCK_CASE))`.

## A C64 billentyűi

PETSCII-vonalon a `readkey` ezeket adja:

| C64-billentyű | Bájt | `readkey` |
|---|---|---|
| Betű | `0x41`–`0x5a` | kisbetűs String (`"a"`) |
| Shift + betű | `0xc1`–`0xda` (vagy `0x61`–`0x7a`) | nagybetűs String (`"A"`) |
| Számjegyek, szóköz, írásjelek | `0x20`–`0x40`, `[`, `]` | az adott karakter |
| `£` | `0x5c` | `"£"` |
| `↑` | `0x5e` | `"^"` |
| Shift + szóköz | `0xa0` | `" "` |
| RETURN | `0x0d` | `:enter` |
| INST/DEL | `0x14` | `:backspace` |
| Kurzor le / fel | `0x11` / `0x91` | `:down` / `:up` |
| Kurzor jobbra / balra | `0x1d` / `0x9d` | `:right` / `:left` |
| CLR/HOME | `0x13` | `:home` |
| RUN/STOP | `0x03` | `:escape` |
| `←` (bal felső billentyű) | `0x5f` | `:escape` |
| F1 … F8 | `0x85`, `0x89`, `0x86`, `0x8a`, `0x87`, `0x8b`, `0x88`, `0x8c` | `:f1` … `:f8` |
| Minden más (színbillentyűk, CLR, …) |  | kimarad |

C64-en nincs `:tab`, nincs `:ctrl_…`, és nincs egér. Egy mindkét gépen működő board a RETURN-re, a kurzorbillentyűkre, a betűkre és a számjegyekre építsen, és a `:escape`-et vegye „vissza”-nak (a RUN/STOP és a `←` is ezt adja).

A `readline(mask:)` PETSCII-vonalon ugyanezeket a billentyűket fogadja, PETSCII-ben visszhangoz (vagy a maszkot), és az INST/DEL egy teljes karaktert töröl. RETURN-re UTF-8 stringként adja vissza a sort.

# BBS::Terminal: milyen gép hív

A kétarcú boardnak az első képernyője előtt tudnia kell, hogy ANSI-t vagy PETSCII-t beszéljen. A `BBS::Terminal` (`lib/bbs/terminal.rb`) ez az első kérdés, közösen minden C64-es arcú boardnak.

```
MY BOARD

1 - MODERN (ANSI/XTERM, 256 COLORS)
2 - COMMODORE 64 (PETSCII, 40 COLUMNS)

YOUR TERMINAL [2]:
```

- **Sima ASCII, nagybetűkkel.** Egy ANSI-terminál és egy C64 bármelyik karakterkészlettel olvashatóan írja ki (a kisbetűs készletben a nagybetűk kisbetűként látszanak). Minden sor megnevezi, mi megy a vonalon.
- **Az alapértelmezés a tipp**, szögletes zárójelben.
- **Egy billentyű csak kijelöl, a RETURN véglegesít.** A soros módú telnet a számjegyet és a RETURN-t együtt küldi, és a vonalon maradt RETURN az első képernyőn kötne ki. Megváltozott választásnál a prompt új sorban újra megjelenik (a két gép máshogy lép vissza egy karaktert). A Backspace (vagy a C64 DEL-je, ami a vonal átkapcsolása előtt `:ctrl_t`-ként érkezik) törli a választást.
- **Ha nem jön válasz** 60 másodpercig, a választás, vagy ha nincs, a tipp érvényes.

| Konstans | Érték | Jelentés |
|---|---|---|
| `Terminal::SETTLE` | `1.5` | Ennyi másodpercig vár a `guess` a telnet-válaszokra. |
| `Terminal::WAIT` | `60` | Ennyi másodpercig vár a kérdés billentyűre, mielőtt az alapértelmezést veszi. |
| `Terminal::FACES` | hash | Arconként: a számjegy, a felirat és a kijelölő billentyűk. |

| Arc | Számjegy | Kijelölő billentyűk |
|---|---|---|
| `:modern` | `1` | `1`, `a`, `A`, `m`, `M` |
| `:petscii` | `2` | `2`, `c`, `C`, `p`, `P` |

## `Terminal.guess(session)`

```ruby
BBS::Terminal.guess(session)   # => :modern or :petscii
```

Meghívja a `session.settle(timeout: 1.5)`-öt: megvárja a kliens ablakméretét, és eldobja, amit közben gépel. A **40 oszlopnál szélesebb** jelentett ablak `:modern`; a 40 vagy kevesebb oszlop, vagy ha **semmilyen jelentés nem jön** (a WiFi-modem mögötti C64 sima TCP-t beszél), `:petscii`.

## `Terminal.flow(title:, modern:, petscii:)`

```ruby
BBS::Terminal.flow(title: 'My Board', modern: MODERN_FLOW, petscii: PETSCII_FLOW)   # => a BBS::Flow
```

Az egész első lépés egy Flow-ként, a `config.flow`-ba téve:

1. felteszi a kérdést (a `Terminal::Question` az 1.0 óta privát: kérdezni csak a `Terminal.flow`-val lehet); megszakadt vonalnál véget ér a hívás;
2. `:petscii`-nél meghívja a `session.petscii!`-et; `:modern`-nél a `session.enable_mouse`-t, de csak ha a `BBS.config.mouse` be van kapcsolva (a 0.16.1 óta);
3. lefuttatja az adott arc Flow-ját ugyanazzal a contexttel (`ctx[:session_id]` és minden, ami előtte bekerült).

**Az egér kapcsolása.** Az egérjelentés egy escape-szekvenciával indul (`\e[?1002h\e[?1006h`), ami a C64-en szemét lenne. Ezért a kétarcú board `config.mouse = true`-t állít (a modern arc egeret akar) és `config.mouse_on_connect = false`-t (ne kapcsolja be a kérdés előtt); a `Terminal.flow` a modern választás után kapcsolja be.

```ruby
BBS.configure do |config|
  config.mouse            = true
  config.mouse_on_connect = false   # the mouse's escape sequence is garbage on a C64
  config.flow = BBS::Terminal.flow(title: 'My Board', modern: MODERN, petscii: PETSCII)
end
```

Amit a vonal visz (egy NAWS nélkül kapcsolódó szkript, majd RETURN):

```text
board → caller   ff fb 03 ff fb 01 ff fd 03 ff fd 1f          telnet: WILL SGA, WILL ECHO, DO SGA, DO NAWS
                 ...1.5 s, no NAWS from the caller...
board → caller   "\r\n\r\nMY BOARD\r\n\r\n1 - MODERN ...\r\n2 - COMMODORE 64 ...\r\n\r\nYOUR TERMINAL [2]: "
caller → board   0d                                           RETURN takes the guess
board → caller   32 0d 0a                                     "2", new line
board → caller   0e 08 93                                     petscii!: LOWERCASE, LOCK_CASE, CLEAR
                 ...the C64 face, in PETSCII...
```

# A kétarcú board

Egy port, két arc: a hívó válaszol a `Terminal` kérdésére, és vagy

- **modern arcot** kap — általában egy `Application`-t: ablakok, menüsor, egér, 80 vagy több oszlop, 256 szín; vagy
- **C64-es arcot** — egy 40×25-ös PETSCII-képernyőt, ahogy a C64-es boardokon volt: inverz címsor, RETURN-re váró szövegoldalak, számozott listák, egybillentyűs menük.

A két arc a domain-kódon (adatok, service-ek) osztozik, a UI-ból semmin. A házi felépítés, ahogy a Feedia BBS, az Alexandria BBS és a Teletype BBS használja:

```text
bbs.rb                   BBS.configure + BBS::Terminal.flow(modern:, petscii:)
lib/domain/…             the data: feeds, Wikipedia, …   (no UI)
lib/ui/modern.rb         UI::Modern.flow(...)   → a BBS::Flow that runs an Application
lib/ui/petscii.rb        UI::Petscii.flow(...)  → a BBS::Flow with one call step
lib/ui/petscii/screen.rb UI::Petscii::Screen    → the 40×25 screen helpers
```

## Feedia: a bekötés

`bbs/feedia-bbs/bbs.rb` (egy RSS-olvasó):

```ruby
require 'bbs'
require_relative 'lib/domain/feeds'
require_relative 'lib/domain/inbox'
require_relative 'lib/ui/modern'
require_relative 'lib/ui/petscii'

feeds = Feeds.load(ENV.fetch('FEEDIA_FEEDS', File.join(__dir__, 'config/feeds.yml')))
inbox = Inbox.new(path: ENV.fetch('FEEDIA_INBOX', File.join(__dir__, 'data/inbox.jsonl')),
                  webhook: ENV.fetch('FEEDIA_DISCORD_WEBHOOK', ''))

BBS.configure do |config|
  config.mouse            = true
  config.mouse_on_connect = false
  config.idle_seconds     = 600

  config.flow = BBS::Terminal.flow(title: 'Feedia', modern: UI::Modern.flow(feeds, inbox),
                                   petscii: UI::Petscii.flow(feeds, inbox))
end

BBS.start
```

Az Alexandria BBS (`bbs/alexandria-bbs/bbs.rb`, Wikipédia telnetvonalon) ugyanez, a feeds és az inbox helyett `Wikipedia.new(...)`-val és `title: 'Alexandria BBS'`-szel.

## A PETSCII-arc: `BBS::Petscii::Screen`

A PETSCII-arc nem `Application`-t használ: sorokat ír, ahogy egy C64-es board tette. Egy ilyen board képernyője a rubbs része, a `BBS::Petscii::Screen` (0.29 óta); minden board a `lib/ui/petscii/screen.rb`-ben leszármaztat belőle, ha saját metódusok kellenek. Az ötletei:

**Színek ANSI-ként.** A képernyő ANSI-színeket ír, amelyeket a PETSCII-vonal a C64 színeivé alakít. A rubbs szélességszámoló segédei (a `FrameBuffer.wrap_ansi`, az egyetlen sortördelő, és a `clip_ansi`) ismerik az ANSI-t, a PETSCII-vezérlőkódokat viszont szövegnek számolnák, így ANSI-val a tördelés helyes marad. A `BBS::Petscii::Colors` C64-es nevet ad a színeknek: `WH YE CY LG GR DG LB BL GN RD PU OR`, az inverz be- és kikapcsolása `RV`/`RO`. Ezek az egyetlen paletta, a `BBS::Ansi` színeinek C64-es nevei (a `YE` a `BBS::Ansi::BRIGHT_YELLOW`, az `LG` a `BBS::Ansi::GREY`, a szöveg színe).

```ruby
module UI
  module Petscii
    BBS::Petscii::Colors.install(self)   # bare WH, YE, LG … in every class nested here

    class Screen < BBS::Petscii::Screen
      def banner(text) = puts(" #{YE}#{text}#{LG}")   # the board's own methods
    end
  end
end
```

**A metódusai**, mindegyik egy C64-es boardos szokás (alapból 40×25; a `Screen.new(session, cols: 80, rows: 24)` modern terminált is kiszolgál):

| Metódus | Mit csinál |
|---|---|
| `cls` | Törli a képernyőt: C64-en CLR, modern terminálon ANSI. |
| `title(text, right = '')` | Üres képernyő egy inverz cián címsor alatt, balra a `text`, jobbra a `right` (egy darabszám, egy nyelv), 39 oszlop szélesen. |
| `say(text, color: LG)` | 38 oszlopra tördelt szöveg, egy szóközzel beljebb. A szín kulcsszó: `say('Posted.', color: GN)`. |
| `rule(color: DG)` | Vízszintes vonal, 39 oszlop. |
| `key(timeout:)` | `readkey`, bontáskor `IOError`-t dob (így a `call` lépés csendben ér véget). |
| `choose(prompt, keys, back: 'q')` | Egybillentyűs kérdés: a `keys` (kisbetűk) egyikére vár, a RUN/STOP és a `←` a `back`-et adja, a billentyűt nagybetűvel visszhangozza. |
| `ask(prompt, text:, mask:, max:)` | Egy `BBS::Field`-be gépelt sor, levágott szélekkel. |
| `edit(text = '', title:, single_line: false, max: nil)` | A teljes képernyős `BBS::Editor` a képernyő méretében: mentéskor a szöveg, kilépéskor `nil`. |
| `pause(text = 'return: back')` | Bármely billentyűre vár. |
| `page(heading, lines, right:, wait:, numbers:)` | Képernyőnként (20 sor) mutatja a sorokat a címsor alatt; RETURN-re jön a következő, a `q` vagy a RUN/STOP korábban kilép; `numbers: true` mellett a címsorban ott a `2/5`. |
| `pick(heading, items, empty:, right:, commands:) { label }` | Számozott `BBS::Selector.list` a címsor alatt: kurzorral vagy a számmal választ, aztán RETURN; `q`, `←` vagy RUN/STOP esetén `nil`. |
| `menu(items, back:)` | Egy `BBS::Selector` a kurzornál. |
| `actions(*pairs)` | Billentyűk egy sorban, kurzoros menüként: `screen.actions(%w[r read], %w[q back])`. |
| `plain(text)` | C64-en `BBS::Petscii.plain(text)`: a tipográfiai jelek és az ékezetek ASCII-vé válnak, így a sor ott törik, ahol számolták. |

A képernyő minden menüje `BBS::Selector`, az alapszíneivel, a `BBS::Selector::COLORS`-szal (a `text key bar heading about hint` szerepek, a paletta C64-es neveivel). Ha egy board más láblécet szeretne, ebbe fésüli bele a sajátját: `BBS::Selector::COLORS.merge(hint: "#{CY}#{RV}")`.

**A 0.x-ről az 1.0-ra** egy C64-es arc három helyen változik: a `say(text, GN)` és a `rule(DG)` helyett `say(text, color: GN)` és `rule(color: DG)` kell; az `editor(max, title)` helyett `edit(title: title, max: max).to_s` (kilépéskor `nil`-t ad); a `Petscii::Screen::MENU` pedig megszűnt, ezért a `colors: MENU` elhagyható, a `MENU.merge(…)` helyére `BBS::Selector::COLORS.merge(…)` kerül. A `Petscii::Screen.plain` helyett `BBS::Petscii.plain` van. A teljes lista a gem `UPGRADING.md`-jében.

**Az arc flow-ja** egyetlen `call` lépés, amely az egész hívást viszi, és egy második, amely elköszön; az elköszönés elkapja a bontási hibákat, mert egy menü közben bontó hívó ne hagyjon backtrace-t maga után:

```ruby
def self.flow(feeds, inbox)
  BBS::Flow.define do
    call do |ctx, runner|
      Reader.new(feeds, inbox, Screen.new(runner.session), ctx).run
      nil
    end
    call do |_ctx, runner|
      C64.goodbye(Screen.new(runner.session))
      nil
    rescue IOError, Errno::EPIPE, Errno::ECONNRESET
      nil
    end
  end
end
```

A `Reader` maga a board: egy fejléc, egy `F`/`S`/`W`/`Q` menü, a feedek a `pick`-en át, egy elem a `page`-en át (a Markdownja `BBS::Markdown.to_lines(body, width: 38, theme: {...})`-val, a képernyő színeiben). Az Alexandria `Proxy`-ja ugyanilyen felépítésű: egy prompt, amely címet vár, a találatok a `pick`-en, egy szócikk szakaszai a `page`-en át.

**Rajzolás 40 oszlopra.** A logók inverz szóközökből állnak (`RV` + szóközök + `RO`), ezek mindkét gépen ugyanúgy látszanak; a keretrajzoló karakterek és a `█` is rendben vannak, az Encoder leképezi őket. A képernyő a címsort és a vonalakat 39, a szöveget 38 oszlopon tartja; a pontosan 40 karakteres sor is működik, mert az Encoder lenyeli a C64 saját sortörése utáni RETURN-t.

## Egy teljes kétarcú board

Egy kicsi, futtatható board: hírek, modern terminálon egy `Application`, C64-en a rubbs `BBS::Petscii::Screen`-je.

```ruby
require 'bbs'

NEWS = [
  ['Board opens', 'The board is open for callers from today. Call often, and tell your friends.'],
  ['PETSCII face', 'A Commodore 64 gets the same news on 40 columns, in PETSCII.']
].freeze

# The modern face: an Application with a mouse.
MODERN_APP = BBS::Application.define do
  theme BBS::Theme.synchronet
  body do
    NEWS.each_with_index do |(title, text), i|
      add BBS::Widgets::Label.new(text: "#{title}: #{text}", style_key: :text,
                                  bounds: BBS::Widget::Rect.new(3, 3 + i, 74, 1))
    end
  end
  status_hint('F10', 'Log off') { :halt }
end

MODERN = BBS::Flow.define do
  app MODERN_APP
end

# The PETSCII face: a 40x25 screen, the news as a numbered cursor menu.
PETSCII = BBS::Flow.define do
  call do |_ctx, runner|
    screen = BBS::Petscii::Screen.new(runner.session)
    while (item = screen.pick('my board', NEWS, right: 'q: log off', &:first))
      title, text = item
      screen.title(title)
      screen.say(text)
      screen.pause
    end
    nil
  end
end

BBS.configure do |config|
  config.mouse            = true
  config.mouse_on_connect = false
  config.idle_seconds     = 600
  config.flow = BBS::Terminal.flow(title: 'My Board', modern: MODERN, petscii: PETSCII)
end

BBS.start
```

A C64 oldalán a RETURN utáni első képernyő így kezdődik (rubbs 1.0):

```text
0e 08 93            petscii!: lowercase set, case locked, clear
93 9f 12 20 4d 59 20 42 4f 41 52 44 20 … 51 3a 20 4c 4f 47 20 4f 46 46 20 92 9b
                    CLEAR, cyan, RVS ON, " my board … q: log off ", RVS OFF, light grey
9f 12 20 31 20 c2 4f 41 52 44 20 4f 50 45 4e 53 20 … 92 9b 0d
                    the lit item: cyan, RVS ON, " 1 Board opens", RVS OFF
9e 20 32 20 9b d0 c5 d4 d3 c3 c9 c9 20 46 41 43 45 … 0d
                    yellow " 2 ", light grey "PETSCII face"
```

## A PETSCII-arc más módjai

- **40 oszlopra rajzolt Flow.** Az Impostor BBS ugyanazt a `BBS::Flow`-t futtatja mindkét arcon, a PETSCII-arcon a `screens/petscii/` 40 oszlopra rajzolt ERB-képernyőivel: `Impostor::Petscii.flow(sessions: store)`.
- **FakeOS.** Egy 40 oszlopba férő képernyőjű `BBS::FakeOS` változtatás nélkül fut PETSCII-vonalon.
- **Door.** Egy 40 oszlopra rajzolt door C64-es arcról is működik; egy `BBS::Gateway` a PETSCII-hívót továbbadja egy másik boardnak, és mivel nem jelent ablakméretet, a túloldali kétarcú board C64-re tippel (lásd [Doorok](https://wiki.teletypegames.org/hu/engines/rubbs/doors)).

# Lásd még

- [rubbs](https://wiki.teletypegames.org/hu/engines/rubbs) — az áttekintés
- [Telnet, session és store](https://wiki.teletypegames.org/hu/engines/rubbs/telnet-and-store) — `readkey`, `readline`, `settle`, NAWS
- [Flow](https://wiki.teletypegames.org/hu/engines/rubbs/flow) — a `call` lépés és a context
- [Application](https://wiki.teletypegames.org/hu/engines/rubbs/application) — a modern arc
- [Doorok](https://wiki.teletypegames.org/hu/engines/rubbs/doors) — doorok és a `BBS::Gateway`
- [Feedia BBS](https://wiki.teletypegames.org/hu/services/feedia-bbs), [Alexandria BBS](https://wiki.teletypegames.org/hu/services/alexandria-bbs) — a példaként használt boardok

'; var prevLevel = 2; for (var i = 0; i < headings.length; i++) { var h = headings\[i\]; var text = h.textContent.trim(); var slug = text.toLowerCase() .replace(/\[áàâä\]/g,'a').replace(/\[éèêë\]/g,'e').replace(/\[íìîï\]/g,'i') .replace(/\[óòôöő\]/g,'o').replace(/\[úùûüű\]/g,'u') .replace(/\[^a-z0-9\]+/g, '-').replace(/^-|-$/g, ''); if (!slug) slug = 'heading-' + i; if (used\[slug\]) { slug += '-' + i; } used\[slug\] = true; h.id = slug; var level = parseInt(h.tagName.charAt(1)); var cls = level > 2 ? ' class="toc-child"' : ''; html += '- [' + text + '](#'%20+%20slug%20+%20')
'; } html += '
'; toc.innerHTML = html; // Scroll-spy: highlight current heading var links = toc.querySelectorAll('a'); var ids = \[\]; for (var j = 0; j < links.length; j++) ids.push(links\[j\].getAttribute('href').slice(1)); function updateActive() { var current = ''; for (var k = 0; k < ids.length; k++) { var el = document.getElementById(ids\[k\]); if (el && el.getBoundingClientRect().top <= 80) current = ids\[k\]; } for (var m = 0; m < links.length; m++) { links\[m\].parentNode.classList.toggle('active', links\[m\].getAttribute('href') === '#' + current); } } window.addEventListener('scroll', updateActive, { passive: true }); updateActive(); })();

---

## Navigation

- Parent: [RuBBS](https://wiki.teletypegames.org/hu/engines/rubbs.md)
- Previous: [Változásnapló](https://wiki.teletypegames.org/hu/engines/rubbs/changelog.md)
- Next: [Adat és hálózat](https://wiki.teletypegames.org/hu/engines/rubbs/data-and-network.md)
