The task is to produce a matrix of 20 hexagon shaped widgets in a honeycomb arrangement. The matrix should be arranged in such a manner that there are five
columns of four hexagons. The hexagons in columns one, three and five are aligned horizontally, whereas the hexagons in columns two and four occupy a lower position within the arrangement. Each hexagon should be the same colour, and should
display a unique randomly selected single capital letter on the front. The application should now wait for the user to select a hexagon, either by using a pointing device, or by pressing a key that carries a corresponding letter on a hexagon. For platforms that support pointing devices and keyboards, the application should support both methods of selection. A record of the chosen letters should be maintained and the code should be suitably commented, at the point where the the selected letter has been determined. The selected hexagon should now change colour on the display. The cycle repeats until the user has chosen all of the letters. Note that each letter can only be selected once and previously selected hexagons retain their colour after selection. The program terminates when all letters have been chosen.

Optionally: output the list of selected letters and show the last selected letter, cater for a different number of columns or a different number of hexagons in each column, cater for two players, (turns alternate and the hexagons change a different colour depending on whether they were selected by player one or player two and records of both players selections are maintained.)



Shoes.app(title: "Honeycombs", height: 700, width: 700) do
  C = Math::cos(Math::PI/3)
  S = Math::sin(Math::PI/3)
  Radius = 60.0
  letters = [
    %w[L A R N D 1 2], 
    %w[G U I Y T 3 4], 
    %w[P C F E B 5 6], 
    %w[V S O M K 7 8],
    %w[Q X J Z H 9 0],
  ]
 
  def highlight(hexagon)
    hexagon.style(fill: magenta)
  end
 
  def unhighlight(hexagon)
    hexagon.style(fill: yellow)
  end
 
  def choose(hexagon)
    hexagon.choose
    highlight hexagon
    chosen = @hexagons.find_all {|h| h.chosen?}.map {|h| h.letter}
    if chosen.size == @hexagons.size
      @chosen.text = 'Every hexagon has been chosen.'
    else
      @chosen.text = "Chosen: #{chosen.sort.join(',')}\n" +
                     "Last Chosen: #{hexagon.letter}" 
    end
  end
 
  width = 20 + (Radius*(7*letters[0].size - 3)/4.0).ceil
  height = 60 + (Radius*(1 + 2*S*letters.size)).ceil
  @hexagons = []
  letter_to_hex = {}
 
  # create the GUI
  stack(height: height, width: width) do
    @chosen = para("Chosen:\nLast chosen:")
 
    # draw the hexagrams
    letters.each_index do |row|
      letters[0].each_index do |column|
        x = 60 + column * Radius * 0.75 + (1-S) * Radius
        y = 80 + row * S * Radius + (column.odd? ? S * Radius * 0.5 : 0)
        h = shape(x-Radius, y-S*Radius) do
          strokewidth 3
          move_to(x-C*Radius, y-S*Radius)
          line_to(x+C*Radius, y-S*Radius)
          line_to(x+Radius,   y)
          line_to(x+C*Radius, y+S*Radius)
          line_to(x-C*Radius, y+S*Radius)
          line_to(x-Radius,   y)
          line_to(x-C*Radius, y-S*Radius)
        end
 
        # add some attributes and methods to the shape
        class << h
          attr_accessor :x, :y, :state, :letter
          def chosen?
            not @state.nil?
          end
          def choose
            @state = :chosen
          end
          def contains?(px, py)
            if @x-Radius < px and px <= @x-C*Radius
              ratio = (px - @x + Radius)/(Radius*(1-C))
              @y - ratio*S*Radius < py and py <= @y + ratio*S*Radius
            elsif @x-C*Radius < px and px <= @x+C*Radius
              @y - S*Radius < py and py < @y + S*Radius
            elsif @x+C*Radius < px and px <= @x+Radius
              ratio = (@x + Radius - px)/(Radius*(1-C))
              @y - ratio*S*Radius < py and py <= @y + ratio*S*Radius
            else
              false
            end
          end
          def inspect
            %q(<%s,"%s",%s,%d@%d>) % [self.class, letter, chosen?, x, y]
          end
        end
 
        h.x = x + x - Radius
        h.y = y + y - S*Radius
        h.letter = letters[row][column]
        unhighlight h
 
        @hexagons << h
        letter_to_hex[h.letter.downcase] = h
        letter_to_hex[h.letter.upcase] = h
 
        # add the letter to the hexagon
        para(h.letter).style(size:56, stroke:red) \
                      .move(h.x - C*Radius, h.y - S*Radius)
      end
    end
 
    # highlight the hexagon under the mouse
    hex_over = nil
    motion do |x, y|
      hex = @hexagons.find {|h| h.contains?(x,y)}
      unless hex.nil? or hex.chosen?
        highlight hex 
      end
      unless hex_over == hex or hex_over.nil? or hex_over.chosen?
        unhighlight hex_over 
      end
      hex_over = hex
    end
 
    # handle mouse clicks
    click do |button, x, y|
      info("button #{button} clicked at (#{x}, #{y})")
      hexagon = @hexagons.find {|h| h.contains?(x,y)}
      if hexagon
        info("clicked hexagon #{hexagon}")
        choose hexagon
      end
    end
 
    # handle keystrokes
    keypress do |key| 
      if key == "\x11"  # control-Q
        exit
      elsif key == "?"
        info @hexagons.collect {|h| h.inspect}.join("\n")
      elsif letter_to_hex.has_key?(key)
        info("pressed key #{key} -> #{letter_to_hex[key]}")
        choose letter_to_hex[key]
      end
    end
  end
end