2026年9月10日木曜日

イベ終了

※画像がアップできないので仮アップ版
だいぶ前に書いた通り、今回は甲乙丁丁乙での攻略。しかもE5乙の攻略中に半月延長のアナウンスを見ると言う状況でした…

水着ラ級…上記の通りのクリア状況だったので、初めて見たのは最終日前日にE1-3を掘りに戻った時でした

いや、初めから2カ月と知っていればやりようはあったのですが…
と言う訳で報酬は…装備面はどうにもならなかったのですが獲れた艦はかなりの数になります…
新艦全てと前回イベントで獲れなかったグロリアス、ワスプ、樫。
それ以外は…

アイオワ(3隻目)リシュリュー(3隻目)メリーランド(2隻目)インディアナ(2隻目)
レキシントン(2隻目)イントレピット(2隻目)ビクトリアス(2隻目)アクィラ(2隻目)(3隻目)サラトガ(3隻目)ラングレイ(2隻目)
ヒューストン(6隻目)ポーラ(4隻目)
アブルッツィ(3隻目)ガルバルディ(3隻目)シェフィールド(2隻目)
冬月(2隻目)ジェイナス(2隻目)マエストラーレ(5隻目)(6隻目)夏雲(2隻目)
伊201(4隻目) 伊36(2隻目) 伊400(4隻目)(5隻目)伊14(3隻目)カッペリーニ(2隻目)

クリアしてから1カ月以上掘りだった事もあり、色が付いている艦は既に改になっています。
E3の周回が長かったせいかアメリカ艦の層がかなり厚くなっていますね…。
潜水艦は…本体より晴嵐狙いではありますが、それなりに大漁です。

欧州方面に関してはコレクションした艦が報われはしましたが…
育てた艦が報われたかと言いますと…


…うーん。
ちなみに、伊勢と日向は誤出撃、アイオワはクリア後のE3掘りに出していましたが2隻目のインディアナが出た事もあり直ぐに引っ込める事に。
これも、情報が早ければサブ以外の大和も出せたし陸奥や長門の出番はあったと思いますし、サブのみで組んで出したイギリス艦隊も本命を出せたのでは。

何年か前に書きましたが、ウチは南雲機動部隊の改二空母が5セット居ます。
そんなものがフル出場になるイベントは御免こうむりますが…出番があったのは改にもなっていない拾ったばかりの海外空母でしたね…

31戦隊はともかく夕雲型は推していた割に出番が…
はい、これくしょんは報われましたが、育成する意味があるのか?とは思いました。
通常海域でも4以下と5以上及びEO以上の海域では命中率/回避率があからさまに違います。
どんなにレベルを上げて当たらなければ意味がない/も食らえばアウトの世界で、命中/回避をテキトーに作ればどこまで行っても運ゲーです。
夜戦カットインに関してもLv157以降はパラメータが上昇しないと言われていますが、流石に育てる意味あるのかな?とは思いました。


情報の後出しは悪
資材が無かったのもそうですが、1カ月半で掘り艦6と聴けば低難易度でクリアして掘りに集中という人も一定数出るとは思うのですが…

後出し延長

いや、開始だって4日延長したし、延長を読めない方が悪いと言う話もあるのですが「地震を理由にイベントを半月延長」なんて物が読めるなら気象庁に勤めていると思いますよ。
前々から先行勢はかわいそうだと書いていましたが、自分が準先行ともなると本当に酷さを実感します。
日枝丸も行き当たりばったりな投入でしたし、こんな場当たり的なイベントに2か月以上もつき合った提督達は本当にお疲れ様です。
そして情報提供者潰し機能(高レベル提督が低難度海域に出撃できない期間の実装)だけきっちり実装した運営は何を考えているのやら。

今後は北上改三の投入だそうですが…
…古参提督狙い撃ちです。16年辺りにプレーしていた層はハイパーズを複数組作っていたりしますからね…以前の飛竜改三も同様で、蒼龍と共に江草/友永牧場で古い提督は複数改二を作っている可能性がありました。
ただ、そもそもが「お仕置きルート」常連の北上が改三になってもイベントで活躍できるかは謎です。あと、雷巡の中では恐らく木曽が改三になれないので置いていかれる事になりますね(むしろ阿武隈が雷巡になるかも)。

何を売りたいのやら
今回も投入した日本艦は小型艦ばかり、何年も前から在庫切れ感がでています。最近は「味方が増やせないなら敵を売ろう」というラ級を始めとした敵を売る流れで来ていますが…滑ってますよね。
毎回喋って演出出されても、下手すると3桁周回しないといけないゲーム性と致命的に合わない。
どうしても売り込みたいなら敵艦を図鑑化してそちらで閲覧させる方法を実装すれば良いと思うのですけどね。
早く攻略する攻略法に「音声を切ってセリフをスキップする」とか書かれている時点で売り方を間違えていると思います。

2026年9月9日水曜日

師匠の難題6

 前回ソースコード全文を載せたので、今回は差分のみ。

ソースここから

class LineBuffer:
    def __init__(self):
        self.l = 0 # left
        self.r = 0 # right

        # 始点
        self.src_sx = 0
        self.src_sy = 0
        
        # 終点
        self.src_tx = 0
        self.src_ty = 0

