glimmer_wordle 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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