JRuby Swing-based JButton clicking game

I decided this weekend to create a [very] simple Swing-based game using JRuby; in a matter of hours. Although I spent most of my time failing to make it pretty with image sprites (I fell back on background colors), I ended up with a simple prototype.

#!/usr/bin/env jruby

# import java classes
import javax.swing.JFrame
import javax.swing.ImageIcon
import java.awt.Color

class Game

  # define struct to represent a button
  SwingButton = Struct.new(:jbutton, :row, :column, :value)

  def initialize
    @frame = javax.swing.JFrame.new

    # define how many in a row collapse
    @adjacent_count = 3

    # button/window display settings
    @button_width = 100
    @button_height = 100
    @button_rows = 5
    @button_cols = 5
    @button_spacing = 5
    @frame.set_size @button_width*@button_cols+((@button_cols-1)*@button_spacing),
      @button_height*@button_cols+((@button_rows-1)*@button_spacing)

    # create buttons
    @buttons = []
    define_buttons_colors
    create_buttons

    # create swing frame
    @frame.set_layout(java.awt.GridLayout.new(@button_rows, @button_cols, @button_spacing, @button_spacing))
    @frame.setDefaultCloseOperation JFrame::EXIT_ON_CLOSE
    @frame.setResizable false
    @frame.show
  end

  # method to define RGB values for a button value
  def define_buttons_colors
    @button_colors = [
      [255,255,255], #ffffff white
      [255,153,153], #ff9999 pink
      [255,153,102], #ff9966 orange
      [255,255,153], #ffff99 yellow
      [153,255,153], #99ff99 green
      [102,255,204], #66ffcc green/blue
      [153,255,255], #99ffff light blue
      [102,204,255], #66ccff blue
      [153,153,255], #9999ff purple
      [255,153,255], #ff99ff purple/pink
    ]
  end

  def create_buttons
    (1..@button_rows).each do |row|
      (1..@button_cols).each do |column|

        # create a new swing button
        swing_button = SwingButton.new(javax.swing.JButton.new("0"), row, column, 0)
        swing_button.jbutton.setOpaque true
        swing_button.jbutton.setBorderPainted false
        swing_button.jbutton.setBackground Color.new(*@button_colors[swing_button.value])

        # add "click" action listener
        swing_button.jbutton.add_action_listener do |evt|
          handle_click(swing_button, evt)
        end
        @buttons << swing_button
      end
    end
    @buttons.each {|button| @frame.add button.jbutton}
  end

  # button click event handler
  def handle_click(swing_button, evt)

    # for now, if the button has already been clicked, return
    return if swing_button.value > 0

    swing_button.value += 1

    # looping until nothing left to collapse
    begin

      # check for adjacent similar values, todo
      adjacent_buttons = check_adjacent_count_same_value swing_button

      # update single button
      if adjacent_buttons.size < @adjacent_count
        swing_button.jbutton.setText swing_button.value.to_s
        swing_button.jbutton.setBackground Color.new(*@button_colors[swing_button.value])
        return
      end

      adjacent_buttons.each do |button|
        # clicked button
        if button == swing_button
          button.value += 1
          button.jbutton.setText button.value.to_s
          button.jbutton.setBackground Color.new(*@button_colors[button.value])
        # adjacent buttons
        else
          button.value = 0
          button.jbutton.setText button.value.to_s
          button.jbutton.setBackground Color.new(*@button_colors[button.value])
        end
      end

    end until check_adjacent_count_same_value(swing_button).size < @adjacent_count

  end

  def check_adjacent_count_same_value(swing_button)

    checked = []
    not_checked = [swing_button]

    while !not_checked.empty?
      button = not_checked.pop
      checked << button
      adjacent_buttons_with_same_value(button).each do |button|
        next if checked.include? button
        next if not_checked.include? button
        not_checked << button
      end

    end

    checked

  end

  def adjacent_buttons_with_same_value(swing_button)

    coordinates = []

    # top
    coordinates << {row: swing_button.row-1, column: swing_button.column} if swing_button.row > 1

    # right
    coordinates << {row: swing_button.row, column: swing_button.column+1} if swing_button.column < @button_cols

    # bottom
    coordinates << {row: swing_button.row+1, column: swing_button.column} if swing_button.row < @button_rows

    # left
    coordinates << {row: swing_button.row, column: swing_button.column-1} if swing_button.column > 1

    @buttons.select {|button| coordinates.include?({row: button.row, column: button.column}) }.select {|button| button.value == swing_button.value}

  end

end

Game.new
# run
./main.rb

The premise is to click on the buttons and 3 or more adjacent buttons in a row collapse and increment to the next higher value. Sound familiar?

Screenshot:
jruby swing game

Todo:

  • umm, scoring?!
  • image sprites
  • wildcards
  • hazards