class RollBlt:
    
    def __init__(self):
        self.buff = []
        for i in range(SCREEN_SIZE_H):
            self.buff.append(LineBuffer())

        self.top = 0
        self.bottom = 0

        self.vbre = VBresenham()
        self.bre  = Bresenham()

    def setup(self):
        pass
        
    def blt(self,dest_x, dest_y, bank, src_x, src_y, src_w, src_h, angle):

        # 角度のまるめ
        angle &= 127

        # 原点から見た縦方向の端の座標
        tv_x = my_sin(angle,src_h-1)+dest_x
        tv_y = my_cos(angle,src_h-1)+dest_y

        # 原点から見た横方向の端の座標
        th_x = my_sin(angle+32,src_w-1)+dest_x
        th_y = my_cos(angle+32,src_w-1)+dest_y
        
        # 4頂点と比較し上下の座標を求める
        self.top = dest_y
        if tv_y < self.top:
            self.top = tv_y
        if th_y < self.top:
            self.top = th_y
        if (tv_y + th_y - dest_y) < self.top:
            self.top = tv_y + th_y - dest_y
        if self.top < 0:
            self.top = 0

        self.bottom = dest_y
        if tv_y > self.bottom:
            self.bottom = tv_y
        if th_y > self.bottom:
            self.bottom = th_y
        if (tv_y + th_y - dest_y) > self.bottom:
            self.bottom = tv_y + th_y - dest_y
        if self.bottom > (SCREEN_SIZE_H-1):
            self.bottom = SCREEN_SIZE_H-1

        # テーブル初期化
        for y in range (self.top, self.bottom+1):
            self.buff[y].l = EMPTY
            self.buff[y].r = EMPTY

        # 画像縦方向処理
        reg = self.vbre.setup(dest_x,dest_y,tv_x,tv_y)
        self.bre.setup(src_h-1,reg-1)

        match angle:
            # 垂直にコピーする時は縦方向の処理を行わない
            case a if a == 32 or a == 96:
                pass
                
            case a if a < 32:
                
                for i in range (reg):
                    x,y = self.vbre.step()
                    add_src_y = self.bre.step()
                    
                    if self.buff[y].l == EMPTY:
                        self.buff[y].l = x
                    pyxel.pset(x,y,6)

                    self.buff[y].src_sx = src_x
                    self.buff[y].src_sy = src_y + add_src_y
                    
                    x = x - dest_x + th_x
                    y = y - dest_y + th_y
                    
                    self.buff[y].r = x
                    pyxel.pset(x,y,6)

                    self.buff[y].src_tx = src_x + src_w-1
                    self.buff[y].src_ty = src_y + add_src_y

            case a if a < 64:

                for i in range (reg):
                    x,y = self.vbre.step()
                    add_src_y = self.bre.step()
                    
                    self.buff[y].r = x
                    pyxel.pset(x,y,6)

                    self.buff[y].src_tx = src_x
                    self.buff[y].src_ty = src_y + add_src_y
                    
                    x = x - dest_x + th_x
                    y = y - dest_y + th_y
                    
                    if self.buff[y].l == EMPTY:
                        self.buff[y].l = x
                    pyxel.pset(x,y,6)

                    self.buff[y].src_sx = src_x + src_w-1
                    self.buff[y].src_sy = src_y + add_src_y
            
            case a if a < 96:

                for i in range (reg):
                    x,y = self.vbre.step()
                    add_src_y = self.bre.step()
                    
                    if self.buff[y].r == EMPTY:
                        self.buff[y].r = x
                    pyxel.pset(x,y,6)

                    self.buff[y].src_tx = src_x
                    self.buff[y].src_ty = src_y + add_src_y
                    
                    x = x - dest_x + th_x
                    y = y - dest_y + th_y
                    
                    self.buff[y].l = x
                    pyxel.pset(x,y,6)

                    self.buff[y].src_sx = src_x + src_w-1
                    self.buff[y].src_sy = src_y + add_src_y

            case _:

                for i in range (reg):
                    x,y = self.vbre.step()
                    add_src_y = self.bre.step()
                
                    self.buff[y].l = x
                    pyxel.pset(x,y,6)

                    self.buff[y].src_sx = src_x
                    self.buff[y].src_sy = src_y + add_src_y

                    x = x - dest_x + th_x
                    y = y - dest_y + th_y
                    
                    if self.buff[y].r == EMPTY:
                        self.buff[y].r = x
                    pyxel.pset(x,y,6)

                    self.buff[y].src_tx = src_x + src_w-1
                    self.buff[y].src_ty = src_y + add_src_y

        # 画像横方向処理
        reg = self.vbre.setup(dest_x,dest_y,th_x,th_y)
        self.bre.setup(src_w-1,reg-1)

        match angle:
            # 水平にコピーする時は横方向の処理を行わない
            case a if angle == 0 or angle == 64:
                pass
                
            case a if a < 32:
                
                for i in range (reg):
                    x,y = self.vbre.step()
                    add_src_x = self.bre.step()
                    
                    if self.buff[y].l == EMPTY:
                        self.buff[y].l = x
                    pyxel.pset(x,y,6)

                    self.buff[y].src_sx = src_x + add_src_x
                    self.buff[y].src_sy = src_y
                    
                    x = x - dest_x + tv_x
                    y = y - dest_y + tv_y

                    self.buff[y].r = x
                    pyxel.pset(x,y,6)

                    self.buff[y].src_tx = src_x + add_src_x
                    self.buff[y].src_ty = src_y + src_h-1
                
            case a if a < 64:

                for i in range (reg):
                    x,y = self.vbre.step()
                    add_src_x = self.bre.step()
                
                    self.buff[y].l = x
                    pyxel.pset(x,y,6)

                    self.buff[y].src_sx = src_x + add_src_x
                    self.buff[y].src_sy = src_y
                    
                    x = x - dest_x + tv_x
                    y = y - dest_y + tv_y

                    if self.buff[y].r == EMPTY:
                        self.buff[y].r = x
                    pyxel.pset(x,y,6)

                    self.buff[y].src_tx = src_x + add_src_x
                    self.buff[y].src_ty = src_y + src_h-1

            case a if a < 96:

                for i in range (reg):
                    x,y = self.vbre.step()
                    add_src_x = self.bre.step()

                    if self.buff[y].r == EMPTY:
                        self.buff[y].r = x
                    pyxel.pset(x,y,6)

                    self.buff[y].src_tx = src_x + add_src_x
                    self.buff[y].src_ty = src_y
                    
                    x = x - dest_x + tv_x
                    y = y - dest_y + tv_y

                    self.buff[y].l = x
                    pyxel.pset(x,y,6)

                    self.buff[y].src_sx = src_x + add_src_x
                    self.buff[y].src_sy = src_y + src_h-1

            case _:
                
                for i in range (reg):
                    x,y = self.vbre.step()
                    add_src_x = self.bre.step()
                    
                    self.buff[y].r = x
                    pyxel.pset(x,y,6)

                    self.buff[y].src_tx = src_x + add_src_x
                    self.buff[y].src_ty = src_y
                    
                    x = x - dest_x + tv_x
                    y = y - dest_y + tv_y

                    if self.buff[y].l == EMPTY:
                        self.buff[y].l = x
                    pyxel.pset(x,y,6)

                    self.buff[y].src_sx = src_x + add_src_x
                    self.buff[y].src_sy = src_y + src_h-1

        # バッファを基に転送を行う
        for y in range (self.top, self.bottom+1):
            reg = self.vbre.setup(self.buff[y].src_sx,self.buff[y].src_sy,self.buff[y].src_tx,self.buff[y].src_ty)
            self.bre.setup(reg-1,self.buff[y].r - self.buff[y].l)
            
            sx,sy = self.vbre.step()
            old_src = 0
            new_src = 0

            for x in range (self.buff[y].l,self.buff[y].r+1):
                new_src = self.bre.step()
                for step_src in range (old_src, new_src):
                    sx,sy = self.vbre.step()
                old_src = new_src
                
                pyxel.blt(x, y, 0, sx, sy, 1, 1)

