glimmer-dsl-libui 0.2.19 → 0.2.23

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.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.19
1
+ 0.2.23
@@ -49,7 +49,7 @@ window('Area Gallery', 400, 400) {
49
49
  }
50
50
  text(161, 40, 100) { # x, y, width
51
51
  string('Area Gallery') {
52
- font family: 'Arial', size: 14
52
+ font family: 'Arial', size: (OS.mac? ? 14 : 11)
53
53
  color :black
54
54
  }
55
55
  }
@@ -149,7 +149,7 @@ window('Area Gallery', 400, 400) {
149
149
  width 100
150
150
 
151
151
  string {
152
- font family: 'Arial', size: 14
152
+ font family: 'Arial', size: (OS.mac? ? 14 : 11)
153
153
  color :black
154
154
 
155
155
  'Area Gallery'
@@ -50,7 +50,7 @@ window('Area Gallery', 400, 400) {
50
50
  }
51
51
  text(161, 40, 100) { # x, y, width
52
52
  string('Area Gallery') {
53
- font family: 'Arial', size: 14
53
+ font family: 'Arial', size: (OS.mac? ? 14 : 11)
54
54
  color :black
55
55
  }
56
56
  }
@@ -150,7 +150,7 @@ window('Area Gallery', 400, 400) {
150
150
  width 100
151
151
 
152
152
  string {
153
- font family: 'Arial', size: 14
153
+ font family: 'Arial', size: (OS.mac? ? 14 : 11)
154
154
  color :black
155
155
 
156
156
  'Area Gallery'
@@ -43,7 +43,7 @@ class MetaExample
43
43
 
44
44
  def run_example(example)
45
45
  Thread.new do
46
- command = "ruby -r #{glimmer_dsl_libui_file} #{example} 2>&1"
46
+ command = "#{RbConfig.ruby} -r #{glimmer_dsl_libui_file} #{example} 2>&1"
47
47
  result = ''
48
48
  IO.popen(command) do |f|
49
49
  sleep(0.0001) # yield to main thread
@@ -0,0 +1,33 @@
1
+ class Snake
2
+ module Model
3
+ class Apple
4
+ attr_reader :game
5
+ attr_accessor :row, :column
6
+
7
+ def initialize(game)
8
+ @game = game
9
+ end
10
+
11
+ # generates a new location from scratch or via dependency injection of what cell is (for testing purposes)
12
+ def generate(initial_row: nil, initial_column: nil)
13
+ if initial_row && initial_column
14
+ self.row, self.column = initial_row, initial_column
15
+ else
16
+ self.row, self.column = @game.height.times.zip(@game.width.times).reject do |row, column|
17
+ @game.snake.vertebrae.map {|v| [v.row, v.column]}.include?([row, column])
18
+ end.sample
19
+ end
20
+ end
21
+
22
+ def remove
23
+ self.row = nil
24
+ self.column = nil
25
+ end
26
+
27
+ # inspect is overridden to prevent printing very long stack traces
28
+ def inspect
29
+ "#{super[0, 120]}... >"
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,51 @@
1
+ require 'fileutils'
2
+
3
+ require_relative 'snake'
4
+ require_relative 'apple'
5
+
6
+ class Snake
7
+ module Model
8
+ class Game
9
+ WIDTH_DEFAULT = 40
10
+ HEIGHT_DEFAULT = 40
11
+ FILE_HIGH_SCORE = File.expand_path(File.join(Dir.home, '.glimmer-snake'))
12
+
13
+ attr_reader :width, :height
14
+ attr_accessor :snake, :apple, :over, :score, :high_score
15
+ alias over? over
16
+
17
+ def initialize(width = WIDTH_DEFAULT, height = HEIGHT_DEFAULT)
18
+ @width = width
19
+ @height = height
20
+ @snake = Snake.new(self)
21
+ @apple = Apple.new(self)
22
+ FileUtils.touch(FILE_HIGH_SCORE)
23
+ @high_score = File.read(FILE_HIGH_SCORE).to_i rescue 0
24
+ end
25
+
26
+ def score=(new_score)
27
+ @score = new_score
28
+ self.high_score = @score if @score > @high_score
29
+ end
30
+
31
+ def high_score=(new_high_score)
32
+ @high_score = new_high_score
33
+ File.write(FILE_HIGH_SCORE, @high_score.to_s)
34
+ rescue => e
35
+ puts e.full_message
36
+ end
37
+
38
+ def start
39
+ self.over = false
40
+ self.score = 0
41
+ self.snake.generate
42
+ self.apple.generate
43
+ end
44
+
45
+ # inspect is overridden to prevent printing very long stack traces
46
+ def inspect
47
+ "#{super[0, 75]}... >"
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,95 @@
1
+ require_relative 'vertebra'
2
+
3
+ class Snake
4
+ module Model
5
+ class Snake
6
+ SCORE_EAT_APPLE = 50
7
+ RIGHT_TURN_MAP = {
8
+ north: :east,
9
+ east: :south,
10
+ south: :west,
11
+ west: :north
12
+ }
13
+ LEFT_TURN_MAP = RIGHT_TURN_MAP.invert
14
+
15
+ attr_accessor :collided
16
+ alias collided? collided
17
+
18
+ attr_reader :game
19
+ # vertebrae and joins are ordered from tail to head
20
+ attr_accessor :vertebrae
21
+
22
+ def initialize(game)
23
+ @game = game
24
+ end
25
+
26
+ # generates a new snake location and orientation from scratch or via dependency injection of what head_cell and orientation are (for testing purposes)
27
+ def generate(initial_row: nil, initial_column: nil, initial_orientation: nil)
28
+ self.collided = false
29
+ initial_vertebra = Vertebra.new(snake: self, row: initial_row, column: initial_column, orientation: initial_orientation)
30
+ self.vertebrae = [initial_vertebra]
31
+ end
32
+
33
+ def length
34
+ @vertebrae.length
35
+ end
36
+
37
+ def head
38
+ @vertebrae.last
39
+ end
40
+
41
+ def tail
42
+ @vertebrae.first
43
+ end
44
+
45
+ def remove
46
+ self.vertebrae.clear
47
+ self.joins.clear
48
+ end
49
+
50
+ def move
51
+ @old_tail = tail.dup
52
+ @new_head = head.dup
53
+ case @new_head.orientation
54
+ when :east
55
+ @new_head.column = (@new_head.column + 1) % @game.width
56
+ when :west
57
+ @new_head.column = (@new_head.column - 1) % @game.width
58
+ when :south
59
+ @new_head.row = (@new_head.row + 1) % @game.height
60
+ when :north
61
+ @new_head.row = (@new_head.row - 1) % @game.height
62
+ end
63
+ if @vertebrae.map {|v| [v.row, v.column]}.include?([@new_head.row, @new_head.column])
64
+ self.collided = true
65
+ @game.over = true
66
+ else
67
+ @vertebrae.append(@new_head)
68
+ @vertebrae.delete(tail)
69
+ if head.row == @game.apple.row && head.column == @game.apple.column
70
+ grow
71
+ @game.apple.generate
72
+ end
73
+ end
74
+ end
75
+
76
+ def turn_right
77
+ head.orientation = RIGHT_TURN_MAP[head.orientation]
78
+ end
79
+
80
+ def turn_left
81
+ head.orientation = LEFT_TURN_MAP[head.orientation]
82
+ end
83
+
84
+ def grow
85
+ @game.score += SCORE_EAT_APPLE
86
+ @vertebrae.prepend(@old_tail)
87
+ end
88
+
89
+ # inspect is overridden to prevent printing very long stack traces
90
+ def inspect
91
+ "#{super[0, 150]}... >"
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,22 @@
1
+ class Snake
2
+ module Model
3
+ class Vertebra
4
+ ORIENTATIONS = %i[north east south west]
5
+ # orientation is needed for snake occuppied cells (but not apple cells)
6
+ attr_reader :snake
7
+ attr_accessor :row, :column, :orientation
8
+
9
+ def initialize(snake: , row: , column: , orientation: )
10
+ @row = row || rand(snake.game.height)
11
+ @column = column || rand(snake.game.width)
12
+ @orientation = orientation || ORIENTATIONS.sample
13
+ @snake = snake
14
+ end
15
+
16
+ # inspect is overridden to prevent printing very long stack traces
17
+ def inspect
18
+ "#{super[0, 150]}... >"
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,27 @@
1
+ class Snake
2
+ module Presenter
3
+ class Cell
4
+ COLOR_CLEAR = :white
5
+ COLOR_SNAKE = :green
6
+ COLOR_APPLE = :red
7
+
8
+ attr_reader :row, :column, :grid
9
+ attr_accessor :color
10
+
11
+ def initialize(grid: ,row: ,column: )
12
+ @row = row
13
+ @column = column
14
+ @grid = grid
15
+ end
16
+
17
+ def clear
18
+ self.color = COLOR_CLEAR unless color == COLOR_CLEAR
19
+ end
20
+
21
+ # inspect is overridden to prevent printing very long stack traces
22
+ def inspect
23
+ "#{super[0, 150]}... >"
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,47 @@
1
+ require 'glimmer/data_binding/observer'
2
+ require_relative '../model/game'
3
+ require_relative 'cell'
4
+
5
+ class Snake
6
+ module Presenter
7
+ class Grid
8
+ attr_reader :game, :cells
9
+
10
+ def initialize(game = Model::Game.new)
11
+ @game = game
12
+ @cells = @game.height.times.map do |row|
13
+ @game.width.times.map do |column|
14
+ Cell.new(grid: self, row: row, column: column)
15
+ end
16
+ end
17
+ Glimmer::DataBinding::Observer.proc do |new_vertebrae|
18
+ occupied_snake_positions = @game.snake.vertebrae.map {|v| [v.row, v.column]}
19
+ @cells.each_with_index do |row_cells, row|
20
+ row_cells.each_with_index do |cell, column|
21
+ if [@game.apple.row, @game.apple.column] == [row, column]
22
+ cell.color = Cell::COLOR_APPLE
23
+ elsif occupied_snake_positions.include?([row, column])
24
+ cell.color = Cell::COLOR_SNAKE
25
+ else
26
+ cell.clear
27
+ end
28
+ end
29
+ end
30
+ end.observe(@game.snake, :vertebrae)
31
+ end
32
+
33
+ def clear
34
+ @cells.each do |row_cells|
35
+ row_cells.each do |cell|
36
+ cell.clear
37
+ end
38
+ end
39
+ end
40
+
41
+ # inspect is overridden to prevent printing very long stack traces
42
+ def inspect
43
+ "#{super[0, 75]}... >"
44
+ end
45
+ end
46
+ end
47
+ end
data/examples/snake.rb ADDED
@@ -0,0 +1,90 @@
1
+ require 'glimmer-dsl-libui'
2
+ require 'glimmer/data_binding/observer'
3
+
4
+ require_relative 'snake/presenter/grid'
5
+
6
+ class Snake
7
+ CELL_SIZE = 15
8
+ SNAKE_MOVE_DELAY = 0.1
9
+ include Glimmer
10
+
11
+ def initialize
12
+ @game = Model::Game.new
13
+ @grid = Presenter::Grid.new(@game)
14
+ @game.start
15
+ create_gui
16
+ register_observers
17
+ end
18
+
19
+ def launch
20
+ @main_window.show
21
+ end
22
+
23
+ def register_observers
24
+ @game.height.times do |row|
25
+ @game.width.times do |column|
26
+ Glimmer::DataBinding::Observer.proc do |new_color|
27
+ @cell_grid[row][column].fill = new_color
28
+ end.observe(@grid.cells[row][column], :color)
29
+ end
30
+ end
31
+
32
+ Glimmer::DataBinding::Observer.proc do |game_over|
33
+ Glimmer::LibUI.queue_main do
34
+ if game_over
35
+ msg_box('Game Over!', "Score: #{@game.score} | High Score: #{@game.high_score}")
36
+ @game.start
37
+ end
38
+ end
39
+ end.observe(@game, :over)
40
+
41
+ Glimmer::LibUI.timer(SNAKE_MOVE_DELAY) do
42
+ unless @game.over?
43
+ @game.snake.move
44
+ @main_window.title = "Glimmer Snake (Score: #{@game.score} | High Score: #{@game.high_score})"
45
+ end
46
+ end
47
+ end
48
+
49
+ def create_gui
50
+ @cell_grid = []
51
+ @main_window = window('Glimmer Snake', @game.width * CELL_SIZE, @game.height * CELL_SIZE) {
52
+ resizable false
53
+
54
+ vertical_box {
55
+ padded false
56
+
57
+ @game.height.times do |row|
58
+ @cell_grid << []
59
+ horizontal_box {
60
+ padded false
61
+
62
+ @game.width.times do |column|
63
+ area {
64
+ @cell_grid.last << path {
65
+ square(0, 0, CELL_SIZE)
66
+
67
+ fill Presenter::Cell::COLOR_CLEAR
68
+ }
69
+
70
+ on_key_up do |area_key_event|
71
+ orientation_and_key = [@game.snake.head.orientation, area_key_event[:ext_key]]
72
+ case orientation_and_key
73
+ in [:north, :right] | [:east, :down] | [:south, :left] | [:west, :up]
74
+ @game.snake.turn_right
75
+ in [:north, :left] | [:west, :down] | [:south, :right] | [:east, :up]
76
+ @game.snake.turn_left
77
+ else
78
+ # No Op
79
+ end
80
+ end
81
+ }
82
+ end
83
+ }
84
+ end
85
+ }
86
+ }
87
+ end
88
+ end
89
+
90
+ Snake.new.launch
@@ -85,11 +85,13 @@ class Tetris
85
85
 
86
86
  def clear_high_scores!
87
87
  high_scores.clear
88
+ save_high_scores!
88
89
  end
89
90
 
90
91
  def add_high_score!
91
92
  self.added_high_score = true
92
93
  high_scores.prepend(PastGame.new("Player #{high_scores.count + 1}", score, lines, level))
94
+ save_high_scores!
93
95
  end
94
96
 
95
97
  def save_high_scores!
@@ -111,7 +113,7 @@ class Tetris
111
113
  end
112
114
 
113
115
  def tetris_dir
114
- @tetris_dir ||= File.join(File.expand_path('~'), '.glimmer-tetris')
116
+ @tetris_dir ||= File.join(Dir.home, '.glimmer-tetris')
115
117
  end
116
118
 
117
119
  def tetris_high_score_file
data/examples/tetris.rb CHANGED
@@ -9,8 +9,6 @@ class Tetris
9
9
  BEVEL_CONSTANT = 20
10
10
  COLOR_GRAY = {r: 192, g: 192, b: 192}
11
11
 
12
- attr_reader :game
13
-
14
12
  def initialize
15
13
  @game = Model::Game.new
16
14
  end
@@ -23,8 +21,11 @@ class Tetris
23
21
  end
24
22
 
25
23
  def create_gui
24
+ menu_bar
25
+
26
26
  @main_window = window('Glimmer Tetris') {
27
27
  content_size Model::Game::PLAYFIELD_WIDTH * BLOCK_SIZE, Model::Game::PLAYFIELD_HEIGHT * BLOCK_SIZE + 98
28
+ resizable false
28
29
 
29
30
  vertical_box {
30
31
  label { # filler
@@ -43,8 +44,10 @@ class Tetris
43
44
  def register_observers
44
45
  Glimmer::DataBinding::Observer.proc do |game_over|
45
46
  if game_over
47
+ @pause_menu_item.enabled = false
46
48
  show_game_over_dialog
47
49
  else
50
+ @pause_menu_item.enabled = true
48
51
  start_moving_tetrominos_down
49
52
  end
50
53
  end.observe(@game, :game_over)
@@ -102,6 +105,78 @@ class Tetris
102
105
  end.observe(@game, :level)
103
106
  end
104
107
 
108
+ def menu_bar
109
+ menu('Game') {
110
+ @pause_menu_item = check_menu_item('Pause') {
111
+ enabled false
112
+
113
+ on_clicked do
114
+ @game.paused = @pause_menu_item.checked?
115
+ end
116
+ }
117
+ menu_item('Restart') {
118
+ on_clicked do
119
+ @game.restart!
120
+ end
121
+ }
122
+ separator_menu_item
123
+ menu_item('Exit') {
124
+ on_clicked do
125
+ exit(0)
126
+ end
127
+ }
128
+ quit_menu_item if OS.mac?
129
+ }
130
+
131
+ menu('View') {
132
+ menu_item('Show High Scores') {
133
+ on_clicked do
134
+ show_high_scores
135
+ end
136
+ }
137
+ menu_item('Clear High Scores') {
138
+ on_clicked {
139
+ @game.clear_high_scores!
140
+ }
141
+ }
142
+ }
143
+
144
+ menu('Options') {
145
+ radio_menu_item('Instant Down on Up Arrow') {
146
+ on_clicked do
147
+ @game.instant_down_on_up = true
148
+ end
149
+ }
150
+ radio_menu_item('Rotate Right on Up Arrow') {
151
+ on_clicked do
152
+ @game.rotate_right_on_up = true
153
+ end
154
+ }
155
+ radio_menu_item('Rotate Left on Up Arrow') {
156
+ checked true
157
+
158
+ on_clicked do
159
+ @game.rotate_left_on_up = true
160
+ end
161
+ }
162
+ }
163
+
164
+ menu('Help') {
165
+ if OS.mac?
166
+ about_menu_item {
167
+ on_clicked do
168
+ show_about_dialog
169
+ end
170
+ }
171
+ end
172
+ menu_item('About') {
173
+ on_clicked do
174
+ show_about_dialog
175
+ end
176
+ }
177
+ }
178
+ end
179
+
105
180
  def playfield(playfield_width: , playfield_height: , block_size: , &extra_content)
106
181
  blocks = []
107
182
  vertical_box {
@@ -162,26 +237,38 @@ class Tetris
162
237
  on_key_down do |key_event|
163
238
  case key_event
164
239
  in ext_key: :down
165
- game.down!
240
+ if OS.windows?
241
+ # rate limit downs in Windows as they go too fast when key is held
242
+ @queued_downs ||= 0
243
+ if @queued_downs < 2
244
+ @queued_downs += 1
245
+ Glimmer::LibUI.timer(0.01, repeat: false) do
246
+ @game.down! if @queued_downs < 2
247
+ @queued_downs -= 1
248
+ end
249
+ end
250
+ else
251
+ @game.down!
252
+ end
166
253
  in key: ' '
167
- game.down!(instant: true)
254
+ @game.down!(instant: true)
168
255
  in ext_key: :up
169
- case game.up_arrow_action
256
+ case @game.up_arrow_action
170
257
  when :instant_down
171
- game.down!(instant: true)
258
+ @game.down!(instant: true)
172
259
  when :rotate_right
173
- game.rotate!(:right)
260
+ @game.rotate!(:right)
174
261
  when :rotate_left
175
- game.rotate!(:left)
262
+ @game.rotate!(:left)
176
263
  end
177
264
  in ext_key: :left
178
- game.left!
265
+ @game.left!
179
266
  in ext_key: :right
180
- game.right!
267
+ @game.right!
181
268
  in modifier: :shift
182
- game.rotate!(:right)
269
+ @game.rotate!(:right)
183
270
  in modifier: :control
184
- game.rotate!(:left)
271
+ @game.rotate!(:left)
185
272
  else
186
273
  # Do Nothing
187
274
  end
@@ -246,17 +333,42 @@ class Tetris
246
333
  end
247
334
 
248
335
  def start_moving_tetrominos_down
249
- Glimmer::LibUI.timer(@game.delay) do
250
- @game.down! if !@game.game_over? && !@game.paused?
336
+ unless @tetrominos_start_moving_down
337
+ @tetrominos_start_moving_down = true
338
+ Glimmer::LibUI.timer(@game.delay) do
339
+ @game.down! if !@game.game_over? && !@game.paused?
340
+ end
251
341
  end
252
342
  end
253
343
 
254
344
  def show_game_over_dialog
255
345
  Glimmer::LibUI.queue_main do
256
- msg_box('Game Over', "Score: #{@game.high_scores.first.score}\nLines: #{@game.high_scores.first.lines}\nLevel: #{@game.high_scores.first.level}")
346
+ msg_box('Game Over!', "Score: #{@game.high_scores.first.score}\nLines: #{@game.high_scores.first.lines}\nLevel: #{@game.high_scores.first.level}")
257
347
  @game.restart!
258
348
  end
259
349
  end
350
+
351
+ def show_high_scores
352
+ Glimmer::LibUI.queue_main do
353
+ game_paused = !!@game.paused
354
+ @game.paused = true
355
+ if @game.high_scores.empty?
356
+ high_scores_string = "No games have been scored yet."
357
+ else
358
+ high_scores_string = @game.high_scores.map do |high_score|
359
+ "#{high_score.name} | Score: #{high_score.score} | Lines: #{high_score.lines} | Level: #{high_score.level}"
360
+ end.join("\n")
361
+ end
362
+ msg_box('High Scores', high_scores_string)
363
+ @game.paused = game_paused
364
+ end
365
+ end
366
+
367
+ def show_about_dialog
368
+ Glimmer::LibUI.queue_main do
369
+ msg_box('About', 'Glimmer Tetris - Glimmer DSL for LibUI Example - Copyright (c) 2021 Andy Maleh')
370
+ end
371
+ end
260
372
  end
261
373
 
262
374
  Tetris.new.launch