qmk_games icon indicating copy to clipboard operation
qmk_games copied to clipboard

QMK Firmware 上で動かすゲーム

QMK Games

QMK Firmware の OLED 上で動くゲームです。

Tetris

某ゲーム。以下のような感じで動作します(リンク先に動画があります)。

128x32 の OLED しか想定していないので、それ以外の環境ではまず動きません。

確認した動作環境は、 Helix に付いてくる 128x32 の SSD1306 の OLED だけです。

遊び方

  • h - 左へ移動
  • l - 右へ移動
  • j - 反時計回りに回転
  • k - 時計回りに回転
  • スペース - 落とす

インストール方法

まず QMK Firmwareドキュメント の通りに実行してキーボードに転送できるようにしておいて下さい。

Helix でデフォルトのキー配置を利用する場合は make helix:default でビルドして make helix:default:avrdude でキーボードに転送できます。

次に、このリポジトリの games/ ディレクトリを QMK Firmware リポジトリの keyboards/helix/games/ ディレクトリにコピーします。

cp -r games/ /path/to/qmk_firmware/keyboards/helix/games/

次に、既存のソースコードにいくつか変更を加えます。

Helix の rev2/default のキー配置をそのまま使っている場合、以下のパッチを適用するだけで構いません。

パッチの適用は、以下のパッチを適当なファイル(今回の場合は tetris.patch)に保存し、QMK Firmware のリポジトリ上で patch -p1 < tetris.patch と実行します。

diff --git a/keyboards/helix/rev2/keymaps/default/keymap.c b/keyboards/helix/rev2/keymaps/default/keymap.c
index a64eed3e7..1f8f9ae9a 100644
--- a/keyboards/helix/rev2/keymaps/default/keymap.c
+++ b/keyboards/helix/rev2/keymaps/default/keymap.c
@@ -11,6 +11,11 @@
   #include "ssd1306.h"
 #endif
 
+#include "games/tetris.h"
+#include "games/screen.h"
+
+Tetris g_tetris;
+
 extern keymap_config_t keymap_config;
 
 #ifdef RGBLIGHT_ENABLE