ソースココまで

書き換えたのは LineBuffer クラスと RollBlt クラスのみです。

LineBuffer の (sx,sy) (tx,ty) が前回の説明の「どこから、どこ」の座標に当たります。
そして、それらをバンクに格納する処理は各 case 文の中に追記しましたが…
それぞれ全てを解説するのは無理なので端折りますが、転送元画像とバンク内の画像の座標は図の通りになっている事を念頭に読んでいただけたらと思います。

Blt 関数の最後で転送を行っています

for step_src in range (old_src, new_src):

このループですが、あらかじめ

self.bre.setup(reg-1,self.buff[y].r - self.buff[y].l)

という初期化を行っています。
これは転送先の幅を主体に、転送元の幅の現在位置を返す為のブレゼンハムアルゴリズムの初期化であり、動きがなければ値が変化せず、したがって for 文内は実行されません。
逆に値が1以上増える事もあり、その分 self.vbre.step() が呼ばれて転送元の座標が動いていく仕組みです。

そして出力結果は…
うーん。微妙なような、30年前の回転ってこんなモンだったような…
久しぶりに昔の写真を観たら思い出より美しく無かった時のような微妙な気分になる出来ですね。


さて、今回で見た目上は完成したかに見えますが…
この転送、横に画面外に出る分には元の pyxel.blt 関数がなんとかしてくれるのですが、縦に画面外に出てしまうと、画面の縦幅分しかラインバッファを用意していない関係で index error を吐いて止まってしまいます。
あと、どうせここまでやったら拡大/縮小をふやしたり、回転の中心を画像の中心にする転送も行いたい所です。
と言う事で次回は細かな修正とまとめ、そして以前書いた「師匠の本当の課題」とは?という部分を書いて〆たいと思います。

2026年9月8日火曜日

師匠の難題5

前回の回転でモワレが出てしまう原因は何か?それは転送の方法です。

前回はこのように「横に読み込んで斜めに」転送したのですが…

正しくはこのように「斜めに読み込んで横に」転送する方法ならば隙間ができません。
以前「線」を描画するプログラムは書きましたので、画面の縦幅分のテーブル(広義のラインバッファ)を設けて、「転送先:左端、右端 転送元:どこから、どこ」のデータをため込んで、出力すれば可能なハズです。
ただ、この方法をいっきに作るのは大変なので、処理を分けて今回は「転送先:左端、右端」のバッファのみを作って「回転する長方形」を描画する処理を書いていきます。

さて、既に線描のプログラムは作っているので線描で回転する長方形を作るのは簡単に作れました。この点描した座標をテーブルに都度書き込んで、バッファの情報を元に左端から右端までの間を埋めていけば良い…

アレ?
これは先ほどの水色の線描処理の上からバッファに入った「左端/右端」を元に赤紫で塗りつぶしたのですが…なんかちらちら水色が見えてますね。
線描を拡大すると判るのですが、同一Y座標上でも複数のドットがあり、どちらの端のドットをバッファに積み込むか?を正しく選択しないとこうなります。
正しく積み込めばこの通りなのですが…

この正しい積み込み方法は360度法で90度おきに変わります、しかも縦軸と横軸で処理が違います…
と言う訳でソースコードを今回は全文載せてしまうとこんな感じ…

ソースここから

import pyxel

SCREEN_SIZE_W = 160
SCREEN_SIZE_H = 120
CENTER_X = 80
CENTER_Y = 60
EMPTY = -32767

