10yroの開発日記

福岡にある株式会社10yro(トイロ)のエンジニアが書いています

ブラウザ上でのネットワーク速度制限

ブラウザで通信速度を変更したり、オフラインにしたりする方法を記載します。

開発していて動作確認やテスト時などに、ブラウザをオフラインにしたりネットワークの速度を遅くしたりしたいケースがあるかと思います。

そんな時に使えるブラウザ(Dev tool)の機能を紹介します。

ブラウザで [F12] キーを押すと開発者ツールが開きます。

開いた開発者ツールの中でネットワークアイコンの横にドロップダウンリストが表示されています。

※基本的に何もしていなければ「No throttling」になっていると思います。

ここを開くと以下のようにいくつか選択肢があります。

ここで、「Fast 3G」や「Slow 3G」を選択するとそれに応じて回線速度が遅くなり

「Offline」を選べば以下のようにブラウザ上のみオフラインの状態となるなど回線速度の設定ができます。

さらに細かい設定がしたい場合は [Custom] から細かい指定も可能となっています。

Webアプリでのネットワークが繋がっていない場合のテストや 通信速度が遅く(または大容量通信)時間がかかる場合のテストをしたいときなど PC自体をオフラインにしたり、データを多く用意するなどせずに確認できるためラクですね。

以上、ブラウザ上での回線速度を制御する方法でした。

作業の生産性を向上させるツール

新年度になって移動や入社などで忙しい時期も少しづつ落ち着いてきたと思います。
そこで新しい職場や環境でのスタートを成功させるために、便利なツールやアプリを活用して仕事を効率化しましょう。
これから紹介するものは、特に役立つツールなので仕事をよりスムーズに進めるためのツールを紹介します。もちろん個人での利用にも役に立ちます。

1. PowerToys

Microsoftが提供するWindows向けのユーティリティーソフトウェア。
Windowsを使っている人にとって便利なツール集で、普段の作業がものすごく楽になります。 ウィンドウを一瞬で左右に分割したり、画面上のテキストをすぐコピーできたり、ショートカットキーをカスタマイズしたり・・・。 あとは、ウィンドウの切り替えをもっとスムーズにしたり、画面のレイアウトを整えたりするのも可能です。 apps.microsoft.com github.com

2. Devtoys

開発者向けのツールやリソースを提供するプラットフォーム。
開発者がコーディングやデバッグ作業を効率化するための様々なツールや情報を見つけることができ、スキルを向上させるための情報も豊富です。 開発作業がスムーズになり、より高い品質のソフトウェアを開発することができます。 開発者としてのキャリアを積んでいく上で、標準で入っていないが不思議なくらいです。 apps.microsoft.com devtoys.app

3. PowerAutomate

Microsoftの自動化ツール。
ビジネスプロセスやタスクの自動化を容易に行えます。 メールの自動送信、ファイルの移動、データの更新など、日常的に行う作業を自動化することができて、直感的なフローデザイナーを備えており、コーディングの知識がなくても簡単にワークフローを作成できます。 さらに、様々なアプリケーションやサービスとの連携も可能で、業務効率を大幅に向上させることもできます。 様々な場面で活用できるPowerAutomateは、新入社員からベテランまで、幅広いユーザーにとって役立つツールです。
但し、Microsoftアカウントが必要でオフラインでは使用できません。 apps.microsoft.com www.microsoft.com

4. Autoruns

MicrosoftのSysinternalsツールの1つ。
Windowsの起動時に自動的に実行されるプログラムやサービスを管理するユーティリティーソフトウェアです。 システムの起動時に自動的に起動するプログラムやサービスを一覧表示し、必要なものと不要なものを見分けることができます。 また、レジストリやスタートアップフォルダーだけでなく、各種設定やアプリケーションから自動的に起動するプログラムやサービスも確認できるため、より包括的な管理が可能です。 セキュリティやパフォーマンスの観点からも重要なツールで、Windowsのシステム管理者やエンドユーザーにとって、有用な情報を提供します。 learn.microsoft.com github.com

5. Windows Terminal

Microsoftが提供するコマンドライン インターフェース アプリケーション。
パワーシェル、コマンドプロンプト、WSL(Windows Subsystem for Linux)など複数のシェルを同時に実行できるため、Windows上でのコマンドライン操作を効率化します。 また、タブを使用して複数のセッションを簡単に切り替えたり、ウィンドウを分割して複数のセッションを同時に表示したりすることも可能です。 さらに、カスタマイズ可能なテーマやフォント、キーバインディングなどの機能も提供されており、ユーザーが自分好みに設定できます。 開発者やシステム管理者など、コマンドラインを頻繁に使用するユーザーにとって、Windows Terminalは非常に便利なツールとなります。 apps.microsoft.com github.com

6. 7-Zip

Windows上で利用できるフリーウェアのファイルアーカイブユーティリティ。
様々な圧縮形式のファイルを作成したり、解凍したりすることができ、高い圧縮率を誇ります。 特に7z形式では他のアーカイバーよりも優れた圧縮率を実現します。 さらに、ZIP、RAR、TAR、GZIP、ISOなど、多くの一般的なアーカイブ形式に対応していて、コマンドラインインターフェースも提供されており、自動化やバッチ処理にも活用できます。7-Zipは使いやすく、軽量でありながら強力な機能を備えており、個人ユーザーから企業ユーザーまで幅広く利用されています。 www.7-zip.org

