glimmer_wordle 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 18a1772cb458e49ced9e103096f736c307ae5b4e100457ea5e19ba04e4f4d28a
4
+ data.tar.gz: 9e341e855c03fb9a587f0b537988b60fe8771e9e4d3ba19d38a9678f98bee849
5
+ SHA512:
6
+ metadata.gz: 370bc2a11daa444256e6dcd7fb43129d3f09fe17fdd0f6db4b972ce2455e1437ad3473682ac54c71cbf208c81f6490a93a6d1978d3b0fe4ede55b6ab7ae39e3d
7
+ data.tar.gz: 53ee100edcbacaa594ed6adf5b97a8041d44f1a25f281722093c1c9159df445c8bb4deb5b292186dfa5910e22e70665668378a4a514e8a3e587ba92f820cc564
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2022 Andy Maleh
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,74 @@
1
+ # <img src='https://raw.githubusercontent.com/AndyObtiva/glimmer_wordle/master/icons/linux/Glimmer Wordle.png' height=85 /> Wordle
2
+ ## Word Game
3
+ [![Gem Version](https://badge.fury.io/rb/glimmer_wordle.svg)](http://badge.fury.io/rb/glimmer_wordle)
4
+
5
+ [Wordle](https://en.wikipedia.org/wiki/Wordle) word game desktop GUI app written [test-first](https://github.com/AndyObtiva/wordle/blob/master/spec/app/model/five_letter_word_spec.rb) using [Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-swt) (JRuby Desktop Development GUI Framework) as inspiration by [Kevin Newton's blog post](https://kddnewton.com/2022/01/29/solving-wordle-in-ruby.html).
6
+
7
+ ![Wordle Screenshot](screenshots/glimmer-wordle.png)
8
+
9
+ ## Demo
10
+
11
+ ![Wordle Screenshot](screenshots/glimmer-wordle.gif)
12
+
13
+ ## Setup
14
+
15
+ ### Download
16
+
17
+ [<img src='https://raw.githubusercontent.com/AndyObtiva/glimmer_wordle/master/icons/linux/Glimmer Wordle.png' height=40 /> Download Glimmer Wordle DMG for Mac ARM64 Monterey and Older](https://www.dropbox.com/s/t77si617tsaxe0c/Glimmer%20Wordle-arm64-1.0.0.dmg?dl=1)
18
+
19
+ [<img src='https://raw.githubusercontent.com/AndyObtiva/glimmer_wordle/master/icons/linux/Glimmer Wordle.png' height=40 /> Download Glimmer Wordle DMG for Mac x86_64 Catalina and Older](https://www.dropbox.com/s/amlrm4nufdlzlb1/Glimmer%20Wordle-1.0.0.dmg?dl=1)
20
+
21
+ ### Install Ruby Gem
22
+
23
+ If you are a software engineer and would rather install this game as a Ruby gem, please follow these instructions.
24
+
25
+ First, make sure you have all the [prerequisites of Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-swt#pre-requisites) setup on your machine like Java and JRuby.
26
+
27
+ Run:
28
+
29
+ ```
30
+ gem install glimmer_wordle
31
+ ```
32
+
33
+ Afterwards, run:
34
+
35
+ ```
36
+ glimmer_wordle
37
+ ```
38
+
39
+ ## Contributing
40
+
41
+ - Check out the latest master to make sure the feature hasn't been
42
+ implemented or the bug hasn't been fixed yet.
43
+ - Check out the issue tracker to make sure someone already hasn't
44
+ requested it and/or contributed it.
45
+ - Fork the project.
46
+ - Start a feature/bugfix branch.
47
+ - Commit and push until you are happy with your contribution.
48
+ - Make sure to add tests for it. This is important so I don't break it
49
+ in a future version unintentionally.
50
+ - Please try not to mess with the Rakefile, version, or history. If
51
+ you want to have your own version, or is otherwise necessary, that
52
+ is fine, but please isolate to its own commit so I can cherry-pick
53
+ around it.
54
+
55
+ ## TODO
56
+
57
+ [TODO.md](TODO.md)
58
+
59
+ ## Change Log
60
+
61
+ [CHANGELOG.md](CHANGELOG.md)
62
+
63
+ ## Copyright
64
+
65
+ [MIT](LICENSE.txt)
66
+
67
+ Copyright (c) 2022 Andy Maleh. See
68
+ [LICENSE.txt](LICENSE.txt) for further details.
69
+
70
+ --
71
+
72
+ [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=40 />](https://github.com/AndyObtiva/glimmer) Built with [Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-swt) (JRuby Desktop Development GUI Framework)
73
+
74
+ Glimmer Wordle icon made by <a href="https://www.flaticon.com/authors/freepik" title="Freepik">Freepik</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a>
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,3 @@
1
+ require_relative '../wordle'
2
+
3
+ Wordle::View::AppView.launch
@@ -0,0 +1,69 @@
1
+ class Wordle
2
+ module Model
3
+ class FiveLetterWord
4
+ class << self
5
+ def random_word
6
+ WORLD_ANSWERS_ALPHABETICAL.sample
7
+ end
8
+ end
9
+
10
+ WORLD_ANSWERS_ALPHABETICAL_FILE = File.join(APP_ROOT, 'config', 'wordle-answers-alphabetical.txt')
11
+ WORLD_ALLOWED_GUESSES_FILE = File.join(APP_ROOT, 'config', 'wordle-allowed-guesses.txt')
12
+ WORLD_ANSWERS_ALPHABETICAL = File.read(WORLD_ANSWERS_ALPHABETICAL_FILE).lines(chomp: true).sort
13
+ WORLD_ALLOWED_GUESSES = (WORLD_ANSWERS_ALPHABETICAL + File.read(WORLD_ALLOWED_GUESSES_FILE).lines(chomp: true)).sort
14
+ COLORS = [:green, :yellow, :gray]
15
+ STATUSES = [:in_progress, :win, :loss]
16
+
17
+ attr_accessor :guesses, :guess_results, :status, :answer, :colored_alphabets
18
+
19
+ def initialize
20
+ refresh
21
+ end
22
+
23
+ def refresh
24
+ self.guesses = []
25
+ self.guess_results = []
26
+ self.status = :in_progress
27
+ self.answer = self.class.random_word
28
+ self.colored_alphabets = (97..122).map {|n| n.chr}.reduce({}) {|hash, letter| hash.merge(letter => nil) }
29
+ end
30
+
31
+ def guess(word_guess)
32
+ word_guess = word_guess.downcase
33
+ return [] if status == :win || status == :loss
34
+ guesses << word_guess
35
+ guess_results << guess_result_for(word_guess)
36
+ update_colored_alphabets(word_guess, guess_results.last)
37
+ self.status = :win if guesses.last == answer
38
+ self.status = :loss if guesses.last != answer && guesses.count == 6
39
+ guess_results.last
40
+ end
41
+
42
+ private
43
+
44
+ def guess_result_for(word_guess)
45
+ 5.times.map do |i|
46
+ if answer[i] == word_guess[i]
47
+ :green
48
+ elsif answer.include?(word_guess[i])
49
+ :yellow
50
+ else
51
+ :gray
52
+ end
53
+ end
54
+ end
55
+
56
+ def update_colored_alphabets(word_guess, guess_result)
57
+ 5.times do |i|
58
+ if guess_result[i] == :green
59
+ self.colored_alphabets[word_guess[i]] = :green
60
+ elsif guess_result[i] == :yellow
61
+ self.colored_alphabets[word_guess[i]] = :yellow if self.colored_alphabets[word_guess[i]] != :green
62
+ elsif guess_result[i] == :gray
63
+ self.colored_alphabets[word_guess[i]] = :gray if self.colored_alphabets[word_guess[i]].nil?
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,361 @@
1
+ class Wordle
2
+ module View
3
+ class AppView
4
+ include Glimmer::UI::CustomShell
5
+
6
+ COLOR_TO_BACKGROUND_COLOR_MAP = {
7
+ green: :dark_green,
8
+ yellow: :dark_yellow,
9
+ gray: :dark_gray,
10
+ }
11
+
12
+ COLOR_TO_TEXT_COLOR_MAP = {
13
+ green: :white,
14
+ yellow: :white,
15
+ gray: :white,
16
+ }
17
+
18
+ ALPHABET_ROW1 = %w[Q W E R T Y U I O P]
19
+ ALPHABET_ROW2 = %w[A S D F G H J K L]
20
+ ALPHABET_ROW3 = %w[Z X C V B N M]
21
+
22
+
23
+ before_body do
24
+ @display = display {
25
+ on_about {
26
+ display_about_dialog
27
+ }
28
+ on_preferences {
29
+ display_about_dialog
30
+ }
31
+ on_swt_keydown do |key_event|
32
+ if key_event.keyCode == 8
33
+ do_backspace
34
+ elsif key_event.keyCode == swt(:cr)
35
+ if @five_letter_word.status == :in_progress
36
+ do_guess
37
+ else
38
+ do_restart
39
+ end
40
+ elsif valid_character?((key_event.keyCode.chr rescue ''))
41
+ do_type(key_event.keyCode.chr)
42
+ end
43
+ end
44
+ }
45
+ @five_letter_word = Model::FiveLetterWord.new
46
+ end
47
+
48
+ ## Add widget content inside custom shell body
49
+ ## Top-most widget must be a shell or another custom shell
50
+ #
51
+ body {
52
+ shell(:no_resize) {
53
+ grid_layout {
54
+ margin_width 10
55
+ margin_height 10
56
+ vertical_spacing 0
57
+ }
58
+
59
+ # Replace example content below with custom shell content
60
+ minimum_size 420, 540
61
+ image File.join(APP_ROOT, 'icons', 'windows', "Glimmer Wordle.ico") if OS.windows?
62
+ image File.join(APP_ROOT, 'icons', 'linux', "Glimmer Wordle.png") unless OS.windows?
63
+ text "Glimmer Wordle"
64
+ background :white
65
+
66
+ app_menu_bar
67
+
68
+ alphabets
69
+
70
+ label {
71
+ layout_data :center, :center, true, false
72
+ text 'You have 6 tries to guess a 5-letter word'
73
+ }
74
+
75
+ word_guesser
76
+
77
+ guess_button
78
+ }
79
+ }
80
+
81
+ def app_menu_bar
82
+ menu_bar {
83
+ menu {
84
+ text '&Game'
85
+
86
+ menu_item {
87
+ text '&Restart'
88
+
89
+ on_widget_selected {
90
+ do_restart
91
+ }
92
+ }
93
+
94
+ menu_item {
95
+ text 'E&xit'
96
+
97
+ on_widget_selected {
98
+ exit(0)
99
+ }
100
+ }
101
+ }
102
+
103
+ menu {
104
+ text '&Help'
105
+
106
+ menu_item {
107
+ text '&About...'
108
+
109
+ on_widget_selected {
110
+ display_about_dialog
111
+ }
112
+ }
113
+ }
114
+ }
115
+ end
116
+
117
+ def display_about_dialog
118
+ message_box(body_root) {
119
+ text 'About'
120
+ message "Glimmer Wordle #{VERSION}\n\n#{LICENSE}"
121
+ }.open
122
+ end
123
+
124
+ def alphabets
125
+ alphabet_row(ALPHABET_ROW1) {
126
+ layout_data(:center, :center, true, false) {
127
+ width_hint 318
128
+ height_hint 50
129
+ }
130
+ }
131
+
132
+ alphabet_row(ALPHABET_ROW2) {
133
+ layout_data(:center, :center, true, false) {
134
+ width_hint 288
135
+ height_hint 50
136
+ }
137
+ }
138
+
139
+ alphabet_row(ALPHABET_ROW3) {
140
+ layout_data(:center, :center, true, false) {
141
+ width_hint 222
142
+ height_hint 50
143
+ }
144
+ }
145
+ end
146
+
147
+ def alphabet_row(alphabet_characters, &block)
148
+ canvas {
149
+ block.call
150
+ background :white
151
+
152
+ @alphabet_rectangles ||= []
153
+ @alphabet_borders ||= []
154
+ @alphabet_letters ||= []
155
+ alphabet_characters.each_with_index do |alphabet_character, i|
156
+ @alphabet_rectangles << rectangle(1 + i*32, @alphabet_row_offset_y, 28, 28) {
157
+ background :transparent
158
+
159
+ @alphabet_borders << rectangle {
160
+ foreground :gray
161
+ line_width 2
162
+ }
163
+
164
+ @alphabet_letters << text(alphabet_character) {
165
+ font style: :bold, height: 28
166
+ }
167
+ }
168
+ end
169
+ }
170
+ end
171
+
172
+ def word_guesser
173
+ @canvasses ||= []
174
+ margin_x = 5
175
+ margin_y = 5
176
+ @canvasses << canvas {
177
+ layout_data(:center, :center, true, false) {
178
+ width_hint 230
179
+ height_hint 50
180
+ }
181
+ background :white
182
+ focus true
183
+
184
+ @rectangles = []
185
+ @borders = []
186
+ @letters = []
187
+ 5.times do |i|
188
+ @rectangles << rectangle(margin_x + i*45, margin_y, 40, 40) {
189
+ background :transparent
190
+
191
+ @borders << rectangle {
192
+ foreground i == 0 ? :title_background : :gray
193
+ line_width 2
194
+ }
195
+
196
+ @letters << text('') {
197
+ font style: :bold, height: 40
198
+ }
199
+ }
200
+ end
201
+ }
202
+ end
203
+
204
+ def guess_button
205
+ @guess_button = button {
206
+ layout_data :center, :center, true, false
207
+ text 'Guess'
208
+
209
+ on_widget_selected do
210
+ do_guess
211
+ end
212
+ }
213
+ end
214
+
215
+ def do_backspace
216
+ @letter = @letters.find {|letter| letter.string == ''}
217
+ index = @letter ? @letters.index(@letter) - 1 : 4
218
+ @letter = @letters[index]
219
+ @letter.string = ''
220
+ if index > 0
221
+ @borders.each { |caret| caret.foreground = :gray}
222
+ @borders[index - 1].foreground = :title_background
223
+ end
224
+ end
225
+
226
+ def do_guess
227
+ return if !word_filled_up?
228
+ word = @letters.map(&:string).join
229
+ if invalid_word?(word)
230
+ message_box {
231
+ text 'Invalid Word'
232
+ message "The word you entered is not an allowed guess!\n\nPlease try another word!"
233
+ }.open
234
+ return
235
+ end
236
+ guess_result = @five_letter_word.guess(word)
237
+ update_guess_word_background_colors(guess_result)
238
+ update_alphabet_background_colors
239
+ if @five_letter_word.status == :in_progress
240
+ @guess_button.dispose
241
+ body_root.content {
242
+ word_guesser
243
+ guess_button
244
+ }
245
+ else
246
+ @guess_button.dispose
247
+ body_root.content {
248
+ @restart_button = button {
249
+ layout_data :center, :center, true, false
250
+ text 'Restart'
251
+ focus true
252
+
253
+ on_widget_selected do
254
+ do_restart
255
+ end
256
+ }
257
+ }
258
+ display_share_text_dialog
259
+ end
260
+ body_root.layout(true, true)
261
+ body_root.pack(true)
262
+ end
263
+
264
+ def do_type(character)
265
+ @letter = @letters.find {|letter| letter.string == ''}
266
+ index = @letters.index(@letter)
267
+ if @letter
268
+ @borders.each { |caret| caret.foreground = :gray}
269
+ @borders[index == 4 ? 4 : index + 1].foreground = :title_background
270
+ @letter.string = character.upcase
271
+ end
272
+ end
273
+
274
+ def do_restart
275
+ (ALPHABET_ROW1 + ALPHABET_ROW2 + ALPHABET_ROW3).each_with_index do |alphabet_character, i|
276
+ @alphabet_borders[i].foreground = :gray
277
+ @alphabet_rectangles[i].background = :white
278
+ @alphabet_letters[i].foreground = :black
279
+ end
280
+ @restart_button.dispose
281
+ @canvasses.dup.each(&:dispose)
282
+ @canvasses.clear
283
+ body_root.content { word_guesser }
284
+ body_root.layout(true, true)
285
+ body_root.pack(true)
286
+ @five_letter_word.refresh
287
+ end
288
+
289
+ def update_guess_word_background_colors(guess_result)
290
+ guess_result.each_with_index do |result_color, i|
291
+ background_color = COLOR_TO_BACKGROUND_COLOR_MAP[result_color]
292
+ @borders[i].foreground = background_color
293
+ @rectangles[i].background = background_color
294
+ @letters[i].foreground = COLOR_TO_TEXT_COLOR_MAP[result_color]
295
+ async_exec { @canvasses.last.redraw }
296
+ end
297
+ end
298
+
299
+ def update_alphabet_background_colors
300
+ (ALPHABET_ROW1 + ALPHABET_ROW2 + ALPHABET_ROW3).each_with_index do |alphabet_character, i|
301
+ result_color = @five_letter_word.colored_alphabets[alphabet_character.downcase]
302
+ if result_color
303
+ background_color = COLOR_TO_BACKGROUND_COLOR_MAP[result_color]
304
+ @alphabet_borders[i].foreground = background_color
305
+ @alphabet_rectangles[i].background = background_color
306
+ @alphabet_letters[i].foreground = COLOR_TO_TEXT_COLOR_MAP[result_color]
307
+ end
308
+ end
309
+ end
310
+
311
+ def word_filled_up?
312
+ @letters.find {|letter| letter.string == ''}.nil?
313
+ end
314
+
315
+ def invalid_word?(word)
316
+ !Model::FiveLetterWord::WORLD_ALLOWED_GUESSES.include?(word.downcase)
317
+ end
318
+
319
+ def valid_character?(character)
320
+ ((65..90).to_a + (97..122).to_a).map {|n| n.chr}.include?(character)
321
+ end
322
+
323
+ def display_share_text_dialog
324
+ result = ''
325
+ @five_letter_word.guess_results.each do |row|
326
+ row.each do |result_color|
327
+ case result_color
328
+ when :green
329
+ result << "🟩"
330
+ when :yellow
331
+ result << "🟨"
332
+ when :gray
333
+ result << "⬜"
334
+ end
335
+ end
336
+ result << "\n"
337
+ end
338
+
339
+ Clipboard.copy(result)
340
+
341
+ dialog(body_root) {
342
+ grid_layout
343
+ text 'Share Result'
344
+
345
+ label {
346
+ layout_data :center, :center, true, false
347
+ text 'Result is copied to clipboard!'
348
+ }
349
+
350
+ styled_text {
351
+ layout_data :fill, :fill, true, true
352
+ editable false
353
+ caret nil
354
+ alignment :center
355
+ text result
356
+ }
357
+ }.open
358
+ end
359
+ end
360
+ end
361
+ end
data/app/wordle.rb ADDED
@@ -0,0 +1,27 @@
1
+ $LOAD_PATH.unshift(File.expand_path('..', __FILE__))
2
+
3
+ begin
4
+ require 'bundler/setup'
5
+ Bundler.require(:default)
6
+ rescue
7
+ # this runs when packaged as a gem (no bundler)
8
+ require 'glimmer-dsl-swt'
9
+ # add more gems if needed
10
+ end
11
+
12
+ Clipboard.implementation = Clipboard::Java
13
+ Clipboard.copy(Clipboard.paste) # pre-initialize library to avoid slowdown during use
14
+
15
+ class Wordle
16
+ include Glimmer
17
+
18
+ APP_ROOT = File.expand_path('../..', __FILE__)
19
+ VERSION = File.read(File.join(APP_ROOT, 'VERSION'))
20
+ LICENSE = File.read(File.join(APP_ROOT, 'LICENSE.txt'))
21
+
22
+ Display.app_name = 'Glimmer Wordle'
23
+ Display.app_version = VERSION
24
+ end
25
+
26
+ require 'wordle/model/five_letter_word'
27
+ require 'wordle/view/app_view'
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env jruby
2
+
3
+ runner = File.expand_path('../../app/wordle/launch.rb', __FILE__)
4
+
5
+ # Detect if inside a JAR file or not
6
+ if runner.include?('uri:classloader')
7
+ require runner
8
+ else
9
+ require 'glimmer/launcher'
10
+
11
+ launcher = Glimmer::Launcher.new([runner] + ARGV)
12
+ launcher.launch
13
+ end