Andy Maleh
Posted on October 18, 2023
Glimmer Wordle 1.1.5 (open-source desktop game) has just been released with official support for Windows. Although the game worked on Windows before, thanks to the platform-independent Glimmer DSL for SWT GUI library it was built with, styling was not tweaked for Windows till now in version 1.1.5.
To give you an idea of how the game flows, here is an animated Gif with it running on the Mac:
Here is the Glimmer code of the GUI View:
class Wordle
module View
class AppView
include Glimmer::UI::CustomShell
COLOR_TO_BACKGROUND_COLOR_MAP = {
green: rgb(106, 170, 100),
yellow: rgb(201, 180, 88),
gray: rgb(120, 124, 126),
}
COLOR_TO_TEXT_COLOR_MAP = {
green: :white,
yellow: :white,
gray: :white,
}
ALPHABET_LAYOUTS = {
alphabetical: [
%w[A B C D E F G H I J],
%w[K L M N O P Q R S],
%w[T U V W X Y Z],
],
qwerty: [
%w[Q W E R T Y U I O P],
%w[A S D F G H J K L],
%w[Z X C V B N M],
],
}
CONFIG_FILE = File.join(Dir.home, '.glimmer_wordle')
before_body do
@display = display {
on_about {
display_about_dialog
}
on_preferences {
display_about_dialog
}
on_swt_keydown do |key_event|
if key_event.keyCode == 8
do_backspace
elsif key_event.keyCode == swt(:arrow_left)
do_left
elsif key_event.keyCode == swt(:arrow_right)
do_right
elsif key_event.keyCode == swt(:cr)
if @five_letter_word.status == :in_progress
do_guess
else
do_restart
end
elsif valid_character?((key_event.keyCode.chr rescue ''))
do_type(key_event.keyCode.chr)
end
end
}
@five_letter_word = Model::FiveLetterWord.new
config = load_config
@alphabet_layout = config[:alphabet_layout] || :alphabetical
end
## Add widget content inside custom shell body
## Top-most widget must be a shell or another custom shell
#
body {
shell(:no_resize) {
grid_layout {
margin_width 10
margin_height 10
vertical_spacing 0
}
# Replace example content below with custom shell content
minimum_size 420, 540
image File.join(APP_ROOT, 'icons', 'windows', "Glimmer Wordle.ico") if OS.windows?
image File.join(APP_ROOT, 'icons', 'linux', "Glimmer Wordle.png") unless OS.windows?
text "Glimmer Wordle"
background :white
app_menu_bar
alphabet_container
label {
layout_data :center, :center, true, false
text 'You have 6 tries to guess a 5-letter word'
background :transparent if OS.windows?
}
word_guesser
guess_button
}
}
def app_menu_bar
menu_bar {
menu {
text '&Game'
menu_item {
text '&Restart'
on_widget_selected {
do_restart
}
}
menu_item {
text 'E&xit'
on_widget_selected {
exit(0)
}
}
}
menu {
text '&View'
menu {
text 'Alphabet &Layout'
menu_item(:radio) {
text '&Alphabetical'
selection @alphabet_layout == :alphabetical
on_widget_selected {
self.alphabet_layout = :alphabetical
rebuild_alphabet_container
}
}
menu_item(:radio) {
text '&Qwerty'
selection @alphabet_layout == :qwerty
on_widget_selected {
self.alphabet_layout = :qwerty
rebuild_alphabet_container
}
}
}
}
menu {
text '&Help'
menu_item {
text '&Instructions'
on_widget_selected {
display_instructions_dialog
}
}
menu_item {
text '&About'
on_widget_selected {
display_about_dialog
}
}
}
}
end
def display_instructions_dialog
message_box(body_root) {
text 'Instructions'
message <<~MULTI_LINE_STRING
Make 6 guesses for a 5-letter word.
If you enter a letter that is part of the word, and at the right location, it becomes green,
If you enter a letter that is part of the word, but at the wrong location, it becomes yellow.
If you enter a letter that is not part of the word, it becomes red.
MULTI_LINE_STRING
}.open
end
def display_about_dialog
message_box(body_root) {
text 'About'
message "Glimmer Wordle #{VERSION}\n\n#{LICENSE}"
}.open
end
def alphabet_container
@alphabet_container = composite {
layout_data(:center, :center, true, false)
grid_layout {
margin_width 0
margin_height 0
vertical_spacing 0
}
background :white
alphabets
}
end
def alphabets
alphabet_row(ALPHABET_LAYOUTS[@alphabet_layout][0]) {
layout_data(:center, :center, true, false) {
width_hint 318
height_hint 50
}
}
alphabet_row(ALPHABET_LAYOUTS[@alphabet_layout][1]) {
layout_data(:center, :center, true, false) {
width_hint 288
height_hint 50
}
}
alphabet_row(ALPHABET_LAYOUTS[@alphabet_layout][2]) {
layout_data(:center, :center, true, false) {
width_hint 222
height_hint 50
}
}
end
def alphabet_row(alphabet_characters, &block)
canvas {
block.call
background :white
@alphabet_rectangles ||= []
@alphabet_borders ||= []
@alphabet_letters ||= []
alphabet_characters.each_with_index do |alphabet_character, i|
@alphabet_rectangles << rectangle(1 + i*32, @alphabet_row_offset_y, 28, 28) {
background :transparent
@alphabet_borders << rectangle {
foreground :gray
line_width 2
}
@alphabet_letters << text(alphabet_character, :default, [:default, OS.linux? ? 5 : (OS.windows? ? 1 : 0)]) {
font alphabet_font
}
}
end
}
end
def alphabet_layout_alphabets
ALPHABET_LAYOUTS[@alphabet_layout].reduce(:+)
end
def word_guesser
@canvasses ||= []
margin_x = 5
margin_y = 5
@canvasses << canvas {
layout_data(:center, :center, true, false) {
width_hint 230
height_hint 50
}
background :white
focus true
@rectangles = []
@borders = []
@letters = []
5.times do |i|
@rectangles << rectangle(margin_x + i*45, margin_y, 40, 40) {
background :transparent
@borders << rectangle {
foreground i == 0 ? :title_background : :gray
line_width 2
}
@letters << text('', :default, [:default, OS.linux? ? 6 : (OS.windows? ? 1 : 0)]) {
font letter_font
}
}
end
}
end
def guess_button
@guess_button = button {
layout_data :center, :center, true, false
text 'Guess'
on_widget_selected do
do_guess
end
}
end
def do_backspace
@letter = @letters[highlighted_letter_index]
if @letter.string != ''
index_to_delete = highlighted_letter_index
else
index_to_delete = [highlighted_letter_index - 1, 0].max
end
@letter = @letters[index_to_delete]
@letter.string = ''
@borders.each { |caret| caret.foreground = :gray} # refactor this reusable code into a method that highlights the caret
@borders[index_to_delete].foreground = :title_background
end
def do_guess
return if !word_filled_up?
word = @letters.map(&:string).join
if invalid_word?(word)
message_box {
text 'Invalid Word'
message "The word you entered is not an allowed guess!\n\nPlease try another word!"
}.open
return
end
guess_result = @five_letter_word.guess(word)
update_guess_word_background_colors(guess_result)
update_alphabet_background_colors
if @five_letter_word.status == :in_progress
@guess_button.dispose
body_root.content {
word_guesser
guess_button
}
else
@guess_button.dispose
body_root.content {
@restart_button = button {
layout_data :center, :center, true, false
text 'Restart'
focus true
on_widget_selected do
do_restart
end
}
}
display_share_text_dialog
end
body_root.layout(true, true)
body_root.pack(true)
end
def highlighted_letter_index
@borders.each_with_index.find {|border, i| border.foreground.first == color(:title_background).swt_color }.last
end
def do_type(character)
index = highlighted_letter_index
@letter = @letters[index]
@letter.string = character.upcase
if @letters.any? {|letter| letter.string == ''}
@borders.each { |caret| caret.foreground = :gray} # refactor this reusable code into a method that highlights the caret
@borders[index == 4 ? 4 : index + 1].foreground = :title_background
end
end
def do_left
index = [highlighted_letter_index - 1, 0].max
@borders.each { |caret| caret.foreground = :gray} # refactor this reusable code into a method that highlights the caret
@borders[index].foreground = :title_background
end
def do_right
index = [highlighted_letter_index + 1, @letters.count - 1].min
@borders.each { |caret| caret.foreground = :gray} # refactor this reusable code into a method that highlights the caret
@borders[index].foreground = :title_background
end
def do_restart
@share_text_dialog&.close
alphabet_layout_alphabets.each_with_index do |alphabet_character, i|
@alphabet_borders[i].foreground = :gray
@alphabet_rectangles[i].background = :white
@alphabet_letters[i].foreground = :black
end
@restart_button&.dispose
@canvasses.dup.each(&:dispose)
@canvasses.clear
@guess_button&.dispose
body_root.content {
word_guesser
guess_button
}
body_root.layout(true, true)
body_root.pack(true)
@five_letter_word.refresh
end
def update_guess_word_background_colors(guess_result)
guess_result.each_with_index do |result_color, i|
background_color = COLOR_TO_BACKGROUND_COLOR_MAP[result_color]
@borders[i].foreground = background_color
@rectangles[i].background = background_color
@letters[i].foreground = COLOR_TO_TEXT_COLOR_MAP[result_color]
async_exec { @canvasses.last.redraw }
end
end
def update_alphabet_background_colors
alphabet_layout_alphabets.each_with_index do |alphabet_character, i|
result_color = @five_letter_word.colored_alphabets[alphabet_character.downcase]
if result_color
background_color = COLOR_TO_BACKGROUND_COLOR_MAP[result_color]
@alphabet_borders[i].foreground = background_color
@alphabet_rectangles[i].background = background_color
@alphabet_letters[i].foreground = COLOR_TO_TEXT_COLOR_MAP[result_color]
end
end
end
def word_filled_up?
@letters.find {|letter| letter.string == ''}.nil?
end
def invalid_word?(word)
!Model::FiveLetterWord::WORLD_ALLOWED_GUESSES.include?(word.downcase)
end
def valid_character?(character)
((65..90).to_a + (97..122).to_a).map {|n| n.chr}.include?(character)
end
def display_share_text_dialog
result = "#{@five_letter_word.answer.upcase}\n\n#{emoji_result}"
Clipboard.copy(result)
@share_text_dialog = dialog(body_root) {
grid_layout
text 'Share Result'
label {
layout_data :center, :center, true, false
text 'Result is copied to clipboard!'
}
styled_text {
layout_data :fill, :fill, true, true
editable false
caret nil
alignment :center
font name: 'Segoe UI Emoji' if OS.windows?
text result
}
}
@share_text_dialog.open
end
def alphabet_font
the_font = {style: :bold, height: 28}
the_font.merge!(name: 'Helvetica', height: 21) if OS.linux?
the_font.merge!(name: 'Arial', height: 25) if OS.windows?
the_font
end
def letter_font
the_font = {style: :bold, height: 40}
the_font.merge!(name: 'Helvetica', height: 30) if OS.linux?
the_font.merge!(name: 'Arial', height: 32) if OS.windows?
the_font
end
def dispose_alphabet_container_children
@alphabet_rectangles.clear
@alphabet_borders.clear
@alphabet_letters.clear
@alphabet_container.children.each(&:dispose)
end
def rebuild_alphabet_container
dispose_alphabet_container_children
@alphabet_container.content {
alphabets
}
@alphabet_container.layout(true, true)
@alphabet_container.pack(true)
update_alphabet_background_colors
end
def alphabet_layout=(value)
@alphabet_layout = value
save_config
end
def new_config
{
alphabet_layout: @alphabet_layout
}
end
def save_config
File.write(CONFIG_FILE, YAML.dump(new_config))
rescue => e
puts e.full_message
end
def load_config
File.exist?(CONFIG_FILE) ? YAML.load(File.read(CONFIG_FILE)) : {}
rescue => e
puts e.full_message
{}
end
def emoji_result
result = ''
@five_letter_word.guess_results.each do |row|
row.each do |result_color|
case result_color
when :green
result << "🟩"
when :yellow
result << "🟨"
when :gray
result << "⬜"
end
end
result << "\n"
end
result
end
end
end
end
GitHub: https://github.com/AndyObtiva/glimmer_wordle
RubyGem: https://rubygems.org/gems/glimmer_wordle
Blog Post Announcement: https://andymaleh.blogspot.com/2023/10/glimmer-wordle-115.html
💖 💪 🙅 🚩
Andy Maleh
Posted on October 18, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.