7. CLaunch

カスタマイズ可能なランチャーツール。
Windowsユーザーがアプリケーションやファイルへの素早いアクセスを可能にします。 任意のキーにアプリケーションやファイルを割り当て、それらを素早く起動することができます。 また、タブ機能も備えており、複数のカテゴリやグループを作成して、アプリケーションを整理したり、作業フローを効率化したりすることができます。 さらに、キーバインディングのカスタマイズやテーマの変更などの様々なオプションが提供されています。 hp.vector.co.jp forest.watch.impress.co.jp

8. Clibor

クリップボードの履歴を管理するユーティリティーソフトウェア。
過去にコピーしたテキストや画像などのデータを簡単に呼び出すことができます。 通常のクリップボードには保存されない複数のコピー履歴を保持し、必要な情報をいつでもアクセスできるようになります。 コピーしたデータを一元管理し、テキストや画像などの種類に関係なく簡単に参照できるため、作業効率を向上させます。 また、キーボードショートカットを使ってクリップボードの履歴を呼び出すこともでき、よりスムーズな操作が可能です。 情報の共有やコピー&ペースト作業が頻繁に行われる環境で特に役立つツールです。 forest.watch.impress.co.jp chigusa-web.com

9. Biscuit

ウェブブラウザとタスク管理ツールを統合したアプリケーション。
ウェブサイトをブックマークしたり、タスクを管理したりすることができ、ウェブブラウザとタスク管理ツールを一つのインターフェースに統合することで、作業効率を向上させます。 ウェブサイトへのアクセスやタスクの管理が、同じ場所で行えるため、作業中にウィンドウやタブを切り替える手間が省け、セッションが別なので複数アカウントの場合でも使用できます。 また、Biscuitはクロスプラットフォーム対応であり、WindowsMacLinuxなど、様々なデバイスで利用できます。 Biscuitを使えば、ウェブ閲覧とタスク管理を一元化し、作業をスムーズに進めることができます。 eatbiscuit.com

10. EveryThing

Windows上で動作する高速で効率的なファイル検索ツール。
パソコン上のファイルやフォルダを素早く検索し、目的のファイルやフォルダを見つけることができます。 ファイル名やパスなどの情報をリアルタイムでインデックス化し、非常に高速な検索を実現します。 検索結果はリアルタイムで表示され、キーワードを入力するだけで目的のファイルやフォルダを即座に見つけることができます。 また、フィルタリングや正規表現を使った高度な検索もサポートしており、大規模なファイルシステムでも高速に動作し、ファイルの整理や検索作業を効率化します。 www.voidtools.com

11. EverythingToolbar

WindowsのタスクバーにEverythingの機能を統合するツールです。
EveryThingがインストールされていることが前提ですが、Windowsのタスクバーから直接ファイルやフォルダを検索し、素早くアクセスすることができます。 EverythingToolbarをインストールすると、タスクバーに検索ボックスが追加され、キーワードを入力するだけで関連するファイルやフォルダをリアルタイムで検索できます。 さらに、検索結果をクリックすると、ファイルやフォルダがすぐに開き、Windowsの標準的なファイル検索機能を拡張し、より迅速で効率的な検索体験を提供します。 github.com

12. QuickLook

Windows上でファイルのプレビューを迅速に表示するユーティリティーアプリケーション。
ファイルを選択した瞬間に、その内容をすばやくプレビューすることができます。 例えば、画像、テキストファイル、PDF、Office文書など、様々なファイル形式のプレビューがサポートされています。 ファイルを選択した後にプレビューを閉じたい場合は、Escapeキーを押すだけで簡単に閉じることができます。 ファイルの内容を素早く確認したい場合や、ファイルのプレビューを簡単に行いたい場合(ソフトをインストールしていない場合)に便利なツールです。 apps.microsoft.com

13. DeepL

高度な機械翻訳を提供する人工知能(AI)ベースの翻訳ツール。
文章や文書を自然な言語に翻訳することができます。他の翻訳ツールよりも優れた翻訳品質を実現しています。 さらに、多くの言語に対応しており、細かいニュアンスや文脈を正確に捉えた翻訳が可能です。 翻訳の速度が速く、使いやすいインターフェースを備えており、ビジネス文書やメール、ウェブページなど、様々な文書の翻訳に活用されています。 www.deepl.com

14. Feedly

RSSフィードリーダーの一種で、ニュースやブログ記事などのコンテンツを収集して閲覧するためのプラットフォーム。
お気に入りのウェブサイトやブログの最新記事を一元管理し、一つの場所から効率的に閲覧することができます。 ユーザーが興味のあるトピックやテーマに基づいてフィードをカスタマイズすることができ、常に最新の情報にアクセスできるようになります。 また、記事をブックマークしたり、後で読むために保存したりする機能も備えており、ウェブ上の情報を効率的に収集し、整理するための優れたツールであり、ビジネスや個人の情報収集に幅広く活用されています。 feedly.com

15. Tablacus Explorer

