ベータコンピューティングの活動や技術、開発のこだわりなどを紹介するブログです。



AndroidでQRコードとJANコードを扱ってみる

Beta ComputingのAndroid担当のd-ariakeです。
Android案件でバーコードを扱う機会があったので、実装方法を残したいと思います。

行うこと

今回は以下の2点を共有したいと思います。

  • ZXing Android Embedded を使い、QRコードとJANコードを生成する。
  • Glide のカスタムModel Loaderで生成・キャッシュさせる。

ZXing Android Embeddedによるバーコードの生成

ZXing Android EmbeddedZxing をAndroid向けに使いやすくしたライブラリです。
このライブラリを使うと以下のようなコードを書くだけで、簡単にバーコードを生成することができます。

//  QRコードを生成するコード
val hints = mapOf(
    EncodeHintType.ERROR_CORRECTION to ErrorCorrectionLevel.H,
    EncodeHintType.CHARACTER_SET to "Shift_JIS",
    EncodeHintType.QR_VERSION to 12
)
val bitmap: android.graphics.Bitmap =
    BarcodeEncoder().encodeBitmap(contents, BarcodeFormat.QR, width, height, hints)
//  JAN (EAN) コードを生成するコード
val bitmap: android.graphics.Bitmap =
    BarcodeEncoder().encodeBitmap(contents, BarcodeFormat.EAN_13, width, height)

本当に簡単に生成できちゃいます!

余談:
JANコードを生成する際には12桁か13桁の数値文字列を渡す必要があります。
13桁目はチェックサムとなっていて、誤り検出に使われるみたいです。
EAN13Writer.javaUPCEANReader.java のソースを読んだ感じ、12桁のコードを渡すと自動的にチェックサムを計算して13桁として生成してくれるようです。

GlideのカスタムModel Loaderの実装

Androidで画像を扱う際には何かしらの画像管理ライブラリを使うかと思います。
今回は私が一番慣れているGlideを使用して、QRコード・JANコードを生成・キャッシュし、ImageView に表示させるといったことを行いました。

ネット上の画像の場合はそのままURL文字列を渡してしまえばすぐに実現できてしまいます。

Glide.with(this.applicationContext)
    .load("https://画像のリンク.png")
    .placeholder(R.drawable.プレースホルダー)
    .into(this.imageView)

しかし、今回のようにQR・JANコードを生成する場合は自分で ModelLoader を実装する必要があります。
以下に実装手順を載せていきます。

(公式ドキュメント: Glide v4 : Writing a custom ModelLoader)

まず、以下のようにライブラリを追加します。
(必要に応じて apply plugin: 'kotlin-kapt' を追記してください。)

//  https://mvnrepository.com/artifact/com.journeyapps/zxing-android-embedded
implementation("com.journeyapps:zxing-android-embedded:4.1.0")

//  https://mvnrepository.com/artifact/com.github.bumptech.glide/glide
implementation("com.github.bumptech.glide:glide:4.11.0")

//  https://mvnrepository.com/artifact/com.github.bumptech.glide/compiler
kapt("com.github.bumptech.glide:compiler:4.11.0")

次に、Glideの Model に相当する BarcodeRequest を定義します。
sealed class で定義し、Qr 型と Jan 型で表現しました。

package jp.co.betacomputing.barcodeapp

import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel

sealed class BarcodeRequest {

    abstract val content: String

    data class Qr(override val content: String, val level: ErrorCorrectionLevel) : BarcodeRequest()
    data class Jan(override val content: String) : BarcodeRequest()
}

次に ModelLoader<Model (= BarcodeRequest,) Data (= Bitmap)>DataFetcher<Data (= Bitmap)> の実装です。
BarcodeRequestwidthheight とともに Fetcher に渡し、そこで Bitmap を生成しています。

今回は特にネット経由で何かをダウンロードするといったことはないので、getDataSource()DataSource.LOCAL を指定しており、リソースの解放処理も不要なので clearnup() では何もしていません。
(公式のドキュメントのBase64の例でも同じような感じでしたね。)

loadData() で実際のバーコードの生成処理を行います。
オプションを指定して BarcodeEncoder().encodeBitmap() を叩くだけです。
生成に成功したら DataCallback#onDataReady() を叩いて完了を通知します。

