glimmer-dsl-swt 4.22.0.0 → 4.22.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +191 -169
  3. data/README.md +5 -5
  4. data/VERSION +1 -1
  5. data/docs/reference/GLIMMER_COMMAND.md +2 -2
  6. data/docs/reference/GLIMMER_CONFIGURATION.md +14 -3
  7. data/docs/reference/GLIMMER_GUI_DSL_SYNTAX.md +98 -20
  8. data/docs/reference/GLIMMER_SAMPLES.md +13 -0
  9. data/glimmer-dsl-swt.gemspec +0 -0
  10. data/lib/ext/glimmer/config.rb +41 -24
  11. data/lib/glimmer/data_binding/observable_widget.rb +6 -6
  12. data/lib/glimmer/data_binding/widget_binding.rb +2 -1
  13. data/lib/glimmer/dsl/swt/observe_expression.rb +2 -1
  14. data/lib/glimmer/dsl/swt/transform_expression.rb +1 -1
  15. data/lib/glimmer/launcher.rb +15 -14
  16. data/lib/glimmer/rake_task/scaffold.rb +2 -14
  17. data/lib/glimmer/swt/color_proxy.rb +5 -5
  18. data/lib/glimmer/swt/custom/drawable.rb +4 -0
  19. data/lib/glimmer/swt/custom/shape/line.rb +0 -1
  20. data/lib/glimmer/swt/custom/shape/path.rb +2 -2
  21. data/lib/glimmer/swt/custom/shape/path_segment.rb +2 -2
  22. data/lib/glimmer/swt/custom/shape/point.rb +8 -1
  23. data/lib/glimmer/swt/custom/shape.rb +170 -69
  24. data/lib/glimmer/swt/display_proxy.rb +15 -10
  25. data/lib/glimmer/swt/image_proxy.rb +5 -5
  26. data/lib/glimmer/swt/message_box_proxy.rb +5 -5
  27. data/lib/glimmer/swt/shape_listener_proxy.rb +55 -0
  28. data/lib/glimmer/swt/transform_proxy.rb +3 -3
  29. data/lib/glimmer/swt/tray_proxy.rb +4 -4
  30. data/lib/glimmer/swt/widget_proxy.rb +5 -7
  31. data/lib/glimmer/ui/custom_shape.rb +34 -10
  32. data/lib/glimmer/ui/custom_widget.rb +3 -8
  33. data/lib/glimmer-dsl-swt.rb +6 -2
  34. data/samples/elaborate/klondike_solitaire/model/column_pile.rb +0 -1
  35. data/samples/elaborate/klondike_solitaire/view/column_pile.rb +3 -16
  36. data/samples/elaborate/klondike_solitaire/view/dealing_pile.rb +1 -1
  37. data/samples/elaborate/klondike_solitaire/view/dealt_pile.rb +12 -5
  38. data/samples/elaborate/klondike_solitaire/view/empty_playing_card.rb +2 -1
  39. data/samples/elaborate/klondike_solitaire/view/foundation_pile.rb +2 -2
  40. data/samples/elaborate/klondike_solitaire/view/hidden_playing_card.rb +2 -2
  41. data/samples/elaborate/klondike_solitaire/view/klondike_solitaire_menu_bar.rb +60 -0
  42. data/samples/elaborate/klondike_solitaire/view/playing_card.rb +3 -2
  43. data/samples/elaborate/klondike_solitaire.rb +13 -55
  44. data/samples/elaborate/mandelbrot_fractal.rb +3 -1
  45. data/samples/elaborate/quarto/model/game.rb +124 -0
  46. data/samples/elaborate/quarto/model/piece/cube.rb +31 -0
  47. data/samples/elaborate/quarto/model/piece/cylinder.rb +31 -0
  48. data/samples/elaborate/quarto/model/piece.rb +70 -0
  49. data/samples/elaborate/quarto/view/available_pieces_area.rb +72 -0
  50. data/samples/elaborate/quarto/view/board.rb +65 -0
  51. data/samples/elaborate/quarto/view/cell.rb +85 -0
  52. data/samples/elaborate/quarto/view/cube.rb +73 -0
  53. data/samples/elaborate/quarto/view/cylinder.rb +72 -0
  54. data/samples/elaborate/quarto/view/message_box_panel.rb +114 -0
  55. data/samples/elaborate/quarto/view/piece.rb +56 -0
  56. data/samples/elaborate/quarto/view/selected_piece_area.rb +69 -0
  57. data/samples/elaborate/quarto.rb +190 -0
  58. data/samples/hello/hello_custom_widget.rb +23 -5
  59. metadata +17 -23
  60. data/bin/glimmer_runner.rb +0 -4