カスタマイズ可能なファイルマネージャーで、Windows Explorerの代替として使用できます。
シンプルなインターフェースと豊富な機能を備えており、ファイルやフォルダの操作を効率的に行うことができます。 タブ機能をサポートしており、複数のフォルダを同時に開くことができ、カスタマイズ可能なツールバーやショートカットキー、外観の変更など、多彩なカスタマイズオプションが提供されています。 また、プラグインも豊富に備えており、ユーザーが必要に応じて機能を拡張することができます。 ユーザーが個々のニーズに合わせてファイル管理を行うための柔軟性と操作性を提供します。 tablacus.github.io

16. Giga Text Viewer

大容量のテキストファイルを効率的に閲覧できるテキストビューア。
数ギガバイトに及ぶ巨大なテキストファイルを高速かつスムーズに表示することができます。 テキストファイルの読み込みと表示に特化しており、大規模なログファイルやデータベースエクスポートなどの処理に適しています。 さらに、検索機能や行番号の表示、文字コードの自動判別など、便利な機能も備えており、大規模なテキストファイルを扱う際に生じる問題に対処するための頼れるツールであり、プログラマーやシステム管理者など、テキスト処理を行うユーザーにとって重宝されています。 kansai.me

17. WinMerge

ファイルやフォルダーの比較およびマージを行うオープンソースの差分表示ツール。 異なるバージョンのファイルやフォルダーを視覚的に比較し、変更点を確認することができ、コードやテキストファイルの変更を検出するための強力なアルゴリズムを備えており、変更点を行ごとにハイライトして表示します。 また、ファイルのマージもサポートしており、複数のバージョンのファイルを統合する際に役立ちます。 さらに、ファイルやフォルダーの比較結果を保存したり、カスタムフィルターを適用したりするなど、様々な機能を提供しています。 ソフトウェア開発者やプロジェクト管理者など、ファイルの変更や統合を頻繁に行うユーザーにとって便利なツールです。 プラグインもたくさんあり、用途により様々なツールと併用して使用できます。 winmerge.org

18. TresGrep

テキストファイル内でのパターン検索や置換を行うユーティリティーソフトウェア。 大規模なテキストファイルや複数のファイルから特定の文字列やパターンを検索し、必要に応じて置換することができます。 正規表現をサポートしており、高度な検索条件を指定することができ、検索結果を一覧表示し、該当箇所をすばやく確認することができます。 また、複数のファイルやディレクトリを対象に検索を行うことも可能です。 開発者やシステム管理者など、テキスト処理を行うユーザーにとって便利なツールです。 設定(以下サイト参照)が必要ですが、ExcelやWord等のOffice製品も比較できます。 www.vector.co.jp

19. 方眼Diff

方眼Diffは、ファイルやテキストの差分を視覚的に表示するユーティリティーソフトウェア。 比較対象のファイルやテキストを並べて表示し、変更点や差分を簡単に確認することができ、テキストやコードの変更点を行ごとにハイライトして表示し、元ファイルとの違いをわかりやすく示します。 また、差分をインラインで表示したり、マージ操作を行ったりする機能も提供されています。 ソフトウェア開発者やライター、編集者など、変更や差分を追跡する必要があるユーザーにとって便利なツールです。 hogandiff.hotchpotch.xyz

20. VScode

Microsoftが提供するオープンソーステキストエディタであり、統合開発環境IDE)。
様々なプログラミング言語フレームワークに対応し、開発者がアプリケーションやウェブサイトの開発を効率的に行うことができます。 VScodeは軽量かつ高機能であり、豊富な拡張機能やカスタマイズオプションを備えています。 また、インテリセンスやデバッグ機能など、開発作業をサポートする機能が豊富に提供されています。 VScodeクロスプラットフォームで動作し、WindowsMacLinuxなど、様々な環境で利用できます。 開発者コミュニティから広く支持されており、プログラミングにおける開発作業の効率化に貢献しています。 apps.microsoft.com code.visualstudio.com

以下に記載するものは、Windowsをカスタマイズしたい方や設定が好みではない方は試してみてください。

21. ExplorerPatcher

Windowsエクスプローラーの外観や機能をカスタマイズするためのユーティリティツール。
Windowsの標準的なファイルマネージャであるエクスプローラーの見た目や動作を変更することができます。 例えば、エクスプローラーの背景色やフォルダーアイコン、コンテキストメニューの項目などをカスタマイズすることができます。 ユーザーは自分好みのエクスプローラーを作成し、操作性や視覚的な快適さを向上させることができます。 エクスプローラーの見た目や機能にこだわりを持つユーザーや、個人用途や特定の作業環境に合わせてカスタマイズしたいユーザーにとって、有用なツールとなっています。 github.com

22. TranslucentTB

Windowsのタスクバーをカスタマイズして透明にするためのユーティリティーツール。
通常のタスクバーを透明にしたり、背景をぼかしたりすることができます。 Windows 10やWindows 11のタスクバーの外観を個人の好みやデスクトップのテーマに合わせて調整するのに役立ちます。 また、透明度やぼかしの程度を細かく調整したり、特定のウィンドウをタスクバーの下に隠したりすることも可能で、Windowsのデスクトップ環境を美しくカスタマイズし、より快適な操作体験を実現することができます。 apps.microsoft.com