my_table = (
    0,   6,  12,  18,  24,  31,  37,  43,  48,  54,  60,  65,  71,  76,  81,  85,
   90,  94,  98, 102, 106, 109, 112, 115, 118, 120, 122, 124, 125, 126, 127, 127,
  128, 127, 127, 126, 125, 124, 122, 120, 118, 115, 112, 109, 106, 102,  98,  94,
   90,  85,  81,  76,  71,  65,  60,  54,  48,  43,  37,  31,  24,  18,  12,   6,
    0,  -6, -12, -18, -24, -31, -37, -43, -48, -54, -60, -65, -71, -76, -81, -85,
  -90, -94, -98,-102,-106,-109,-112,-115,-118,-120,-122,-124,-125,-126,-127,-127,
 -128,-127,-127,-126,-125,-124,-122,-120,-118,-115,-112,-109,-106,-102, -98, -94,
  -90, -85, -81, -76, -71, -65, -60, -54, -48, -43, -37, -31, -24, -18, -12,  -6,
    0,   6,  12,  18,  24,  31,  37,  43,  48,  54,  60,  65,  71,  76,  81,  85,
   90,  94,  98, 102, 106, 109, 112, 115, 118, 120, 122, 124, 125, 126, 127, 127
   )

def my_sin(angle,r):
    angle &= 127
    return r * my_table[angle]>>7
    
def my_cos(angle,r):
    angle &= 127
    angle += 32
    return r * my_table[angle]>>7

class VBresenham:
    
    def __init__(self):
        self.l_side = 0
        self.s_side = 0
        
        # horizonatial side is longer
        self.b_long_hs = True

        # Direction (1 or -1)
        self.dct_x = 1
        self.dct_y = 1

        self.counter = 0
        self.adder_x = 0
        self.adder_y = 0
        
        self.start_x = 0
        self.start_y = 0

    # start x,y terminus x,y
    def setup(self,s_x,s_y,t_x,t_y):
        
        self.b_long_hs = True
        self.dct_x = 1
        self.dct_y = 1
        self.counter = 0
        self.adder_x = 0
        self.adder_y = 0

        self.start_x = s_x
        self.start_y = s_y
        
        if (s_x > t_x):
            self.l_side = s_x - t_x
            self.dct_x = -1
        else:
            self.l_side = t_x - s_x

        if (s_y > t_y):
            self.s_side = s_y - t_y
            self.dct_y = -1
        else:
            self.s_side = t_y - s_y
            
        if (self.s_side > self.l_side):
            # swap
            self.s_side,self.l_side = self.l_side,self.s_side
            self.b_long_hs = False

        self.l_side += 1
        self.s_side += 1
        return self.l_side

    def step(self):
        self.counter += self.s_side
        i = self.counter - self.l_side
        
        ret_x = self.start_x+self.adder_x
        ret_y = self.start_y+self.adder_y

        if self.b_long_hs:
            self.adder_x += self.dct_x
            if i >= 0:
                self.counter = i
                self.adder_y += self.dct_y
        else:
            self.adder_y += self.dct_y
            if i >= 0:
                self.counter = i
                self.adder_x += self.dct_x
                
        return ret_x,ret_y

class Bresenham:
    
    def __init__(self):
        self.l_side = 0
        self.s_side = 0
        self.counter = 0
        self.adder = 0
        
        # Src side is longer
        self.b_long_ss = True

    def setup(self,Dest_side,Src_side):
        self.counter = 0
        self.adder = 0
        
        if Dest_side > Src_side:
            self.b_long_ss = False
            self.l_side = Dest_side+1
            self.s_side = Src_side+1
            return self.l_side
            
        self.b_long_ss = True
        self.l_side = Src_side+1
        self.s_side = Dest_side+1
        return self.s_side
        
    def step(self):
        ret = self.adder
        
        if self.b_long_ss:
            self.counter += self.s_side
            i = self.counter - self.l_side
            if i >= 0:
                self.counter = i
                self.adder += 1
        else:
            while self.counter <= self.l_side:
                self.adder += 1
                self.counter += self.s_side
                
            self.counter -= self.l_side

        return ret

class LineBuffer:
    def __init__(self):
        self.l = 0 # left
        self.r = 0 # right