@@ -578,11 +578,9 @@ module Glimmer
578
578
  flyweight_swt_widget_classes[underscored_widget_name] = swt_widget_class
579
579
  rescue SyntaxError, NameError => e
580
580
  Glimmer::Config.logger.debug {e.full_message}
581
- # Glimmer::Config.logger.debug {"#{e.message}\n#{e.backtrace.join("\n")}"}
582
581
  nil
583
582
  rescue => e
584
583
  Glimmer::Config.logger.debug {e.full_message}
585
- # Glimmer::Config.logger.debug {"#{e.message}\n#{e.backtrace.join("\n")}"}
586
584
  nil
587
585
  end
588
586
  end
@@ -778,20 +776,20 @@ module Glimmer
778
776
  end
779
777
  end
780
778
 
781
- def method_missing(method, *args, &block)
779
+ def method_missing(method_name, *args, &block)
782
780
  # TODO push most of this logic down to Properties (and perhaps create Listeners module as well)
783
- if can_handle_observation_request?(method)
784
- handle_observation_request(method, &block)
781
+ if block && can_handle_observation_request?(method_name)
782
+ handle_observation_request(method_name, &block)
785
783
  else
786
784
  super
787
785
  end
788
786
  end
789
787
 
790
- def respond_to?(method, *args, &block)
788
+ def respond_to?(method_name, *args, &block)
791
789
  result = super
792
790
  return true if result
793
791
  auto_exec do
794
- can_handle_observation_request?(method)
792
+ can_handle_observation_request?(method_name)
795
793
  end
796
794
  end
797
795
 
@@ -158,17 +158,12 @@ module Glimmer
158
158
  def after_body(&block)
159
159
  @after_body_block = block
160
160
  end
161
-
162
- # Current custom shapes being rendered. Useful to yoke all observers evaluated during rendering of their custom shapes for automatical disposal on_shape_disposed
163
- def current_custom_shapes
164
- @current_custom_shapes ||= []
165
- end
166
161
  end
167
162
 
168
163
  attr_reader :body_root, :args, :parent, :parent_proxy, :options
169
164
 
170
165
  def initialize(parent, *args, options, &content)
171
- Glimmer::UI::CustomShape.current_custom_shapes << self
166
+ SWT::DisplayProxy.current_custom_widgets_and_shapes << self
172
167
  @parent_proxy = @parent = parent
173
168
  @parent_proxy = @parent&.get_data('proxy') if @parent.respond_to?(:get_data) && @parent.get_data('proxy')
174
169
  @args = args
@@ -183,13 +178,26 @@ module Glimmer
183
178
  auto_exec do # TODO is this necessary given shape is a lightweight construct (not SWT widget) ?
184
179
  @body_root.set_data('custom_shape', self)
185
180
  end
181
+ auto_exec do
182
+ @dispose_listener_registration = @body_root.on_shape_disposed do
183
+ unless @body_root.shell_proxy.last_shell_closing?
184
+ observer_registrations.compact.each(&:deregister)
185
+ observer_registrations.clear
186
+ end
187
+ end
188
+ end
186
189
  execute_hook('after_body')
190
+ post_add_content if content.nil?
187
191
  end
188
192
 
189
193
  # Subclasses may override to perform post initialization work on an added child
190
194
  def post_initialize_child(child)
191
195
  # No Op by default
192
196
  end
197
+
198
+ def post_add_content
199
+ SWT::DisplayProxy.current_custom_widgets_and_shapes.delete(self)
200
+ end
193
201
 
194
202
  def observer_registrations
195
203
  @observer_registrations ||= []
@@ -276,17 +284,33 @@ module Glimmer
276
284
  end