Filamentの導入と管理画面構築

Filamentの導入方法と管理画面の構築方法について解説します。
Filamentはデータ駆動型の管理パネルの機能とUIをセットで効率的に作ることができます。

Filamentとは

Filamentは、Laravelフレームワーク向けの管理パネルビルダーです。簡単なコマンドや設定を使って、CRUD操作やデータ管理のための管理画面を作成できます。データベースモデルに基づいて自動生成されるため、アプリケーションの管理を容易に行うことができます。

デモページ

導入方法

インストール
composer require filament/filament:"^3.2" -W
php artisan filament:install --panels

上記のコマンドを実行することでFilament Panel Builderがインストールされます。
また、新しいLaravelサービスプロバイダーが作成され、app/Providers/Filament/AdminPanelProvider.phpというファイルに登録されます。

管理者ユーザーの登録
php artisan make:filament-user
日本語化

config/app.phpの言語設定を変更することで日本語化できます。
タイムゾーンも日本時間に設定します。

config/app.php

    'timezone' => 'UTC',
        ↓
    'timezone' => 'Asia/Tokyo',
*********************************
    'locale' => 'en',
        ↓
    'locale' => 'ja',
********************************
    'faker_locale' => 'en',
        ↓
    'faker_locale' => 'ja',
管理画面へのログイン

作成したユーザーアカウントで管理画面にログインすることができます。

http://localhost/admin/login

最後に

Filamentは、迅速かつ効率的に管理画面を構築するための優れたツールです。その使いやすさや柔軟性から、開発者は手間をかけずに豊富な機能を持つ管理パネルを作成できます。
今後も、Filamentリソースの作成方法やカスタマイズ手法に関する情報を更新し、品質の高いアプリケーションを開発するための知識を共有していきたいと考えています。

マインスイーパーを作ってみた

お手頃なゲームを作りたいと思いお題を探したらふとマインスイーパーを思い出したので、C#で作ってみました。
昔はwindowsにも標準搭載されてたあれです。

まずはルール確認(wikipediaより引用 マインスイーパ - Wikipedia )

ゲーム画面は正方形のマスが敷き詰められた長方形のフィールドから構成されている。それぞれのマスは開けることができるが、地雷の置かれているマスを開けると負けとなる。地雷の置かれていないマスを開けたときは、隣接する8方向のマスのいずれかに地雷がある場合はその個数が表示され、隣接するマスに地雷が置かれていないときは、それらが自動的に開けられる。地雷の置かれていないマスをすべて開ければ勝ちとなる。

ルールは仕様は下記とします。

  • 今回は、爆弾のマスは9x9の爆弾の数は10個とする。
  • 各マスは、「爆弾」または「隣接する爆弾の数」で構成される。
  • 爆弾のマスを開いた場合はゲーム終了(負け)とする。
  • すべての爆弾が置かれていないマスを開いた場合はゲーム終了(勝ち)とする。
    • 開いてないマスが全て爆弾マスの場合でもゲーム終了とする。
  • 開いたマスが爆弾のないマスの場合は、隣接する爆弾の数を表示する。
    • →隣接する爆弾がない場合は、隣接する爆弾が存在するマスまで自動的に表示する。
  • 開いたマスの数字と周囲の旗の数が一致している状態で、数字マスをクリックした場合は、周囲のマスを全て空ける。(便利機能)

開発言語

  • .net 8.0 C#
  • タイマー等のデジタル表示には、「SevenSegment 1.0.0」を利用してます。

とりあえず、完成イメージ

ダウンロード

ソース全体は、

github.com

まずはブロック(マス)を作成
ラベルを継承して、作成してます。
ブロックの状態を管理して、状態が変更された際に適切な描画をするようにしています。
画像等は爆弾と旗はここから貰った画像を加工して使ってそれ以外は文字で表示してます。

using minesweeper.Properties;

namespace minesweeper
{
    enum BlockState
    {
        Opened, // 開いた状態
        Closed, // 閉じた状態
        Flagged, // 旗が立てられた状態
        Flagged_NG, // 旗が間違って立てられた状態
        Bomb, // 爆弾のブロック
        Bomb_OPENDED, // 開いた爆弾のブロック
    }
    internal class Positon(int x, int y)
    {
        public int X { get; set; } = x;
        public int Y { get; set; } = y;
    }
    internal class Block : Label
    {
        public Block()
        {
            TextAlign = ContentAlignment.MiddleCenter;
            BackgroundImageLayout = ImageLayout.Stretch;
            Font = new Font("Arial", 15, FontStyle.Bold);
            AutoSize = false;
            SetImage();
        }