package jp.co.betacomputing.barcodeapp

import android.graphics.Bitmap
import com.bumptech.glide.Priority
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.Options
import com.bumptech.glide.load.data.DataFetcher
import com.bumptech.glide.load.model.ModelLoader
import com.bumptech.glide.signature.ObjectKey
import com.google.zxing.BarcodeFormat
import com.google.zxing.EncodeHintType
import com.journeyapps.barcodescanner.BarcodeEncoder
import jp.co.betacomputing.barcodeapp.BarcodeRequest as Req

internal class BarcodeModelLoader : ModelLoader<Req, Bitmap> {

    override fun buildLoadData(model: Req, width: Int, height: Int, options: Options): ModelLoader.LoadData<Bitmap>? =
        ModelLoader.LoadData(ObjectKey(model), Fetcher(model, width, height))

    override fun handles(model: Req): Boolean = true

    private class Fetcher(
        private val request: Req,
        private val width: Int,
        private val height: Int
    ) : DataFetcher<Bitmap> {

        override fun getDataClass(): Class<Bitmap> = Bitmap::class.java

        override fun cleanup() = Unit

        override fun getDataSource(): DataSource = DataSource.LOCAL

        override fun cancel() = Unit

        override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in Bitmap>) {
            when (this.request) {
                is Req.Qr -> this.generateQr(this.request, callback)
                is Req.Jan -> this.generateJan(this.request, callback)
            }
        }

        private fun generateQr(request: Req.Qr, callback: DataFetcher.DataCallback<in Bitmap>) {
            try {
                val hints = mapOf(EncodeHintType.ERROR_CORRECTION to request.level)
                val bitmap = BarcodeEncoder()
                    .encodeBitmap(this.request.content, BarcodeFormat.QR_CODE, this.width, this.height, hints)

                callback.onDataReady(bitmap)
            } catch (e: Exception) {
                callback.onLoadFailed(e)
            }
        }

        private fun generateJan(request: Req.Jan, callback: DataFetcher.DataCallback<in Bitmap>) {
            try {
                val bitmap = BarcodeEncoder()
                    .encodeBitmap(request.content, BarcodeFormat.EAN_13, this.width, this.height)

                callback.onDataReady(bitmap)
            } catch (e: Exception) {
                callback.onLoadFailed(e)
            }
        }
    }
}

そして、この BarcodeModelLoader をGlideに登録する部分です。
これはほとんどドキュメント通りです。

package jp.co.betacomputing.barcodeapp

import android.content.Context
import android.graphics.Bitmap
import com.bumptech.glide.Glide
import com.bumptech.glide.Registry
import com.bumptech.glide.annotation.GlideModule
import com.bumptech.glide.load.model.ModelLoader
import com.bumptech.glide.load.model.ModelLoaderFactory
import com.bumptech.glide.load.model.MultiModelLoaderFactory
import com.bumptech.glide.module.AppGlideModule

@GlideModule
internal class BarcodeGlideModule : AppGlideModule() {

    override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
        registry.prepend(BarcodeRequest::class.java, Bitmap::class.java, Factory())
    }

    private class Factory : ModelLoaderFactory<BarcodeRequest, Bitmap> {

        override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<BarcodeRequest, Bitmap> =
            BarcodeModelLoader()

        override fun teardown() = Unit
    }
}

最後に、この BarcodeModelLoader を実際に使った簡単なアプリを作りました。
ボタンを押すと、このModel Loader経由でバーコードが生成され、ImageView に設定されます。

f:id:betacomputing3:20200824180053p:plain:w300

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <ImageView
        android:id="@+id/qrImageView"
        android:layout_width="256dp"
        android:layout_height="256dp"
        android:layout_margin="10dp"
        android:background="#ef5350"
        android:contentDescription="@null" />

    <ImageView
        android:id="@+id/janImageView"
        android:layout_width="256dp"
        android:layout_height="102dp"
        android:layout_margin="10dp"
        android:background="#42a5f5"
        android:contentDescription="@null" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:text="PUSH ME!"
        tools:ignore="HardcodedText" />

