2026年3月30日月曜日

TBD(準備中)

Redirecting

3秒後にページを移動します。

すぐ移動する場合は こちら をクリックしてください。

⚖️ストラングラーフィグパターン(Strangler Fig Pattern)は、古いシステムを新しいシステムに段階的に置き換えるためのソフトウェア開発手法です

 ストラングラーフィグパターン(Strangler Fig Pattern)は、古いシステムを新しいシステムに段階的に置き換えるためのソフトウェア開発手法です。このパターンは、熱帯雨林に生育するストラングラーフィグ(絞め殺しのイチジク)に由来しており、古い木に絡みつきながら成長し、最終的には元の木を置き換える様子に似ています。

概要

ストラングラーフィグパターンは、レガシーシステム(古いシステム)の完全な置き換えを行う代わりに、徐々に新しいシステムを導入していく手法です。既存の機能を新しいコンポーネントで置き換えていくことで、システムを完全に再構築するリスクを減らし、開発プロセスを管理しやすくします。

利点

  • リスクの軽減: 全てを一度に置き換えるのではなく、段階的に移行することで、ビジネスへの影響を最小限に抑えることができます。

  • 段階的な改善: 新しい技術やアーキテクチャを部分的に導入しながら、既存システムの改善を行えます。

  • 柔軟性の向上: 各機能を独立して移行できるため、優先度に応じてプロジェクトを進めることが可能です。

実装手順

  1. システムの分析: レガシーシステムの機能を分析し、どの部分を新しいシステムに置き換えるかを決定します。

  2. 新しいコンポーネントの設計: レガシーシステムと共存できるように、新しいコンポーネントを設計します。これにより、部分的に新しい機能を追加できます。

  3. 段階的な移行: 新しいコンポーネントを導入し、古いシステムの該当部分を置き換えていきます。このプロセスは、時間をかけて繰り返されます。

  4. 完全な移行: すべてのレガシーコンポーネントが置き換えられた後、古いシステムを完全に削除します。

使用例

  • マイクロサービスアーキテクチャへの移行: 単一のモノリシックシステムを分割し、マイクロサービスとして再構築する際に活用されます。

  • APIのバージョンアップ: 古いAPIを段階的に新しいAPIに置き換えることで、互換性を保ちながら移行できます。

このパターンは、複雑なシステムのリファクタリングや移行に非常に有効であり、既存システムの価値を保持しつつ、徐々に新しい技術を取り入れる戦略として広く採用されています。

🌙Particle Life Simulation粒子生命シミュレーション

 原子のような粒子間の引力や斥力の単純なルールを使って、原始的な人工生命をシミュレートし、複雑な自己組織化する生命のようなパターンを作り出すシンプルなプログラム。GUI要素を除くと、コードは1ページにも満たない。ビデオチュートリアルとチュートリアルは以下からご覧いただけます。

A simple program to simulate artificial life using attraction/reuplsion forces between many particles - hunar4321/particle-life


画像
Source Code and Program: https://github.com/hunar4321/particle-life Click here for a live demo: https://hunar4321.github.io/particle-life/particle_life.html#91651088029 This project was inspired by: Jeffery Ventrella's Clusters http://www.ventrella.com/Clusters/ I don't have access to Ventrella's code but I guess the main difference of my project with the other particle life projects is that I didn't implement collision detection and this has simplified the simulation.
YouTube/14:37

このコードは、おそらく世の中にある他の人工生命コードよりも桁違いに単純なものだろう。というのも、私はこのコードを、非プログラマーや一般聴衆のための教材として、単純さから複雑さが生まれるという点を証明するためだけに始めたからだ。