@@ -333,6 +338,26 @@ void update_tri_layer_RGB(uint8_t layer1, uint8_t layer2, uint8_t layer3) {
 }
 
 bool process_record_user(uint16_t keycode, keyrecord_t *record) {
+  if (record->event.pressed) {
+    switch (keycode) {
+    case KC_H:
+      tetris_move(&g_tetris, 0);
+      break;
+    case KC_L:
+      tetris_move(&g_tetris, 1);
+      break;
+    case KC_J:
+      tetris_rotate(&g_tetris, 0);
+      break;
+    case KC_K:
+      tetris_rotate(&g_tetris, 1);
+      break;
+    case KC_SPC:
+      tetris_move(&g_tetris, 2);
+      break;
+    }
+  }
+
   switch (keycode) {
     case QWERTY:
       if (record->event.pressed) {
@@ -525,16 +550,16 @@ void matrix_update(struct CharacterMatrix *dest,
 #define L_ADJUST (1<<_ADJUST)
 #define L_ADJUST_TRI (L_ADJUST|L_RAISE|L_LOWER)
 
-static void render_logo(struct CharacterMatrix *matrix) {
-
-  static char logo[]={
-    0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f,0x90,0x91,0x92,0x93,0x94,
-    0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf,0xb0,0xb1,0xb2,0xb3,0xb4,
-    0xc0,0xc1,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xcb,0xcc,0xcd,0xce,0xcf,0xd0,0xd1,0xd2,0xd3,0xd4,
-    0};
-  matrix_write(matrix, logo);
-  //matrix_write_P(&matrix, PSTR(" Split keyboard kit"));
-}
+//static void render_logo(struct CharacterMatrix *matrix) {
+//
+//  static char logo[]={
+//    0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f,0x90,0x91,0x92,0x93,0x94,
+//    0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf,0xb0,0xb1,0xb2,0xb3,0xb4,
+//    0xc0,0xc1,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xcb,0xcc,0xcd,0xce,0xcf,0xd0,0xd1,0xd2,0xd3,0xd4,
+//    0};
+//  matrix_write(matrix, logo);
+//  //matrix_write_P(&matrix, PSTR(" Split keyboard kit"));
+//}
 
 
 
@@ -584,6 +609,7 @@ void render_status(struct CharacterMatrix *matrix) {
 }
 
 
+/*
 void iota_gfx_task_user(void) {
   struct CharacterMatrix matrix;
 
@@ -601,5 +627,16 @@ void iota_gfx_task_user(void) {
   }
   matrix_update(&display, &matrix);
 }
+*/
+
+void iota_gfx_task_user(void) {
+  ScreenMatrix matrix;
+  screen_clear(&matrix);
+
+  tetris_update(&g_tetris);
+  tetris_render(&g_tetris, &matrix);
+
+  screen_update(&g_screen, &matrix);
+}
 
 #endif
diff --git a/keyboards/helix/rev2/keymaps/default/rules.mk b/keyboards/helix/rev2/keymaps/default/rules.mk
index 37ef8632d..016781bfe 100644
--- a/keyboards/helix/rev2/keymaps/default/rules.mk
+++ b/keyboards/helix/rev2/keymaps/default/rules.mk
@@ -30,7 +30,7 @@ endef
 # you can edit follows 7 Variables
 #  jp: 以下の7つの変数を必要に応じて編集します。
 HELIX_ROWS = 5              # Helix Rows is 4 or 5
-OLED_ENABLE = no            # OLED_ENABLE
+OLED_ENABLE = yes           # OLED_ENABLE
 LOCAL_GLCDFONT = no         # use each keymaps "helixfont.h" insted of "common/glcdfont.c"
 LED_BACK_ENABLE = no        # LED backlight (Enable WS2812 RGB underlight.)
 LED_UNDERGLOW_ENABLE = no   # LED underglow (Enable WS2812 RGB underlight.)
diff --git a/keyboards/helix/rules.mk b/keyboards/helix/rules.mk
index be234e60e..4462ef142 100644
--- a/keyboards/helix/rules.mk
+++ b/keyboards/helix/rules.mk
@@ -1,6 +1,9 @@
 SRC += i2c.c
 SRC += serial.c
-SRC += ssd1306.c
+SRC += games/ssd1306.c
+SRC += games/screen.c
+SRC += games/xorshift.c
+SRC += games/tetris.c
 
 # MCU name
 #MCU = at90usb1287

無事パッチの適用とビルドが出来たら、make helix:default:avrdude を実行してキーボードに転送します。

デフォルトのキー配置でない場合、あるいは上記のパッチの適用に失敗した場合は、以下の説明を読んで手動で変更して下さい。

keyboards/helix/rules.mk:

既存の ssd1306.c を除けて、games/ 以下のファイルを追加します。

 SRC += i2c.c
 SRC += serial.c
-SRC += ssd1306.c
+SRC += games/ssd1306.c
+SRC += games/screen.c
+SRC += games/xorshift.c
+SRC += games/tetris.c

keyboards/helix/rev2/keymaps/default/rules.mk:

OLED を有効にします。

 # you can edit follows 7 Variables
 #  jp: 以下の7つの変数を必要に応じて編集します。
 HELIX_ROWS = 5              # Helix Rows is 4 or 5
-OLED_ENABLE = no            # OLED_ENABLE
+OLED_ENABLE = yes           # OLED_ENABLE
 LOCAL_GLCDFONT = no         # use each keymaps "helixfont.h" insted of "common/glcdfont.c"
 LED_BACK_ENABLE = no        # LED backlight (Enable WS2812 RGB underlight.)
 LED_UNDERGLOW_ENABLE = no   # LED underglow (Enable WS2812 RGB underlight.)

keyboards/helix/rev2/keymaps/default/keymap.c:

ファイルの先頭付近で、必要なファイルの #includeTetris オブジェクトの作成を行います。

+#include "games/tetris.h"
+#include "games/screen.h"
+
+Tetris g_tetris;

ユーザのキー入力を処理する関数(今回の場合は process_record_user)で、キーが入力された時の処理を書きます。 ブロックの移動や回転するキーをカスタマイズしたい場合はここを変更します。

 bool process_record_user(uint16_t keycode, keyrecord_t *record) {
+  if (record->event.pressed) {
+    switch (keycode) {
+    case KC_H:
+      tetris_move(&g_tetris, 0);
+      break;
+    case KC_L:
+      tetris_move(&g_tetris, 1);
+      break;
+    case KC_J:
+      tetris_rotate(&g_tetris, 0);
+      break;
+    case KC_K:
+      tetris_rotate(&g_tetris, 1);
+      break;
+    case KC_SPC:
+      tetris_move(&g_tetris, 2);
+      break;
+    }
+  }
+

render_logo 関数を使っていないという警告がエラーとして報告されるので、削除します。 他のキーマップで出るかどうかは分かりません。

-static void render_logo(struct CharacterMatrix *matrix) {
-
-  static char logo[]={
-    0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f,0x90,0x91,0x92,0x93,0x94,
-    0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf,0xb0,0xb1,0xb2,0xb3,0xb4,
-    0xc0,0xc1,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xcb,0xcc,0xcd,0xce,0xcf,0xd0,0xd1,0xd2,0xd3,0xd4,
-    0};
-  matrix_write(matrix, logo);
-  //matrix_write_P(&matrix, PSTR(" Split keyboard kit"));
-}
+//static void render_logo(struct CharacterMatrix *matrix) {
+//
+//  static char logo[]={
+//    0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f,0x90,0x91,0x92,0x93,0x94,
+//    0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf,0xb0,0xb1,0xb2,0xb3,0xb4,
+//    0xc0,0xc1,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xcb,0xcc,0xcd,0xce,0xcf,0xd0,0xd1,0xd2,0xd3,0xd4,
+//    0};
+//  matrix_write(matrix, logo);
+//  //matrix_write_P(&matrix, PSTR(" Split keyboard kit"));
+//}

iota_gfx_task_user(void) 関数を既存の処理から完全に別にして、ゲームのロジック処理や描画処理を行います。

+/*
 void iota_gfx_task_user(void) {
   struct CharacterMatrix matrix;
 
@@ -601,5 +627,16 @@ void iota_gfx_task_user(void) {
   }
   matrix_update(&display, &matrix);
 }
+*/
+
+void iota_gfx_task_user(void) {
+  ScreenMatrix matrix;
+  screen_clear(&matrix);
+
+  tetris_update(&g_tetris);
+  tetris_render(&g_tetris, &matrix);
+
+  screen_update(&g_screen, &matrix);
+}

Helix 以外の環境にインストールする

games/ssd1306.c というファイルは QMK Firmware の keyboards/helix/ssd1306.c を改変したものです。

そのため Helix 以外の環境である場合は内容が合わないと思うので、以下の qmk_firmware/keyboards/helix/ssd1306.cqmk_games/games/ssd1306.c の差分を見て手動で修正して下さい。

基本的に matrix_render 関数の代わりに、自作の screen_render 関数を利用するように変更しているだけです。

--- qmk_firmware/keyboards/helix/ssd1306.c	2019-04-13 00:13:44.000000000 +0900
+++ qmk_games/games/ssd1306.c	2019-04-13 00:09:15.000000000 +0900
@@ -2,6 +2,7 @@
 #ifdef SSD1306OLED
 
 #include "ssd1306.h"
+#include "screen.h"
 #include "i2c.h"
 #include <string.h>
 #include "print.h"
@@ -313,8 +314,52 @@
 #endif
 }
 
+// ビット反転
+uint8_t revbits8(uint8_t v) {
+  v = ((v >> 1) & 0x55) | ((v & 0x55) << 1);
+  v = ((v >> 2) & 0x33) | ((v & 0x33) << 2);
+  v = ((v >> 4) & 0x0F) | ((v & 0x0F) << 4);
+  return v;
+}
+
+void screen_render(ScreenMatrix* screen) {
+  last_flush = timer_read();
+  iota_gfx_on();
+#if DEBUG_TO_SCREEN
+  ++displaying;
+#endif
+
+  // Move to the home position
+  send_cmd3(PageAddr, 0, (DisplayHeight / 8) - 1);
+  send_cmd3(ColumnAddr, 0, DisplayWidth - 1);
+
+  if (i2c_start_write(SSD1306_ADDRESS)) {
+    goto done;
+  }
+  if (i2c_master_write(0x40)) {
+    // Data mode
+    goto done;
+  }
+
+  for (uint8_t row = 0; row < MatrixRows; ++row) {
+    for (uint8_t col = 0; col < DisplayWidth; ++col) {
+      uint8_t bits = screen->screen[DisplayWidth - col - 1][row];
+      i2c_master_write(revbits8(bits));
+    }
+  }
+
+  screen->dirty = false;
+
+done:
+  i2c_master_stop();
+#if DEBUG_TO_SCREEN
+  --displaying;
+#endif
+}
+
 void iota_gfx_flush(void) {
-  matrix_render(&display);
+  //matrix_render(&display);
+  screen_render(&g_screen);
 }
 
 __attribute__ ((weak))
@@ -324,7 +369,7 @@
 void iota_gfx_task(void) {
   iota_gfx_task_user();
 
-  if (display.dirty|| force_dirty) {
+  if (g_screen.dirty || force_dirty) {
     iota_gfx_flush();
     force_dirty = false;
   }

問題の報告について

このリポジトリの issue に報告して下さい。