        private bool _IsPressed = false;
        public bool _IsOpened = false;
        private BlockState state = BlockState.Closed;
        // ブロックの状態
        public BlockState State
        {
            get { return state; }
            set
            {
                state = value;
                SetImage();
            }
        }
        // 周辺の爆弾の数
        public int BombCount = 0;
        // 爆弾のブロックかどうか
        public bool IsBomb = false;
        // ブロックの位置
        public Positon Positon = new Positon(0, 0);
        // ブロックが押されたかどうか
        public bool IsPressed
        {
            get { return _IsPressed; }
            set
            {
                _IsPressed = value;
                SetImage();
            }
        }
        // ブロックの画像を設定
        private void SetImage()
        {
            if (_IsPressed)
            {
                BackgroundImage = Resources.block_pressed;
                return;
            }
            switch (state)
            {
                case BlockState.Opened:
                    BackgroundImage = Resources.block_pressed;
                    SetBombCountText();
                    break;
                case BlockState.Closed:
                    BackgroundImage = Resources.block;
                    break;
                case BlockState.Flagged:
                    BackgroundImage = Resources.flag;
                    break;
                case BlockState.Flagged_NG:
                    BackgroundImage = Resources.flag;
                    Text = "X";
                    Font = new Font("Arial", 18, FontStyle.Bold);
                    ForeColor = Color.Red;
                    break;
                case BlockState.Bomb:
                    BackgroundImage = Resources.bomb;
                    BackColor = ColorTranslator.FromHtml("#c6c6c6");
                    break;
                case BlockState.Bomb_OPENDED:
                    BackgroundImage = Resources.bomb;
                    BackColor = Color.Red;
                    break;
            }
        }
        private void SetBombCountText()
        {
            if (BombCount == 0) return;
            Text = BombCount.ToString();
            switch (BombCount)
            {
                case 1:
                    ForeColor = ColorTranslator.FromHtml("#0000F7");
                    break;
                case 2:
                    ForeColor = ColorTranslator.FromHtml("#007C00");
                    break;
                case 3:
                    ForeColor = ColorTranslator.FromHtml("#EC1F1F");
                    break;
                case 4:
                    ForeColor = ColorTranslator.FromHtml("#00007C");
                    break;
                case 5:
                    ForeColor = ColorTranslator.FromHtml("#7C0000");
                    break;
                case 6:
                    ForeColor = ColorTranslator.FromHtml("#007C7C");
                    break;
                case 7:
                    ForeColor = ColorTranslator.FromHtml("#000000");
                    break;
                case 8:
                    ForeColor = ColorTranslator.FromHtml("#7C7C7C");
                    break;

            }
        }
    }
}

次はゲーム画面の作成

InitGameFiled()で盤面を作成しています。
動的に上記で作成したBlockを並べてランダムで爆弾の位置を設定しています。
細かい動作はソースコメントで。
ドラッグ中はドラッグを開始したコントロールが以外イベントが発生しないようなので、
マウス座標から現在のコントロール(マス)を特定するようにしてます。

using minesweeper.Properties;
using Timer = System.Windows.Forms.Timer;

namespace minesweeper
{
    public partial class Form1 : Form
    {
        enum GameStatus
        {
            Ready,
            Playing,
            GameEnd,
        }
        // 横のブロック数
        const int xLength = 9;
        // 縦のブロック数
        const int yLength = 9;
        // 爆弾の数
        const int bombCount = 10;
        // 1ブロックのサイズ
        const int BlockSixe = 50;
        // タイマー
        Timer timer = new Timer();

        GameStatus gameState = GameStatus.Ready;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            timer.Interval = 1000; // 1秒
            txtBombCount.Value = "";
            timer.Tick += Timer_tick;
            InitGameFiled();
        }

        /// <summary>
        /// ゲームの経過時間のタイマー
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void Timer_tick(object? sender, EventArgs e)
        {
            this.txtBombTimer.Value = (int.Parse(this.txtBombTimer.Value) + 1).ToString();
        }

        /// <summary>
        /// ゲームフィールドの初期化
        /// </summary>
        private void InitGameFiled()
        {
            this.txtBombCount.Value = bombCount.ToString();
            this.txtBombTimer.Value = "0";
            timer.Stop();
            this.gameState = GameStatus.Ready;
            blockArea.Controls.Clear();
            for (int x = 0; x < xLength; x++)
            {
                for (int y = 0; y < yLength; y++)
                {
                    var label = new Block();
                    label.Name = "Block" + x + y;
                    label.Size = new Size(BlockSixe, BlockSixe);
                    label.Location = new Point(x * BlockSixe, y * BlockSixe);
                    label.Positon = new Positon(x, y);
                    label.MouseUp += Block_MouseUp;
                    // マウスダウン時のイベント。
                    label.MouseDown += (sender, e) =>
                    {
                        if (this.gameState == GameStatus.GameEnd) return;
                        if ((e.Button & MouseButtons.Left) != MouseButtons.Left) return;
                        // ゲーム開始
                        if (this.gameState == GameStatus.Ready)
                        {
                            this.gameState = GameStatus.Playing;
                            timer.Start();
                        }
                        PressBlock(label);
                    };
                    label.MouseMove += Block_MouseMove;

                    blockArea.Controls.Add(label);
                }
            }
            // 爆弾の位置をランダムに決定
            Random rnd = new Random();
            var setBombCount = 0;
            while (setBombCount != bombCount)
            {
                int x = rnd.Next(0, xLength);
                int y = rnd.Next(0, yLength);
                var block = (Block?)blockArea.Controls["Block" + x + y];

                // 既に設定されている場合はスキップ
                if (block.IsBomb)
                {
                    continue;
                }
                setBombCount++;
                block.IsBomb = true;
            }

            // 爆弾の周囲のマスに数字を表示
            foreach (Control control in blockArea.Controls)
            {
                if (control == null && (control is not Block)) return;
                Block block = (Block)control;
                foreach (var target in GetAroundBlok(block))
                {
                    if (target.IsBomb) block.BombCount++;
                }
            }

            this.Size = new Size(BlockSixe * xLength + 20, BlockSixe * yLength + infoArea.Height + SystemInformation.CaptionHeight + 20);
            this.FormBorderStyle = FormBorderStyle.FixedSingle;
        }