class RollBlt:
    
    def __init__(self):
        self.buff = []
        for i in range(SCREEN_SIZE_H):
            self.buff.append(LineBuffer())

        self.top = 0
        self.bottom =0

        self.vbre = VBresenham()
        self.vbre_v = VBresenham()
        self.vbre_h = VBresenham()

        self.bre_v = Bresenham()
        self.bre_h = Bresenham()

    def setup(self):
        pass
        
    def blt(self,dest_x, dest_y, bank, src_x, src_y, src_w, src_h, angle):

        # 角度のまるめ
        angle &= 127

        # 原点から見た縦方向の端の座標
        tv_x = my_sin(angle,src_h-1)+dest_x
        tv_y = my_cos(angle,src_h-1)+dest_y

        # 原点から見た横方向の端の座標
        th_x = my_sin(angle+32,src_w-1)+dest_x
        th_y = my_cos(angle+32,src_w-1)+dest_y
        
        # 4頂点と比較し上下の座標を求める
        self.top = dest_y
        if tv_y < self.top:
            self.top = tv_y
        if th_y < self.top:
            self.top = th_y
        if (tv_y + th_y - dest_y) < self.top:
            self.top = tv_y + th_y - dest_y
        if self.top < 0:
            self.top = 0

        self.bottom = dest_y
        if tv_y > self.bottom:
            self.bottom = tv_y
        if th_y > self.bottom:
            self.bottom = th_y
        if (tv_y + th_y - dest_y) > self.bottom:
            self.bottom = tv_y + th_y - dest_y
        if self.bottom > (SCREEN_SIZE_H-1):
            self.bottom = SCREEN_SIZE_H-1

        # テーブル初期化
        for y in range (self.top, self.bottom+1):
            self.buff[y].l = EMPTY
            self.buff[y].r = EMPTY

        # 画像縦方向処理
        reg = self.vbre.setup(dest_x,dest_y,tv_x,tv_y)
        
        match angle:
            # 垂直にコピーする時は縦方向の処理を行わない
            case a if a == 32 or a == 96:
                pass
                
            case a if a < 32:
                
                for i in range (reg):
                    x,y = self.vbre.step()

                    if self.buff[y].l == EMPTY:
                        self.buff[y].l = x
                    pyxel.pset(x,y,6)
                    
                    x = x - dest_x + th_x
                    y = y - dest_y + th_y
                    
                    self.buff[y].r = x
                    pyxel.pset(x,y,6)

            case a if a < 64:

                for i in range (reg):
                    x,y = self.vbre.step()
                    
                    self.buff[y].r = x
                    pyxel.pset(x,y,6)
                    
                    x = x - dest_x + th_x
                    y = y - dest_y + th_y
                    
                    if self.buff[y].l == EMPTY:
                        self.buff[y].l = x
                    pyxel.pset(x,y,6)
            
            case a if a < 96:

                for i in range (reg):
                    x,y = self.vbre.step()
                    
                    if self.buff[y].r == EMPTY:
                        self.buff[y].r = x
                    pyxel.pset(x,y,6)
                    
                    x = x - dest_x + th_x
                    y = y - dest_y + th_y
                    
                    self.buff[y].l = x
                    pyxel.pset(x,y,6)

            case _:

                for i in range (reg):
                    x,y = self.vbre.step()
                
                    self.buff[y].l = x
                    pyxel.pset(x,y,6)
                    
                    x = x - dest_x + th_x
                    y = y - dest_y + th_y
                    
                    if self.buff[y].r == EMPTY:
                        self.buff[y].r = x
                    pyxel.pset(x,y,6)

        # 画像横方向処理
        reg = self.vbre.setup(dest_x,dest_y,th_x,th_y)

        match angle:
            # 水平にコピーする時は横方向の処理を行わない
            case a if angle == 0 or angle == 64:
                pass
                
            case a if a < 32:
                
                for i in range (reg):
                    x,y = self.vbre.step()
                    
                    if self.buff[y].l == EMPTY:
                        self.buff[y].l = x
                    pyxel.pset(x,y,6)
                    
                    x = x - dest_x + tv_x
                    y = y - dest_y + tv_y

                    self.buff[y].r = x
                    pyxel.pset(x,y,6)
                
            case a if a < 64:

                for i in range (reg):
                    x,y = self.vbre.step()
                
                    self.buff[y].l = x
                    pyxel.pset(x,y,6)
                    
                    x = x - dest_x + tv_x
                    y = y - dest_y + tv_y

                    if self.buff[y].r == EMPTY:
                        self.buff[y].r = x
                    pyxel.pset(x,y,6)

            case a if a < 96:

                for i in range (reg):
                    x,y = self.vbre.step()

                    if self.buff[y].r == EMPTY:
                        self.buff[y].r = x
                    pyxel.pset(x,y,6)
                    
                    x = x - dest_x + tv_x
                    y = y - dest_y + tv_y

                    self.buff[y].l = x
                    pyxel.pset(x,y,6)

            case _:
                
                for i in range (reg):
                    x,y = self.vbre.step()
                    
                    self.buff[y].r = x
                    pyxel.pset(x,y,6)
                    
                    x = x - dest_x + tv_x
                    y = y - dest_y + tv_y

                    if self.buff[y].l == EMPTY:
                        self.buff[y].l = x
                    pyxel.pset(x,y,6)

        # テーブルを基に線描で塗りつぶし
        for y in range (self.top, self.bottom+1):
            pyxel.line(self.buff[y].l,y,self.buff[y].r,y,2)


class App:

    def __init__(self):
        # 画面サイズ 160x120 で初期化
        pyxel.init(SCREEN_SIZE_W, SCREEN_SIZE_H, title="RollBlt")
        self.add_x = 0
        pyxel.load("yajirushi.pyxres")

        self.rb = RollBlt()
        
        self.angle = 0

        pyxel.run(self.update, self.draw)

    def update(self):
        self.angle += 1
        self.angle &= 127

    def draw(self):
        # 画面を黒(色番号0)でクリア
        pyxel.cls(0)
        
        self.rb.blt(CENTER_X, CENTER_Y, 0, 0, 0, 32, 16, self.angle)

App()

ソースここまで

冗長。
解説すると、ラインバッファテーブルを作りたかったのですが、C言語のような構造体テーブルは作れない(NumPyライブラリを使うのは主題とずれるので…)ため、LineBuffer クラスを作り、これをノードとして使用します。
そして、今回から回転転送を扱う RollBlt クラスを新設してこちらで一括して処理する事になりました。
この為、メインの App クラスは角度の加算と RollBlt の blt 関数呼び出しのみを行う事になり、シンプルになったのですが…

全体の解説を言語化すると頭がパンクしそうなので端折りますが
この blt 関数の中に2か所ある match angle: ~ からが処理のメインとなります。
縦軸にしろ横軸にしろ、中心点から放射状に座標を求めています。この為、バッファに積み込むのは最初に求めたX座標か最後に求めたX座標となります。
最後に求めたX座標に関しては上書きを繰り返せば良いだけですが、最初に求めたX座標のみを積み込むには仕掛けが要ります。
これが