画像
https://editor.p5js.org/setapolo/sketches/tLT__cLGO
  1. パラメーターの保存と読み込み機能の追加(見つけた面白いモデルを簡単に共有できるようにするため)

  2. 粒子の種類を増やす機能(現在は4種類に固定されています。)

  3. 現在、最大のボトルネックは、入れ子になったfor-loop(全粒子間のペア距離を計算する)であり、計算量を2次関数化しています。これを回避する方法が見つかれば素晴らしいことです。

  4. ポイント3の代替案として、ペアワイズ距離を計算するのは恥ずかしいほど並列なので、GPUで計算できます。

  5. スクリーンのサイズを変更する機能を追加し、境界チェックを改善する。

  6. より直感的なUIを追加し、パラメータをより細かくコントロールできるようにする。

  7. ランダム化ボタンを追加するか、あるいは、初期ルールを継続的かつ再帰的に変異させる単純なメタルールを持つことがさらによい。こうすることで、パターンが局所的な最大値に留まることはなく、変化し続ける!

  8. より良い微調整の方法は、進化的アルゴリズムを使ってパラメーターを選択し最適化することだが、そのためにはフィットネス関数を書く必要がある。今のところ、このプログラムの領域でフィットネス関数が何に相当するのか、私にはわからない。我々の世界では、適性関数は競争であり、適者生存である。しかし、ここでは、我々にとって興味深いパターンを生み出すパラメーターを微調整し、選択する!

引力と反発力は物理学の基本概念であり、粒子(ここではアトム)の相互作用をシミュレートする際に使用されます。このスクリプトでは、これらの力をシンプルなモデルで表現しています。

引力(Attraction)

引力は、2つの粒子が互いに引き寄せ合う力です。このスクリプトでは、引力が正の力(g > 0)として表現されています。

反発力(Repulsion)

反発力は、2つの粒子が互いに押し離す力です。このスクリプトでは、反発力が負の力(g < 0)として表現されています。

rule(green, green, -0.32);
rule(green, red, -0.17);
rule(green, yellow, 0.34);
rule(red, red, -0.1);
rule(red, green, -0.34);
rule(yellow, yellow, 0.15);
rule(yellow, green, -0.2);
  • 緑のアトム同士は弱い反発力(-0.32)を持ちます。

  • 緑のアトムと赤のアトムは引き合う(-0.17)。

  • 緑のアトムと黄色のアトムは強く引き合う(0.34)。

  • 赤のアトム同士は弱い反発力(-0.1)を持ちます。

  • 赤のアトムと緑のアトムは強く引き合う(-0.34)。

  • 黄色のアトム同士は弱い引力(0.15)を持ちます。

  • 黄色のアトムと緑のアトムは中程度の引力(-0.2)を持ちます。

これにより、キャンバス上でアトムが引き合ったり反発し合いながら動く様子がシミュレートされ、複雑なパターンや動きが生まれます。

☕Scriptタグからonloadに対して渡せる情報ってないですよね

 <script> タグの onload イベントは、通常そのスクリプトの読み込みが完了したことを検知するためのもので、直接パラメータを渡すことはできません。onload はただ単に「読み込みが完了した」というイベントです。

しかし、間接的に情報を渡すことは可能です。たとえば、スクリプトを動的に生成する際に、dataset 属性やカスタムプロパティを使って、追加情報を onload で取得する方法があります。以下はその一例です。

<script>
// スクリプトを動的に追加
const script = document.createElement('script');
script.src = "example.js";
script.dataset.info = "Hello, World!"; // カスタムデータを追加
script.onload = function() {
console.log(this.dataset.info); // onload時にカスタムデータを参照
};
document.head.appendChild(script);
</script>

この例では、dataset を使用して情報をスクリプトに関連付け、その情報を onload イベント内で参照しています。

example.jsからの情報はdatasetに代入できないですよね

そうですね、example.js の中で生成された情報を dataset に直接代入することはできません。dataset は基本的に HTML 要素のカスタムデータ属性を操作するためのものであり、JavaScript の実行中に生成されたデータは、dataset 経由で渡すことができません。

ただし、example.js 側で window オブジェクトやグローバルな変数を使用して、onload イベント内で動的に取得する情報を設定することが可能です。以下のように、example.js 内で window オブジェクトにデータを格納し、それを onload イベントで取得する方法が考えられます。

example.js

// スクリプト内でグローバル変数を設定
window.scriptData = { message: "Data from example.js" };