        /// <summary>
        /// 指定したブロックの周囲のブロックを取得
        /// </summary>
        /// <param name="block">基準のブロック</param>
        /// <returns></returns>
        private List<Block> GetAroundBlok(Block block)
        {
            List<Block> blocks = new List<Block>();
            for (int x = -1; x <= 1; x++)
            {
                for (int y = -1; y <= 1; y++)
                {
                    if (block.Positon.X + x < 0 || block.Positon.X + x >= xLength || block.Positon.Y + y < 0 || block.Positon.Y + y >= yLength) continue;
                    var target = (Block?)blockArea.Controls["Block" + (block.Positon.X + x) + (block.Positon.Y + y)];
                    if (target == null) return blocks;
                    blocks.Add(target);
                }
            }
            return blocks;
        }

        /// <summary>
        /// 現在のマウスカーソルにあるブロックを取得
        /// </summary>
        /// <returns>対象のブロックのリスト</returns>
        private Block? GetActiveBlock()
        {
            Control? control = blockArea.GetChildAtPoint(blockArea.PointToClient(Cursor.Position));
            if (control == null && (control is not Block)) return null;
            return (Block)control;
        }

        /// <summary>
        /// マウスアップ時のイベント
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Block_MouseUp(object? sender, MouseEventArgs e)
        {
            if (this.gameState == GameStatus.GameEnd) return;
            ResetPreessedBlock(null);
            var block = this.GetActiveBlock();
            if (block == null) return;

            // 左クリックで旗以外をクリックした時
            if ((e.Button & MouseButtons.Left) == MouseButtons.Left && block.State != BlockState.Flagged)
            {
                // 爆弾の場合ゲームオーバー
                if (block.IsBomb)
                {
                    GameOver(block);
                    return;
                }

                // 数字ブロックの場合は一括で開く
                if (block.State == BlockState.Opened && CanBlockOpen(block))
                {
                    OpenBlock(block, true);
                    CheckGameClear();
                    return;
                }

                if (block.BombCount == 0)
                {
                    OpenBlock(block, false);
                }
                else
                {
                    block.State = BlockState.Opened;
                }
            }

            // 右クリックの時は旗をON・OFFする
            if ((e.Button & MouseButtons.Right) == MouseButtons.Right && block.State != BlockState.Opened)
            {
                if (block.State == BlockState.Flagged)
                {
                    block.State = BlockState.Closed;
                }
                else
                {
                    block.State = BlockState.Flagged;
                }
            }

            // クリア判定
            CheckGameClear();
        }

        /// <summary>
        /// 左クリック押した状態でマウス移動時はブロックを押下状態にする
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Block_MouseMove(object? sender, MouseEventArgs e)
        {
            if (this.gameState == GameStatus.GameEnd) return;
            if ((Control.MouseButtons & MouseButtons.Left) != MouseButtons.Left) return;
            var block = GetActiveBlock();
            if (block == null) return;
            if (block.State == BlockState.Opened) return;
            block.IsPressed = true;
            ResetPreessedBlock(block);
        }

        /// <summary>
        /// ブロックの押下状態をリセット
        /// </summary>
        /// <param name="current">対象外にするブロック</param>
        private void ResetPreessedBlock(Block? current)
        {
            foreach (Control control in blockArea.Controls)
            {
                if (control == null && (control is not Block)) return;
                Block block = (Block)control;
                if (block.State == BlockState.Closed && current != control)
                {
                    ((Block)control).IsPressed = false;
                }
            }
        }

        /// <summary>
        /// ブロックを押す(クリックが確定してない情報)
        /// </summary>
        /// <param name="block"></param>
        private void PressBlock(Block block)
        {
            if (block == null) return;
            if (block.State == BlockState.Closed)
            {
                block.IsPressed = true;
            }
            else
            {
                foreach (var target in GetAroundBlok(block))
                {
                    if (target.State == BlockState.Closed)
                    {
                        target.IsPressed = true;
                    }
                }
            }
        }
        /// <summary>
        /// 周囲のブロックを開く。
        /// </summary>
        /// <param name="block">基準のブロック</param>
        /// <param name="isOpenBomb">爆弾のあるブロックは開くか</param>
        private void OpenBlock(Block block, bool isOpenBomb)
        {
            if (block == null) return;

            foreach (var target in GetAroundBlok(block))
            {
                if (target.State != BlockState.Closed) continue;
                if (isOpenBomb)
                {
                    if (target.IsBomb)
                    {
                        GameOver(target);
                        return;
                    }
                }
                else
                {
                    if (target.IsBomb) continue;
                }

                target.State = BlockState.Opened;
                if (target.BombCount == 0)
                {
                    OpenBlock(target, isOpenBomb);
                }
            }
        }