self.buff[y].l = EMPTY
self.buff[y].r = EMPTY

で行っている初期化で、バッファに EMPTY が入っていれば未入力と言う事で入力。以後入力しない…という処理を組んで最初に求めたX座標のみを積み込んでいます。
また、各軸で90度おき(128度法で32度おき)に処理を変えているのですが…図示しながら解説すると、長くなるので解説は省略します。

各 case の中に同じ for i in range (reg): ループが書かれていますが…
確かに match angle: 分岐の外に for 文を書けばソースコードは短くなります…が、実測はしていませんが match case 文が重い分岐処理である事は想像に難くないです。
その上で、毎回分岐結果が同じと判っている物をループ内で処理すると遅くなるので分岐の中にループを書いています。

毎度書かれている pyxel.pset(x,y,6)
これが水色の点を描画する処理で、 blt 関数末尾の塗りつぶし処理をコメントアウトすると水色の線描の長方形になります。
これはチェック用に書いた物であり、最初に示した通り処理が失敗している場合は端に水色が表示されるようになります。
最終的には全て削除する予定です。


…次で一応回転コピーが見た目上できるようになる…予定です
あくまで見た目上。このままでは実用に耐えませんし。そして、恐らく師匠が用意したであろう真の課題…も想像がつくのですが、そこまではやらないかもしれません。
正直、ここまででも相当ドはまりしてますので…ホントに大変な課題を出されたんだなあと実感します。

2026年8月31日月曜日

その後のメモリ玉突き移植

以前書いた、メモリの玉突き移植。色々と波及が有りまして。

Latitude5430
たしかにゲーミング性能は完全に期待外れだったわけですが、12世代 Core i5 マシンは流石に廃棄は惜しいので、メモリを32GBにして、Windows11 をインストール。Office 専用機になりました。
で、旧 Office 専用機がどんな奴かといえば…

YOGA310
Celeron の2コア2スレッド。当然 Windows11 の機能上の必要条件に合わず弾かれていました…
というか、こんな状況。
99%以上のリソースをMS社が使ってしまい、おこぼれを使わせてもらっているような状況。
なんせこの写真、リソース無さすぎてスクショが撮れなかったのでスマホで撮っています。

あと、なんだかヒンジ付近のケースがパカパカ浮いてるのですが…
開けてみたらケースのネジ受けがごっそり大破。
破片を集めて…
再構築。

ヒンジ付近はもう少し頑丈に作っても良いような…といっても、コレ元から廉価モデルですからねえ…

なんとか直したものの性能的に廃棄でも構わない気がしますが、Office マシンが安定してきたので、Linux でプログラミングマシンにしました。
もちろん LMDE7 がいくら軽いと言ってもフォルダを開いただけでももたついたりはしますが…
半分くらいはリソースに余裕が有ります。
※というか、 MS社がユーザーのリソースを勝手に使いすぎなんですよ…
プログラミングがおかしければ影響が出るレベルで弱いスペックなので、逆説的にプログラミングの練習用には良いのではないでしょうか?
最近書いている「師匠の難題」はコーディングから gif ファイルの撮影までこれ一台でこなしています。
プログラミングの訓練はロースペックマシンの方が良いってのは私の持論です…16ビットの時代に8ビット MSX2 からプログラマーはじめましたからね…。

Latitude7420
で、Latitude5430が Office マシンになってしまうと、去年の秋に買った11世代は浮きます。どうしようかと思って、久しぶりに引っ張り出したのですが…
以前より加水分解が進んでベタベタに。使い道がどうこう以前にこれでは使えない…

という事で、ダイソーで壁紙を買ってきまして…

ざっくりと形を合わせまして…

ヒンジ部分でアタリを付けまして…

はい。

まあ、正面はそこそこの出来ですが、側面は…。


そもそも、パソコンを載せられるような安定した作業台がアイロン台くらいしか無いのですよ私の部屋。そこを差し引いても不器用ですけどね。特に通風口は貼ったこと自体を後悔するレベルでした。
ピーチスキン加工が剥がれるという事態は以前 ThinkPad S30 で経験があるのですが、DELL の塗装能力の低さはちょっと異常です。
普通に購入したら保証期間内に塗装が溶けるのはどうかと思います。去年廃棄した EliteBook なんて15年近く稼働して塗装ハゲはなかったですよ(ただし、タッチパッドのボタンや、モニタと本体の間のゴムは溶けてましたが)。
で、せっかく直しましたがこの機体には特に振れる役割がないんですよね。
Windows いれっぱなしにして、Windows アプリのアクティベーションマシンにしますかね…滅多に起動しないからウィルス感染も漏洩も無いでしょうし。

最後は大元の EliteMini X500
以前もざっくり触れましたが、まずまずパワフルです。
セカンドドライブとして2.5インチ及び短い M.2 SATA が刺さるのですが、以前 NAS のドライブを組み替えた際のあまり SSD を載せました。
ただこれ、4個あったうち、1個は反応しないので廃棄、正常な2個は別のマシンに組み込み済み…で、これはエラーが出ていました。
結局90GB以上はだめになってしまいましたが、HDD と違って機械的に壊れているわけでもないですし、このまま使うとします。