</LinearLayout>
package jp.co.betacomputing.barcodeapp

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.bumptech.glide.Glide
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        this.setContentView(R.layout.activity_main)
        this.button.setOnClickListener {

            //  QRコード
            Glide.with(this)
                .load(BarcodeRequest.Qr(content = "あいうえお", level = ErrorCorrectionLevel.H))
                .into(this.qrImageView)

            //  JANコード
            Glide.with(this)
                .load(BarcodeRequest.Jan(content = "020000100001"))
                .into(this.janImageView)
        }
    }
}

以上、GlideのカスタムModel Loaderを作って、バーコードを表示させる方法でした。
Glideのキャッシュやロード機能を使いたいけれども、画像の取得部分は自分で実装したいというときは是非やってみてください。

RxJavaとKotlin CoroutinesでAndroidのBLE制御をした話

はじめまして、新入社員のd-ariakeと申します。
主にAndroidアプリの開発を担当しています。
宜しくお願い致します。

今回扱う内容

私の初めてのお仕事は業務用Androidアプリの開発案件でした。
ものすごく簡単に言うと、組み込み機器とAndroid端末をOOBでペアリング・接続し、その機器から送られてくる商品のデータをAndroidアプリで受信・管理するといった内容です。

Bluetooth通信を行っているので、当然処理は非同期で行う必要が出てきます。
標準のBLEライブラリも非同期コールバックで書くようになっているのですが、なかなか取り扱いづらいです。
(触ったことがある人にはわかってもらえるかと思います。)

そのため、私は RxJavaKotlin Coroutines を使って非同期な処理を実装することにしました。
その結果、より直感的で分かりやすい実装を行うことができました。
今回はその知見を共有したいと思います。

RxJava2・RxAndroidBleの導入

AndroidのBluetoothまわりのAPIはとても扱いづらいです。
普通に書いているだけでコールバック地獄になり、とてもじゃないですが、メンテナンスできない状態になってしまいます。
そこで、本プロジェクトでは RxAndroidBle というライブラリを導入することにしました。
このライブラリはBLEの接続やread/wriet処理などをRxJavaの Observable<T>Single<T> のストリームとして提供してくれます。

以下、ライブラリの README.md からの引用です。

BLEのスキャン処理の例:

Disposable scanSubscription = rxBleClient.scanBleDevices(
        new ScanSettings.Builder()
            // .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) // change if needed
            // .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) // change if needed
            .build()
        // add filters if needed
)
    .subscribe(
        scanResult -> {
            // Process scan result here.
        },
        throwable -> {
            // Handle an error here.
        }
    );

// When done, just dispose.
scanSubscription.dispose();

BLEのwrite処理の例:

device.establishConnection(false)
    .flatMapSingle(rxBleConnection -> rxBleConnection.writeCharacteristic(characteristicUUID, bytesToWrite))
    .subscribe(
        characteristicValue -> {
            // Characteristic value confirmed.
        },
        throwable -> {
            // Handle an error here.
        }
    );

flatMapSingle()Disposablesubscirbe() など、Rxらしさのあるコードになっていますね。

本プロジェクトでも、タイムアウトやリトライ処理などをRxのオペレータを使って記述しています。
例えば、BLEのread/write処理に対して、遅延付きリトライを行う (疑似) オペレータは以下のようになります。

//  ワンショット系のBLE処理をリトライ付きで実行する。
//  リトライ時には遅延を掛けるようにする。
//  ただし、BLE接続が切断された場合はこれ以上リトライしない。
fun Single<ByteArray>.retryBleOperation(
    retryTimes: Long,
    delay: Long,
    unit: TimeUnit,
    scheduler: Scheduler = Schedulers.computation(),
    onRetry: (Throwable) -> Unit = {}
): Single<ByteArray> = this.retryWhen { errorStream: Flowable<Throwable> ->
    errorStream.zipWith(IntRange(0, Int.MAX_VALUE).asIterable()) { error, index ->
        //  指定回数までリトライを掛ける。
        //  ただし切断エラーの場合はどうあがいても絶対にコマンドは失敗するため、リトライを中止する。
        if (index < retryTimes && error !is BleDisconnectedException) error
        else throw error
    }.flatMap { error -> Flowable.timer(delay, unit, scheduler).doOnNext { onRetry(error) } }
}

