Saturday, January 23, 2021

Glimmer Tetris Cont'd: Scoring, Leveling, and Preview

The Glimmer Tetris game that took one day to develop just got improved with Scoring, Leveling, and Next Preview with one more day, thanks to Glimmer DSL for SWT's incredibly malleable architecture and extensive feature set. 

Seeing is believing!


One thing that is noteworthy is the idea of mapping Tetromino Blocks to Playfield Model Blocks, which are in turn data-bound to View Blocks with regards to Color. So, whenever a Tetromino moves, it changes the colors of Playfield Model Blocks (previous and new position), which propogate changes to the GUI automatically, thanks to the intelligent bidirectional data-binding support in Glimmer DSL for SWT. This enables writing much more decoupled logic code that is easy to maintain given it is not directly bound to the View, which is only connected to the Model declaratively (no imperative render logic).

Another thing that is noteworthy was how I very quickly built the Preview Next Tetromini feature by simply reusing the 'playfield' custom widget but with much smaller dimensions (4x2). Otherwise, I reused the logic for placing a tetromino on it (via mapping to its blocks as explained above), and voila! The power of modular componentization, a great asset of Object Oriented Programming applied to rapid GUI development.

It is included in the new Glimmer DSL for SWT v4.18.2.4

Happy Glimmering!

Friday, January 22, 2021

Glimmer Tetris in One Day!

I present to you Glimmer Tetris in one day! Not bad, right!? It is included with the just released Glimmer DSL for SWT v4.18.2.3.

Top-level Glimmer GUI code:


Believe me when I tell you the code above is the cleanest Tetris implementation on Earth! In fact, the final part, which is the GUI body is only about 6 lines of code. This is testament to Glimmer DSL for SWT's great architecture and design.

That is thanks in part to Glimmer DSL for SWT's ultra-modularity. After all, Tetris was declared as a Custom Shell (meaning it is a reusable app that can run standalone or as a window in another Glimmer app). Additionally, the GUI was broken up into multiple Custom Widgets (`playfield` and `block`):

  • Playfield: represents the play area of the GUI that has the Tetromino blocks falling in
  • Block: represents a single square block

Architecture is MVC with a bit of MVP (or MVVM with bidirectional data-binding), having the following models:

  • Game: embodies the rules of the game with the ability to start or restart a game (happens automatically upon launching the app or encountering game over)
  • Tetromino: represents the Tetris shapes with all their intricate operations such as moving down, right, and left, and rotation right (clockwise) and left (counterclockwise). It also houses the majority of the collision detection logic in collaboration with Game
  • Block: represents a single block that could form a tetromino shape or be a block in the playfield

Tetromino logic including collision detection obviously relied on some heavy math skills like calculus and linear algebra, so I had to dust up my Computer Science learning in Computer Graphics with matrix operations such as clockwise and counterclockwise rotations. Thankfully, Ruby supports matrix mathematical operations via the Matrix class.

Keyboard events are handled by a general display on_swt_keydown filter handler that ensures correct action by the model based on user key presses (e.g. left, right, down, right-shift for rotate right and left-shift for rotate left with some other alternatives for rotation like the letters 'a' and 'd'). It is declared in the `before_body`block on the Tetris custom shell class, meaning configuration is done before building the GUI since the GUI relies on it.

Multi-threaded programming is used where necessary, mainly to kick start the main loop for moving the tetrominoes down the playfield about every second while allowing the user to speed up the movement with the down arrow key if desired. Keep in mind that any interaction with the GUI from a thread other than the main thread must happen through sync_exec. It is declared as a `after_body` block on the Tetris custom shell class, meaning it runs after building the GUI body to ensure it is available for interaction, but before showing the GUI to the user, which happens last.

Otherwise, bidirectional data-binding handles everything relating to updating the GUI, so the code is squeaky concise and clean. The model logic just worries about the model of the game logic and is completely oblivious to the intricacies of the GUI.

The square blocks took advantage of the newly added Glimmer Canvas Shape DSL for border decorations.

Stay tuned for more changes coming to the Tetris elaborate sample like:

  • Scoring
  • Leveling
  • Preview Next Tetromino

Until then, Happy Glimmering


 

For another game created with Glimmer DSL for SWT, check out Tic Tac Toe! (also super-concise and clean)

Tuesday, January 19, 2021

Canvas Graphics Come to Glimmer DSL for SWT, Finally!

Glimmer DSL for SWT v4.18.1.0 just got released with the following changes:

Canvas graphics have been on my radar for quite a while, but I have been busy focusing on integrating Glimmer DSL for SWT with Glimmer DSL for Opal with webready app scaffolding and the webify glimmer task as that enables users to instantly turn a Glimmer desktop app into a web app.

