glimmer-dsl-swt 4.22.0.0 → 4.22.1.0

Sign up to get free protection for your applications and to get access to all the features.
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