HTML 内の <script> タグ

<script>
const script = document.createElement('script');
script.src = "example.js";
script.onload = function() {
console.log(window.scriptData.message); // example.jsからのデータを取得
};
document.head.appendChild(script);
</script>

この方法で、example.js 内で設定された window のデータを onload イベントで取得することができます。


🦠Tunnel connection failed: 403 Forbidden に遭遇したよpythonanywhere使ってて

 「Tunnel connection failed: 403 Forbidden」というエラーメッセージは、通常、ウェブブラウザや他のクライアントがウェブサーバーに接続しようとするときに発生するものです。エラーコード「403 Forbidden」は、「禁止されている」を意味します。これは、クライアントがリクエストしたリソースへのアクセスがサーバーによって明確に禁止されていることを示しています。

一方、「Tunnel connection failed」は通常、クライアントがサーバーに接続するための「トンネル」を確立できなかったことを示しています。これはプロキシサーバーを介して接続を試みている場合によく見られます。このエラーは、サーバーがリクエストを理解できなかった、または扱うことができなかったときに発生します。

https://www.pythonanywhere.com/forums/topic/14326/

有料アカウントはどのウェブサイトにもアクセスできますが、無料アカウントからアクセスできるAPIのホワイトリストがあります。PythonAnywhereの開発者は、要求されたAPIをホワイトリストに追加することができる。リクエストするには API のドキュメントへのリンクも提供する必要があります。

https://www.pythonanywhere.com/forums/topic/14326/

だいたいこんな感じ


画像
ホワイトリストに入ってないからエラーを出すらしい


画像
https://www.pythonanywhere.com/whitelist/

私たちは、公式で公開され、文書化された API を持つサイト -- つまり、人間が利用するのではなく、機械が利用することを目的として設計されたサイト -- だけをリストに追加します。
サイトを許可リストに追加したい場合は、このページでリクエストを送信できます。"Anaconda Notebooks/PythonAnywhere Allow List Request" オプションを選択してください。以下の情報を提供する必要があります:
完了した時や質問がある時にお知らせするためのメールアドレス。
APIドキュメントへのリンク。特に、APIが公開されているドメインを記載したドキュメントへのリンク。
APIが提供されるドメイン(サブドメインを含む)。

https://help.pythonanywhere.com/pages/RequestingAllowlistAdditions/
画像
githubならいけそう



HTTP の 403 Forbidden クライアントエラーレスポンスコードは、サーバーがリクエストを理解したものの、認可が拒否されたことを示します。

https://developer.mozilla.org/ja/docs/Web/HTTP/Status/403