In the meantime, I got contacted on Gitter (Glimmer's Open-Source Help Chat) for help with Canvas graphics, which Glimmer did not officially support with its DSL since it focused more on building standard business applications with plain old widgets like button, text field, and table. This all changes with version 4.18.1.0.

Glimmer introduces two new sub-DSLs of the Glimmer GUI DSL:

I'll let the two new samples speak for themselves.

Hello, Canvas!


 

Hello, Canvas Animation!


 


 

Please keep in mind that these are first cuts of the Shape and Animation DSLs, so they will likely undergo some more battle-testing and further improvements before the APIs stabalize. 

In the meantime, Happy Glimmering!

 

Monday, January 11, 2021

Gladiator (Glimmer Editor) v0.7.0

Gladiator (Glimmer Editor) v0.7.0 (code text editor built in Ruby via Glimmer DSL for SWT) has been released! 



The focus of this version was maximizing the split pane and editor area via keyboard shortcuts (and menu items), thus providing maximum use of screen real estate, in addition to fixing some bugs. 

Gladiator Demo Video:



Change Log for v0.7.0:

  • Upgrade to glimmer-dsl-swt v4.18.0.0
  • Implement sash form for separator between file area and editing area to make resizable
  • Change shortcut for open project from CMD+SHIFT+P to CMD+O
  • Unsplit pane Menu Item / Keyboard Shortcut (CMD+SHIFT+U)
  • Maximize Split Pane Size Menu Item / Keyboard Shortcut (CMD+SHIFT+M)
  • Reset Split Pane Menu Item / Keyboard Shortcut (CMD+SHIFT+M again while maximized or CMD+SHIFT+P to reset split widths)
  • Maximize Editor Menu Item / Keyboard Shortcut (CMD+CTRL+M)
  • Reset Editor Menu Item / Keyboard Shortcut (CMD+CTRL+M again while maximized or CMD+CTRL+R to reset all sizes)
  • Make CMD R and CMD T shortcuts show the file lookup/file explorer if collapsed (just like CMD + F shows navigation area)
  • Display Accelerators (keyboard shortcuts) on Mac Menu Items
  • Show exception dialog on error when invoking Run -> Ruby
  • Fix issue with creating a new directory in a different project from the main one open
  • Fix issue with quitting gladiator when multiple projects are open requiring multiple presses of CMD+Q

Enjoy Gladiator, the Ugliest Text Editor Ever!

Thursday, December 31, 2020

How to Use Ruby Case Statements with === / Higher Order Lambdas / Pattern Matching

Happy New Year!

In this blog post, I will go over a practical example from a real project of how to use the Ruby `case` statement with Class implicit `is_a?` comparisons via `===` , higher order lambdas, and the new Ruby 3 pattern matching.

I just had to refactor some code in my new project YASL (Yet Another Serialization Library), which was originally in this form:


The when statements rely implicitly on the Class `===` method in object comparisons, which is implemented to test if `object.is_a?(SomeClass)` by default (e.g. `object.is_a?(Time)` for `when Time`) .

Although the code is pretty concise and readable, there is one big issue in it, mainly that `Date`, `DateTime`, and `BigDecimal` aren't loaded by default in Ruby, and in some older versions of Ruby, `Set` isn't loaded either. As such, that code would work in most cases, but bomb in cases in which comparison reaches `Date` and `DateTime` while not loaded via `require 'date'`, `BigDecimal` while not loaded via `require 'bigdecimal'`, or `Set` while not loaded via `require 'set'`. Unfortunately, pre-loading `Date`, `DateTime`, `BigDecimal` and `Set` could raise the memory footprint of the library for no important reason, so it is not desirable as a solution.

To circumvent this problem, I ended up comparing to class name strings instead of loaded classes by relying on higher order lambdas (in Ruby versions prior to Ruby 3) to achieve readable code without reliance on unwieldy if..elsif statements, albeit less pretty than the original code:


The reason that works is because `Proc` objects produced from lambdas have `===` implemented as simply `call(object)`. This ensures that a `Proc` object is first called with the class name(s) as strings (not actual loaded classes), returning another `Proc` ready to do the comparison on the particular object being tested (array of ancestor class names). Unfortunately, it is not very pretty due to the logic-unrelated lower-level `.call` methods. Thankfully, more recent versions of Ruby allow dropping them while keeping the `.` only for a more concise version:


This is prettier and more readable, but still a bit awkward. Can we drop the dot (`.`) entirely? Sure. Just switch parentheses to square brackets, and you could drop the dot (`.`) in newish versions of Ruby, resulting in more readable code:


That said, in the newly released Ruby 3, one could just rely on Array Pattern Matching via `case in` instead of `case when` to avoid higher order lambdas altogether:


That takes away the need to use higher order lambdas, which are a more complicated construct that is better avoided when possible. That said, YASL (Yet Another Serialization Library) still needs to support older Ruby versions, so I am stuck with higher order lambdas for now.

Just as a final note, keep in mind that the case statement could be eliminated completely by relying on Object Oriented Programming Design Patterns, such as Strategy. This is usually done on a case by case basis (no pun intended), and in this case I deemed it over-engineering to use the Strategy Pattern, but it's certainly an option on the table if needed in future refactorings.

In summary, case statements provide multiple ways to test objects through:

Have a Happy 2021!

Git Gem Contribution of JRuby/Windows Compatibility

Just a heads up that I got a contribution to the Ruby git gem accepted and released:

Windows/JRuby fixes/tests/refactorings/travis-ci

https://github.com/ruby-git/ruby-git/commit/1b5256cacb1c137b87be63e58253a974800d4b59

https://github.com/ruby-git/ruby-git/releases/tag/v1.8.0

Enjoy!

Wednesday, December 30, 2020

RVM Contribution of Command Aliases

 Just a quick note that I got an RVM (Ruby enVironment Manager) contribution accepted:

  • Alias `rvm delete` as `rvm remove`
  • Alias `rvm gemset remove` as `rvm gemset delete`
  • Alias `rvm gemset move` as `rvm gemset rename`

https://github.com/rvm/rvm/commit/00c482731f99d4d2928e542f889ecd6532108a14

https://github.com/rvm/rvm/releases/tag/1.29.11

As for the back story, I got tired of having to remember that to remove a Ruby, I gotta use "rvm remove", and yet to remove a Gemset, "rvm gemset remove" fails, and I gotta use "rvm gemset delete" instead (and vice versa). So, I followed the Ruby principle of "more ways than one" to accomplish a task (e.g. `Array#size`, `Array#length`, & `Array#count`), and added both "delete" and "remove" as options for both Rubies and Gemsets. I also had a similar confusion with "rvm gemset move" and "rvm gemset rename" because I'm used to "move" from Git, so I fixed it by adding a "move" alias for RVM Gemsets as well.

Enjoy!