mumble_game 1.0.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/CHANGELOG.md +43 -0
- data/LICENSE +21 -0
- data/README.md +130 -0
- data/bin/mumble +6 -0
- data/lib/mumble/animations.rb +112 -0
- data/lib/mumble/box.rb +139 -0
- data/lib/mumble/colors.rb +83 -0
- data/lib/mumble/config.rb +172 -0
- data/lib/mumble/cursor.rb +82 -0
- data/lib/mumble/error_handler.rb +74 -0
- data/lib/mumble/grid.rb +182 -0
- data/lib/mumble/hangman.rb +440 -0
- data/lib/mumble/high_scores.rb +109 -0
- data/lib/mumble/input.rb +143 -0
- data/lib/mumble/layout.rb +208 -0
- data/lib/mumble/scorer.rb +78 -0
- data/lib/mumble/screen.rb +61 -0
- data/lib/mumble/screens/base.rb +142 -0
- data/lib/mumble/screens/gameplay.rb +433 -0
- data/lib/mumble/screens/high_scores.rb +126 -0
- data/lib/mumble/screens/lose.rb +108 -0
- data/lib/mumble/screens/main_menu.rb +130 -0
- data/lib/mumble/screens/name_input.rb +121 -0
- data/lib/mumble/screens/play_again.rb +97 -0
- data/lib/mumble/screens/profile.rb +154 -0
- data/lib/mumble/screens/quit_confirm.rb +103 -0
- data/lib/mumble/screens/rules.rb +102 -0
- data/lib/mumble/screens/splash.rb +130 -0
- data/lib/mumble/screens/win.rb +139 -0
- data/lib/mumble/storage.rb +85 -0
- data/lib/mumble/version.rb +5 -0
- data/lib/mumble/word_cache.rb +131 -0
- data/lib/mumble/word_service.rb +192 -0
- data/lib/mumble.rb +340 -0
- metadata +137 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 7d7549dec66263103042334d5385c1341fb7233ea59c921ed040971391db6a48
|
|
4
|
+
data.tar.gz: 017b9cc6b4951ed33519a76db06e549bc4abc30b08554e0ddc672e62047aaa46
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 68c0e669ce3d2c2d22d764c2ecd5ad85b4dfeed8a8db3f108e24ccfd5a15d8b3c57f7f2c3d1af2b175e645ffb85b1b6b815ecbed0eb5c8f9f300fbf5576c66ff
|
|
7
|
+
data.tar.gz: 52648f20b2dbdc082a00cb0e1b237e98bd255302b594c95c4fa17d18d69a48006fa6e459535de94a8da1f2c2df760fed3000eb298caf0b9bdaa8e1129e9c4d94
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.0] - 2026-01-23
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Complete Wordle + Hangman gameplay
|
|
13
|
+
- Terminal UI with colors and animations
|
|
14
|
+
- Word fetching from Datamuse API with caching
|
|
15
|
+
- High scores leaderboard (top 10)
|
|
16
|
+
- Player profiles with statistics
|
|
17
|
+
- Win/Lose screens with score breakdown
|
|
18
|
+
- Level progression system
|
|
19
|
+
- Responsive design for small/large terminals
|
|
20
|
+
- Graceful error handling
|
|
21
|
+
- Ctrl+C clean exit
|
|
22
|
+
|
|
23
|
+
### Features
|
|
24
|
+
|
|
25
|
+
- 5-letter word guessing
|
|
26
|
+
- Color-coded letter feedback (green/orange/red)
|
|
27
|
+
- Hangman visualization (7 stages)
|
|
28
|
+
- Score calculation with time/guess penalties
|
|
29
|
+
- Level multiplier bonuses
|
|
30
|
+
- Current/best streak tracking
|
|
31
|
+
- Animated letter reveals
|
|
32
|
+
- Loading spinner for API calls
|
|
33
|
+
- Flashing win/lose screens
|
|
34
|
+
|
|
35
|
+
## [0.1.0] - 2026-01-20
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
|
|
39
|
+
- Initial project structure
|
|
40
|
+
- Gem skeleton with `mumble` executable
|
|
41
|
+
- RSpec test configuration
|
|
42
|
+
- RuboCop linting configuration
|
|
43
|
+
- MIT License
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hady Mohamed
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# 🎮 Mumble
|
|
2
|
+
|
|
3
|
+
A terminal-based word guessing game combining Wordle and Hangman mechanics, built in Ruby.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- 🎯 **Wordle-style gameplay** - Guess the 5-letter word with color-coded feedback
|
|
10
|
+
- ☠️ **Hangman integration** - Wrong guesses build the hangman
|
|
11
|
+
- 🏆 **High scores** - Track your best performances
|
|
12
|
+
- 📊 **Statistics** - Games played, win rate, streaks
|
|
13
|
+
- 🎨 **Beautiful terminal UI** - Colors, animations, ASCII art
|
|
14
|
+
- 💾 **Persistent data** - Your progress is saved locally
|
|
15
|
+
- 🌐 **Online word fetching** - Fresh words from Datamuse API
|
|
16
|
+
- 📱 **Responsive design** - Adapts to small and large terminals
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
gem install mumble_game
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Or add to your Gemfile:
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
gem "mumble_game"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
Simply run:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
mumble
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Controls
|
|
39
|
+
|
|
40
|
+
| Key | Action |
|
|
41
|
+
|-----|--------|
|
|
42
|
+
| A-Z | Type letters |
|
|
43
|
+
| Backspace | Delete letter |
|
|
44
|
+
| Enter | Submit guess |
|
|
45
|
+
| ↑↓ | Navigate menus |
|
|
46
|
+
| 1-5 | Quick menu select |
|
|
47
|
+
| Ctrl+C | Exit game |
|
|
48
|
+
|
|
49
|
+
### Color Guide
|
|
50
|
+
|
|
51
|
+
| Color | Meaning |
|
|
52
|
+
|-------|---------|
|
|
53
|
+
| 🟩 Green | Correct letter, correct position |
|
|
54
|
+
| 🟧 Orange | Correct letter, wrong position |
|
|
55
|
+
| 🟥 Red | Letter not in word |
|
|
56
|
+
|
|
57
|
+
## Requirements
|
|
58
|
+
|
|
59
|
+
- Ruby 3.0+
|
|
60
|
+
- Terminal with 80x24 minimum size
|
|
61
|
+
- Internet connection (for word fetching, optional)
|
|
62
|
+
|
|
63
|
+
## Screenshots
|
|
64
|
+
|
|
65
|
+
### Main Menu
|
|
66
|
+
|
|
67
|
+

