Glimmer Battleship
Tags: ruby, desktop, gui, dsl
Published: 2021-08-07 20:27:14

[Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-swt) 4.20.13.15 just shipped with [Glimmer Battleship](https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/docs/reference/GLIMMER_SAMPLES.md#battleship).


Mid-game battle

![Glimmer Battleship](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vu2ra95bonuy54kn623z.png)

Placing ships on grid using drag and drop

![Placement](https://github.com/AndyObtiva/glimmer-dsl-swt/raw/master/images/glimmer-battleship-placement.png)

All ships placed. Ready for battle.

![Ready for Battle](https://github.com/AndyObtiva/glimmer-dsl-swt/raw/master/images/glimmer-battleship-ready-for-battle.png)

Finished game, beating the enemy.

![Finished Win](https://github.com/AndyObtiva/glimmer-dsl-swt/raw/master/images/glimmer-battleship-won.png)

Game over message box dialog.

![Game Over](https://github.com/AndyObtiva/glimmer-dsl-swt/raw/master/images/glimmer-battleship-game-over.png)

[You may check out the code over here.](https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/samples/elaborate/battleship.rb)

```ruby
# From: https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/docs/reference/GLIMMER_SAMPLES.md#battleship

require 'glimmer-dsl-swt'
require 'facets/string/titlecase'
require 'facets/string/underscore'

require_relative 'battleship/model/game'

require_relative 'battleship/view/grid'
require_relative 'battleship/view/ship_collection'
require_relative 'battleship/view/action_panel'

class Battleship
  include Glimmer::UI::CustomShell

  COLOR_WATER = rgb(156, 211, 219)
  COLOR_SHIP = :dark_gray
  
  before_body do
    @game = Model::Game.new
  end
    
  after_body do
    observe(@game, :over) do |game_over_value|
      if game_over_value
        game_over_message = if game_over_value == :you
          "Game over!\nYou Won!"
        else
          "Game over!\nYou Lost!"
        end
        
        message_box {
          text 'Game Over!'
          message game_over_message
        }.open
      end
    end
  end
  
  body {
    shell(:no_resize) {
      grid_layout(2, false) {
        horizontal_spacing 15
        vertical_spacing 15
      }
      
      text 'Glimmer Battleship'

      @enemy_grid = grid(game: @game, player: :enemy)
      @enemy_ship_collection = ship_collection(game: @game, player: :enemy)
      
      @player_grid = grid(game: @game, player: :you)
      @player_ship_collection = ship_collection(game: @game, player: :you)

      action_panel(game: @game)
    }
  }
end

Battleship.launch
```

```ruby
# From: https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/docs/reference/GLIMMER_SAMPLES.md#battleship

require_relative '../model/grid'

require_relative 'cell'

class Battleship
  module View
    class Grid
      include Glimmer::UI::CustomWidget
      
      options :game, :player
      
      body {
        composite {
          grid_layout(Model::Grid::WIDTH + 1, true) {
            margin_width 0
            margin_height 0
            horizontal_spacing 0
            vertical_spacing 0
          }
    
          label(:center) {
            layout_data(:fill, :center, true, false) {
              horizontal_span (Model::Grid::WIDTH + 1)
            }
            
            text player.to_s.capitalize
            font height: 20, style: :bold
          }
          
          label # filler
          Model::Grid::WIDTH.times do |column_index|
            label {
              text (column_index + 1).to_s
              font height: 16
            }
          end
          
          Model::Grid::HEIGHT.times do |row_index|
            label {
              text Model::Grid::ROW_ALPHABETS[row_index]
              font height: 16
            }
            Model::Grid::WIDTH.times do |column_index|
              cell(game: game, player: player, row_index: row_index, column_index: column_index) {
                layout_data {
                  width_hint 25
                  height_hint 25
                }
              }
            end
          end
        }
      }
    end
  end
end
```

```ruby
# From: https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/docs/reference/GLIMMER_SAMPLES.md#battleship

class Battleship
  module View
    class Cell
      include Glimmer::UI::CustomWidget
      
      class << self
        attr_accessor :dragging
        alias dragging? dragging
      end
      
      COLOR_WATER = rgb(156, 211, 219)
      COLOR_SHIP = :gray
      COLOR_PLACED = :white
      COLOR_EMPTY = :black
      COLOR_NO_HIT = :white
      COLOR_HIT = :red
      
      options :game, :player, :row_index, :column_index, :ship
      option :type, default: :grid # other type is :ship
      
      body {
        canvas {
          if type == :grid
            if player == :you
              background <= [model, :ship, on_read: ->(s) {s ? COLOR_SHIP : COLOR_WATER}]
            else
              background COLOR_WATER
            end
          else
            background <= [ship, :sunk, on_read: ->(s) {s ? COLOR_HIT : COLOR_PLACED}]
            background <= [ship, :top_left_cell, on_read: ->(c) {c ? COLOR_PLACED : COLOR_SHIP}]
          end
          
          rectangle(0, 0, [:max, -1], [:max, -1])
          oval(:default, :default, 10, 10) {
            foreground <= [model, :hit, on_read: ->(h) {h == nil ? COLOR_EMPTY : (h ? COLOR_HIT : COLOR_NO_HIT)}]
          }
          oval(:default, :default, 5, 5) {
            background <= [model, :hit, on_read: ->(h) {h == nil ? COLOR_EMPTY : (h ? COLOR_HIT : COLOR_NO_HIT)}]
          }
          
          on_mouse_move do |event|
            if game.started?
              if type == :grid
                if player == :enemy
                  body_root.cursor = :cross
                else
                  body_root.cursor = :arrow
                end
              else
                body_root.cursor = :arrow
              end
            end
          end
                    
          if player == :enemy
            on_mouse_up do
              game.attack!(row_index, column_index)
            end
          end
          
          if player == :you
            on_drag_detected do |event|
              unless game.started? || game.over?
                Cell.dragging = true
                body_root.cursor = :hand if type == :grid
              end
            end
          
            on_drag_set_data do |event|
              the_ship = ship || model&.ship
              if the_ship && !game.started? && !game.over? && !(type == :ship && the_ship.top_left_cell)
                event.data = the_ship.name.to_s
              else
                event.doit = false
                Cell.dragging = false
              end
            end
            
            on_mouse_up do
              unless game.started? || game.over?
                Cell.dragging = false
                change_cursor
              end
            end
                        
            if type == :grid
              on_mouse_move do |event|
                unless game.started? || game.over?
                  change_cursor
                end
              end
              
              on_mouse_hover do |event|
                unless game.started? || game.over?
                  change_cursor
                end
              end
              
              on_mouse_up do |event|
                unless game.started? || game.over?
                  begin
                    model.ship&.toggle_orientation!
                  rescue => e
                    Glimmer::Config.logger.debug e.full_message
                  end
                  change_cursor
                end
              end
              
              on_drop do |event|
                unless game.started? || game.over?
                  ship_name = event.data
                  place_ship(ship_name.to_s.to_sym) if ship_name
                  Cell.dragging = false
                  change_cursor
                end
              end
            end
          end
        }
      }
      
      def model
        game.grids[player].cell_rows[row_index][column_index] if type == :grid
      end
      
      def place_ship(ship_name)
        ship = game.ship_collections[player].ships[ship_name]
        model.place_ship!(ship)
      end
      
      def change_cursor
        if type == :grid && model.ship && !Cell.dragging?
          body_root.cursor = model.ship.orientation == :horizontal ? :sizens : :sizewe
        elsif !Cell.dragging?
          body_root.cursor = :arrow
        end
      end
    end
  end
end
```

```ruby
# From: https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/docs/reference/GLIMMER_SAMPLES.md#battleship

require_relative '../model/game'
require_relative '../model/ship_collection'

require_relative 'ship'

class Battleship
  module View
    class ShipCollection
      include Glimmer::UI::CustomWidget
      
      options :game, :player
      
      body {
        composite {
          row_layout(:vertical) {
            fill true
            margin_width 0
            margin_height 0
          }
          
          Model::ShipCollection::BATTLESHIPS.each do |ship_name, ship_length|
            ship(game: game, player: player, ship_name: ship_name, ship_length: ship_length)
          end
        }
      }
    end
  end
end
```

```ruby
# From: https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/docs/reference/GLIMMER_SAMPLES.md#battleship

require_relative 'cell'

class Battleship
  module View
    class Ship
      include Glimmer::UI::CustomWidget
      
      options :game, :player, :ship_name, :ship_length
      
      body {
        composite {
          row_layout(:vertical) {
            fill true
            margin_width 0
            margin_height 0
          }
      
          label {
            text ship_name.to_s.titlecase
            font height: 16
          }
        
          composite {
            grid_layout(ship_length, true) {
              margin_width 0
              margin_height 0
              horizontal_spacing 0
              vertical_spacing 0
            }
            
            ship_length.times do |column_index|
              cell(game: game, player: player, type: :ship, column_index: column_index, ship: game.ship_collections[player].ships[ship_name]) {
                layout_data {
                  width_hint 25
                  height_hint 25
                }
              }
            end
          }
        }
      }
    end
  end
end
```

```ruby
# From: https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/docs/reference/GLIMMER_SAMPLES.md#battleship

class Battleship
  module View
    class ActionPanel
      include Glimmer::UI::CustomWidget
      
      options :game
      
      body {
        composite {
          row_layout(:horizontal) {
            margin_width 0
            margin_height 0
          }
          
          layout_data(:center, :center, true, false)
                  
          @battle_button = button {
            text 'Battle!'
            enabled <= [game.ship_collections[:you], :placed_count, on_read: ->(c) {c == 5}]
            
            on_widget_selected do
              game.battle!
              @battle_button.enabled = false
            end
          }
                  
          button {
            text 'Restart'
            
            on_widget_selected do
              game.reset!
            end
          }
        }
      }
    end
  end
end
```

This is another reminder that [Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-swt) lets you build apps that normally take months and years in just days and weeks.

Happy [Glimmering](https://github.com/AndyObtiva/glimmer-dsl-swt)!