glimmer-dsl-libui 0.2.19 → 0.2.23

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