で、以前のところに追記した通り、ゲームはそこそこ動きますが…やりたいことはそれじゃない。
BIOS だとただの VRAM16GB、メモリ48GBマシンになってしまいますので…。
で、VRAM とメインメモリを兼任できる ttm メモリを設定しようとして全くうまく行かなかったのですが…。
目標としたのは合計40GB。
tmm はデフォルトでメインメモリの50%が割り振られる。BIOS で割り振った VRAM は16GB。
※16+24GBがデフォルト値…
…積んだメモリは64GBなので…。あれ?デフォルト値が40GBじゃね?
そりゃあ、設定しても変化がない訳です。
と言う訳で、今後AI生成も覚えないと…いけないのですが、流石にキャパオーバー。ひとまずプログラムの方が一区切りするまで置いておく事にします。

師匠の難題4

いよいよ地獄の窯が開きます。これまでの中間総集編となる内容です。

1.画像回転を処理するにあたり、まず「何処に」転送するかから考えていきます
前回の線の描画ルーチンを元に、縦方向の転送先座標を順に計算していきます。
次いで、90度(128度法で32度)角度を進めて横方向の座標を求める計算を入れ子して求めていきます。

2.次に「どこから」転送するかを考えていきます
転送先が斜めになった際、縦幅にしろ横幅にしろ、元の幅より狭くなります。
つまり、そのまま転送するのではなく数ドット間引きして縮めて送る必要が出てきます。
第一回で行ったブレゼンハムアルゴリズムは「長辺を元に短辺の位置」を算出していましたが、
今回は逆に短い幅を元に長い幅(元画像の幅)を求めなくてはなりません。
このため、第一回で作ったアルゴリズムを主体が短辺の時も対応できるように改造します。

ソースここから

import pyxel

