fantasy-cli 1.2.6
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 +456 -0
- data/bin/gsd +8 -0
- data/bin/gsd-core-darwin-amd64 +0 -0
- data/bin/gsd-core-darwin-arm64 +0 -0
- data/bin/gsd-core-linux-amd64 +0 -0
- data/bin/gsd-core-linux-arm64 +0 -0
- data/bin/gsd-core-windows-amd64.exe +0 -0
- data/bin/gsd-core-windows-arm64.exe +0 -0
- data/bin/gsd-core.exe +0 -0
- data/lib/gsd/agents/coordinator.rb +195 -0
- data/lib/gsd/agents/task_manager.rb +158 -0
- data/lib/gsd/agents/worker.rb +162 -0
- data/lib/gsd/agents.rb +30 -0
- data/lib/gsd/ai/chat.rb +486 -0
- data/lib/gsd/ai/cli.rb +248 -0
- data/lib/gsd/ai/command_parser.rb +97 -0
- data/lib/gsd/ai/commands/base.rb +42 -0
- data/lib/gsd/ai/commands/clear.rb +20 -0
- data/lib/gsd/ai/commands/context.rb +30 -0
- data/lib/gsd/ai/commands/cost.rb +30 -0
- data/lib/gsd/ai/commands/export.rb +42 -0
- data/lib/gsd/ai/commands/help.rb +61 -0
- data/lib/gsd/ai/commands/model.rb +67 -0
- data/lib/gsd/ai/commands/reset.rb +22 -0
- data/lib/gsd/ai/config.rb +256 -0
- data/lib/gsd/ai/context.rb +324 -0
- data/lib/gsd/ai/cost_tracker.rb +361 -0
- data/lib/gsd/ai/git_context.rb +169 -0
- data/lib/gsd/ai/history.rb +384 -0
- data/lib/gsd/ai/providers/anthropic.rb +429 -0
- data/lib/gsd/ai/providers/base.rb +282 -0
- data/lib/gsd/ai/providers/lmstudio.rb +279 -0
- data/lib/gsd/ai/providers/ollama.rb +336 -0
- data/lib/gsd/ai/providers/openai.rb +396 -0
- data/lib/gsd/ai/providers/openrouter.rb +429 -0
- data/lib/gsd/ai/reference_resolver.rb +225 -0
- data/lib/gsd/ai/repl.rb +349 -0
- data/lib/gsd/ai/streaming.rb +438 -0
- data/lib/gsd/ai/ui.rb +429 -0
- data/lib/gsd/buddy/cli.rb +284 -0
- data/lib/gsd/buddy/gacha.rb +148 -0
- data/lib/gsd/buddy/renderer.rb +108 -0
- data/lib/gsd/buddy/species.rb +190 -0
- data/lib/gsd/buddy/stats.rb +156 -0
- data/lib/gsd/buddy.rb +28 -0
- data/lib/gsd/cli.rb +455 -0
- data/lib/gsd/commands.rb +198 -0
- data/lib/gsd/config.rb +183 -0
- data/lib/gsd/error.rb +188 -0
- data/lib/gsd/frontmatter.rb +123 -0
- data/lib/gsd/go/bridge.rb +173 -0
- data/lib/gsd/history.rb +76 -0
- data/lib/gsd/milestone.rb +75 -0
- data/lib/gsd/output.rb +184 -0
- data/lib/gsd/phase.rb +102 -0
- data/lib/gsd/plugins/base.rb +92 -0
- data/lib/gsd/plugins/cli.rb +330 -0
- data/lib/gsd/plugins/config.rb +164 -0
- data/lib/gsd/plugins/hooks.rb +132 -0
- data/lib/gsd/plugins/installer.rb +158 -0
- data/lib/gsd/plugins/loader.rb +122 -0
- data/lib/gsd/plugins/manager.rb +187 -0
- data/lib/gsd/plugins/marketplace.rb +142 -0
- data/lib/gsd/plugins/sandbox.rb +114 -0
- data/lib/gsd/plugins/search.rb +131 -0
- data/lib/gsd/plugins/validator.rb +157 -0
- data/lib/gsd/plugins.rb +48 -0
- data/lib/gsd/profile.rb +127 -0
- data/lib/gsd/research.rb +85 -0
- data/lib/gsd/roadmap.rb +90 -0
- data/lib/gsd/skills/bundled/commit.md +58 -0
- data/lib/gsd/skills/bundled/debug.md +28 -0
- data/lib/gsd/skills/bundled/explain.md +41 -0
- data/lib/gsd/skills/bundled/plan.md +42 -0
- data/lib/gsd/skills/bundled/verify.md +26 -0
- data/lib/gsd/skills/loader.rb +189 -0
- data/lib/gsd/state.rb +102 -0
- data/lib/gsd/template.rb +106 -0
- data/lib/gsd/tools/ask_user_question.rb +179 -0
- data/lib/gsd/tools/base.rb +204 -0
- data/lib/gsd/tools/bash.rb +246 -0
- data/lib/gsd/tools/file_edit.rb +297 -0
- data/lib/gsd/tools/file_read.rb +199 -0
- data/lib/gsd/tools/file_write.rb +153 -0
- data/lib/gsd/tools/glob.rb +202 -0
- data/lib/gsd/tools/grep.rb +227 -0
- data/lib/gsd/tools/gsd_frontmatter.rb +165 -0
- data/lib/gsd/tools/gsd_phase.rb +140 -0
- data/lib/gsd/tools/gsd_roadmap.rb +108 -0
- data/lib/gsd/tools/gsd_state.rb +143 -0
- data/lib/gsd/tools/gsd_template.rb +157 -0
- data/lib/gsd/tools/gsd_verify.rb +159 -0
- data/lib/gsd/tools/registry.rb +103 -0
- data/lib/gsd/tools/task.rb +235 -0
- data/lib/gsd/tools/todo_write.rb +290 -0
- data/lib/gsd/tools/web.rb +260 -0
- data/lib/gsd/tui/app.rb +366 -0
- data/lib/gsd/tui/auto_complete.rb +79 -0
- data/lib/gsd/tui/colors.rb +111 -0
- data/lib/gsd/tui/command_palette.rb +126 -0
- data/lib/gsd/tui/header.rb +38 -0
- data/lib/gsd/tui/input_box.rb +199 -0
- data/lib/gsd/tui/spinner.rb +40 -0
- data/lib/gsd/tui/status_bar.rb +51 -0
- data/lib/gsd/tui.rb +17 -0
- data/lib/gsd/validator.rb +216 -0
- data/lib/gsd/verify.rb +175 -0
- data/lib/gsd/version.rb +5 -0
- data/lib/gsd/workstream.rb +91 -0
- metadata +231 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'digest/md5'
|
|
4
|
+
|
|
5
|
+
module Gsd
|
|
6
|
+
module Buddy
|
|
7
|
+
# Gacha - Sistema gacha para obter buddies
|
|
8
|
+
#
|
|
9
|
+
# Features:
|
|
10
|
+
# - Deterministic (seed = userId)
|
|
11
|
+
# - Rarity weights
|
|
12
|
+
# - Pity system (guaranteed epic+ after 50 pulls)
|
|
13
|
+
class Gacha
|
|
14
|
+
attr_reader :user_id, :pull_count, :pity_count
|
|
15
|
+
|
|
16
|
+
# Inicializa o Gacha
|
|
17
|
+
#
|
|
18
|
+
# @param user_id [String] ID do usuário (seed)
|
|
19
|
+
def initialize(user_id:)
|
|
20
|
+
@user_id = user_id
|
|
21
|
+
@pull_count = 0
|
|
22
|
+
@pity_count = 0
|
|
23
|
+
@seed = Digest::MD5.hexdigest(user_id).to_i(16)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Faz um pull no gacha
|
|
27
|
+
#
|
|
28
|
+
# @return [Hash] Resultado do pull
|
|
29
|
+
def pull
|
|
30
|
+
@pull_count += 1
|
|
31
|
+
@pity_count += 1
|
|
32
|
+
|
|
33
|
+
# Pity system: guaranteed epic+ after 50 pulls
|
|
34
|
+
if @pity_count >= 50
|
|
35
|
+
result = pull_with_rarity(:epic_or_legendary)
|
|
36
|
+
@pity_count = 0
|
|
37
|
+
return result
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Roll for rarity
|
|
41
|
+
rarity = roll_rarity
|
|
42
|
+
|
|
43
|
+
# Pull species of that rarity
|
|
44
|
+
pull_with_rarity(rarity)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Faz múltiplos pulls
|
|
48
|
+
#
|
|
49
|
+
# @param count [Integer] Número de pulls
|
|
50
|
+
# @return [Array<Hash>] Resultados
|
|
51
|
+
def pull_many(count)
|
|
52
|
+
count.times.map { pull }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Reseta pity counter
|
|
56
|
+
#
|
|
57
|
+
# @return [void]
|
|
58
|
+
def reset_pity
|
|
59
|
+
@pity_count = 0
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Estatísticas do gacha
|
|
63
|
+
#
|
|
64
|
+
# @return [Hash] Estatísticas
|
|
65
|
+
def stats
|
|
66
|
+
{
|
|
67
|
+
user_id: @user_id,
|
|
68
|
+
pull_count: @pull_count,
|
|
69
|
+
pity_count: @pity_count,
|
|
70
|
+
seed: @seed
|
|
71
|
+
}
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
# Roll para determinar raridade
|
|
77
|
+
#
|
|
78
|
+
# @return [Symbol] Raridade
|
|
79
|
+
def roll_rarity
|
|
80
|
+
weights = Species.rarity_weights
|
|
81
|
+
total = weights.values.sum
|
|
82
|
+
roll = seeded_random % total
|
|
83
|
+
|
|
84
|
+
cumulative = 0
|
|
85
|
+
weights.each do |rarity, weight|
|
|
86
|
+
cumulative += weight
|
|
87
|
+
return rarity if roll < cumulative
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
:common
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Pull com raridade específica
|
|
94
|
+
#
|
|
95
|
+
# @param rarity [Symbol] Raridade
|
|
96
|
+
# @return [Hash] Resultado
|
|
97
|
+
def pull_with_rarity(rarity)
|
|
98
|
+
# Handle epic_or_legendary for pity
|
|
99
|
+
if rarity == :epic_or_legendary
|
|
100
|
+
rarity = seeded_random(2) == 0 ? :epic : :legendary
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Get species of that rarity
|
|
104
|
+
species_keys = Species.by_rarity(rarity)
|
|
105
|
+
species_key = species_keys[seeded_random(species_keys.length)]
|
|
106
|
+
species = Species.get(species_key)
|
|
107
|
+
|
|
108
|
+
# Generate stats with some variance
|
|
109
|
+
stats = generate_stats(species[:base_stats])
|
|
110
|
+
|
|
111
|
+
result = {
|
|
112
|
+
species: species_key,
|
|
113
|
+
name: species[:name],
|
|
114
|
+
rarity: rarity,
|
|
115
|
+
stats: stats,
|
|
116
|
+
ascii: species[:ascii],
|
|
117
|
+
pull_number: @pull_count
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
result
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Gera stats com variância
|
|
124
|
+
#
|
|
125
|
+
# @param base_stats [Hash] Stats base
|
|
126
|
+
# @return [Hash] Stats gerados
|
|
127
|
+
def generate_stats(base_stats)
|
|
128
|
+
stats = {}
|
|
129
|
+
base_stats.each do |stat, value|
|
|
130
|
+
# Variance: -1 to +2
|
|
131
|
+
variance = seeded_random(4) - 1
|
|
132
|
+
stats[stat] = [1, value + variance, 10].clamp
|
|
133
|
+
end
|
|
134
|
+
stats
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Random number generator com seed
|
|
138
|
+
#
|
|
139
|
+
# @param max [Integer] Maximum value
|
|
140
|
+
# @return [Integer] Random number
|
|
141
|
+
def seeded_random(max = 100)
|
|
142
|
+
# Linear congruential generator
|
|
143
|
+
@seed = (@seed * 1103515245 + 12345) & 0x7fffffff
|
|
144
|
+
@seed % max
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gsd
|
|
4
|
+
module Buddy
|
|
5
|
+
# Renderer - Renderiza buddy no terminal
|
|
6
|
+
#
|
|
7
|
+
# Features:
|
|
8
|
+
# - ASCII art
|
|
9
|
+
# - Stats display
|
|
10
|
+
# - Stage indicator
|
|
11
|
+
class Renderer
|
|
12
|
+
# Renderiza buddy
|
|
13
|
+
#
|
|
14
|
+
# @param buddy [Buddy] Buddy para renderizar
|
|
15
|
+
# @return [String] Buddy renderizado
|
|
16
|
+
def render(buddy)
|
|
17
|
+
lines = []
|
|
18
|
+
|
|
19
|
+
# ASCII art
|
|
20
|
+
lines << buddy.ascii
|
|
21
|
+
lines << ''
|
|
22
|
+
|
|
23
|
+
# Info
|
|
24
|
+
lines << "Name: #{buddy.name}"
|
|
25
|
+
lines << "Stage: #{buddy.stage}"
|
|
26
|
+
lines << "Level: #{buddy.level}"
|
|
27
|
+
lines << ''
|
|
28
|
+
|
|
29
|
+
# Stats
|
|
30
|
+
lines << 'Stats:'
|
|
31
|
+
buddy.stats.all.each do |stat, value|
|
|
32
|
+
bar = render_bar(value, 10)
|
|
33
|
+
lines << " #{stat.to_s.upcase.ljust(12)}: #{bar} #{value}/10"
|
|
34
|
+
end
|
|
35
|
+
lines << ''
|
|
36
|
+
|
|
37
|
+
# Interactions
|
|
38
|
+
lines << "Interactions: #{buddy.interactions}"
|
|
39
|
+
lines << "Total Score: #{buddy.stats.total_score}"
|
|
40
|
+
|
|
41
|
+
lines.join("\n")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Renderiza lista de buddies
|
|
45
|
+
#
|
|
46
|
+
# @param buddies [Array<Buddy>] Lista de buddies
|
|
47
|
+
# @return [String] Lista renderizada
|
|
48
|
+
def render_list(buddies)
|
|
49
|
+
lines = []
|
|
50
|
+
lines << 'Your Buddies:'
|
|
51
|
+
lines << '=' * 60
|
|
52
|
+
|
|
53
|
+
buddies.each do |buddy|
|
|
54
|
+
lines << "#{buddy.ascii} #{buddy.name} (#{buddy.rarity}) - Level #{buddy.level}"
|
|
55
|
+
lines << " Stage: #{buddy.stage} | Score: #{buddy.stats.total_score}"
|
|
56
|
+
lines << ''
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
lines << '=' * 60
|
|
60
|
+
lines << "Total: #{buddies.count} buddy(ies)"
|
|
61
|
+
|
|
62
|
+
lines.join("\n")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Renderiza barra de progresso
|
|
66
|
+
#
|
|
67
|
+
# @param current [Integer] Valor atual
|
|
68
|
+
# @param max [Integer] Valor máximo
|
|
69
|
+
# @param filled [String] Caractere preenchido
|
|
70
|
+
# @param empty [String] Caractere vazio
|
|
71
|
+
# @return [String] Barra renderizada
|
|
72
|
+
def render_bar(current, max, filled: '█', empty: '░')
|
|
73
|
+
filled_count = (current.to_f / max * 10).round
|
|
74
|
+
empty_count = 10 - filled_count
|
|
75
|
+
filled * filled_count + empty * empty_count
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Renderiza comparação de stats
|
|
79
|
+
#
|
|
80
|
+
# @param buddy1 [Buddy] Primeiro buddy
|
|
81
|
+
# @param buddy2 [Buddy] Segundo buddy
|
|
82
|
+
# @return [String] Comparação renderizada
|
|
83
|
+
def render_comparison(buddy1, buddy2)
|
|
84
|
+
lines = []
|
|
85
|
+
lines << 'Stats Comparison:'
|
|
86
|
+
lines << '=' * 60
|
|
87
|
+
|
|
88
|
+
Buddy::Stats::STAT_NAMES.each do |stat|
|
|
89
|
+
v1 = buddy1.stats.get(stat)
|
|
90
|
+
v2 = buddy2.stats.get(stat)
|
|
91
|
+
|
|
92
|
+
bar1 = render_bar(v1, 10)
|
|
93
|
+
bar2 = render_bar(v2, 10)
|
|
94
|
+
|
|
95
|
+
lines << "#{stat.to_s.upcase.ljust(12)}:"
|
|
96
|
+
lines << " #{buddy1.name.ljust(15)}: #{bar1} #{v1}"
|
|
97
|
+
lines << " #{buddy2.name.ljust(15)}: #{bar2} #{v2}"
|
|
98
|
+
lines << ''
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
lines << '=' * 60
|
|
102
|
+
lines << "Total: #{buddy1.stats.total_score} vs #{buddy2.stats.total_score}"
|
|
103
|
+
|
|
104
|
+
lines.join("\n")
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gsd
|
|
4
|
+
module Buddy
|
|
5
|
+
# Species - Definição das 18 espécies de buddies
|
|
6
|
+
#
|
|
7
|
+
# Espécies por raridade:
|
|
8
|
+
# - Common (50%): Pebblecrab, Codebug, Scriptling
|
|
9
|
+
# - Uncommon (30%): Bytebird, Loopfox, Debugdeer
|
|
10
|
+
# - Rare (15%): Testurtle, Refactoray, Mergebat
|
|
11
|
+
# - Epic (4%): Deploydragon, Pipelineon, Commitcat
|
|
12
|
+
# - Legendary (1%): Nebulynx, Quantumquail
|
|
13
|
+
module Species
|
|
14
|
+
# Todas as espécies
|
|
15
|
+
ALL = {
|
|
16
|
+
# Common
|
|
17
|
+
pebblecrab: {
|
|
18
|
+
name: 'Pebblecrab',
|
|
19
|
+
rarity: :common,
|
|
20
|
+
base_stats: { debugging: 3, chaos: 2, snark: 1, focus: 4, creativity: 2 },
|
|
21
|
+
ascii: '/ᐠ。‿。ᐟ\\',
|
|
22
|
+
description: 'A cute crab that lives in pebbles. Good at debugging.'
|
|
23
|
+
},
|
|
24
|
+
codebug: {
|
|
25
|
+
name: 'Codebug',
|
|
26
|
+
rarity: :common,
|
|
27
|
+
base_stats: { debugging: 4, chaos: 3, snark: 1, focus: 3, creativity: 1 },
|
|
28
|
+
ascii: '🐛',
|
|
29
|
+
description: 'A friendly bug that helps find other bugs.'
|
|
30
|
+
},
|
|
31
|
+
scriptling: {
|
|
32
|
+
name: 'Scriptling',
|
|
33
|
+
rarity: :common,
|
|
34
|
+
base_stats: { debugging: 2, chaos: 2, snark: 2, focus: 3, creativity: 3 },
|
|
35
|
+
ascii: '📜',
|
|
36
|
+
description: 'A small spirit that inhabits scripts.'
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
# Uncommon
|
|
40
|
+
bytebird: {
|
|
41
|
+
name: 'Bytebird',
|
|
42
|
+
rarity: :uncommon,
|
|
43
|
+
base_stats: { debugging: 4, chaos: 2, snark: 2, focus: 4, creativity: 3 },
|
|
44
|
+
ascii: '🐦',
|
|
45
|
+
description: 'A bird that carries bytes of information.'
|
|
46
|
+
},
|
|
47
|
+
loopfox: {
|
|
48
|
+
name: 'Loopfox',
|
|
49
|
+
rarity: :uncommon,
|
|
50
|
+
base_stats: { debugging: 5, chaos: 3, snark: 3, focus: 4, creativity: 2 },
|
|
51
|
+
ascii: '🦊',
|
|
52
|
+
description: 'A clever fox that loops around problems.'
|
|
53
|
+
},
|
|
54
|
+
debugdeer: {
|
|
55
|
+
name: 'Debugdeer',
|
|
56
|
+
rarity: :uncommon,
|
|
57
|
+
base_stats: { debugging: 6, chaos: 2, snark: 2, focus: 5, creativity: 2 },
|
|
58
|
+
ascii: '🦌',
|
|
59
|
+
description: 'A deer with antlers that point to bugs.'
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
# Rare
|
|
63
|
+
testurtle: {
|
|
64
|
+
name: 'Testurtle',
|
|
65
|
+
rarity: :rare,
|
|
66
|
+
base_stats: { debugging: 5, chaos: 1, snark: 2, focus: 6, creativity: 3 },
|
|
67
|
+
ascii: '🐢',
|
|
68
|
+
description: 'A slow but steady turtle that tests everything.'
|
|
69
|
+
},
|
|
70
|
+
refactoray: {
|
|
71
|
+
name: 'Refactoray',
|
|
72
|
+
rarity: :rare,
|
|
73
|
+
base_stats: { debugging: 4, chaos: 2, snark: 3, focus: 5, creativity: 5 },
|
|
74
|
+
ascii: '✨',
|
|
75
|
+
description: 'A manta ray that glides through code refactoring.'
|
|
76
|
+
},
|
|
77
|
+
mergebat: {
|
|
78
|
+
name: 'Mergebat',
|
|
79
|
+
rarity: :rare,
|
|
80
|
+
base_stats: { debugging: 5, chaos: 4, snark: 3, focus: 4, creativity: 3 },
|
|
81
|
+
ascii: '🦇',
|
|
82
|
+
description: 'A bat that merges code in the dark.'
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
# Epic
|
|
86
|
+
deploydragon: {
|
|
87
|
+
name: 'Deploydragon',
|
|
88
|
+
rarity: :epic,
|
|
89
|
+
base_stats: { debugging: 7, chaos: 5, snark: 4, focus: 6, creativity: 5 },
|
|
90
|
+
ascii: '🐉',
|
|
91
|
+
description: 'A mighty dragon that breathes fire on deployments.'
|
|
92
|
+
},
|
|
93
|
+
pipelineon: {
|
|
94
|
+
name: 'Pipelineon',
|
|
95
|
+
rarity: :epic,
|
|
96
|
+
base_stats: { debugging: 6, chaos: 3, snark: 3, focus: 8, creativity: 4 },
|
|
97
|
+
ascii: '🔷',
|
|
98
|
+
description: 'A lion that rules the CI/CD pipeline.'
|
|
99
|
+
},
|
|
100
|
+
commitcat: {
|
|
101
|
+
name: 'Commitcat',
|
|
102
|
+
rarity: :epic,
|
|
103
|
+
base_stats: { debugging: 6, chaos: 4, snark: 5, focus: 6, creativity: 5 },
|
|
104
|
+
ascii: '🐱',
|
|
105
|
+
description: 'A cat that commits code with style.'
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
# Legendary
|
|
109
|
+
nebulynx: {
|
|
110
|
+
name: 'Nebulynx',
|
|
111
|
+
rarity: :legendary,
|
|
112
|
+
base_stats: { debugging: 9, chaos: 4, snark: 5, focus: 8, creativity: 7 },
|
|
113
|
+
ascii: '🔮',
|
|
114
|
+
description: 'A mystical lynx from the nebula. Ultimate debugging powers.'
|
|
115
|
+
},
|
|
116
|
+
quantumquail: {
|
|
117
|
+
name: 'Quantumquail',
|
|
118
|
+
rarity: :legendary,
|
|
119
|
+
base_stats: { debugging: 8, chaos: 6, snark: 4, focus: 7, creativity: 9 },
|
|
120
|
+
ascii: '⚛️',
|
|
121
|
+
description: 'A quail that exists in multiple states simultaneously.'
|
|
122
|
+
}
|
|
123
|
+
}.freeze
|
|
124
|
+
|
|
125
|
+
# Retorna espécie por nome
|
|
126
|
+
#
|
|
127
|
+
# @param name [Symbol] Nome da espécie
|
|
128
|
+
# @return [Hash, nil] Espécie ou nil
|
|
129
|
+
def self.get(name)
|
|
130
|
+
ALL[name]
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Retorna espécies por raridade
|
|
134
|
+
#
|
|
135
|
+
# @param rarity [Symbol] Raridade
|
|
136
|
+
# @return [Array<Symbol>] Lista de espécies
|
|
137
|
+
def self.by_rarity(rarity)
|
|
138
|
+
ALL.select { |_, s| s[:rarity] == rarity }.keys
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Retorna todas as espécies comuns
|
|
142
|
+
#
|
|
143
|
+
# @return [Array<Symbol>] Espécies comuns
|
|
144
|
+
def self.common
|
|
145
|
+
by_rarity(:common)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Retorna todas as espécies incomuns
|
|
149
|
+
#
|
|
150
|
+
# @return [Array<Symbol>] Espécies incomuns
|
|
151
|
+
def self.uncommon
|
|
152
|
+
by_rarity(:uncommon)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Retorna todas as espécies raras
|
|
156
|
+
#
|
|
157
|
+
# @return [Array<Symbol>] Espécies raras
|
|
158
|
+
def self.rare
|
|
159
|
+
by_rarity(:rare)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Retorna todas as espécies épicas
|
|
163
|
+
#
|
|
164
|
+
# @return [Array<Symbol>] Espécies épicas
|
|
165
|
+
def self.epic
|
|
166
|
+
by_rarity(:epic)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Retorna todas as espécies lendárias
|
|
170
|
+
#
|
|
171
|
+
# @return [Array<Symbol>] Espécies lendárias
|
|
172
|
+
def self.legendary
|
|
173
|
+
by_rarity(:legendary)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Pesos de raridade para gacha
|
|
177
|
+
#
|
|
178
|
+
# @return [Hash] Pesos
|
|
179
|
+
def self.rarity_weights
|
|
180
|
+
{
|
|
181
|
+
common: 50,
|
|
182
|
+
uncommon: 30,
|
|
183
|
+
rare: 15,
|
|
184
|
+
epic: 4,
|
|
185
|
+
legendary: 1
|
|
186
|
+
}
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gsd
|
|
4
|
+
module Buddy
|
|
5
|
+
# Stats - Gerenciamento de stats do buddy
|
|
6
|
+
#
|
|
7
|
+
# Stats:
|
|
8
|
+
# - DEBUGGING: Habilidade de debug
|
|
9
|
+
# - CHAOS: Tendência a causar bugs
|
|
10
|
+
# - SNARK: Sarcasmo nas respostas
|
|
11
|
+
# - FOCUS: Concentração em tarefas
|
|
12
|
+
# - CREATIVITY: Criatividade em soluções
|
|
13
|
+
class Stats
|
|
14
|
+
STAT_NAMES = [:debugging, :chaos, :snark, :focus, :creativity].freeze
|
|
15
|
+
|
|
16
|
+
attr_reader :stats, :interactions, :level
|
|
17
|
+
|
|
18
|
+
# Inicializa os Stats
|
|
19
|
+
#
|
|
20
|
+
# @param base_stats [Hash] Stats base da espécie
|
|
21
|
+
def initialize(base_stats)
|
|
22
|
+
@stats = base_stats.dup
|
|
23
|
+
@interactions = 0
|
|
24
|
+
@level = 1
|
|
25
|
+
@exp = 0
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Incrementa interação
|
|
29
|
+
#
|
|
30
|
+
# @return [void]
|
|
31
|
+
def increment_interaction
|
|
32
|
+
@interactions += 1
|
|
33
|
+
@exp += 10
|
|
34
|
+
check_level_up
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Verifica se subiu de nível
|
|
38
|
+
#
|
|
39
|
+
# @return [Boolean] true se subiu de nível
|
|
40
|
+
def check_level_up
|
|
41
|
+
exp_needed = @level * 100
|
|
42
|
+
|
|
43
|
+
if @exp >= exp_needed
|
|
44
|
+
@level += 1
|
|
45
|
+
@exp = 0
|
|
46
|
+
level_up
|
|
47
|
+
true
|
|
48
|
+
else
|
|
49
|
+
false
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Sobe de nível
|
|
54
|
+
#
|
|
55
|
+
# @return [void]
|
|
56
|
+
def level_up
|
|
57
|
+
# Increase random stat
|
|
58
|
+
stat = STAT_NAMES.sample
|
|
59
|
+
@stats[stat] = [@stats[stat] + 1, 10].min
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Obtém stat
|
|
63
|
+
#
|
|
64
|
+
# @param name [Symbol] Nome do stat
|
|
65
|
+
# @return [Integer] Valor do stat
|
|
66
|
+
def get(name)
|
|
67
|
+
@stats[name] || 0
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Define stat
|
|
71
|
+
#
|
|
72
|
+
# @param name [Symbol] Nome do stat
|
|
73
|
+
# @param value [Integer] Valor
|
|
74
|
+
# @return [void]
|
|
75
|
+
def set(name, value)
|
|
76
|
+
@stats[name] = value.clamp(1, 10)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Retorna todos os stats
|
|
80
|
+
#
|
|
81
|
+
# @return [Hash] Stats
|
|
82
|
+
def all
|
|
83
|
+
@stats.dup
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Retorna stat mais alto
|
|
87
|
+
#
|
|
88
|
+
# @return [Symbol] Nome do stat
|
|
89
|
+
def highest_stat
|
|
90
|
+
@stats.max_by { |_, v| v }&.first
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Retorna stat mais baixo
|
|
94
|
+
#
|
|
95
|
+
# @return [Symbol] Nome do stat
|
|
96
|
+
def lowest_stat
|
|
97
|
+
@stats.min_by { |_, v| v }&.first
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Calcula score total
|
|
101
|
+
#
|
|
102
|
+
# @return [Integer] Score total
|
|
103
|
+
def total_score
|
|
104
|
+
@stats.values.sum
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Verifica se pode evoluir
|
|
108
|
+
#
|
|
109
|
+
# @param stage [Symbol] Stage atual
|
|
110
|
+
# @return [Boolean] true se pode evoluir
|
|
111
|
+
def can_evolve?(stage)
|
|
112
|
+
case stage
|
|
113
|
+
when :baby
|
|
114
|
+
@interactions >= 10
|
|
115
|
+
when :juvenile
|
|
116
|
+
@interactions >= 50 && @level >= 5
|
|
117
|
+
when :adult
|
|
118
|
+
@interactions >= 100 && @level >= 10 && total_score >= 40
|
|
119
|
+
else
|
|
120
|
+
false
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Retorna stage atual
|
|
125
|
+
#
|
|
126
|
+
# @return [Symbol] Stage
|
|
127
|
+
def stage
|
|
128
|
+
if @interactions < 10
|
|
129
|
+
:baby
|
|
130
|
+
elsif @interactions < 50
|
|
131
|
+
:juvenile
|
|
132
|
+
elsif @interactions < 100
|
|
133
|
+
:adult
|
|
134
|
+
else
|
|
135
|
+
:master
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Exporta stats para hash
|
|
140
|
+
#
|
|
141
|
+
# @return [Hash] Stats exportados
|
|
142
|
+
def to_h
|
|
143
|
+
{
|
|
144
|
+
stats: @stats,
|
|
145
|
+
interactions: @interactions,
|
|
146
|
+
level: @level,
|
|
147
|
+
exp: @exp,
|
|
148
|
+
stage: stage,
|
|
149
|
+
total_score: total_score,
|
|
150
|
+
highest_stat: highest_stat,
|
|
151
|
+
lowest_stat: lowest_stat
|
|
152
|
+
}
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
data/lib/gsd/buddy.rb
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# GSD Buddy Module
|
|
4
|
+
#
|
|
5
|
+
# Tamagotchi-style companion system for GSD Tools
|
|
6
|
+
#
|
|
7
|
+
# Uso:
|
|
8
|
+
# require 'gsd/buddy'
|
|
9
|
+
#
|
|
10
|
+
# # Pull gacha
|
|
11
|
+
# gacha = Gsd::Buddy::Gacha.new(user_id: 'user-123')
|
|
12
|
+
# result = gacha.pull
|
|
13
|
+
#
|
|
14
|
+
# # CLI
|
|
15
|
+
# cli = Gsd::Buddy::CLI.new(['get'])
|
|
16
|
+
# cli.run
|
|
17
|
+
|
|
18
|
+
module Gsd
|
|
19
|
+
module Buddy
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Load buddy files
|
|
24
|
+
require 'gsd/buddy/species'
|
|
25
|
+
require 'gsd/buddy/gacha'
|
|
26
|
+
require 'gsd/buddy/stats'
|
|
27
|
+
require 'gsd/buddy/renderer'
|
|
28
|
+
require 'gsd/buddy/cli'
|