0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pyxelでウラムの螺旋を描いてみる

Last updated at Posted at 2025-12-01

1. 中心から、連続して、らせん状に正方形をsetしていく

大きさ10の正方形を使って、色を順に1から15を割り当て、画面640x480の中央でset,
右1進みset
上1進みset
左2進みset
下2進みsetと、らせん状に進めていく。中心から、連続して、正方形をsetしていくようにする。

import pyxel

# 色範囲の定義(Pyxelの16色パレットから使用する範囲)
COLOR_START = 1  # 開始色番号
COLOR_END = 15   # 終了色番号
SIZE = 10        # 各正方形のサイズ(ピクセル)
WIDTH = 640      # 画面幅
HEIGHT = 480     # 画面高さ

class SpiralSquares:
    def __init__(self):
        # Pyxelの初期化(画面サイズとタイトルを設定)
        pyxel.init(WIDTH, HEIGHT, title="Spiral Squares")
        
        # 使用する色のリストを生成(COLOR_STARTからCOLOR_ENDまで)
        self.colors = [i for i in range(COLOR_START, COLOR_END + 1)]
        
        # 正方形の位置と色を格納するリスト(各要素は(x, y, color)のタプル)
        self.positions = []
        
        # 螺旋の中心座標(画面中央)
        self.cx = WIDTH // 2
        self.cy = HEIGHT // 2
        
        # 現在の描画位置(中心からスタート)
        self.x = self.cx
        self.y = self.cy
        
        # 移動方向ベクトル(最初は右方向)
        self.dx = 1   # x方向の移動量(正:右、負:左)
        self.dy = 0   # y方向の移動量(正:下、負:上)
        
        # 螺旋のセグメント管理変数
        self.segment_length = 1    # 現在のセグメントの長さ(何マス進むか)
        self.segment_passed = 0    # 現在のセグメント内で進んだマス数
        self.segment_count = 0     # 完了したセグメント数(方向転換の回数)
        
        # 色の管理変数
        self.color_index = 0       # 現在使用している色のインデックス
        
        # 配置した正方形の総数(アニメーション進行度の管理)
        self.set_count = 0
        
        # メインループを開始(updateとdrawを毎フレーム呼び出す)
        pyxel.run(self.update, self.draw)
    
    def update(self):
        """
        フレームごとの更新処理
        Rキーでリセット、毎フレーム1個ずつ正方形を追加してアニメーション
        """
        # Rキーが押されたら全ての状態を初期化してリセット
        if pyxel.btnp(pyxel.KEY_R):
            # 描画位置を中心に戻す
            self.x = self.cx
            self.y = self.cy
            
            # 移動方向を右にリセット
            self.dx = 1
            self.dy = 0
            
            # セグメント管理変数をリセット
            self.segment_length = 1
            self.segment_passed = 0
            self.segment_count = 0
            
            # 色インデックスをリセット
            self.color_index = 0
            
            # 配置済みの正方形をクリア
            self.positions = []
            
            # カウンターをリセット
            self.set_count = 0
        
        # まだ目標数に達していない場合、毎フレーム1個ずつ正方形を追加
        # 2209 = 47×47(画面を埋め尽くすのに十分な数)
        if self.set_count < 2209:
            # 現在位置に正方形を配置(座標と色を記録)
            self.positions.append((self.x, self.y, self.colors[self.color_index]))
            
            # 次の色に進む(色リストの範囲を超えたら最初に戻る)
            self.color_index += 1
            if self.color_index > COLOR_END - COLOR_START:
                self.color_index = 0
            
            # 現在の方向に1マス進む(SIZE分だけ移動)
            self.x += self.dx * SIZE
            self.y += self.dy * SIZE
            
            # 現在のセグメント内での進行カウントを増やす
            self.segment_passed += 1
            
            # 配置した正方形の総数をカウント
            self.set_count += 1
            
            # 現在のセグメントを完了したら方向転換
            if self.segment_passed == self.segment_length:
                # セグメント内カウンターをリセット
                self.segment_passed = 0
                
                # セグメント完了回数を増やす
                self.segment_count += 1
                
                # 時計回りに90度回転(右→下→左→上→右...)
                # (dx, dy) を (-dy, dx) に変換することで90度回転
                # 例: (1,0)→(0,1)→(-1,0)→(0,-1)→(1,0)
                self.dx, self.dy = -self.dy, self.dx
                
                # 2回方向転換するごとにセグメント長を1増やす
                # 螺旋パターン: 1右→1下→2左→2上→3右→3下→...
                if self.segment_count % 2 == 0:
                    self.segment_length += 1
    
    def draw(self):
        """
        フレームごとの描画処理
        画面をクリアして現在配置されている全ての正方形を描画
        """
        # 画面を黒(色番号0)でクリア
        pyxel.cls(0)
        
        # これまでに配置された全ての正方形を描画
        for px, py, c in self.positions:
            # 各正方形を指定位置・サイズ・色で描画
            # rect(x, y, width, height, color)
            pyxel.rect(px, py, SIZE, SIZE, c)