Rxを使わずにリトライのような処理を実装しようとすると、コールバックのネストになったり、一時的な状態を保持するためのフィールドが乱立したりしてしまうと思います。
また、時間が絡んでくるような処理はテストが非常にやりづらいです。

そこで、上記のようにRxを使って実装することで、(呼び出し元に時間の管理やリトライ回数の処理などを意識させる必要のない) 非常にスッキリとしたコードにすることができたと思います。
さらに TestScheduler を使用することによって、時間を扱うような処理にも関わらず、簡単に単体テストを書くことができます。

以下がそのテストコードの例となります。

    @Test
    fun BLE接続が切断されていればリトライせずにエラーを通知する() {
        val testScheduler = TestScheduler()

        var counter = 0
        val source: Single<ByteArray> = Single.create { emitter ->
            when (++counter) {
                
                //  1回目・2回目の発火時は通常のエラー
                1, 2 -> emitter.onError(Exception())
                
                //  3回目の発火時は切断エラー
                3 -> emitter.onError(mockk<BleDisconnectedException>(relaxed = true))
                
                else -> emitter.onSuccess(byteArrayOf())
            }
        }
        val testObserver = source.retryBleOperation(
            retryTimes = 5L,
            delay = 100L,
            unit = TimeUnit.MILLISECONDS,
            scheduler = testScheduler
        ).test()

        //  1回目の発火
        //  はじめは失敗しているが、まだエラーは通知されていない。
        testObserver.assertNoValues()
        testObserver.assertNoErrors()

        //  2回目の発火
        //  最初のリトライを掛けて失敗する。
        testScheduler.advanceTimeBy(100L, TimeUnit.MILLISECONDS)
        testScheduler.triggerActions()
        testObserver.assertNoValues()
        testObserver.assertNoErrors()

        //  3回目の発火
        //  再びリトライを掛けて失敗する。
        //  BleDisconnectedExceptionによって失敗するため、これ以上リトライは行わない。
        testScheduler.advanceTimeBy(100L, TimeUnit.MILLISECONDS)
        testScheduler.triggerActions()
        testObserver.assertNoValues()

        //  切断エラーによってストリームが異常終了しているはず。
        testObserver.assertError(BleDisconnectedException::class.java)
    }

Rxの経験が十分にあり、AndroidでBLEを扱う機会がある際には、ぜひ導入を検討してみることをおすすめいたします。
(Rxの購読やHot/Coldに関する知識がないとちょっと厳しいかもしれません。)

kotlinx-coroutines-rx2の導入

本プロジェクトの非同期処理では全般的にKotlin Coroutinesを利用しています。
非同期の部分をsuspend関数にすることによって、非同期処理を同期処理のような呼び出しで扱うことができます。
特に非同期処理を連続して呼び出す際に、通常ならばコールバックのネストが必要な部分を通常の同期処理のような呼び出しとして記述できるため、とても直感的で分かりやすいコードにすることができます。

通常のコールバック形式で記述した際はこのようなコードになりますが、

//  通常のコールバック形式でのコード
fun doSomethingAsync() {
    doAsync1(
        onSuccess =  { result1 ->
            doAsync2(
                result1,
                onSuccess = { /* 処理1と処理2が完了した際の処理 */ },
                onFailure = { /* 処理2のエラー処理 */ }
            )
        },
        onFailure = { /* 処理1のエラー処理 */ }
    )
}

Kotlin Coroutinesを使うことにより、このような同期処理のようなコードになります。

//  Kotlin Coroutinesを利用したときのコード
suspend fun doSomethingAsync() {

    //  try-catchを使用した例
    val result1 = try {
        doAsync1()
    } catch (e: SomethingThrowable) {
        //  処理1のエラー処理
    }

    val result2 = try {
        doAsync2(result1)
    } catch (e: SomethingThrowable) {
        //  処理2のエラー処理
    }


    //  runCatchingを使用した例
    runCatching {
        doAsync1()
    }.onFailure {
        //  処理1のエラー処理
    }.mapCatching { result1 ->
        doAsync2(result1)
    }.onFailure {
        //  処理2のエラー処理
    }
}