277
285
  alias getData get_data # for compatibility with SWT APIs
278
286
  alias data get_data # for compatibility with SWT APIs
287
+
288
+ def dispose(dispose_images: true, dispose_patterns: true, redraw: true)
289
+ body_root.dispose(dispose_images: dispose_images, dispose_patterns: dispose_patterns, redraw: redraw)
290
+ # @dispose_listener_registration.deregister # TODO enable once returning a true listener object or observer proc registration
291
+ end
292
+
293
+ def disposed
294
+ body_root.disposed
295
+ end
296
+ alias disposed? disposed
297
+ alias is_disposed disposed
279
298
 
280
- def method_missing(method, *args, &block)
299
+ def method_missing(method_name, *args, &block)
281
300
  # TODO Consider supporting a glimmer error silencing option for methods defined here
282
301
  # but fail the glimmer DSL for the right reason to avoid seeing noise in the log output
283
- body_root.send(method, *args, &block)
302
+ if block && can_handle_observation_request?(method_name)
303
+ handle_observation_request(method_name, &block)
304
+ else
305
+ body_root.send(method_name, *args, &block)
306
+ end
284
307
  end
285
308
 
286
309
  alias local_respond_to? respond_to?
287
- def respond_to?(method, *args, &block)
310
+ def respond_to?(method_name, *args, &block)
288
311
  super or
289
- body_root.respond_to?(method, *args, &block)
312
+ can_handle_observation_request?(method_name) or
313
+ body_root.respond_to?(method_name, *args, &block)
290
314
  end
291
315
 
292
316
  private
@@ -159,17 +159,12 @@ module Glimmer
159
159
  def after_body(&block)
160
160
  @after_body_block = block
161
161
  end
162
-
163
- # Current custom widgets being rendered. Useful to yoke all observers evaluated during rendering of their custom widgets for automatical disposal on_widget_disposed
164
- def current_custom_widgets
165
- @current_custom_widgets ||= []
166
- end
167
162
  end
168
163
 
169
164
  attr_reader :body_root, :swt_widget, :parent, :parent_proxy, :swt_style, :options
170
165
 
171
166
  def initialize(parent, *swt_constants, options, &content)
172
- Glimmer::UI::CustomWidget.current_custom_widgets << self
167
+ SWT::DisplayProxy.current_custom_widgets_and_shapes << self
173
168
  @parent_proxy = @parent = parent
174
169
  @parent_proxy = @parent&.get_data('proxy') if @parent.respond_to?(:get_data) && @parent.get_data('proxy')
175
170
  @swt_style = SWT::SWTProxy[*swt_constants]
@@ -203,7 +198,7 @@ module Glimmer
203
198
  end
204
199
 
205
200
  def post_add_content
206
- Glimmer::UI::CustomWidget.current_custom_widgets.delete(self)
201
+ SWT::DisplayProxy.current_custom_widgets_and_shapes.delete(self)
207
202
  end
208
203
 
209
204
  def observer_registrations
@@ -312,7 +307,7 @@ module Glimmer
312
307
  def method_missing(method, *args, &block)
313
308
  # TODO Consider supporting a glimmer error silencing option for methods defined here
314
309
  # but fail the glimmer DSL for the right reason to avoid seeing noise in the log output
315
- if can_handle_observation_request?(method)
310
+ if block && can_handle_observation_request?(method)
316
311
  handle_observation_request(method, &block)
317
312
  else
318
313
  body_root.send(method, *args, &block)
@@ -30,9 +30,13 @@ if !['', 'false'].include?(ENV['GLIMMER_BUNDLER_SETUP'].to_s.strip.downcase)
30
30
  end
31
31
  require 'java'
32
32
  require 'puts_debuggerer' if ("#{ENV['pd']}#{ENV['PD']}").to_s.downcase.include?('true')
33
- require 'concurrent-ruby' # ensures glimmer relies on Concurrent data-structure classes (e.g. Concurrent::Array)
33
+
34
+ # concurrent-ruby gem ensures glimmer relies on Concurrent data-structure classes
35
+ require 'concurrent/array'
36
+ require 'concurrent/hash'
37
+ require 'concurrent/set'
34
38
  require 'glimmer'