        /// <summary>
        /// 旗と周囲の爆弾の数が一致しているかどうか
        /// </summary>
        /// <param name="block"></param>
        /// <returns></returns>
        private Boolean CanBlockOpen(Block block)
        {
            if (block == null) return false;
            int flagCount = 0;
            foreach (var target in GetAroundBlok(block))
            {
                if (target.State == BlockState.Flagged)
                {
                    flagCount++;
                }
            }
            return block.BombCount == flagCount;
        }

        /// <summary>
        /// ゲームクリアかどうか
        /// </summary>
        private void CheckGameClear()
        {
            int flagCount = 0;
            int openCount = 0;
            int unmacthCount = 0;
            foreach (Control control in blockArea.Controls)
            {
                if (control == null && (control is not Block)) return;
                Block block = (Block)control;
                if (block.State == BlockState.Flagged)
                {
                    flagCount++;
                }
                if (block.State == BlockState.Opened)
                {
                    openCount++;
                }

                if (block.State == BlockState.Flagged && !block.IsBomb)
                {
                    unmacthCount++;
                }
            }
            // 間違った旗の数がないかつ全ての爆弾以外のブロックが開かれている場合はクリア
            if (unmacthCount == 0 && openCount == xLength * yLength - bombCount)
            {
                timer.Stop();
                this.btnGame.BackgroundImage = Resources.button_clear;
                this.gameState = GameStatus.GameEnd;

                foreach (Control control in blockArea.Controls)
                {
                    if (control == null && (control is not Block)) return;
                    Block block = (Block)control;
                    if (block.IsBomb)
                    {
                        block.State = BlockState.Flagged;
                    }
                }
                this.txtBombCount.Value = "0";
            }
            else
            {
                this.txtBombCount.Value = (bombCount - flagCount).ToString();
            }
        }

        /// <summary>
        /// ゲームオーバー
        /// </summary>
        /// <param name="currentBlock"></param>
        private void GameOver(Block currentBlock)
        {
            timer.Stop();
            foreach (Control control in blockArea.Controls)
            {
                if (control == null && (control is not Block)) return;
                Block block = (Block)control;

                // 爆発したブロックを赤くする
                if (block == currentBlock)
                {
                    block.State = BlockState.Bomb_Opened;
                    continue;
                }

                // 間違いの旗を表示
                if (block.State == BlockState.Flagged && !block.IsBomb)
                {
                    block.State = BlockState.Flagged_NG;
                    continue;
                }

                // 未開放の爆弾の位置を表示
                if (block.State != BlockState.Flagged && block.IsBomb)
                {
                    block.State = BlockState.Bomb;
                    continue;
                }
                // 閉じているブロックを開く
                if (block.State == BlockState.Closed)
                {
                    block.State = BlockState.Opened;
                    continue;
                }
            }
            this.btnGame.BackgroundImage = Resources.button_gameorver;
            this.gameState = GameStatus.GameEnd;
        }

        /// <summary>
        /// ゲームリセット
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnGame_Click(object sender, EventArgs e)
        {
            InitGameFiled();
            this.btnGame.BackgroundImage = Resources.button_default;
        }
    }
}

Laravelで外部APIへアクセスする方法

弊社ではフォトシンス社のAkerunというスマートロックと連携するシステムの開発をしています。

akerun.com

AkerunにはAPIが用意されているので、そのAPIを利用してAkerunの情報を取得する等が可能です。
今回はLaravelを利用して外部APIへアクセスする方法についてご紹介いたします。
LaravelにはGuzzleというライブラリがあるので、それを利用してAPIへアクセスします。
docs.guzzlephp.org

Guzzleのインストール

以下のコマンドでGuzzleをインストールします。 (composerはインストールされている前提です)

composer require guzzlehttp/guzzle

API呼び出しの実装

以下に実装例を記載します。

<?php
use GuzzleHttp\Client;

class ApiAccessor
{
    public function getAccesses()
    {
        $url = "https://api.akerun.com/v3/organizations/{ORGANIZATION_ID}/accesses";

        // Guzzleを使ってAPIに接続
        $client = new Client();
        $response = $client->request(
            'GET',
            $url, // URLを設定
            [
                'headers' => 
                [
                    'Authorization' => "Bearer {$accessToken}"
                ],
                'debug' => false,
                'query' => $params, // パラメーターがあれば設定 ?foo=barの場合は $params = ['foo' => 'bar']
            ]
        );

        // レスポンスボディを取得
        $responseBody = $response->getBody()->getContents();
        
        return json_decode($responseBody);
    }
}

GuzzleのClientクラスのrequestメソッドを利用してアクセスします。
requestメソッドには、メソッド(今回はGET)、APIのURL、ヘッダー情報、パラメータ(query)をセットしています。
アクセスして取得したResponseBodyのContentsのJsonをデコードします。
簡単ですね。

最後に

弊社ではAkerunと連携したシステム開発のご対応が可能です。
もし、Akerunと連携したシステム開発についてご相談やご質問・その他ご不明な点などございましたら、以下の問合せフォームよりお気軽にご連絡ください。
その他システム開発についてもお気軽にご連絡ください。
10yro.co.jp

C# Pollyを使った回復力の高いAPIアクセス方法