my_table = (
※第三回参照

def my_sin(angle,r):
※第三回参照
    
def my_cos(angle,r):
※第三回参照

class VBresenham:
※第二回参照

class Bresenham:
    
    def __init__(self):
        self.l_side = 0
        self.s_side = 0
        self.counter = 0
        self.adder = 0
        
        # Src side is longer
        self.b_long_ss = True

    def setup(self,Dest_side,Src_side):
        self.counter = 0
        self.adder = 0
        
        if Dest_side > Src_side:
            self.b_long_ss = False
            self.l_side = Dest_side+1
            self.s_side = Src_side+1
            return self.l_side
            
        self.b_long_ss = True
        self.l_side = Src_side+1
        self.s_side = Dest_side+1
        return self.s_side
        
    def step(self):
        ret = self.adder
        
        if self.b_long_ss:
            self.counter += self.s_side
            i = self.counter - self.l_side
            if i >= 0:
                self.counter = i
                self.adder += 1
        else:
            while self.counter <= self.l_side:
                self.adder += 1
                self.counter += self.s_side
                
            self.counter -= self.l_side

        return ret

class App:

    def __init__(self):
        # 画面サイズ 160x120 で初期化
        pyxel.init(160, 120, title="RollBlt")
        self.add_x = 0
        pyxel.load("yajirushi.pyxres")
        
        self.x = 0
        self.y = 0
        self.angle = 0

        self.vbre_v = VBresenham()
        self.vbre_h = VBresenham()

        self.bre_v = Bresenham()
        self.bre_h = Bresenham()

        pyxel.run(self.update, self.draw)

    def update(self):
        self.angle += 1
        self.angle &= 127
        
        self.x = my_sin(self.angle,15)+80
        self.y = my_cos(self.angle,15)+60

    def draw(self):
        # 画面を黒(色番号0)でクリア
        pyxel.cls(0)

        # 画像縦方向ループ
        reg_v = self.vbre_v.setup(80,60,self.x,self.y)

        self.bre_v.setup(15,reg_v-1)

        for i in range (reg_v):
            x_v,y_v = self.vbre_v.step()

            src_y = self.bre_v.step()
            
            # 画像横方向ループ
            x_h = my_sin(self.angle+32,31)+x_v
            y_h = my_cos(self.angle+32,31)+y_v
            reg_h = self.vbre_h.setup(x_v,y_v,x_h,y_h)
            
            self.bre_h.setup(31,reg_h-1)
            
            for j in range (reg_h):
                x,y = self.vbre_h.step()
                
                src_x = self.bre_h.step()
            
                pyxel.blt(x, y, 0, src_x, src_y, 1, 1)

App()


ソースここまで

※紫色にしている部分が短辺主体に追加した部分

※追記
class Bresenham: 関係のプログラムを改修…
ブレゼンハムアルゴリズムで2辺のどちらを主体にどちらを求める~なのですが、
setup 関数の入力値を Dest_side,Src_side としています。

Dest : Destination :出力先
Src    : Source :入力

の略で、プログラムの引数の慣例で Dest Src の順で記述したのですが…
一般的には出力先(Dest)が主体ですが、この場合は主体が入力(Src)では無いか?と言う事で書き直しました。

…で、ふと思ったのですよ、値の主体と言う事なら普通 Master と記述するよね?と。ただしそうなると対義語は Slave となります。
調べた所、2020年辺りから人権に配慮して Master/Slave の記述は廃される方向だとか。

コーディングにおいてコードの理解しやすさに優先するものがよもや人権とは…
時代は変わったなあと感じました。


そして出力
…はい、理屈の上でのアルゴリズムは間違っていません。ただ、ブレゼンハムアルゴリズムは本来アナログな直線を整数に区切るアルゴリズムです。
当然、ある程度のズレが生じます。45度付近になると顕著にズレるため、このような表示になります。
いよいよ地獄が見えてきました。

正直に言えばこうなる事は最初から解っていましたが、段階を踏むとはこう言う事です。
次は更に踏み込んでいくのですが…今回のプログラムだけでも半日かかっていまして…次からはほぼ未知の領域なので予定がたたないのですよね…。

※9/5追記
長辺から短辺を求める/短辺から長辺を求めるの両対応ブレゼンハムアルゴリズムを作ったと言う事は…ひょっとしてアレができるのでは?と試した結果…

はい、拡縮機能ですね、拡大/縮小共に動作しています。
私の世代だとファミコンからスーファミに変わった所謂16ビット戦争の時代を思い出しますが…もう40年近く昔の話です。当時は画期的だったんですけどねえ…。

2026年8月30日日曜日

師匠の難題3

今回は計算済み三角関数テーブルによる回転計算(?)です。

ソースここから

import pyxel

my_table = (0,6,12,18,24,31,37,43,48,54,60,65,71,76,81,85,
90,94,98,102,106,109,112,115,118,120,122,124,125,126,127,127,128)

def my_sin(angle,r):
    angle &= 127
    work = angle & 63
    if work > 32:
        work = 64 - work
    ret = (r * my_table[work]>>7)
    if angle > 64:
        ret *= -1
    return ret

def my_cos(angle,r):
    return my_sin(angle+32,r)

class VBresenham:
※第2回参照

class App:

    def __init__(self):
        # 画面サイズ 160x120 で初期化
        pyxel.init(160, 120, title="Line")
        
        self.x = 0
        self.y = 0
        self.angle = 0

        self.vbre = VBresenham()

        pyxel.run(self.update, self.draw)

    def update(self):
        self.angle += 1
        self.angle &= 127
        
        self.x = my_sin(self.angle,40)+80
        self.y = my_cos(self.angle,40)+60

    def draw(self):
        # 画面を黒(色番号0)でクリア
        pyxel.cls(0)

        reg = self.vbre.setup(80,60,self.x,self.y)

        for i in range (reg):
            x,y = self.vbre.step()
            pyxel.pset(x,y,6)

App()

ソースここまで

はい、360度法で言う所の0度~90度までを今回は128度法にするので0~32度までの33テーブルとし、単位長を128とするので128をかけた値をテーブル(今回はタプル)で用意(my_table)します。
尚、このテーブルを算出したプログラムは以下の通り。

ソースここから

import math

f : float = 0.0
for i in range (33):
    f = i/32.0
    print(str(int(128 * math.sin(f*math.pi/2.0)))+",",end="")

ソースここまで

サイン、コサインについてですが、360度法で90度分のデータがあれば360度分をカバーできます。
※説明は全て360度法

当然、メモリが許すならば全て計算済みのテーブルを用意する方が処理が短くて済むので高速になります。

ソースここから

my_table = (
    0,   6,  12,  18,  24,  31,  37,  43,  48,  54,  60,  65,  71,  76,  81,  85,
   90,  94,  98, 102, 106, 109, 112, 115, 118, 120, 122, 124, 125, 126, 127, 127,
  128, 127, 127, 126, 125, 124, 122, 120, 118, 115, 112, 109, 106, 102,  98,  94,
   90,  85,  81,  76,  71,  65,  60,  54,  48,  43,  37,  31,  24,  18,  12,   6,
    0,  -6, -12, -18, -24, -31, -37, -43, -48, -54, -60, -65, -71, -76, -81, -85,
  -90, -94, -98,-102,-106,-109,-112,-115,-118,-120,-122,-124,-125,-126,-127,-127,
 -128,-127,-127,-126,-125,-124,-122,-120,-118,-115,-112,-109,-106,-102, -98, -94,
  -90, -85, -81, -76, -71, -65, -60, -54, -48, -43, -37, -31, -24, -18, -12,  -6,
    0,   6,  12,  18,  24,  31,  37,  43,  48,  54,  60,  65,  71,  76,  81,  85,
   90,  94,  98, 102, 106, 109, 112, 115, 118, 120, 122, 124, 125, 126, 127, 127
   )

def my_sin(angle,r):
    angle &= 127
    return r * my_table[angle]>>7
    
def my_cos(angle,r):
    angle &= 127
    angle += 32
    return r * my_table[angle]>>7

ソースここまで

※出力結果は当然同じになります

…これをやって初めて気づいたのですが、Python の右ビットシフト処理って符号ありビットシフトだったんですね…

ただし、最初の短いテーブル版は0~128の正数であるため1バイトとなり、これを33テーブル用意しているので33バイトで済むのに対して、長いテーブル版は-128~128の符号付2バイトの上で128+32テーブルの320バイトを使います。
…正直この程度のバイト数で悩むのは8ビット機くらいだと思いますが、300バイトくらい在ればまともな処理が組める事と、33バイト程度ならキャッシュに置いておくという手もあるので悩む人も居た…かも。

なぜ単位長が128なのか?
単位長が127なら符号付でも1バイトですがなぜ128なのか?解る方は多いと思いますが一応解説。たとえば、10進数でnを100で割って余りを除いた答え(商)を求めよと言われた場合、まともに計算せずに末端2桁を削除しますよね。

6789÷100=?

6789→67

同様に128は2進数では

10000000b

となるので、ビットシフト演算を使って末端7桁を削除すると割り算の代わりになる為です。
…まあ、端数切り捨てになるので本来は上記のテーブルもあらかじめ四捨五入済みのテーブルを作る等の工夫が必要なのですが、今回はザックリ作っています。


反時計回りに回転していますが、コレは単にY軸が下がプラスという座標系な為です。

…さて、ここまでで準備が整いました。
次回から、コレのタイトルが課題でも宿題でも無く難題としている理由が牙をむく…予定です。
というか、ここからは本当に難しいので終るか判らないのですよ、なんせ20年近くほったらかしていて、これ以上放置していると墓場まで持っていきそうなので始めたくらいなので。