本プロジェクトで、BLEの制御に関しては非同期をRxで扱っていましたが、ワンショットなストリームに関してはsuspend関数への変換を行い、同期処理的な書き方をできるようにしました。

そのために kotlinx-coroutines-rx2 という公式のサポートライブラリを使います。
このライブラリではRx (Observable<T>, Single<T>, ...) とKotlin Coroutines (suspend関数, Flow<T>) の相互変換用の関数を提供してくれています。

とあるBLEのwrite処理をsuspend関数化すると以下のようになります。

//  商品データの送信をリクエストする。
//  (といったような処理が実際に扱った機器にはありました。)
//  内部でタイムアウトとリトライの処理を掛ける。
suspend fun RxBleConnection.request(): ByteArray {
    val command = byteArrayOf(/* ... */)
    val uuid = UUID.fromString("12345678-1234-1234-1234-1234567890AB")

    return this.writeCharacteristic(uuid, command)
        .timeout(REQUEST_COMMAND_TIMEOUT_IN_MS, TimeUnit.MILLISECONDS)
        .retryBleOperation(
            retryTimes = REQUEST_COMMAND_RETRY_TIMES,
            delay = REQUEST_COMMAND_RETRY_DELAY_IN_MS,
            unit = TimeUnit.MILLISECONDS,
        )
        .await()
}

このようなsuspend関数を作っておき、処理の呼び出し側で通常の同期処理のように呼び出すだけです。
エラーハンドリングも普通にtry-catch (runCatching) で行うことができます。

private suspend fun executeRequestCommand() {
    val handle = this.connectionHandle ?: return

    //  BLEのコマンドを実行して結果を待機する。
    val isSuccess = runCatching { 
        handle.request()
    }.isSuccess

    if (isSuccess) { /* 成功時の処理 */ }
    else { /* 失敗時の処理 */ }
}

このようにRxのオペレータでリトライとタイムアウトの処理を行い、待機をKotlin Coroutinesで行うという連携ができました。
RxもKotlin Coroutinesも非常に便利ですね。
AndroidでBLEを扱うことがあれば、ぜひ参考にしてみてください。

ちはやふるのキャラクターをゲームに落とし込む

iOS/Android版アプリ「競技かるた ONLINE」において、アニメ「ちはやふる3」とのコラボレーションイベントを2020年1月15日~2月15日の期間で実施いたしました。
期間中にGETした「ちはやふる」のキャラクター達と競技かるたで対戦することができます。

 

f:id:betacomputing3:20200214185943p:plain

 

対戦している間にキャラクターのカットインが発生し、対戦の臨場感を演出します。

試合状況に合わせてキャラクターの感情が変化するので、プレイヤーの実力に応じてキャラクターの反応も変化します。

 

今回はキャラクターの感情変化について、どのようなことをしているかご紹介いたします。

f:id:betacomputing3:20200214191917j:plain f:id:betacomputing3:20200214191816j:plain

 

キャラクターの感情

キャラクターの感情は、リラックス/幸せ/興奮/落ち着き/通常/緊張/落ち込み/動揺/ナーバスの9種類があります。

札を取った/取られた、お手つきした、から札・・・など対戦の展開からキャラクターの感情パラメータが増減します。

札が読まれる前にキャラクターの感情パラメータを判定し、感情が変化している場合にカットインが出現します。

感情マップ

感情のパラメータの大小により、キャラクターの感情を決めています。
その際に用いるのが「感情マップ」です。
感情マップとは、X軸に「勝利への期待」、Y軸に「敗北への不安」を持つ2次元マップになります。

      f:id:betacomputing3:20200214193905p:plain 

※参考 

automaton-media.com

 

例えば、プレイヤーが札を取りつづけ、キャラクターが追い込まれている場合は、キャラクターの「勝利への期待」は低く「敗北への不安」が高くなります。この時キャラクターの感情はマップ上では、「動揺」や「ナーバス」に位置しています。
逆に、キャラクターがプレイヤーを圧倒している時は、キャラクターの「勝利への期待」は高く「敗北への不安」が低いため、キャラクターの感情は「幸せ」「リラックス」に位置することになります。