35
- require 'logging'
39
+
36
40
  require 'nested_inherited_jruby_include_package'
37
41
  require 'super_module'
38
42
  require 'date'
@@ -13,7 +13,6 @@ class KlondikeSolitaire
13
13
  def reset!
14
14
  playing_cards.clear
15
15
  populate!(count.times.map { @game.deck.pop })
16
- notify_observers(:playing_cards)
17
16
  end
18
17
 
19
18
  # this method does not validate
@@ -7,16 +7,6 @@ class KlondikeSolitaire
7
7
  class ColumnPile
8
8
  include Glimmer::UI::CustomShape
9
9
 
10
- IMAGE_EMPTY = image(50, 80) {
11
- rectangle(0, 0, 50, 80) {
12
- background :dark_green
13
-
14
- rectangle(0, 0, 49, 79, 15, 15) {
15
- foreground :gray
16
- }
17
- }
18
- }
19
-
20
10
  options :pile_x, :pile_y, :model
21
11
 
22
12
  after_body {
@@ -36,13 +26,10 @@ class KlondikeSolitaire
36
26
  card_parent_pile = card_shape.parent_pile
37
27
  card_source_model = card_parent_pile.model
38
28
  cards = card_source_model.remove!(card)
39
- if cards.is_a?(Array) # if it is a column pile
40
- cards[1..-1].each do |card|
41
- model.add!(card)
42
- end
43
- end
29
+ cards[1..-1].each { |card| model.add!(card) } if cards.is_a?(Array) # if it is a column pile
44
30
  drop_event.dragged_shape.dispose
45
31
  rescue => e
32
+ Glimmer::Config.logger.debug { "Error encountered on drop of a card to a column pile: #{e.full_message}" }
46
33
  drop_event.doit = false
47
34
  end
48
35
  end
@@ -50,7 +37,7 @@ class KlondikeSolitaire
50
37
  }
51
38
 
52
39
  def build_column_pile(playing_cards)
53
- body_root.shapes.to_a.each(&:dispose)
40
+ body_root.shapes.to_a.dup.each { |shape| shape.dispose(redraw: false) }
54
41
  current_parent = body_root
55
42
  playing_cards.each_with_index do |card, i|
