dopairb 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +190 -0
- data/exe/dopairb +6 -0
- data/lib/dopairb/canvas.rb +164 -0
- data/lib/dopairb/charge_line.rb +107 -0
- data/lib/dopairb/cli.rb +90 -0
- data/lib/dopairb/color.rb +126 -0
- data/lib/dopairb/config.rb +118 -0
- data/lib/dopairb/demo.rb +61 -0
- data/lib/dopairb/director.rb +190 -0
- data/lib/dopairb/font.rb +91 -0
- data/lib/dopairb/fx.rb +154 -0
- data/lib/dopairb/game.rb +182 -0
- data/lib/dopairb/input_fx.rb +499 -0
- data/lib/dopairb/irb_adapter.rb +65 -0
- data/lib/dopairb/output_tap.rb +94 -0
- data/lib/dopairb/probe.rb +159 -0
- data/lib/dopairb/reline_adapter.rb +247 -0
- data/lib/dopairb/scene.rb +154 -0
- data/lib/dopairb/scenes/failure.rb +306 -0
- data/lib/dopairb/scenes/session.rb +146 -0
- data/lib/dopairb/scenes/success.rb +418 -0
- data/lib/dopairb/session.rb +79 -0
- data/lib/dopairb/stage.rb +129 -0
- data/lib/dopairb/term.rb +131 -0
- data/lib/dopairb/version.rb +5 -0
- data/lib/dopairb.rb +126 -0
- data/spec.md +203 -0
- metadata +111 -0
data/lib/dopairb/term.rb
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "io/console"
|
|
4
|
+
require "io/wait"
|
|
5
|
+
|
|
6
|
+
module Dopairb
|
|
7
|
+
# Terminal capabilities and the single writer lock shared by everything
|
|
8
|
+
# that draws transient decorations.
|
|
9
|
+
module Term
|
|
10
|
+
LOCK = Monitor.new
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
attr_writer :out, :input
|
|
14
|
+
# The transient thing on screen that must get out of the way when the
|
|
15
|
+
# user's program writes (a Stage or the ChargeLine). Guarded by LOCK.
|
|
16
|
+
attr_accessor :overlay
|
|
17
|
+
|
|
18
|
+
def out
|
|
19
|
+
@out ||= begin
|
|
20
|
+
io = IO.for_fd(STDOUT.fileno, autoclose: false)
|
|
21
|
+
io.sync = true
|
|
22
|
+
io
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def input
|
|
27
|
+
@input || STDIN
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def interactive?
|
|
31
|
+
return @interactive unless @interactive.nil?
|
|
32
|
+
@interactive = STDIN.tty? && STDOUT.tty? && ENV["TERM"] != "dumb"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def interactive=(v)
|
|
36
|
+
@interactive = v
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def size
|
|
40
|
+
rows, cols = (input.winsize rescue [0, 0])
|
|
41
|
+
rows = (ENV["LINES"] || 24).to_i if rows.to_i <= 0
|
|
42
|
+
cols = (ENV["COLUMNS"] || 80).to_i if cols.to_i <= 0
|
|
43
|
+
[rows, cols]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def rows = size[0]
|
|
47
|
+
def cols = size[1]
|
|
48
|
+
|
|
49
|
+
def depth(config)
|
|
50
|
+
return :none if no_color? || config.color == :none
|
|
51
|
+
return config.color unless config.color == :auto
|
|
52
|
+
ct = ENV["COLORTERM"].to_s
|
|
53
|
+
return :truecolor if ct.include?("truecolor") || ct.include?("24bit")
|
|
54
|
+
return :"256" if ENV["TERM"].to_s.include?("256")
|
|
55
|
+
:"16"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def no_color?
|
|
59
|
+
nc = ENV["NO_COLOR"]
|
|
60
|
+
!(nc.nil? || nc.empty?)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def write(str)
|
|
64
|
+
LOCK.synchronize { out.write(str) }
|
|
65
|
+
rescue IOError, SystemCallError
|
|
66
|
+
nil
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def char_width(ch)
|
|
70
|
+
if defined?(Reline::Unicode)
|
|
71
|
+
Reline::Unicode.get_mbchar_width(ch)
|
|
72
|
+
else
|
|
73
|
+
ch.ord < 0x1100 ? 1 : 2
|
|
74
|
+
end
|
|
75
|
+
rescue StandardError
|
|
76
|
+
1
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def str_width(str)
|
|
80
|
+
if defined?(Reline::Unicode)
|
|
81
|
+
Reline::Unicode.calculate_width(str, true)
|
|
82
|
+
else
|
|
83
|
+
str.gsub(/\e\[[\d;]*[A-Za-z]/, "").each_char.sum { |c| char_width(c) }
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# First candidate that renders one column wide on this terminal.
|
|
88
|
+
def glyph(*candidates)
|
|
89
|
+
@glyphs ||= {}
|
|
90
|
+
@glyphs[candidates] ||= candidates.find { |c| c.ascii_only? || (utf8? && c.each_char.all? { |ch| char_width(ch) == 1 }) } || candidates.last
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def reset_glyphs
|
|
94
|
+
@glyphs = nil
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def utf8?
|
|
98
|
+
return @utf8 unless @utf8.nil?
|
|
99
|
+
@utf8 = Encoding.default_external == Encoding::UTF_8 || ENV.values_at("LC_ALL", "LC_CTYPE", "LANG").compact.any? { |v| v =~ /utf-?8/i }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Keys typed during an effect cut it short; they stay queued for Reline.
|
|
103
|
+
def input_pending?
|
|
104
|
+
if defined?(Reline::IOGate) && Reline::IOGate.respond_to?(:empty_buffer?)
|
|
105
|
+
return true unless Reline::IOGate.empty_buffer?
|
|
106
|
+
end
|
|
107
|
+
input.wait_readable(0) ? true : false
|
|
108
|
+
rescue StandardError
|
|
109
|
+
false
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def raw
|
|
113
|
+
return yield unless input.tty?
|
|
114
|
+
input.raw(intr: true) { yield }
|
|
115
|
+
rescue Errno::ENOTTY, Errno::EINVAL
|
|
116
|
+
yield
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def bell
|
|
120
|
+
write("\a")
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Called (under LOCK) by the output tap before user output hits the terminal.
|
|
124
|
+
def yield_to_output
|
|
125
|
+
ov = overlay
|
|
126
|
+
return if ov.nil? || ov.owner == Thread.current
|
|
127
|
+
ov.yield_to_output
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
data/lib/dopairb.rb
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "monitor"
|
|
4
|
+
require "irb"
|
|
5
|
+
require "reline"
|
|
6
|
+
begin
|
|
7
|
+
require "prism"
|
|
8
|
+
rescue LoadError
|
|
9
|
+
nil
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
require_relative "dopairb/version"
|
|
13
|
+
require_relative "dopairb/config"
|
|
14
|
+
require_relative "dopairb/color"
|
|
15
|
+
require_relative "dopairb/term"
|
|
16
|
+
require_relative "dopairb/canvas"
|
|
17
|
+
require_relative "dopairb/font"
|
|
18
|
+
require_relative "dopairb/fx"
|
|
19
|
+
require_relative "dopairb/stage"
|
|
20
|
+
require_relative "dopairb/game"
|
|
21
|
+
require_relative "dopairb/probe"
|
|
22
|
+
require_relative "dopairb/scene"
|
|
23
|
+
require_relative "dopairb/scenes/success"
|
|
24
|
+
require_relative "dopairb/scenes/failure"
|
|
25
|
+
require_relative "dopairb/scenes/session"
|
|
26
|
+
require_relative "dopairb/director"
|
|
27
|
+
require_relative "dopairb/output_tap"
|
|
28
|
+
require_relative "dopairb/charge_line"
|
|
29
|
+
require_relative "dopairb/input_fx"
|
|
30
|
+
require_relative "dopairb/reline_adapter"
|
|
31
|
+
require_relative "dopairb/irb_adapter"
|
|
32
|
+
require_relative "dopairb/session"
|
|
33
|
+
require_relative "dopairb/demo"
|
|
34
|
+
|
|
35
|
+
# dopairb: IRB where every keystroke, result and exception gets a show.
|
|
36
|
+
#
|
|
37
|
+
# $ dopairb # or, in ~/.irbrc: require "dopairb"; Dopairb.enable
|
|
38
|
+
module Dopairb
|
|
39
|
+
class << self
|
|
40
|
+
attr_reader :session
|
|
41
|
+
|
|
42
|
+
def config
|
|
43
|
+
@config ||= begin
|
|
44
|
+
c = Config.new
|
|
45
|
+
c.apply_string(ENV["DOPAIRB"]) if ENV["DOPAIRB"]
|
|
46
|
+
c
|
|
47
|
+
rescue ArgumentError => e
|
|
48
|
+
warn "dopairb: ignoring DOPAIRB=#{ENV['DOPAIRB'].inspect}: #{e.message}"
|
|
49
|
+
Config.new
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Hook into IRB. Safe to call more than once; options are config settings.
|
|
54
|
+
def enable(intro: nil, **settings)
|
|
55
|
+
settings.each { |k, v| config.set(k, v) }
|
|
56
|
+
config.set(:intro, intro) unless intro.nil?
|
|
57
|
+
@session ||= Session.new(config)
|
|
58
|
+
if IrbAdapter.install
|
|
59
|
+
RelineAdapter.install(@session.fx)
|
|
60
|
+
OutputTap.install
|
|
61
|
+
else
|
|
62
|
+
warn "dopairb: IRB #{defined?(IRB::VERSION) ? IRB::VERSION : '?'} is not supported; running plain IRB"
|
|
63
|
+
end
|
|
64
|
+
@session
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def active?
|
|
68
|
+
@session&.active? || false
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def debug(e)
|
|
72
|
+
return unless ENV["DOPAIRB_DEBUG"]
|
|
73
|
+
File.open(ENV["DOPAIRB_DEBUG"], "a") { |f| f.puts "#{e.class}: #{e.message}", *e.backtrace&.first(8) }
|
|
74
|
+
rescue StandardError
|
|
75
|
+
nil
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
PRESETS = {
|
|
79
|
+
"off" => { intensity: :off },
|
|
80
|
+
"low" => { intensity: :low },
|
|
81
|
+
"normal" => { intensity: :normal },
|
|
82
|
+
"max" => { intensity: :max },
|
|
83
|
+
"calm" => { intensity: :low, flash: :off, duration: 0.7 },
|
|
84
|
+
"party" => { intensity: :max, flash: :full, sound: true, duration: 1.0 },
|
|
85
|
+
}.freeze
|
|
86
|
+
|
|
87
|
+
HELP = <<~TXT
|
|
88
|
+
dopa show settings and session stats
|
|
89
|
+
dopa off|low|normal|max
|
|
90
|
+
dopa calm low intensity, no flash, shorter
|
|
91
|
+
dopa party max intensity, full-screen flash, bell
|
|
92
|
+
dopa demo play every effect once
|
|
93
|
+
dopa KEY=VALUE ... change one setting, e.g. `dopa flash=off duration=0.5`
|
|
94
|
+
|
|
95
|
+
settings:
|
|
96
|
+
TXT
|
|
97
|
+
|
|
98
|
+
def command(arg)
|
|
99
|
+
words = arg.split
|
|
100
|
+
if words.empty? || words == ["status"]
|
|
101
|
+
s = @session&.game
|
|
102
|
+
puts "dopairb #{VERSION} (#{active? ? 'active' : 'inactive: not a terminal or intensity=off'})"
|
|
103
|
+
puts config.describe
|
|
104
|
+
puts " session: #{s.evals} evals, #{s.successes} hits, #{s.failures} errors, max combo #{s.max_combo}, score #{Fx.number_with_commas(s.score)}" if s
|
|
105
|
+
elsif words == ["help"]
|
|
106
|
+
puts HELP + config.describe
|
|
107
|
+
elsif words == ["demo"]
|
|
108
|
+
Demo.run(self)
|
|
109
|
+
elsif words.size == 1 && PRESETS.key?(words[0])
|
|
110
|
+
PRESETS[words[0]].each { |k, v| config.set(k, v) }
|
|
111
|
+
puts "dopairb: #{words[0]}"
|
|
112
|
+
else
|
|
113
|
+
words.each do |w|
|
|
114
|
+
k, v = w.split("=", 2)
|
|
115
|
+
raise ArgumentError, "expected KEY=VALUE, got #{w.inspect}" unless v
|
|
116
|
+
config.set(k, v)
|
|
117
|
+
end
|
|
118
|
+
puts "dopairb: " + words.join(" ")
|
|
119
|
+
end
|
|
120
|
+
nil
|
|
121
|
+
rescue ArgumentError => e
|
|
122
|
+
puts "dopairb: #{e.message}"
|
|
123
|
+
nil
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
data/spec.md
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# dopairb 仕様書(草案)
|
|
2
|
+
|
|
3
|
+
作成日: 2026-09-29
|
|
4
|
+
状態: コンセプト仕様 v0.2
|
|
5
|
+
|
|
6
|
+
## 1. 概要
|
|
7
|
+
|
|
8
|
+
**dopairb** は、入力、評価、結果、例外のすべてを派手な演出で返す、ターミナル上の Ruby 対話環境。コードを書く行為そのものを遊びにする。評価結果が読め、履歴やコピー&ペーストなどの REPL の基本操作も使えることを前提とする。
|
|
9
|
+
|
|
10
|
+
**実装方針:** IRB を拡張する。Ruby の評価、セッション管理、履歴、補完、例外処理は IRB の機能を使い、dopairb は入力・評価・表示の節目に演出を加える。独立した REPL エンジンは作らない。
|
|
11
|
+
|
|
12
|
+
「ドーパミン放出系ゲーム」のように、細かな入力フィードバック、期待を高める間、結果に応じた大きな演出を連続させる。スコアはコードの品質評価ではなく、遊びのための数字である。
|
|
13
|
+
|
|
14
|
+
## 2. 体験の原則
|
|
15
|
+
|
|
16
|
+
1. **毎キー反応する。** 入力・削除・カーソル移動に小さな視覚フィードバックを付ける。通常のタイプ速度を妨げない。
|
|
17
|
+
2. **意味のある節目は一段強く。** 括弧を閉じる、補完を確定する、複数行を完成する、といった瞬間を拾う。
|
|
18
|
+
3. **Enter は見せ場の起点。** 評価の開始、待機、成功、失敗を一続きの演出にする。
|
|
19
|
+
4. **結果とエラーを読める。** アニメーション終了後には、コピーできる通常のテキストを残す。長い値や例外も省略・装飾だけで失わない。
|
|
20
|
+
5. **演出は操作をブロックしない。** 次の入力に入れば演出を即座に収束させる。評価そのものを不必要に遅らせない。
|
|
21
|
+
6. **派手さを選べる。** 初期設定は賑やかにしつつ、音、点滅、動き、速度を個別に調整できる。
|
|
22
|
+
|
|
23
|
+
## 3. 画面構成
|
|
24
|
+
|
|
25
|
+
通常のプロンプトと出力に、演出用の一時的な領域を加える。
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
?─ DOPA IRB ─────────── COMBO 07 SCORE 12,480 ─?
|
|
29
|
+
│ │
|
|
30
|
+
│ dopairb> (1..100).sum │
|
|
31
|
+
│ │
|
|
32
|
+
│ ? PERFECT! ? │
|
|
33
|
+
│ 5,050 │
|
|
34
|
+
│ +720 SCORE │
|
|
35
|
+
│ => 5050 │
|
|
36
|
+
?─────────────────────────────────────────────────?
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
これは画面イメージであり、枠の常時表示は必須ではない。狭い端末ではプロンプトと結果を優先し、HUD や装飾を縮小する。評価値は最後に `=> 5050` のような通常のテキストとして残す。
|
|
40
|
+
|
|
41
|
+
## 4. 演出イベント
|
|
42
|
+
|
|
43
|
+
各イベントは「トリガー → 一時的な演出 → 残す情報」で定義する。入力中の演出は評価済みの Ruby コードを変更しない。
|
|
44
|
+
|
|
45
|
+
### 4.1 入力中
|
|
46
|
+
|
|
47
|
+
| イベント | 演出案 | 強さ |
|
|
48
|
+
| --- | --- | --- |
|
|
49
|
+
| 文字キーを押す | カーソル付近に火花、短い残像、チャージゲージ加算 | 小 |
|
|
50
|
+
| 連続入力 | タイピングコンボと倍率上昇。入力間隔が空けば自然に終了 | 小 |
|
|
51
|
+
| 間を置いて入力再開 | 「CHARGE」から再点火 | 小 |
|
|
52
|
+
| Backspace / Delete | 消した文字が砕ける。直後の再入力に「RECOVERY」 | 小 |
|
|
53
|
+
| カーソル移動 | 移動方向への短い光跡 | 小 |
|
|
54
|
+
| 対応する括弧を閉じる | 括弧の対を発光させ「NICE!」 | 中 |
|
|
55
|
+
| 文字列・ブロックを閉じる | 閉じた範囲を短くハイライト | 中 |
|
|
56
|
+
| 補完を確定する | 候補が入力行に吸い込まれ「LOCK ON」 | 中 |
|
|
57
|
+
| 履歴を呼び出す | 過去の式が巻き戻るように現れる | 中 |
|
|
58
|
+
| 複数行を継続する | 行が増えるごとにチャージを一段上げる | 中 |
|
|
59
|
+
|
|
60
|
+
括弧・文字列・ブロックの判定は、入力エディタが把握できる範囲に限る。誤判定で評価内容を変えたり、入力を止めたりしない。
|
|
61
|
+
|
|
62
|
+
### 4.2 評価と待機
|
|
63
|
+
|
|
64
|
+
| イベント | 演出案 | 強さ |
|
|
65
|
+
| --- | --- | --- |
|
|
66
|
+
| Enter で評価確定 | 入力行が発光し「EXECUTE」。短い溜め | 大 |
|
|
67
|
+
| 即時に評価完了 | 溜めを短縮して結果に着弾 | 大 |
|
|
68
|
+
| 評価が継続 | 経過時間と穏やかなチャージ表示 | 中 |
|
|
69
|
+
| 長時間実行 | 動きの少ない待機表示に移行し、時間を更新 | 小 |
|
|
70
|
+
| Ctrl-C などで中断 | チャージが散り「INTERRUPTED」 | 大 |
|
|
71
|
+
|
|
72
|
+
評価開始演出は処理時間を隠さない。速い評価のために待ち時間を追加する場合は、ごく短い上限を設け、設定で無効化できるようにする。
|
|
73
|
+
|
|
74
|
+
### 4.3 成功と結果
|
|
75
|
+
|
|
76
|
+
| 結果・状況 | 演出案 | 残す情報 |
|
|
77
|
+
| --- | --- | --- |
|
|
78
|
+
| 通常の値 | 値が着地して `=>` が光る | 値の通常表示 |
|
|
79
|
+
| `nil` | 空振りと煙 | `=> nil` |
|
|
80
|
+
| `true` / `false` | 「YES!」/「NO!」の判定スタンプ | `=> true` / `=> false` |
|
|
81
|
+
| 大きな数 | 桁が増えるカウントアップ、倍率と衝撃波 | 正確な数値 |
|
|
82
|
+
| 前回の数値より大きい | 「NEW RECORD」 | 正確な数値 |
|
|
83
|
+
| 長い String | 長さのカウントアップ、文字が流れ込む | 通常の `inspect` 表示 |
|
|
84
|
+
| Array / Hash | 要素数の表示、内容が順に展開 | 通常の `inspect` 表示 |
|
|
85
|
+
| 定義操作 | 「NEW ABILITY」などのアンロック風表示 | Ruby の通常の評価結果 |
|
|
86
|
+
| 連続成功 | COMBO の更新、色とサイズの段階的変化 | 評価結果 |
|
|
87
|
+
| 標準出力が大量 | 「OUTPUT RAIN」と出力行数 | 元の標準出力すべて |
|
|
88
|
+
|
|
89
|
+
型ごとの演出は見せ方の差であり、`inspect` を演出に置き換えない。`inspect` が例外を出す・極端に長い・遅い場合も、通常の REPL と同等以上に操作を妨げないことを目標とする。「定義操作」の識別は初期版では無理に一般化しない。
|
|
90
|
+
|
|
91
|
+
### 4.4 エラーと復帰
|
|
92
|
+
|
|
93
|
+
| イベント | 演出案 | 残す情報 |
|
|
94
|
+
| --- | --- | --- |
|
|
95
|
+
| 構文エラー | 入力行に亀裂、赤い「SYNTAX BREAK」 | エラー本文と位置 |
|
|
96
|
+
| `NameError` | 該当する名前を照らし「UNKNOWN SYMBOL」 | 例外名・メッセージ |
|
|
97
|
+
| `NoMethodError` | レシーバとメソッドの接続が切れる | 例外名・メッセージ |
|
|
98
|
+
| `TypeError` / `ArgumentError` | 要素が衝突して弾ける | 例外名・メッセージ |
|
|
99
|
+
| その他の例外 | 短い赤のフラッシュと「FAILED」 | 例外名・メッセージ・バックトレース |
|
|
100
|
+
| 連続エラー | 失敗回数を更新。通常コンボは切れる | 各回のエラー全文 |
|
|
101
|
+
| エラー後の次の成功 | 「FIXED!」「COMEBACK!」を大きく表示 | 成功結果 |
|
|
102
|
+
|
|
103
|
+
失敗演出は派手にするが、例外を笑いものにする文言は避ける。エラー位置、本文、バックトレースは可読な通常テキストとして残し、表示順序を崩さない。
|
|
104
|
+
|
|
105
|
+
### 4.5 セッション
|
|
106
|
+
|
|
107
|
+
| イベント | 演出案 |
|
|
108
|
+
| --- | --- |
|
|
109
|
+
| 起動 | タイトル表示と短いイントロ |
|
|
110
|
+
| 初回評価 | 「FIRST HIT」 |
|
|
111
|
+
| 評価回数の節目 | 10、100、1000 回などでマイルストーン |
|
|
112
|
+
| 無操作 | HUD が冷める、待機モーション |
|
|
113
|
+
| 終了 | 評価回数、成功数、例外数、最大コンボ、滞在時間のリザルト画面 |
|
|
114
|
+
|
|
115
|
+
## 5. COMBO・CHARGE・SCORE
|
|
116
|
+
|
|
117
|
+
- **CHARGE:** キー入力と入力上の節目で増える。その評価が完了すると消費する。一文字を大量入力するだけで無限に増えないよう上限と逓減を設ける。
|
|
118
|
+
- **COMBO:** 連続した評価成功数。例外・構文エラーで終了。`nil`、`false` も正常終了なので継続する。
|
|
119
|
+
- **SCORE:** 入力、完了、復帰、連鎖の演出用ポイント。コードの正しさ、難しさ、性能を採点しない。長いコードや巨大出力を過度に優遇しない。
|
|
120
|
+
- **COMEBACK:** 直前の評価がエラーで、次の評価が成功した場合に発火する。エラーを直したかどうかを厳密に推論したものではないため、「FIXED!」は演出上の表現として扱う。
|
|
121
|
+
|
|
122
|
+
## 6. 演出の強度と優先順位
|
|
123
|
+
|
|
124
|
+
強度は **小(毎キー)→ 中(構文上の節目)→ 大(評価完了)→ 特大(記録・復帰)**。同時に複数発火した場合は大きいイベントを優先し、小さい演出を省略または合成する。キューに積んであとから大量再生しない。
|
|
125
|
+
|
|
126
|
+
設定の候補:
|
|
127
|
+
|
|
128
|
+
| 設定 | 内容 |
|
|
129
|
+
| --- | --- |
|
|
130
|
+
| `intensity` | `off` / `low` / `normal` / `max`。初期値 `normal` |
|
|
131
|
+
| `motion` | アニメーションの有無と長さ |
|
|
132
|
+
| `flash` | 全画面フラッシュの有無。初期値は控えめ |
|
|
133
|
+
| `sound` | 端末ベル等を使う場合の明示的な有効化。初期値 `off` |
|
|
134
|
+
| `hud` | COMBO・CHARGE・SCORE の表示 |
|
|
135
|
+
| `duration` | 各演出時間の倍率 |
|
|
136
|
+
| `color` | 色数と配色。モノクロでも意味が分かる表示 |
|
|
137
|
+
|
|
138
|
+
ランタイム中に演出を切り替える操作を用意する。点滅を抑える設定は初回起動から選べるようにする。
|
|
139
|
+
|
|
140
|
+
## 7. 入出力・互換性の要件
|
|
141
|
+
|
|
142
|
+
- 評価する Ruby コードと評価順序を変えない。`puts`、警告、例外、評価値を取り違えない。
|
|
143
|
+
- Ruby プログラムが標準出力・標準エラーへ直接書いた内容は失わず、原則として発生順に見せる。演出との競合時はプログラムの出力を優先する。
|
|
144
|
+
- アニメーション終了後のスクロールバックに、入力行、実際の出力、評価結果または例外をテキストとして残す。コピー、検索、ログ保存ができる。
|
|
145
|
+
- 端末幅変更、狭い画面、色なし端末、非対話的な起動では演出を縮退させる。`NO_COLOR` などの一般的な色抑制要求を尊重する。
|
|
146
|
+
- 貼り付けは一括入力として扱い、貼り付けた全文字に毎キー演出を再生しない。
|
|
147
|
+
- 入力中の連続描画は間引き、文字のエコーとカーソル位置を壊さない。
|
|
148
|
+
- ユーザーのコードによる端末制御、別スレッドからの出力、非常に長い表示など、制御できない描画があった場合は装飾を諦めて読める出力に戻す。
|
|
149
|
+
- 非対話環境で演出用の制御文字を標準出力へ混ぜない。
|
|
150
|
+
|
|
151
|
+
### IRB 拡張としての境界
|
|
152
|
+
|
|
153
|
+
- IRB の評価処理を複製しない。dopairb が追加する状態は主に演出状態(COMBO、CHARGE、SCORE など)に限定する。
|
|
154
|
+
- 入力中の毎キー演出は IRB が通常使用する Reline の編集・描画との連携点を利用する。Reline の入力内容、カーソル位置、履歴、補完を dopairb 側で再実装しない。
|
|
155
|
+
- 評価開始、正常終了、例外、結果表示は IRB 側の処理段階に合わせて発火させる。フックが足りない箇所は対象の IRB/Reline バージョンで調査し、必要なら上流への拡張点の提案を検討する。
|
|
156
|
+
- IRB の `binding.irb` 等から起動したセッションへの対応範囲は別途決める。最初は通常の対話起動を対象とする。
|
|
157
|
+
- IRB/Reline の内部実装への依存は一箇所に隔離し、対応するバージョン範囲を明記する。公開された拡張点で可能な部分を優先する。
|
|
158
|
+
|
|
159
|
+
## 8. 状態遷移
|
|
160
|
+
|
|
161
|
+
```mermaid
|
|
162
|
+
stateDiagram-v2
|
|
163
|
+
[*] --> Input
|
|
164
|
+
Input --> Running: Enter で評価
|
|
165
|
+
Running --> Success: 正常終了
|
|
166
|
+
Running --> Failure: 例外・構文エラー
|
|
167
|
+
Running --> Input: 中断
|
|
168
|
+
Success --> Input: 結果を確定
|
|
169
|
+
Failure --> Input: エラーを確定
|
|
170
|
+
Input --> [*]: 終了
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
入力中は小演出を随時発火する。成功または失敗の演出中に次の入力が来たら、残りの演出を短縮して `Input` に戻る。評価中にユーザーコードが出力した場合、その出力が見えることを優先する。
|
|
174
|
+
|
|
175
|
+
## 9. 初期版の範囲
|
|
176
|
+
|
|
177
|
+
最初に体験の核を確かめる範囲:
|
|
178
|
+
|
|
179
|
+
1. 毎キーの軽いエフェクト、削除、括弧対応、Enter 演出。
|
|
180
|
+
2. 成功・例外・中断の異なる演出と、読みやすい最終テキスト。
|
|
181
|
+
3. COMBO と SCORE、エラー後の COMEBACK。
|
|
182
|
+
4. ターミナル幅と色数に応じた縮退、貼り付けの抑制、演出オフ。
|
|
183
|
+
|
|
184
|
+
その後に補完・履歴・型別演出・セッションリザルト・サウンドを追加する。入力中の演出には Reline の行編集との深い連携が要るため、具体的なフックと描画方式は対象バージョンの IRB/Reline を調べてから決める。最初の技術検証では、文字入力のたびに描画しても編集状態や補完を壊さないことを確認する。
|
|
185
|
+
|
|
186
|
+
## 10. 受け入れ条件
|
|
187
|
+
|
|
188
|
+
- 普通に式を入力して Enter を押すと、入力中・評価開始・成功結果で異なる強度の演出が見える。
|
|
189
|
+
- 構文エラーと実行時例外に失敗演出が出て、例外本文とバックトレースを読める。
|
|
190
|
+
- 失敗の直後に成功すると COMEBACK が出る。
|
|
191
|
+
- 高速入力・長文貼り付け中にキーを取りこぼさず、入力内容が変化しない。
|
|
192
|
+
- 評価結果とプログラムの出力をコピーでき、演出を切れば通常の REPL として使える。
|
|
193
|
+
- 狭い端末、色なし端末、リダイレクト先に読めない制御シーケンスを残さない。
|
|
194
|
+
|
|
195
|
+
## 11. 未決定事項
|
|
196
|
+
|
|
197
|
+
- 常時 HUD を画面上部に固定するか、プロンプト周辺の一時表示にするか。
|
|
198
|
+
- `puts` などの評価途中の出力をどの範囲まで捕捉・装飾するか。
|
|
199
|
+
- スコアをセッション内だけにするか、記録を保存するか。
|
|
200
|
+
- 演出素材を Unicode 文字中心にするか、端末によって別の表現を選ぶか。
|
|
201
|
+
- gem の起動コマンドと、通常の `irb` や `.irbrc` から有効化する方法。
|
|
202
|
+
- IRB/Reline の対象バージョンと、毎キー描画に必要な拡張点。
|
|
203
|
+
|
metadata
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: dopairb
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Koichi Sasada
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: irb
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.14'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '2'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '1.14'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '2'
|
|
32
|
+
- !ruby/object:Gem::Dependency
|
|
33
|
+
name: reline
|
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - ">="
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '0.5'
|
|
39
|
+
- - "<"
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: '0.8'
|
|
42
|
+
type: :runtime
|
|
43
|
+
prerelease: false
|
|
44
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - ">="
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '0.5'
|
|
49
|
+
- - "<"
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: '0.8'
|
|
52
|
+
description: 'dopairb extends IRB with game-like terminal effects: sparks while typing,
|
|
53
|
+
a wind-up and impact on every evaluation, big banners for combos and comebacks,
|
|
54
|
+
and readable plain text left behind for copy & paste.'
|
|
55
|
+
email:
|
|
56
|
+
- ko1@atdot.net
|
|
57
|
+
executables:
|
|
58
|
+
- dopairb
|
|
59
|
+
extensions: []
|
|
60
|
+
extra_rdoc_files: []
|
|
61
|
+
files:
|
|
62
|
+
- LICENSE
|
|
63
|
+
- README.md
|
|
64
|
+
- exe/dopairb
|
|
65
|
+
- lib/dopairb.rb
|
|
66
|
+
- lib/dopairb/canvas.rb
|
|
67
|
+
- lib/dopairb/charge_line.rb
|
|
68
|
+
- lib/dopairb/cli.rb
|
|
69
|
+
- lib/dopairb/color.rb
|
|
70
|
+
- lib/dopairb/config.rb
|
|
71
|
+
- lib/dopairb/demo.rb
|
|
72
|
+
- lib/dopairb/director.rb
|
|
73
|
+
- lib/dopairb/font.rb
|
|
74
|
+
- lib/dopairb/fx.rb
|
|
75
|
+
- lib/dopairb/game.rb
|
|
76
|
+
- lib/dopairb/input_fx.rb
|
|
77
|
+
- lib/dopairb/irb_adapter.rb
|
|
78
|
+
- lib/dopairb/output_tap.rb
|
|
79
|
+
- lib/dopairb/probe.rb
|
|
80
|
+
- lib/dopairb/reline_adapter.rb
|
|
81
|
+
- lib/dopairb/scene.rb
|
|
82
|
+
- lib/dopairb/scenes/failure.rb
|
|
83
|
+
- lib/dopairb/scenes/session.rb
|
|
84
|
+
- lib/dopairb/scenes/success.rb
|
|
85
|
+
- lib/dopairb/session.rb
|
|
86
|
+
- lib/dopairb/stage.rb
|
|
87
|
+
- lib/dopairb/term.rb
|
|
88
|
+
- lib/dopairb/version.rb
|
|
89
|
+
- spec.md
|
|
90
|
+
homepage: https://github.com/ko1/dopairb
|
|
91
|
+
licenses:
|
|
92
|
+
- MIT
|
|
93
|
+
metadata: {}
|
|
94
|
+
rdoc_options: []
|
|
95
|
+
require_paths:
|
|
96
|
+
- lib
|
|
97
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
98
|
+
requirements:
|
|
99
|
+
- - ">="
|
|
100
|
+
- !ruby/object:Gem::Version
|
|
101
|
+
version: '3.3'
|
|
102
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
103
|
+
requirements:
|
|
104
|
+
- - ">="
|
|
105
|
+
- !ruby/object:Gem::Version
|
|
106
|
+
version: '0'
|
|
107
|
+
requirements: []
|
|
108
|
+
rubygems_version: 4.0.6
|
|
109
|
+
specification_version: 4
|
|
110
|
+
summary: IRB where every keystroke, result and exception gets a show
|
|
111
|
+
test_files: []
|