Pollyとは、APIへのアクセス時のリトライの実装などを助けてくれるライブラリです。
Pollyを使えば、APIのアクセス時に問題が発生した場合に、自動的にリトライすることができます。
この記事では、C#でPollyを使った基本的なAPIアクセス方法について説明します。

NuGetを使ってPollyをインストール

いつものようにNuGetパッケージマネージャーから、Pollyをインストールします。
コマンドでインストールする場合は以下のコマンドを実行してください。

Install-Package Polly

HttpClientの作成

APIアクセスにはHttpClientを使用します。
以下のようにHttpClientを生成します。

var httpClient = new HttpClient();

Policyの作成

Pollyを使うためには、Policyを作成する必要があります。
Policyは、リトライや回復の方法を定義するものです。

以下は、リトライの回数を3回に設定するPolicyの作成例です。

var retryPolicy = Policy
    .Handle<HttpRequestException>()
    .RetryAsync(3);

APIアクセス

Policyを作成したら、APIにアクセスする準備が整いました。
以下は、Pollyを使ったAPIアクセスの例です。

var result = await retryPolicy.ExecuteAsync(async () =>
{
    var response = await httpClient.GetAsync("https://example.com/api/users");
    response.EnsureSuccessStatusCode();
    return await response.Content.ReadAsStringAsync();
});

この例では、HttpClientを使ってhttps://example.com/api/usersにアクセスしています。
リトライの回数が3回に設定されているため、APIアクセスに失敗した場合には、3回まで自動的にリトライされます。

リトライの条件の指定

Pollyでは、リトライの条件を指定することができます。
以下は、ステータスコードが500番台の場合にリトライするPolicyの作成例です。

var retryPolicy = Policy
    .Handle<HttpRequestException>()
    .OrResult<HttpResponseMessage>(r => (int)r.StatusCode >= 500)
    .RetryAsync(3);

このようにすることで、APIアクセスに失敗した場合に、ステータスコードが500番台の場合にはリトライするように設定することができます。

その他のPolicy

以下の公式ドキュメントを御覧ください。

github.com

まとめ

以上が、C#でPollyを使ったAPIアクセス方法の紹介です。
Pollyを使うことで、APIアクセスの際に問題が発生した場合に、自動的にリトライすることができるため、アプリケーションの回復力を高めることができます。

C# SendGridのAPIを利用してメールを送信する

何かしらプログラムからSMTPサーバー経由でメールを送信することがあるかと思いますが、Microsoft365等のサービスだとスパム対策が強化され迷惑メールに振り分けられることが多くなったようです。

今回、自社HP(Wordpress)をリニューアルした際に、問い合わせフォームから送信するメールが迷惑メールになってしまうようだったので、SendGridというメール配信サービスを利用しました。
SendGridについては以下を参照ください。
sendgrid.kke.co.jp

C#からもSendGridを利用できるようでしたのでやってみました。
SendGridのアカウントを持っていない方は、SendGridのアカウントを作成してください。
不正利用されないようにちゃんとした審査があります。

SendGridでAPI Keyの発行

  1. SendGridにログインしたら、左のメニューのSettings→API Keysをクリックします。
  2. 次に画面右上の「Create API Key」ボタンをクリックします
  3. API Key Nameを入力して、API Key PermissionはRestricted Accessを選択します
  4. Access DetailsではMail Sendの下にあるMail SendをONにします
  5. Create & ViewsボタンをクリックするとAPI Keyが生成され、パスワードが表示されますのでコピーして保存しておいてください

Visual StudioでMailkit(メール送信ライブラリ)のインストール

メール送信にMailKitというライブラリを利用します。
Visual Studioでコンソールプロジェクトを作成して、NuGetでMailKitをインストールしてください。

メール送信

具体的なメール送信プログラムは以下となります。
送信先メールアドレス、宛先メールアドレス、認証の部分のパスワードにはSendGridで生成したAPI Keyを設定してください。
実行すればメールが送信されます。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp5
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var msg = new MimeKit.MimeMessage();
            msg.From.Add(new MimeKit.MailboxAddress("テスト送信先", "from@example.com"));
            msg.To.Add(new MimeKit.MailboxAddress("テスト宛先", "to@example.com"));
            msg.Subject = "テストメール";

            var text = new MimeKit.TextPart("Plain");
            text.Text = "テスト本文\r\n改行します。";
            msg.Body = text;

            using (var client = new MailKit.Net.Smtp.SmtpClient())
            {
                try
                {
                    Console.WriteLine("メール送信 start");
                    
                    // 接続
                    client.Connect("smtp.sendgrid.net", 587, MailKit.Security.SecureSocketOptions.Auto);

                    // 認証
                    client.Authenticate("apikey", "SendGridで生成したAPI Keyのパスワード");

                    // 送信
                    client.Send(msg);

                    // 切断
                    client.Disconnect(true);

                    Console.WriteLine("メール送信 end");
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }
        }
    }
}

まとめ

C#からSendGridのAPIを使ってメール送信するプログラムをご紹介しました。
SnedGridを利用することでメールの到達率が高くなるのではないでしょうか。

弊社では一緒に働いてくれるプログラマー、エンジニアの方を募集しています。
未経験者も大歓迎ですので、ご興味ある方は以下よりお問い合わせください。

10yro.co.jp