ytp-widget

 <div id="ytp-widget-a1b2c3" class="ytp-widget-a1b2c3">

  <style>

    #ytp-widget-a1b2c3 {

      --ytp-bg: #0f1115;

      --ytp-panel: #171a21;

      --ytp-text: #f3f5f7;

      --ytp-sub: #aab4c0;

      --ytp-border: #2a3140;

      --ytp-btn: #202734;

      --ytp-btn-hover: #2a3344;

      --ytp-radius: 14px;

      --ytp-gap: 10px;

      --ytp-font: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;


      font-family: var(--ytp-font);

      color: var(--ytp-text);

      background: var(--ytp-bg);

      border: 1px solid var(--ytp-border);

      border-radius: var(--ytp-radius);

      padding: 14px;

      box-sizing: border-box;

      width: 100%;

      max-width: 960px;

    }


    #ytp-widget-a1b2c3 *,

    #ytp-widget-a1b2c3 *::before,

    #ytp-widget-a1b2c3 *::after {

      box-sizing: border-box;

    }


    #ytp-widget-a1b2c3 .ytp-head {

      display: flex;

      justify-content: space-between;

      align-items: center;

      gap: 12px;

      margin-bottom: 10px;

      flex-wrap: wrap;

    }


    #ytp-widget-a1b2c3 .ytp-title {

      font-size: 15px;

      font-weight: 700;

      letter-spacing: 0.02em;

      margin: 0;

    }


    #ytp-widget-a1b2c3 .ytp-badge {

      font-size: 12px;

      color: var(--ytp-sub);

      border: 1px solid var(--ytp-border);

      border-radius: 999px;

      padding: 4px 10px;

      background: rgba(255,255,255,0.02);

    }


    #ytp-widget-a1b2c3 .ytp-player-wrap {

      position: relative;

      width: 100%;

      aspect-ratio: 16 / 9;

      background: #000;

      border-radius: 12px;

      overflow: hidden;

    }


    #ytp-widget-a1b2c3 .ytp-player {

      width: 100%;

      height: 100%;

    }


    #ytp-widget-a1b2c3 .ytp-controls {

      display: flex;

      flex-wrap: wrap;

      gap: 8px;

      margin-top: 12px;

    }


    #ytp-widget-a1b2c3 .ytp-btn {

      appearance: none;

      border: 1px solid var(--ytp-border);

      background: var(--ytp-btn);

      color: var(--ytp-text);

      padding: 10px 14px;

      border-radius: 10px;

      cursor: pointer;

      font: inherit;

      line-height: 1;

      transition: background 0.2s ease, transform 0.08s ease;

    }


    #ytp-widget-a1b2c3 .ytp-btn:hover {

      background: var(--ytp-btn-hover);

    }


    #ytp-widget-a1b2c3 .ytp-btn:active {

      transform: translateY(1px);

    }


    #ytp-widget-a1b2c3 .ytp-meta {

      margin-top: 12px;

      display: grid;

      grid-template-columns: repeat(2, minmax(0, 1fr));

      gap: 8px;

    }


    #ytp-widget-a1b2c3 .ytp-card {

      background: var(--ytp-panel);

      border: 1px solid var(--ytp-border);

      border-radius: 10px;

      padding: 10px 12px;

      min-width: 0;

    }


    #ytp-widget-a1b2c3 .ytp-label {

      display: block;

      font-size: 11px;

      color: var(--ytp-sub);

      margin-bottom: 4px;

    }


    #ytp-widget-a1b2c3 .ytp-value {

      display: block;

      font-size: 14px;

      font-weight: 600;

      white-space: nowrap;

      overflow: hidden;

      text-overflow: ellipsis;

    }


    #ytp-widget-a1b2c3 .ytp-debug {

      margin-top: 10px;

      font-size: 11px;

      color: var(--ytp-sub);

      white-space: normal;

      word-break: break-word;

    }


    @media (max-width: 640px) {

      #ytp-widget-a1b2c3 .ytp-meta {

        grid-template-columns: 1fr;

      }


      #ytp-widget-a1b2c3 {

        padding: 12px;

      }

    }

  </style>


  <div class="ytp-head">

    <h2 class="ytp-title">Playlist Player</h2>

    <div class="ytp-badge">GA4 direct</div>

  </div>


  <div class="ytp-player-wrap">

    <div id="ytp-player-a1b2c3" class="ytp-player"></div>

  </div>


  <div class="ytp-controls">

    <button type="button" class="ytp-btn" data-act="play">再生</button>

    <button type="button" class="ytp-btn" data-act="pause">停止</button>

    <button type="button" class="ytp-btn" data-act="prev">前へ</button>

    <button type="button" class="ytp-btn" data-act="next">次へ</button>

  </div>


  <div class="ytp-meta">

    <div class="ytp-card">

      <span class="ytp-label">状態</span>

      <span class="ytp-value" data-role="state">未初期化</span>

    </div>

    <div class="ytp-card">

      <span class="ytp-label">動画ID</span>

      <span class="ytp-value" data-role="video-id">-</span>

    </div>

    <div class="ytp-card">

      <span class="ytp-label">Playlist index</span>

      <span class="ytp-value" data-role="playlist-index">-</span>

    </div>

    <div class="ytp-card">

      <span class="ytp-label">再生秒数</span>

      <span class="ytp-value" data-role="current-time">0</span>

    </div>

  </div>


  <div class="ytp-debug" data-role="debug"></div>


  <script>

    (function () {

      const ROOT_ID = 'ytp-widget-a1b2c3';

      const PLAYER_ID = 'ytp-player-a1b2c3';

      const root = document.getElementById(ROOT_ID);

      if (!root) return;


      const CONFIG = {

        gaMeasurementId: 'G-VYN4SZK0E6',      // 例: G-XXXXXXXXXX

        playlistId: 'PLKWIg8_Q-4Urj1UYEUudqpU7ySBROEVAK',      // 例: PLxxxxxxxxxxxxxxxx

        widgetName: 'yt_playlist_widget',

        progressIntervalSec: 10

      };


      const els = {

        state: root.querySelector('[data-role="state"]'),

        videoId: root.querySelector('[data-role="video-id"]'),

        playlistIndex: root.querySelector('[data-role="playlist-index"]'),

        currentTime: root.querySelector('[data-role="current-time"]'),

        debug: root.querySelector('[data-role="debug"]'),

        buttons: root.querySelectorAll('.ytp-btn')

      };


      let player = null;

      let progressTimer = null;

      let lastProgressBucket = -1;

      let lastVideoId = '';

      let lastState = null;


      function debug(msg, extra) {

        const payload = Object.assign({ msg: msg }, extra || {});

        els.debug.textContent = JSON.stringify(payload);

      }


      function ensureGA4(callback) {

        if (window.gtag && window.__ytpGaReadyA1B2C3) {

          callback();

          return;

        }


        if (!document.getElementById('ga4-script-a1b2c3')) {

          const s = document.createElement('script');

          s.id = 'ga4-script-a1b2c3';

          s.async = true;

          s.src = 'https://www.googletagmanager.com/gtag/js?id=' + encodeURIComponent(CONFIG.gaMeasurementId);

          document.head.appendChild(s);

        }


        window.dataLayer = window.dataLayer || [];

        window.gtag = window.gtag || function(){ window.dataLayer.push(arguments); };


        if (!window.__ytpGaReadyA1B2C3) {

          window.gtag('js', new Date());

          window.gtag('config', CONFIG.gaMeasurementId, {

            send_page_view: true

          });

          window.__ytpGaReadyA1B2C3 = true;

        }


        callback();

      }


      function track(eventName, params) {

        ensureGA4(function () {

          window.gtag('event', eventName, Object.assign({

            widget_name: CONFIG.widgetName,

            playlist_id: CONFIG.playlistId

          }, params || {}));

        });

      }


      function stateLabel(state) {

        if (!window.YT || !window.YT.PlayerState) return '不明';

        switch (state) {

          case YT.PlayerState.UNSTARTED: return '未開始';

          case YT.PlayerState.ENDED: return '終了';

          case YT.PlayerState.PLAYING: return '再生中';

          case YT.PlayerState.PAUSED: return '停止中';

          case YT.PlayerState.BUFFERING: return '読込中';

          case YT.PlayerState.CUED: return '準備完了';

          default: return '不明';

        }

      }


      function getVideoId() {

        try {

          return player && player.getVideoData ? (player.getVideoData().video_id || '') : '';

        } catch (e) {

          return '';

        }

      }


      function getPlaylistIndex() {

        try {

          const idx = player && player.getPlaylistIndex ? player.getPlaylistIndex() : null;

          return idx === null || idx === undefined ? '' : String(idx);

        } catch (e) {

          return '';

        }

      }


      function getCurrentTime() {

        try {

          return Math.floor(player && player.getCurrentTime ? player.getCurrentTime() : 0);

        } catch (e) {

          return 0;

        }

      }


      function updateUI() {

        els.videoId.textContent = getVideoId() || '-';

        els.playlistIndex.textContent = getPlaylistIndex() || '-';

        els.currentTime.textContent = String(getCurrentTime());

      }


      function stopProgressTracking() {

        if (progressTimer) {

          clearInterval(progressTimer);

          progressTimer = null;

        }

      }


      function startProgressTracking() {

        stopProgressTracking();

        progressTimer = setInterval(function () {

          if (!player || !window.YT) return;

          if (player.getPlayerState() !== YT.PlayerState.PLAYING) return;


          const sec = getCurrentTime();

          const bucket = Math.floor(sec / CONFIG.progressIntervalSec);

          updateUI();


          if (bucket !== lastProgressBucket) {

            lastProgressBucket = bucket;

            track('yt_playlist_progress', {

              video_id: getVideoId(),

              playlist_index: getPlaylistIndex(),

              progress_seconds: sec

            });

          }

        }, 1000);

      }


      function bindButtons() {

        els.buttons.forEach(function (btn) {

          btn.addEventListener('click', function () {

            if (!player) return;

            const act = btn.getAttribute('data-act');


            if (act === 'play') player.playVideo();

            if (act === 'pause') player.pauseVideo();

            if (act === 'prev') player.previousVideo();

            if (act === 'next') player.nextVideo();


            track('yt_playlist_control_click', {

              control_name: act,

              video_id: getVideoId(),

              playlist_index: getPlaylistIndex(),

              current_time_seconds: getCurrentTime()

            });

          });

        });

      }


      function onPlayerReady() {

        updateUI();

        bindButtons();


        track('yt_playlist_ready', {

          video_id: getVideoId(),

          playlist_index: getPlaylistIndex()

        });


        debug('player_ready', {

          playlistId: CONFIG.playlistId

        });

      }


      function onPlayerStateChange(event) {

        const state = event.data;

        const videoId = getVideoId();


        els.state.textContent = stateLabel(state);

        updateUI();


        if (videoId && videoId !== lastVideoId) {

          lastVideoId = videoId;

          lastProgressBucket = -1;


          track('yt_playlist_video_change', {

            video_id: videoId,

            playlist_index: getPlaylistIndex()

          });

        }


        if (state !== lastState) {

          track('yt_playlist_state_change', {

            video_id: videoId,

            playlist_index: getPlaylistIndex(),

            player_state_code: state,

            player_state_label: stateLabel(state),

            current_time_seconds: getCurrentTime()

          });

          lastState = state;

        }


        if (window.YT && state === YT.PlayerState.PLAYING) {

          startProgressTracking();

        } else {

          stopProgressTracking();

        }


        if (window.YT && state === YT.PlayerState.ENDED) {

          track('yt_playlist_video_complete', {

            video_id: videoId,

            playlist_index: getPlaylistIndex(),

            watched_seconds: getCurrentTime()

          });

        }

      }


      function onPlayerError(event) {

        track('yt_playlist_error', {

          video_id: getVideoId(),

          playlist_index: getPlaylistIndex(),

          error_code: event.data

        });


        debug('player_error', { code: event.data });

      }


      function createPlayer() {

        if (!window.YT || !window.YT.Player) return;


        player = new YT.Player(PLAYER_ID, {

          width: '100%',

          height: '100%',

          playerVars: {

            listType: 'playlist',

            list: CONFIG.playlistId,

            autoplay: 0,

            rel: 0,

            modestbranding: 1,

            playsinline: 1

          },

          events: {

            onReady: onPlayerReady,

            onStateChange: onPlayerStateChange,

            onError: onPlayerError

          }

        });

      }


      function loadYouTubeAPI() {

        if (window.YT && window.YT.Player) {

          createPlayer();

          return;

        }


        const previous = window.onYouTubeIframeAPIReady;


        window.onYouTubeIframeAPIReady = function () {

          if (typeof previous === 'function') previous();

          createPlayer();

        };


        if (!document.getElementById('yt-iframe-api-a1b2c3')) {

          const tag = document.createElement('script');

          tag.id = 'yt-iframe-api-a1b2c3';

          tag.src = 'https://www.youtube.com/iframe_api';

          document.head.appendChild(tag);

        }

      }


      ensureGA4(function () {

        track('yt_playlist_widget_view', {

          component_id: ROOT_ID

        });

      });


      loadYouTubeAPI();

    })();

  </script>

</div>


情報にとって

美とは何か