|
|
68
|
+
|
|
69
|
+
### Gameplay
|
|
70
|
+
|
|
71
|
+

|
|
72
|
+
|
|
73
|
+
### Win Screen
|
|
74
|
+
|
|
75
|
+

|
|
76
|
+
|
|
77
|
+
### Lose Screen
|
|
78
|
+
|
|
79
|
+

|
|
80
|
+
|
|
81
|
+
### High Scores
|
|
82
|
+
|
|
83
|
+

|
|
84
|
+
|
|
85
|
+
## Development
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# Clone the repo
|
|
89
|
+
git clone https://github.com/yourusername/mumble_game.git
|
|
90
|
+
cd mumble_game
|
|
91
|
+
|
|
92
|
+
# Install dependencies
|
|
93
|
+
bundle install
|
|
94
|
+
|
|
95
|
+
# Run the game
|
|
96
|
+
bundle exec rake play
|
|
97
|
+
|
|
98
|
+
# Run tests
|
|
99
|
+
bundle exec rake spec
|
|
100
|
+
|
|
101
|
+
# Run linter
|
|
102
|
+
bundle exec rubocop
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Data Storage
|
|
106
|
+
|
|
107
|
+
Mumble stores data in `~/.mumble/`:
|
|
108
|
+
|
|
109
|
+
- `config.json` - Player profile and settings
|
|
110
|
+
- `high_scores.json` - Leaderboard
|
|
111
|
+
- `word_cache.json` - Cached words for offline play
|
|
112
|
+
- `error.log` - Error logs for debugging
|
|
113
|
+
|
|
114
|
+
To reset all data:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
rm -rf ~/.mumble
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Contributing
|
|
121
|
+
|
|
122
|
+
Bug reports and pull requests are welcome on GitHub. See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
This gem is available as open source under the [MIT License](LICENSE).
|
|
127
|
+
|
|
128
|
+
## Author
|
|
129
|
+
|
|
130
|
+
Built with ❤️ by Hady Mohamed
|
data/bin/mumble
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mumble
|
|
4
|
+
# Reusable animation helpers for terminal effects
|
|
5
|
+
module Animations
|
|
6
|
+
class << self
|
|
7
|
+
# Animate a number counting up from 0 to target
|
|
8
|
+
def count_up(target, duration: 1.0)
|
|
9
|
+
return yield(target) if target <= 0
|
|
10
|
+
|
|
11
|
+
steps = [target, 20].min # Max 20 steps
|
|
12
|
+
delay = duration / steps
|
|
13
|
+
increment = target / steps.to_f
|
|
14
|
+
|
|
15
|
+
current = 0.0
|
|
16
|
+
steps.times do
|
|
17
|
+
current += increment
|
|
18
|
+
yield(current.round)
|
|
19
|
+
sleep(delay)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Ensure we end on exact target
|
|
23
|
+
yield(target)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Display a loading spinner with message
|
|
27
|
+
def spinner(message: "Loading", duration: nil, &block)
|
|
28
|
+
frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
|
|
29
|
+
|
|
30
|
+
if block_given?
|
|
31
|
+
# Run block in thread while showing spinner
|
|
32
|
+
result = nil
|
|
33
|
+
done = false
|
|
34
|
+
|
|
35
|
+
thread = Thread.new do
|
|
36
|
+
result = block.call
|
|
37
|
+
done = true
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
frame_index = 0
|
|
41
|
+
until done
|
|
42
|
+
Cursor.move_to(Screen.center_row, Screen.center_col(message.length + 4))
|
|
43
|
+
print Colors.cyan("#{frames[frame_index]} #{message}...")
|
|
44
|
+
frame_index = (frame_index + 1) % frames.length
|
|
45
|
+
sleep(0.1)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
thread.join
|
|
49
|
+
|
|
50
|
+
# Clear spinner line
|
|
51
|
+
Cursor.move_to(Screen.center_row, Screen.center_col(message.length + 4))
|
|
52
|
+
print " " * (message.length + 10)
|
|
53
|
+
|
|
54
|
+
result
|
|
55
|
+
else
|
|
56
|
+
# Just show spinner for duration
|
|
57
|
+
frames_to_show = ((duration || 1.0) / 0.1).round
|
|
58
|
+
|
|
59
|
+
frames_to_show.times do |i|
|
|
60
|
+
Cursor.move_to(Screen.center_row, Screen.center_col(message.length + 4))
|
|
61
|
+
print Colors.cyan("#{frames[i % frames.length]} #{message}...")
|
|
62
|
+
sleep(0.1)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Flash text between two colors
|
|
68
|
+
def flash(row:, col:, text:, color1:, color2:, times: 3, speed: 0.2)
|
|
69
|
+
times.times do
|
|
70
|
+
Cursor.move_to(row, col)
|
|
71
|
+
print Colors.send(color1, text)
|
|
72
|
+
sleep(speed)
|
|
73
|
+
Cursor.move_to(row, col)
|
|
74
|
+
print Colors.send(color2, text)
|
|
75
|
+
sleep(speed)
|
|
76
|
+
end
|
|
77
|
+
# End on first color
|
|
78
|
+
Cursor.move_to(row, col)
|
|
79
|
+
print Colors.send(color1, text)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Reveal text character by character
|
|
83
|
+
def typewriter(row:, col:, text:, color: :white, speed: 0.05)
|
|
84
|
+
text.each_char.with_index do |char, i|
|
|
85
|
+
Cursor.move_to(row, col + i)
|
|
86
|
+
print Colors.send(color, char)
|
|
87
|
+
sleep(speed)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Fade transition (clear screen with brief pause)
|
|
92
|
+
def fade_transition(duration: 0.3)
|
|
93
|
+
sleep(duration / 2)
|
|
94
|
+
Cursor.clear_screen
|
|
95
|
+
sleep(duration / 2)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Celebrate with rainbow color cycling on text
|
|
99
|
+
def rainbow_text(row:, text:, cycles: 2, speed: 0.1)
|
|
100
|
+
colors = %i[red yellow green cyan blue magenta]
|
|
101
|
+
col = Screen.center_col(text.length)
|
|
102
|
+
|
|
103
|
+
(cycles * colors.length).times do |i|
|
|
104
|
+
color = colors[i % colors.length]
|
|
105
|
+
Cursor.move_to(row, col)
|
|
106
|
+
print Colors.send(color, text)
|
|
107
|
+
sleep(speed)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
data/lib/mumble/box.rb
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "cursor"
|
|
4
|
+
require_relative "colors"
|
|
5
|
+
|
|
6
|
+
module Mumble
|
|
7
|
+
# Draws bordered boxes and UI containers in the terminal
|
|
8
|
+
module Box
|
|
9
|
+
# Box drawing characters (Unicode)
|
|
10
|
+
CHARS = {
|
|
11
|
+
top_left: "╔",
|
|
12
|
+
top_right: "╗",
|
|
13
|
+
bottom_left: "╚",
|
|
14
|
+
bottom_right: "╝",
|
|
15
|
+
horizontal: "═",
|
|
16
|
+
vertical: "║",
|
|
17
|
+
# Single line variants for inner boxes
|
|
18
|
+
single_top_left: "┌",
|
|
19
|
+
single_top_right: "┐",
|
|
20
|
+
single_bottom_left: "└",
|
|
21
|
+
single_bottom_right: "┘",
|
|
22
|
+
single_horizontal: "─",
|
|
23
|
+
single_vertical: "│"
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
class << self
|
|
27
|
+
# Draw a double-line box at specified position
|
|
28
|
+
# row, col = top-left corner position
|
|
29
|
+
# width, height = outer dimensions including border
|
|
30
|
+
def draw(row:, col:, width:, height:, color: nil)
|
|
31
|
+
top = CHARS[:top_left] + (CHARS[:horizontal] * (width - 2)) + CHARS[:top_right]
|
|
32
|
+
bottom = CHARS[:bottom_left] + (CHARS[:horizontal] * (width - 2)) + CHARS[:bottom_right]
|
|
33
|
+
middle = CHARS[:vertical] + (" " * (width - 2)) + CHARS[:vertical]
|
|
34
|
+
|
|
35
|
+
# Apply color if specified
|
|
36
|
+
top = apply_color(top, color)
|
|
37
|
+
bottom = apply_color(bottom, color)
|
|
38
|
+
middle = apply_color(middle, color)
|
|
39
|
+
|
|
40
|
+
# Draw top border
|
|
41
|
+
Cursor.move_to(row, col)
|
|
42
|
+
print top
|
|
43
|
+
|
|
44
|
+
# Draw middle rows
|
|
45
|
+
(height - 2).times do |i|
|
|
46
|
+
Cursor.move_to(row + 1 + i, col)
|
|
47
|
+
print middle
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Draw bottom border
|
|
51
|
+
Cursor.move_to(row + height - 1, col)
|
|
52
|
+
print bottom
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Draw a single-line box (for letter cells)
|
|
56
|
+
def draw_single(row:, col:, width:, height:, color: nil)
|
|
57
|
+
top = CHARS[:single_top_left] + (CHARS[:single_horizontal] * (width - 2)) + CHARS[:single_top_right]
|
|
58
|
+
bottom = CHARS[:single_bottom_left] + (CHARS[:single_horizontal] * (width - 2)) + CHARS[:single_bottom_right]
|
|
59
|
+
middle = CHARS[:single_vertical] + (" " * (width - 2)) + CHARS[:single_vertical]
|
|
60
|
+
|
|
61
|
+
top = apply_color(top, color)
|
|
62
|
+
bottom = apply_color(bottom, color)
|
|
63
|
+
middle = apply_color(middle, color)
|
|
64
|
+
|
|
65
|
+
Cursor.move_to(row, col)
|
|
66
|
+
print top
|
|
67
|
+
|
|
68
|
+
(height - 2).times do |i|
|
|
69
|
+
Cursor.move_to(row + 1 + i, col)
|
|
70
|
+
print middle
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
Cursor.move_to(row + height - 1, col)
|
|
74
|
+
print bottom
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Draw a box with a centered title on the top border
|
|
78
|
+
def draw_with_title(row:, col:, width:, height:, title:, color: nil)
|
|
79
|
+
# Calculate title position
|
|
80
|
+
title_with_padding = " #{title} "
|
|
81
|
+
title_start = (width - title_with_padding.length) / 2
|
|
82
|
+
|
|
83
|
+
# Build top border with title
|
|
84
|
+
left_border = CHARS[:horizontal] * (title_start - 1)
|
|
85
|
+
right_border = CHARS[:horizontal] * (width - title_start - title_with_padding.length - 1)
|
|
86
|
+
top = CHARS[:top_left] + left_border + title_with_padding + right_border + CHARS[:top_right]
|
|
87
|
+
|
|
88
|
+
bottom = CHARS[:bottom_left] + (CHARS[:horizontal] * (width - 2)) + CHARS[:bottom_right]
|
|
89
|
+
middle = CHARS[:vertical] + (" " * (width - 2)) + CHARS[:vertical]
|
|
90
|
+
|
|
91
|
+
top = apply_color(top, color)
|
|
92
|
+
bottom = apply_color(bottom, color)
|
|
93
|
+
middle = apply_color(middle, color)
|
|
94
|
+
|
|
95
|
+
Cursor.move_to(row, col)
|
|
96
|
+
print top
|
|
97
|
+
|
|
98
|
+
(height - 2).times do |i|
|
|
99
|
+
Cursor.move_to(row + 1 + i, col)
|
|
100
|
+
print middle
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
Cursor.move_to(row + height - 1, col)
|
|
104
|
+
print bottom
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Write text inside a box at a specific row offset from top
|
|
108
|
+
# text_row is relative to box interior (1 = first line inside box)
|
|
109
|
+
def write_inside(box_row:, box_col:, box_width:, text_row:, text:, centered: false, color: nil)
|
|
110
|
+
text_col = if centered
|
|
111
|
+
box_col + 1 + ((box_width - 2 - text.length) / 2)
|
|
112
|
+
else
|
|
113
|
+
box_col + 2
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
Cursor.move_to(box_row + text_row, text_col)
|
|
117
|
+
print color ? apply_color(text, color) : text
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
private
|
|
121
|
+
|
|
122
|
+
# Apply color using the Colors module
|
|
123
|
+
def apply_color(text, color)
|
|
124
|
+
return text unless color
|
|
125
|
+
|
|
126
|
+
case color
|
|
127
|
+
when :green then Colors.green(text)
|
|
128
|
+
when :red then Colors.red(text)
|
|
129
|
+
when :orange then Colors.orange(text)
|
|
130
|
+
when :cyan then Colors.cyan(text)
|
|
131
|
+
when :yellow then Colors.yellow(text)
|
|
132
|
+
when :dim then Colors.dim(text)
|
|
133
|
+
when :bold then Colors.bold(text)
|
|
134
|
+
else text
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pastel"
|
|
4
|
+
|
|
5
|
+
module Mumble
|
|
6
|
+
# Provides color helpers for terminal output
|
|
7
|
+
# Uses Pastel gem for cross-platform ANSI color support
|
|
8
|
+
module Colors
|
|
9
|
+
class << self
|
|
10
|
+
# Memoized Pastel instance for performance
|
|
11
|
+
def pastel
|
|
12
|
+
@pastel ||= Pastel.new
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Correct letter, correct position
|
|
16
|
+
def green(text)
|
|
17
|
+
pastel.green(text)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Correct letter, wrong position
|
|
21
|
+
def orange(text)
|
|
22
|
+
"\e[38;5;208m#{text}\e[0m"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Letter not in word
|
|
26
|
+
def red(text)
|
|
27
|
+
pastel.red(text)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# UI accents, titles, highlights
|
|
31
|
+
def cyan(text)
|
|
32
|
+
pastel.cyan(text)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Dimmed/muted text for hints and secondary info
|
|
36
|
+
def dim(text)
|
|
37
|
+
pastel.dim(text)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Bold text for emphasis
|
|
41
|
+
def bold(text)
|
|
42
|
+
pastel.bold(text)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Yellow for warnings and streak indicators
|
|
46
|
+
def yellow(text)
|
|
47
|
+
pastel.yellow(text)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Magenta for special highlights
|
|
51
|
+
def magenta(text)
|
|
52
|
+
pastel.magenta(text)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# White for default bright text
|
|
56
|
+
def white(text)
|
|
57
|
+
pastel.white(text)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Blue for accents
|
|
61
|
+
def blue(text)
|
|
62
|
+
pastel.blue(text)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Combine styles - e.g., bold_green("WIN!")
|
|
66
|
+
def bold_green(text)
|
|
67
|
+
pastel.bold.green(text)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def bold_red(text)
|
|
71
|
+
pastel.bold.red(text)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def bold_cyan(text)
|
|
75
|
+
pastel.bold.cyan(text)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def bold_yellow(text)
|
|
79
|
+
pastel.bold.yellow(text)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|