pwn 0.5.760 → 0.5.761

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.
@@ -105,6 +105,8 @@ module PWN
105
105
 
106
106
  # Read a duplicated terminal rather than stdin, which belongs to tools.
107
107
  class Keyboard
108
+ attr_writer :interval
109
+
108
110
  KEYS = {
109
111
  "\e[A" => :up, "\e[B" => :down, "\e[C" => :right, "\e[D" => :left,
110
112
  "\eOA" => :up, "\eOB" => :down, "\eOC" => :right, "\eOD" => :left,
@@ -120,7 +122,7 @@ module PWN
120
122
  end
121
123
 
122
124
  def call
123
- if @source.wait_readable(@buffer.empty? ? 0.04 : 0)
125
+ if @source.wait_readable(@buffer.empty? ? (@interval || 0.04) : 0)
124
126
  chunk = @source.read_nonblock(4096, exception: false)
125
127
  return :eof if chunk.nil? && @buffer.empty?
126
128
 
@@ -402,6 +404,7 @@ module PWN
402
404
  break if @leaving && !busy?
403
405
 
404
406
  draw
407
+ @getch.interval = @focus == :animation && %i[asteroids galaga frogger].include?(@banner_name) ? 0.01 : 0.04 if @getch.is_a?(Keyboard)
405
408
  handle(@getch.call)
406
409
  rescue Interrupt
407
410
  cancel
@@ -690,7 +693,7 @@ module PWN
690
693
 
691
694
  return clear_view if key == "\u000c"
692
695
 
693
- if ["\u0007", "\u0013"].include?(key)
696
+ if key == "\u0013" || (key == "\u0007" && (@focus != :animation || @workspace))
694
697
  @workspace ? @workspace = nil : open_swarm
695
698
  return
696
699
  end
@@ -801,12 +804,22 @@ module PWN
801
804
  end
802
805
 
803
806
  def handle_animation(key)
804
- if key == :paste_start
807
+ if key == "\u0007"
808
+ names = PWN::Banner.mini_names
809
+ @banner_name = names[(names.index(@banner_name).to_i + 1) % names.length]
810
+ @banner_frame_seconds = PWN::Banner.mini_frame_seconds(name: @banner_name)
811
+ @banner_started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
812
+ @banner_game = @banner_game_key = @banner_game_tick = nil
813
+ elsif key == :paste_start
805
814
  @focus = :mission
806
815
  @paste = true
807
816
  @banner_game&.controls
808
817
  elsif [:up, :down, :left, :right, ' '].include?(key)
818
+ return if %i[asteroids galaga frogger].include?(@banner_name) && @banner_game_frame&.dig(:state, :explosion).to_i.positive?
819
+
809
820
  @banner_game&.key(key)
821
+ # Paint a discrete control immediately, not after the next idle tick.
822
+ @banner_game_tick = nil if %i[asteroids galaga frogger].include?(@banner_name)
810
823
  elsif ["\r", "\n", "\e", :newline].include?(key)
811
824
  @focus = :mission
812
825
  @banner_game&.controls
@@ -1053,7 +1066,7 @@ module PWN
1053
1066
  ['REQUEST', state, "Elapsed #{elapsed}", "Completed tools #{@completed_tools}",
1054
1067
  "Events #{@event_count}", '', 'TOKENS', "#{usage[:input_tokens]} in · #{usage[:output_tokens]} out",
1055
1068
  "cached #{usage[:cached_tokens]} · calls #{usage[:calls]}", "Cost #{cost}",
1056
- '', 'SWARM', swarm[:swarm_id] || 'none', "#{active} active · ^G swarm",
1069
+ '', 'SWARM', swarm[:swarm_id] || 'none', "#{active} active · ^S swarm",
1057
1070
  '', 'VIEW', @verbose ? 'verbose' : 'compact · /verbose', scroll_status,
1058
1071
  '', 'LAST TOOL', @last_tool || 'not observed']
1059
1072
  end
@@ -1226,6 +1239,7 @@ module PWN
1226
1239
  cadence = cells ? @banner_frame_seconds : PWN::Banner::MINI_FRAME_SECONDS
1227
1240
  frame = ((now - @banner_started) / cadence).floor % PWN::Banner::MINI_FRAME_COUNT
1228
1241
  return playable_banner(size, now) if cells && @focus == :animation
1242
+ return automatic_banner(size, now) if cells && size >= 5 && %i[pacman galaga frogger].include?(@banner_name)
1229
1243
  return PWN::Banner.mini_cells(name: @banner_name, frame: frame, width: size, height: size, seed: @banner_seed, branding: false) if cells
1230
1244
 
1231
1245
  art = PWN::Banner.mini_frame(name: @banner_name, frame: frame, width: size, height: size, branding: false)
@@ -1235,6 +1249,24 @@ module PWN
1235
1249
  canvas
1236
1250
  end
1237
1251
 
1252
+ def automatic_banner(size, now)
1253
+ key = [@banner_session, @banner_name, size, @banner_seed]
1254
+ if @banner_demo_key != key
1255
+ @banner_demo_key = key
1256
+ @banner_demo = PWN::Banner::MiniGame.new(name: @banner_name, width: size, height: size, seed: @banner_seed, demo: true)
1257
+ @banner_demo.step
1258
+ @banner_demo_tick = nil
1259
+ end
1260
+ tick = (now / @banner_frame_seconds).floor
1261
+ if @banner_demo_tick != tick
1262
+ # Advance one frame, never build 1800 or catch up after a modal.
1263
+ @banner_demo_frame = @banner_demo.step
1264
+ @banner_demo_cells = @banner_demo.cells(@banner_demo_frame)
1265
+ @banner_demo_tick = tick
1266
+ end
1267
+ @banner_demo_cells
1268
+ end
1269
+
1238
1270
  def playable_banner(size, now)
1239
1271
  key = [@banner_session, @banner_name, size, @banner_seed]
1240
1272
  if @banner_game_key != key
@@ -1242,9 +1274,13 @@ module PWN
1242
1274
  @banner_game = PWN::Banner::MiniGame.new(name: @banner_name, width: size, height: size, seed: @banner_seed)
1243
1275
  @banner_game_tick = nil
1244
1276
  end
1245
- tick = (now / @banner_frame_seconds).floor
1277
+ # Human Snake input needs a full reaction interval on its tiny board;
1278
+ # decorative replay cadence is independent of player movement.
1279
+ cadence = @banner_name == :snake ? 0.2 : @banner_frame_seconds
1280
+ tick = (now / cadence).floor
1246
1281
  if @banner_game_tick != tick
1247
- @banner_game_cells = @banner_game.cells(@banner_game.step)
1282
+ @banner_game_frame = @banner_game.step
1283
+ @banner_game_cells = @banner_game.cells(@banner_game_frame)
1248
1284
  @banner_game_tick = tick
1249
1285
  end
1250
1286
  @banner_game_cells
@@ -1308,9 +1344,10 @@ module PWN
1308
1344
  def footer_text
1309
1345
  text = '/ menu · ^C=cancel · ^D=back · ^L=clear · ^T=toggle pane · ^S=swarm · ^R=search ^O=operations · ↑↓ history · Shift+Enter=newline · Enter=send'
1310
1346
  if @focus == :animation
1311
- controls = { pong: '↑↓ paddle', snake: '↑↓←→ steer', falling_blocks: '←→ move · ↓ faster · Space=rotate',
1312
- asteroids: '←→ turn · ↑↓ thrust/reverse · Space=fire' }.fetch(@banner_name, '↑↓←→ play')
1313
- return text.sub('↑↓ history', controls).sub('Shift+Enter=newline · Enter=send', 'Enter/Esc=draft')
1347
+ controls = { pacman: '↑↓←→ start/steer · ● power', snake: '↑↓←→ start/steer', falling_blocks: '←→ move · ↓ faster · Space=rotate',
1348
+ asteroids: '←→ turn · ↑↓ thrust/reverse · Space=fire', galaga: '↑↓←→ move · Space=fire',
1349
+ frogger: '↑↓←→ hop · logs → goals' }.fetch(@banner_name, '↑↓←→ play')
1350
+ return text.sub('↑↓ history', "^G=next game · #{controls}").sub('Shift+Enter=newline · Enter=send', 'Enter/Esc=draft')
1314
1351
  end
1315
1352
  @focus == :session ? text.sub('↑↓ history', '↑↓ scroll · HOME · PGUP · PGDN · END') : text
1316
1353
  end
data/lib/pwn/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PWN
4
- VERSION = '0.5.760'
4
+ VERSION = '0.5.761'
5
5
  end