class ColorTheCircles

Constants

MARGIN_HEIGHT
MARGIN_WIDTH
SHAPE_MAX_SIZE
SHAPE_MIN_SIZE
TIME_MAX_EASY
TIME_MAX_HARD
TIME_MAX_INSANE
TIME_MAX_MEDIUM
WINDOW_HEIGHT
WINDOW_WIDTH

Attributes

score[RW]

Public Class Methods

new() click to toggle source
# File examples/color_the_circles.rb, line 19
def initialize
  @circles_data = []
  @score = 0
  @time_max = TIME_MAX_HARD
  @game_over = false
  register_observers
  setup_circle_factory
end

Public Instance Methods

add_circle() click to toggle source
# File examples/color_the_circles.rb, line 62
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
color_circle(x, y) click to toggle source
# File examples/color_the_circles.rb, line 82
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
launch() click to toggle source
# File examples/color_the_circles.rb, line 101
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
push_colored_circle_behind_uncolored_circles(colored_circle_data) click to toggle source
# File examples/color_the_circles.rb, line 94
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
register_observers() click to toggle source
# File examples/color_the_circles.rb, line 28
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
restart_game() click to toggle source
# File examples/color_the_circles.rb, line 76
def restart_game
  @score = 0 # update variable directly to avoid notifying observers
  @circles_data.clear
  @game_over = false
end
setup_circle_factory() click to toggle source
# File examples/color_the_circles.rb, line 46
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