# スクリプトとして実行された場合のみインスタンスを生成
if __name__ == "__main__":
    SpiralSquares()

リアルタイム生成: update()内で毎フレーム1個ずつ正方形を追加

  • アニメーション効果: 螺旋が中心から外側に向かって徐々に描かれる様子が見える
  • リセット機能: Rキーで全ての状態を初期化して最初からアニメーションを再生

螺旋生成のアルゴリズム

  • 時計回りの螺旋: 右→下→左→上の順で方向転換
  • セグメント長の規則: 1, 1, 2, 2, 3, 3, 4, 4, ... と増加
  • 色のサイクル: 15色(1〜15)を順番に使用し、繰り返す

配置数の計算

  • 2209 = 47 × 47: 画面サイズ(640×480)をマスサイズ(10×10)で割った値の近似
  • 十分に画面を埋め尽くすための正方形数を確保
    pyxel-20251201-201545.gif

スクリーンショット 2025-12-01 202023.png

2. set_countが素数の時、正方形をsetしていく

import pyxel

# 色範囲の定義(Pyxelの16色パレットから使用する範囲)
COLOR_START = 1  # 開始色番号
COLOR_END = 15   # 終了色番号
SIZE = 10        # 各正方形のサイズ(ピクセル)
WIDTH = 640      # 画面幅
HEIGHT = 480     # 画面高さ