このように感情マップを取り入れることで、試合状況の応じて、キャラクターの多彩な感情を扱うことができます。

ちなみに、感情パラメータの変化率はキャラクターごとに異なり、キャラクターの性格が表れるようにしています。

 

また、キャラクターの感情は取る速さやお手つき率にも影響します。

そのため、試合展開に合わせて流動的にキャラクターの強さが変化します。

ここでもキャラクターごとに変化率が異なります。

 

「カットイン」+「キャラの強さ変化」の組み合わせによって対戦に臨場感を持たせ、プレイヤーが「相手」を感じられるようにしています。


少しでも興味をもっていただけましたら、「競技かるた ONLINE」をプレイしてみて下さい!

 

karuta.betacomputing.co.jp

さばえものづくり博覧会2019に出展

10月25日、26日、27日の3日間、「さばえものづくり博覧会2019」に出展してきました。

今回は自社出展ではなく、株式会社システム・プロモーション様のブースの一部をお借りしての展示になります。

出展内容はもちろん、競技かるた ONLINEです。

システム・プロモーション様の赤外線センサーとRaspberry Piから制作されたもぐらたたきと相まって、たくさんの方々に楽しんでいただけました。

 

さばえものづくり博覧会 来場者アンケートの結果、競技かるた ONLINEが人気度上位ブースになったようです!

f:id:betacomputing3:20191028201537j:plain

f:id:betacomputing3:20191028201717j:plain

 f:id:betacomputing3:20191028193754j:plain

f:id:betacomputing3:20191028201844j:plain

karuta.betacomputing.co.jp

www.spromotion.co.jp

sabae-monohaku.jp

Toyama Gamers Day 2019 にて「競技かるた ONLINE」大会を開催しました

北陸最大のeスポーツイベント「Toyama Gamers Day 2019」にて、「競技かるた ONLINE」の大会を開催しました。

Toyama Gamers Day 2019」は 2019年9月28、29日の2日間開催され、「競技かるた ONLINE」はサテライト会場の魚津市新川文化ホールで、1日目はブース展示、2日目は大会というスケジュールでした。
大会に出場したプレイヤーは6人で、トーナメント形式の試合としました。

試合の解説は弊社吉村と富山県かるた協会会長の渡辺様で行い、かるたを知らない一般のお客様でも楽しんでいただけたように思います。

■結果
 優勝:たーじ
 2位:えつ
 3位タイ:コー
 3位タイ:ハンダ
 優勝者にはトロフィーと賞品が贈呈されました。

f:id:betacomputing3:20191002181843j:plain

f:id:betacomputing3:20191002182401j:plain

f:id:betacomputing3:20191002182416j:plain

今回は初めてのオフライン会場での大会開催でしたので、不安もありましたが、大会は盛り上がりを見せ、無事終えることができました。
出場頂いた選手、富山かるた協会会長、ご協力いただいた運営スタッフの皆様、ご覧いただいた皆様のおかげです。
本当にありがとうございました。


「競技かるた ONLINE」の大会でいうと、以前、「結びつくかるた会」様にて「競技かるた ONLINE」を使った国際大会を開催していただきました。MUSBIC様より記事にしていただいております。

「第41回全国高等学校小倉百人一首かるた選手権大会」にお邪魔しました。

2019年7月20日、21日に「第41回全国高等学校小倉百人一首かるた選手権大会」が開催されました。
本大会はコミック・映画「ちはやふる」の舞台となる競技かるたの全国高校選手権大会です。団体戦は予選を含めた参加校は386校に上り、個人戦は2400名以上がエントリーしていました。

f:id:betacomputing3:20190724165908j:plain

小倉百人一首巻頭の天智天皇御製ゆかりの近江神宮をはじめ大津市内各会場各所で試合があります。特に近江神宮は競技かるた選手の憧れの舞台となっています。

f:id:betacomputing3:20190724165948j:plain

試合会場はまさにコミック・映画で見るような光景で、競技者の皆様も真剣に取り組んでいました。現場では応援者も掛け声をかけあって、団体戦・個人戦それぞれ迫力がありました。

f:id:betacomputing3:20190724170716j:plain

