examples/color_the_circles.rb
require 'glimmer-dsl-libui'
class ColorTheCircles
include Glimmer
WINDOW_WIDTH = 800
WINDOW_HEIGHT = 600
SHAPE_MIN_SIZE = 15
SHAPE_MAX_SIZE = 75
MARGIN_WIDTH = 55
MARGIN_HEIGHT = 155
TIME_MAX_EASY = 4
TIME_MAX_MEDIUM = 3
TIME_MAX_HARD = 2
TIME_MAX_INSANE = 1
attr_accessor :score
def initialize
@circles_data = []
@score = 0
@time_max = TIME_MAX_HARD
@game_over = false
register_observers
setup_circle_factory
end
def register_observers
# observe automatically enhances self to become Glimmer::DataBinding::ObservableModel and notify observer block of score attribute changes
observe(self, :score) do |new_score|
Glimmer::LibUI.queue_main do
@score_label.text = new_score.to_s
if new_score == -20
@game_over = true
msg_box('You Lost!', 'Sorry! Your score reached -20')
restart_game
elsif new_score == 0
@game_over = true
msg_box('You Won!', 'Congratulations! Your score reached 0')
restart_game
end
end
end
end
def setup_circle_factory
consumer = Proc.new do
unless @game_over
if @circles_data.empty?
# start with 3 circles to make more challenging
add_circle until @circles_data.size > 3
else
add_circle
end
end
delay = rand * @time_max
Glimmer::LibUI.timer(delay, repeat: false, &consumer)
end
Glimmer::LibUI.queue_main(&consumer)
end
def add_circle
circle_x = rand * (WINDOW_WIDTH - MARGIN_WIDTH - SHAPE_MAX_SIZE) + SHAPE_MAX_SIZE
circle_y = rand * (WINDOW_HEIGHT - MARGIN_HEIGHT - SHAPE_MAX_SIZE) + SHAPE_MAX_SIZE
circle_size = rand * (SHAPE_MAX_SIZE - SHAPE_MIN_SIZE) + SHAPE_MIN_SIZE
stroke_color = Glimmer::LibUI.x11_colors.sample
@circles_data << {
args: [circle_x, circle_y, circle_size],
fill: nil,
stroke: stroke_color
}
@area.queue_redraw_all
self.score -= 1 # notifies score observers automatically of change
end
def restart_game
@score = 0 # update variable directly to avoid notifying observers
@circles_data.clear
@game_over = false
end
def color_circle(x, y)
clicked_circle_data = @circles_data.find do |circle_data|
circle_data[:fill].nil? && circle_data[:circle]&.contain?(x, y)
end
if clicked_circle_data
clicked_circle_data[:fill] = clicked_circle_data[:stroke]
push_colored_circle_behind_uncolored_circles(clicked_circle_data)
@area.queue_redraw_all
self.score += 1 # notifies score observers automatically of change
end
end
def push_colored_circle_behind_uncolored_circles(colored_circle_data)
removed_colored_circle_data = @circles_data.delete(colored_circle_data)
last_colored_circle_data = @circles_data.select {|cd| cd[:fill]}.last
last_colored_circle_data_index = @circles_data.index(last_colored_circle_data) || -1
@circles_data.insert(last_colored_circle_data_index + 1, removed_colored_circle_data)
end
def launch
menu('Actions') {
menu_item('Restart') {
on_clicked do
restart_game
end
}
quit_menu_item
}
menu('Difficulty') {
radio_menu_item('Easy') {
on_clicked do
@time_max = TIME_MAX_EASY
end
}
radio_menu_item('Medium') {
on_clicked do
@time_max = TIME_MAX_MEDIUM
end
}
radio_menu_item('Hard') {
checked true
on_clicked do
@time_max = TIME_MAX_HARD
end
}
radio_menu_item('Insane') {
on_clicked do
@time_max = TIME_MAX_INSANE
end
}
}
menu('Help') {
menu_item('Instructions') {
on_clicked do
msg_box('Instructions', "Score goes down as circles are added.\nIf it reaches -20, you lose!\n\nClick circles to color and score!\nOnce score reaches 0, you win!\n\nBeware of concealed light-colored circles!\nThey are revealed once darker circles intersect them.\n\nThere are four levels of difficulty.\nChange via difficulty menu if the game gets too tough.")
end
}
}
window('Color The Circles', WINDOW_WIDTH, WINDOW_HEIGHT) {
margined true
grid {
button('Restart') {
left 0
top 0
halign :center
on_clicked do
restart_game
end
}
label('Score goes down as circles are added. If it reaches -20, you lose!') {
left 0
top 1
halign :center
}
label('Click circles to color and score! Once score reaches 0, you win!') {
left 0
top 2
halign :center
}
horizontal_box {
left 0
top 3
halign :center
label('Score:') {
stretchy false
}
@score_label = label(@score.to_s) {
stretchy false
}
}
@area = area {
left 0
top 4
hexpand true
vexpand true
halign :fill
valign :fill
on_draw do |area_draw_params|
path {
rectangle(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT)
fill :white
}
@circles_data.each do |circle_data|
circle_data[:circle] = circle(*circle_data[:args]) {
fill circle_data[:fill]
stroke circle_data[:stroke]
}
end
end
on_mouse_down do |area_mouse_event|
color_circle(area_mouse_event[:x], area_mouse_event[:y])
end
}
}
}.show
end
end
ColorTheCircles.new.launch