56
43
  current_parent.content {
@@ -12,7 +12,7 @@ class KlondikeSolitaire
12
12
 
13
13
  after_body {
14
14
  observe(model, 'playing_cards.empty?') do |empty_value|
15
- body_root.shapes.to_a.each(&:dispose)
15
+ body_root.shapes.to_a.each { |shape| shape.dispose(redraw: false) }
16
16
  if empty_value
17
17
  body_root.content {
18
18
  empty_playing_card
@@ -13,16 +13,23 @@ class KlondikeSolitaire
13
13
  after_body do
14
14
  observe(model, 'playing_cards.empty?') do |empty_value|
15
15
  if empty_value
16
- body_root.shapes.to_a.dup.each(&:dispose)
16
+ body_root.shapes.to_a.dup.each { |shape| shape.dispose(redraw: false) }
17
17
  body_root.content {
18
18
  empty_playing_card
19
19
  }
20
20
  else
21
- body_root.content {
22
- playing_card(model: model.playing_cards.last, parent_pile: self) {
23
- drag_source true
21
+ body_root.shapes.each { |shape| shape.drag_source = false }
22
+ before_last_shape = body_root.shapes[-2] && body_root.shapes[-2].get_data('custom_shape').respond_to?(:model) && body_root.shapes[-2].get_data('custom_shape').model
23
+ if model.playing_cards.last == before_last_shape # happens when dragging card out
24
+ body_root.shapes.last.dispose
25
+ body_root.shapes.last.drag_source = true
26
+ else
27
+ body_root.content {
28
+ playing_card(model: model.playing_cards.last, parent_pile: self) {
29
+ drag_source true
30
+ }
24
31
  }
25
- }
32
+ end
26
33
  end
27
34
  end
28
35
  end
@@ -13,12 +13,13 @@ class KlondikeSolitaire
13
13
  end
14
14
 
15
15
  body {
16
- rectangle(card_x, card_y, 49, 79, 15, 15) {
16
+ rectangle(card_x, card_y, PLAYING_CARD_WIDTH - 1, PLAYING_CARD_HEIGHT - 1, 15, 15) {
17
17
  foreground :gray
18
18
 
19
19
  if suit
20
20
  text {
21
21
  string Model::PlayingCard.suit_text(suit)
22
+ font height: 20
22
23
  x :default
23
24
  y :default
24
25
  is_transparent true
@@ -8,10 +8,9 @@ class KlondikeSolitaire
8
8
 
9
9
  options :pile_x, :pile_y, :game, :suit
10
10
 
11
- attr_accessor :current_image, :model
11
+ attr_accessor :model
12
12
 
13
13
  before_body do
14
- self.current_image = image(50, 80) {empty_playing_card(suit: suit)}
15
14
  self.model = game.foundation_piles[Model::PlayingCard::SUITS.index(suit)]
16
15
  end
17
16
 
@@ -45,6 +44,7 @@ class KlondikeSolitaire
45
44
  card_source_model.remove!(card)
46
45
  drop_event.dragged_shape.dispose
47
46
  rescue => e
47
+ Glimmer::Config.logger.debug { "Error encountered on drop of a card to a foundation pile: #{e.full_message}" }
48
48
  drop_event.doit = false
49
49
  end
50
50
  end
@@ -11,11 +11,11 @@ class KlondikeSolitaire
11
11
  end
12
12
 
13
13
  body {
14
- rectangle(card_x, card_y, 49, 79, 15, 15) {
14
+ rectangle(card_x, card_y, PLAYING_CARD_WIDTH - 1, PLAYING_CARD_HEIGHT - 1, 15, 15) {
15
15
  background :red
16
16
 
17
17
  # border
18
- rectangle(0, 0, 49, 79, 15, 15) {
18
+ rectangle(0, 0, PLAYING_CARD_WIDTH - 1, PLAYING_CARD_HEIGHT - 1, 15, 15) {
19
19
  foreground :black
20
20
  }
21
21
  }
@@ -0,0 +1,60 @@
1
+ class KlondikeSolitaire
2
+ module View
3
+ class KlondikeSolitaireMenuBar
4
+ include Glimmer::UI::CustomWidget
5
+
6
+ option :game
7
+
8
+ before_body do
9
+ @display = display {
10
+ on_about {
11
+ display_about_dialog
12
+ }
13
+ on_preferences {
14
+ display_about_dialog
15
+ }
16
+ }
17
+ end
18
+
19
+ body {
20
+ menu_bar {
21
+ menu {
22
+ text '&Game'
23
+ menu_item {
24
+ text '&Restart'
25
+ accelerator (OS.mac? ? :command : :ctrl), :r
26
+
27
+ on_widget_selected {
28
+ game.restart!
29
+ }
30
+ }
31
+ menu_item {
32
+ text 'E&xit'
33
+ accelerator :alt, :f4
34
+
35
+ on_widget_selected {
36
+ exit(0)
37
+ }
38
+ }
39
+ }
40
+ menu {
41
+ text '&Help'
42
+ menu_item {
43
+ text '&About...'
44
+ on_widget_selected {
45
+ display_about_dialog
46
+ }
47
+ }
48
+ }
49
+ }
50
+ }
51
+
52
+ def display_about_dialog
53
+ message_box(body_root) {
54
+ text 'About'
55
+ message "Glimmer Klondike Solitaire\nGlimmer DSL for SWT Elaborate Sample"
56
+ }.open
57
+ end
58
+ end
59
+ end
60
+ end
@@ -11,17 +11,18 @@ class KlondikeSolitaire
11
11
  end
12
12
 
13
13
  body {
14
- rectangle(card_x, card_y, 49, 79, 15, 15) {
14
+ rectangle(card_x, card_y, PLAYING_CARD_WIDTH - 1, PLAYING_CARD_HEIGHT - 1, 15, 15) {
15
15
  background model.hidden ? :red : :white
16
16
 
17
17
  # border
18
- rectangle(0, 0, 49, 79, 15, 15) {
18
+ rectangle(0, 0, PLAYING_CARD_WIDTH - 1, PLAYING_CARD_HEIGHT - 1, 15, 15) {
19
19
  foreground :black
20
20
  }
21
21
 
22
22
  unless model.hidden?
23
23
  text {
24
24
  string model ? "#{model.rank_text}#{model.suit_text}" : ""
25
+ font height: PLAYING_CARD_FONT_HEIGHT
25
26
  x 5
26
27
  y 5
27
28
  foreground model ? model.color : :transparent
@@ -4,35 +4,26 @@ require_relative 'klondike_solitaire/model/game'
4
4
 
5
5
  require_relative 'klondike_solitaire/view/action_panel'
6
6
  require_relative 'klondike_solitaire/view/tableau'
7
+ require_relative 'klondike_solitaire/view/klondike_solitaire_menu_bar'
7
8
 
8
9
  class KlondikeSolitaire
9
10
  include Glimmer::UI::CustomShell
10
11
 
11
12
  PLAYING_CARD_WIDTH = 50
12
- PLAYING_CARD_HEIGHT = 80
13
+ PLAYING_CARD_HEIGHT = 76
13
14
  PLAYING_CARD_SPACING = 5
15
+ PLAYING_CARD_FONT_HEIGHT = 16
14
16
  MARGIN = 15
15
17
  TABLEAU_WIDTH = 2*MARGIN + 7*(PLAYING_CARD_WIDTH + PLAYING_CARD_SPACING)
16
- TABLEAU_HEIGHT = 400
18
+ TABLEAU_HEIGHT = 420
17
19
 
18
20
  before_body do
19
21
  @game = Model::Game.new
20
- Display.app_name = 'Glimmer Klondike Solitaire'
21
- @display = display {
22
- on_about {
23
- display_about_dialog
24
- }
25
- on_preferences {
26
- display_about_dialog
27
- }
28
- }
29
22
  end
30
23
 
31
24
  body {
32
25
  shell {
33
- row_layout(:vertical) {
34
- fill true
35
- center true
26
+ grid_layout {
36
27
  margin_width 0
37
28
  margin_height 0
38
29
  margin_top 15
@@ -41,53 +32,20 @@ class KlondikeSolitaire
41
32
  text "Glimmer Klondike Solitaire"
42
33
  background :dark_green
43
34
 
44
- action_panel(game: @game)
45
- tableau(game: @game) {
46
- layout_data {
47
- width TABLEAU_WIDTH
48
- height TABLEAU_HEIGHT
49
- }
35
+ klondike_solitaire_menu_bar(game: @game)
36
+
37
+ action_panel(game: @game) {
38
+ layout_data(:fill, :center, true, false)
50
39
  }
51
40
 
52
- menu_bar {
53
- menu {
54
- text '&Game'
55
- menu_item {
56
- text '&Restart'
57
- accelerator (OS.mac? ? :command : :ctrl), :r
58
-
59
- on_widget_selected {
60
- @game.restart!
61
- }
62
- }
63
- menu_item {
64
- text 'E&xit'
65
- accelerator :alt, :f4
66
-
67
- on_widget_selected {
68
- body_root.close
69
- }
70
- }
71
- }
72
- menu {
73
- text '&Help'
74
- menu_item {
75
- text '&About...'
76
- on_widget_selected {
77
- display_about_dialog
78
- }
79
- }
41
+ tableau(game: @game) {
42
+ layout_data(:fill, :fill, true, true) {
43
+ width_hint TABLEAU_WIDTH
44
+ height_hint TABLEAU_HEIGHT
80
45
  }
81
46
  }
82
47
  }
83
48
  }
84
-
85
- def display_about_dialog
86
- message_box(body_root) {
87
- text 'About'
88
- message "Glimmer Klondike Solitaire\nGlimmer DSL for SWT Elaborate Sample"
89
- }.open
90
- end
91
49
  end
92
50
 
93
51
  KlondikeSolitaire.launch
@@ -21,7 +21,9 @@
21
21
 
22
22
  require 'glimmer-dsl-swt'
23
23
  require 'complex'
24
- require 'concurrent-ruby'
24
+ require 'concurrent/executor/fixed_thread_pool'
25
+ require 'concurrent/utility/processor_counter'
26
+ require 'concurrent/array'
25
27
 
26
28
  # Mandelbrot multi-threaded implementation leveraging all processor cores.
27
29
  class Mandelbrot
@@ -0,0 +1,124 @@
1
+ # Copyright (c) 2007-2021 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require_relative 'piece'
23
+ require_relative 'piece/cube'
24
+ require_relative 'piece/cylinder'
25
+
26
+ class Quarto
27
+ module Model
28
+ class Game
29
+ MOVES = [:select_piece, :place_piece]
30
+ PLAYER_NUMBERS = [1, 2]
31
+ ROW_COUNT = 4
32
+ COLUMN_COUNT = 4
33
+
34
+ attr_accessor :board, :available_pieces, :selected_piece, :current_move, :current_player_number, :over, :winner_player_number
35
+ alias over? over
36
+
37
+ def initialize
38
+ start
39
+ end
40
+
41
+ def start
42
+ self.over = false
43
+ self.board = ROW_COUNT.times.map {COLUMN_COUNT.times.map {nil}}
44
+ self.available_pieces = Piece.all_pieces.dup
45
+ self.selected_piece = nil
46
+ self.current_player_number = 1
47
+ self.current_move = MOVES.first
48
+ self.winner_player_number = nil
49
+ end
50
+ alias restart start
51
+
52
+ def select_piece(piece)
53
+ self.selected_piece = piece
54
+ next_player
55
+ next_move
56
+ end
57
+
58
+ def place_piece(piece, row:, column:)
59
+ if @board[row][column].nil?
60
+ @board[row][column] = piece
61
+ if win?
62
+ self.winner_player_number = current_player_number # must set before updating observed over attribute
63
+ self.over = true
64
+ elsif draw?
65
+ self.winner_player_number = nil # must set before updating observed over attribute
66
+ self.over = true
67
+ else
68
+ next_move
69
+ end
70
+ end
71
+ end
72
+
73
+ def next_move
74
+ last_move = current_move
75
+ last_move_index = MOVES.index(last_move)
76
+ self.current_move = MOVES[(last_move_index + 1)%MOVES.size]
77
+ end
78
+
79
+ def next_player
80
+ last_player_number = current_player_number
81
+ last_player_number_index = PLAYER_NUMBERS.index(current_player_number)
82
+ self.current_player_number = PLAYER_NUMBERS[(last_player_number_index + 1)%PLAYER_NUMBERS.size]
83
+ end
84
+
85
+ def draw?
86
+ !win? && board.flatten.compact.count == 12
87
+ end
88
+
89
+ def win?
90
+ row_win? || column_win? || diagonal_win?
91
+ end
92
+
93
+ def row_win?
94
+ ROW_COUNT.times.any? { |row| equivalent_pieces?(@board[row]) }
95
+ end
96
+
97
+ def column_win?
98
+ COLUMN_COUNT.times.any? do |column|
99
+ equivalent_pieces?(ROW_COUNT.times.map {|row| @board[row][column]})
100
+ end
101
+ end
102
+
103
+ def diagonal_win?
104
+ diagonal1_win? || diagonal2_win?
105
+ end
106
+
107
+ def diagonal1_win?
108
+ equivalent_pieces?(ROW_COUNT.times.map { |n| @board[n][n] })
109
+ end
110
+
111
+ def diagonal2_win?
112
+ equivalent_pieces?(ROW_COUNT.times.map { |n| @board[ROW_COUNT - 1 - n][n] })
113
+ end
114
+
115
+ def equivalent_pieces?(the_pieces)
116
+ return false if the_pieces.any?(&:nil?)
117
+ the_pieces.map(&:class).uniq.size == 1 ||
118
+ the_pieces.map(&:pitted).uniq.size == 1 ||
119
+ the_pieces.map(&:height).uniq.size == 1 ||
120
+ the_pieces.map(&:color).uniq.size == 1
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,31 @@
1
+ # Copyright (c) 2007-2021 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require_relative '../piece'
23
+
24
+ class Quarto
25
+ module Model
26
+ class Piece
27
+ class Cube < Piece
28
+ end
29
+ end
30
+ end
31
+ end