中継会場も白熱しています。

f:id:betacomputing3:20190724170024j:plain

試合の様子はYoutubeでも配信されました。

勝敗が決まっていく中、皆様の喜びや涙を目の当たりにして、こちらも大変感動しました。ぜひYoutubeを見て、大会の雰囲気を感じてほしいです。

f:id:betacomputing3:20190724170312j:plain

本大会の会場内で「競技かるた ONLINE」のご紹介をさせていただきながら、皆様のご意見をお聞かせいただきました。今後のアップデートに反映していきたいと思います。

f:id:betacomputing3:20190724170435j:plain

競技者、運営スタッフの皆様お疲れさまでした。

ありがとうございました。

NT金沢2019に出展しました

2019年6月29日(土)から30日(日)にかけてNT金沢2019が開催されました。
また、弊社は去年に引き続き協賛させていただきました。

f:id:betacomputing3:20190704162132j:plain

※NT金沢とは?
ものづくり好きな人が集まり、発表や交流をするイベントです。
電子工作からプラネタリウムまでジャンルは様々。

今年は例年以上に大賑わいで、展示数が2年前と比べて約2倍の135と聞いて驚きました。
どおりで集合写真のときにぎゅうぎゅうになるわけだなと思いました。

NT金沢に出展

そしてついに弊社も出展することになりました。もちろん展示内容は「競技かるた ONLINE」です。
NT金沢でご覧になった方がどういった反応を示してくれるのか、結構ドキドキしていました。展示は初めてではないのですが、慣れませんね。

f:id:betacomputing3:20190704162043j:plain

弊社ブースは駅入り口側に最も近く、通りかかった人が何をやっているんだろう?という疑問で足を止めてくれるなかなか良い場所でした。
その甲斐あってか、午前の開始から多くの親子連れの方に訪れていただきました。
子供がアプリに食いついてくれるおかげで、その間にお父さんお母さんにアプリの名刺を渡し、インストールをお願いするというフローが午後からは出来上がっていました。

皆さまからいただいた感想の一覧です。
・UIが素晴らしい!
・綺麗、美麗
・札の払いが気持ちいい
・初心者でもできるので良い
・反りまで表現しているなんて、札のこだわり方が尋常じゃない。
・高校のとき競技かるたをやっていたんですけど、ついにオンライン対戦ができるようになったんですね!(感激)

厳しい意見もございました。(貴重なご意見です!)
・文字が小さいのでできない
・別にやらないと思う
・タブレットだとやりやすそうだが持っていないのでやらない

中には投げ銭をしてくださった方がいました!目の前でアプリにこれでもかというくらい称賛を浴びせ、それだけでは飽き足らず、お布施までいただけました。
世の中は奇特な人がいるもんだなと思いました。しかもなんと2人も!

本当にありがとうございます!
皆さまのご意見を参考にさせていただきます。

他出展者様の展示

今年は展示側になったため、あまり他の皆さんの展示を見る時間が作れませんでしたが、一部見て回ったところの写真をアップしたいと思います。

f:id:betacomputing3:20190704162406j:plain
(karei_keyさん撮影)

f:id:betacomputing3:20190704162413j:plain
(GomiHgyさん撮影)

f:id:betacomputing3:20190704162423j:plain
(GomiHgyさん撮影)

f:id:betacomputing3:20190704162439j:plain
(Beta Computing撮影)

f:id:betacomputing3:20190704162428j:plain
(GomiHgyさん撮影)

まとめ

今年もノークレームだったとのことです。電源トラブルというハプニングはあったようですが、午前の内には改善していました。素晴らしいです。

展示してみてわかったことは、漫画やアニメ、映画等の影響もあり競技かるたのことを知っている人は結構いたのですが、ルールがわからないという人がほとんどだということでした。
そして、アプリを通してルールの理解をして頂けることも分かったので、競技かるた普及のためにも、もっと露出してアプリの認知度を高めていきたいと思います。

出展者の皆様、運営の方々、ほんとうにお疲れさまでした。皆様のおかげで全く滞りなく展示に集中することができました。
また、来場してくださった皆様、誠にありがとうございました。

f:id:betacomputing3:20190704162604j:plain