class SpiralSquares:
    def __init__(self):
        # Pyxelの初期化(画面サイズとタイトルを設定)
        pyxel.init(WIDTH, HEIGHT, title="Spiral Squares")
        
        # 使用する色のリストを生成(COLOR_STARTからCOLOR_ENDまで)
        self.colors = [i for i in range(COLOR_START, COLOR_END + 1)]
        
        # 正方形の位置と色を格納するリスト(各要素は(x, y, color)のタプル)
        # 素数番目の位置にある正方形のみ記録される
        self.positions = []
        
        # 螺旋の中心座標(画面中央)
        self.cx = WIDTH // 2
        self.cy = HEIGHT // 2
        
        # 現在の描画位置(中心からスタート)
        self.x = self.cx
        self.y = self.cy
        
        # 移動方向ベクトル(最初は右方向)
        self.dx = 1   # x方向の移動量(正:右、負:左)
        self.dy = 0   # y方向の移動量(正:下、負:上)
        
        # 螺旋のセグメント管理変数
        self.segment_length = 1    # 現在のセグメントの長さ(何マス進むか)
        self.segment_passed = 0    # 現在のセグメント内で進んだマス数
        self.segment_count = 0     # 完了したセグメント数(方向転換の回数)
        
        # 色の管理変数
        self.color_index = 0       # 現在使用している色のインデックス
        
        # 螺旋上の位置カウンター(1から開始、素数判定に使用)
        # 1番目、2番目、3番目...という螺旋上の順序を表す
        self.set_count = 1
        
        # メインループを開始(updateとdrawを毎フレーム呼び出す)
        pyxel.run(self.update, self.draw)
    
    def is_prime(self, n):
        """
        素数判定関数
        引数nが素数かどうかを判定する
        
        アルゴリズム:
        1. 1以下は素数ではない
        2. 2は素数
        3. 偶数は素数ではない(2を除く)
        4. 3以降の奇数で割り切れるかチェック(√nまで)
        
        計算量: O(√n)
        """
        # 1以下は素数ではない
        if n <= 1:
            return False
        
        # 2は最小の素数
        if n == 2:
            return True
        
        # 2以外の偶数は素数ではない
        if n % 2 == 0:
            return False
        
        # 3から√nまでの奇数で割り切れるかチェック
        # i * i <= n は i <= √n と同じ意味(浮動小数点演算を避けるため)
        i = 3
        while i * i <= n:
            # nがiで割り切れたら素数ではない
            if n % i == 0:
                return False
            # 次の奇数をチェック(偶数は既に除外済み)
            i += 2
        
        # どの数でも割り切れなかったら素数
        return True
    
    def update(self):
        """
        フレームごとの更新処理
        Rキーでリセット、毎フレーム螺旋上を1マス進み、素数番目のみ正方形を配置
        """
        # Rキーが押されたら全ての状態を初期化してリセット
        if pyxel.btnp(pyxel.KEY_R):
            # 描画位置を中心に戻す
            self.x = self.cx
            self.y = self.cy
            
            # 移動方向を右にリセット
            self.dx = 1
            self.dy = 0
            
            # セグメント管理変数をリセット
            self.segment_length = 1
            self.segment_passed = 0
            self.segment_count = 0
            
            # 色インデックスをリセット
            self.color_index = 0
            
            # 配置済みの正方形をクリア
            self.positions = []
            
            # カウンターを1にリセット
            self.set_count = 1
        
        # まだ目標数に達していない場合、毎フレーム螺旋上を1マス進む
        # 2209 = 47×47(画面を埋め尽くすのに十分な数)
        if self.set_count < 2209:
            # 現在の位置番号が素数かどうかをチェック
            # 素数番目の位置にのみ正方形を配置(素数の視覚化)
            if self.is_prime(self.set_count):
                # 素数番目の位置に正方形を配置(座標と色を記録)
                self.positions.append((self.x, self.y, self.colors[self.color_index]))
                
                # 次の色に進む(色リストの範囲を超えたら最初に戻る)
                self.color_index += 1
                if self.color_index > COLOR_END - COLOR_START:
                    self.color_index = 0
            
            # 素数かどうかに関わらず、螺旋上を1マス進む
            # 現在の方向に1マス移動(SIZE分だけ移動)
            self.x += self.dx * SIZE
            self.y += self.dy * SIZE
            
            # 現在のセグメント内での進行カウントを増やす
            self.segment_passed += 1
            
            # 螺旋上の位置番号を増やす(次のフレームで素数判定に使用)
            self.set_count += 1
            
            # 現在のセグメントを完了したら方向転換
            if self.segment_passed == self.segment_length:
                # セグメント内カウンターをリセット
                self.segment_passed = 0
                
                # セグメント完了回数を増やす
                self.segment_count += 1
                
                # 時計回りに90度回転(右→下→左→上→右...)
                # (dx, dy) を (-dy, dx) に変換することで90度回転
                # 例: (1,0)→(0,1)→(-1,0)→(0,-1)→(1,0)
                self.dx, self.dy = -self.dy, self.dx
                
                # 2回方向転換するごとにセグメント長を1増やす
                # 螺旋パターン: 1右→1下→2左→2上→3右→3下→...
                if self.segment_count % 2 == 0:
                    self.segment_length += 1
    
    def draw(self):
        """
        フレームごとの描画処理
        画面をクリアして素数番目に配置された全ての正方形を描画
        素数の分布パターンを視覚的に表現
        """
        # 画面を黒(色番号0)でクリア
        pyxel.cls(0)
        
        # 素数番目の位置に配置された全ての正方形を描画
        for px, py, c in self.positions:
            # 各正方形を指定位置・サイズ・色で描画
            # rect(x, y, width, height, color)
            pyxel.rect(px, py, SIZE, SIZE, c)

# スクリプトとして実行された場合のみインスタンスを生成
if __name__ == "__main__":
    SpiralSquares()

素数の視覚化(ウラムの螺旋)

  • このプログラムは「ウラムの螺旋(Ulam Spiral)」として知られる数学的パターンを実装しています。

螺旋上の番号付け: 中心から1, 2, 3, 4, ...と順番に番号を振る

  • 素数のみ表示: 素数番目の位置(2, 3, 5, 7, 11, 13, ...)にのみ正方形を配置
  • パターンの出現: 素数が対角線状に並ぶ興味深いパターンが視覚化される

素数判定アルゴリズム

  • 試し割り法: 3から√nまでの奇数で割り切れるかチェック
    最適化:
  • 偶数は事前に除外(2以外の偶数は素数ではない)
  • √nまでしかチェックしない(それ以上は不要)
  • 奇数のみチェック(i += 2で2ずつ増やす)
    スクリーンショット 2025-12-01 203214.png

pyxel-20251201-203402.gif

正方形4にして、if self.set_count < 14161:
スクリーンショット 2025-12-01 210410.png

いろいろ、試してみるですね。ありがとうございます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?