プログラミングや低レイヤで遊ぶ人

基本的には遊んだことを記事にしていく

CSS職人 入門

はじめに

はじめまして、jumdtwです。
ノリで始めたCSS職人について書いていこうと思います。

ソースコード

きっかけ

1, メジロマックイーンを崇拝する
2, マックイーンの絵でも描こうとググる
3, youtubeで戦場ヶ原さんをCSSで書いているやばい人を見かける
4, ノリで始めてしまう

といった感じです。ちなみに、マックイーンを描くのに40時間以上かかっているので皆さんはおとなしくペンタブ買ったほうが賢明だと思います。

完成品

完成品はこちらになります。
f:id:jumdtw:20211218004238j:plain
ウマ娘のゲーム内イラストを参考に作成しました。

CSS 職人基本テクニック

ここからはCSS職人の基本テクニックを紹介していきます。マックイーンも下記の基本テクニックで99%記述しています。

overflow: hidden

overflow: hidden は下記図のように親要素からはみ出した子要素を表示しなくなるといったものです。
f:id:jumdtw:20211218004241j:plain これとborderなどを利用して線画を仕上げていきます。

box-shadow

上記 overflowとborderだけでもかけなくはないのですがとがっている部分や大きな楕円部分は書きにくかったり塗りにくかったりします。そこでbox-shadowを利用します。box-shadowを利用することで尖っている表現をきれいに書くことができます。
f:id:jumdtw:20211218004235j:plain

余談

正直CSSで描くメリットがあまり多くないというは事実。CSSを理解している人からすると努力がなんとなく伝わるとは思うのだが、多くの人はCSSを知らないので見た目上は変わらない。

まとめ

みんなもCSS職人になろう!!!

golangでRPCをやる

 

2021-1-7 @jumdtw
01そもそもRPCってなんぞや
そもそもRPCがよくわかってない。Wiki先輩いわく、 Remote procedure call 遠隔手続き呼出し(RPC、リモートプロシージャコール)のこと。 らしい。
e-Wordsさんでは、RPCとは、あるコンピュータで動作するソフトウェアから、 通信回線やコンピュータネットワークを通じて別のコンピュータ上で動作するソフトウェアへ処理を依頼したり、結果を返したりするための規約。と言っている。
要するに、ローカルで処理する代わりにサーバー使って処理を行うためのプロトコルらしい。これ何に使うの?って思ったが、Wikiを読んでみると、元々は 分散コンピューティングのために使われてたっぽい?現代ではどのように使うのかはよくわからなかった。
02RPCをやる
rpcパッケージの使い方はこちらに書いてあった。 逆にこれ以外に記事がそこまで見つからなかった。
ちなみに、RPCのうちJSONを利用したJSON-RPCというものがあるらしい。 こちらもGolangで使用可能とのことで調べたがよくわからなかった。 というかrpcパッケージ自体がもう新しい機能を受け付けていないらしい。やっぱりもう誰も使わないのかな?
03うごかしてゆく~
以下のようなコードを書いた。また、実行結果も以下に示す。ほとんど先ほどのリンクの引用であるが
mario
遠隔で実行させる関数の定義とそれらに使用する変数の定義
mario
serverの起動とクライアントからのリクエスト処理を行っているmain関数
mario
実行結果
無事想定した結果がかえってきた。だがもちろんサーバーを経由してる分実行が遅いわけでこんな簡単な計算をサーバーに経由しても無意味。 あと、このコード書いててやっと理解したのだが、これクライアント側で定義した関数実行している。てっきり、サーバーにおいてある関数を実行するもんだと思ってた。 WEB APIはこれの部類に入るのか????
参考文献
・GOでわかるシステムプログラミング

2021年現在 恐竜キング 7つのかけらでメガラプトルを入手する方法

 

2021-1-3 @jumdtw
01恐竜キングっていいよな
私は恐竜キングが泣くほど好きです。かつて小学生の時にはお年玉をこれでもかというほど溶かしました。
そんでもってそんな恐竜キング唯一のゲームがDSから出ているのですが、これが曲者(「恐竜キング 七つのかけら」で検索検索~。以降「本ゲーム」とします)。 ゲーム自体の面白さはいいんですが、DS時代によくあった「nintendo wifiコネクション~」、「会場で通信すると~」がいっぱい詰まっている。 これのせいでまず、図鑑のコンプリートが不可能だったりします。
mario
こいつのせいで図鑑が完成しない
上記画像の子が図鑑が完成しない要因の恐竜君です。メガラプトルといいます。本ゲームにおける恐竜の入手方法は基本的に化石の採取を行い、その化石を復元する(一部例外)という やり方なのですが、この子は特殊。この子の入手条件はnintendo wifiコネクションに50回?接続することで入手可能とのことです。しかしそのnintendo wifiコネクションが2014/5/20にサービス終了してしまったのです。
てなわけで通常の方法では入手不可能になったとさ。
02Wiimmfi、それは神
そんでもってそんな中メガラプトルを入手する方法として有志が再現したnintendo wifiコネクションであるWiimmfiに接続すること。
一応公式曰く本ゲームはテスト段階の状態らしい。でも、メガラプトルの入手条件はあくまで「nintendo wifiコネクションへの接続」 のようで私の環境では問題なく入手ができた。
03メガラプトルをとったど~
で、nintendo wifiコネクションへの接続方法は、こちらの動画が参考になった。 私の環境ではWEPで接続しないとダメというエラーが出たので対処した。
そんでもってひたすら接続しまくった結果。。。
mario
やったぜ
やったぜ。ちなみに、ネットの記事の多くにはコダイマンに話かけると入手できると書いてあったが、私の場合は自動的に恐竜ルームに保管されていた。
解析してオッケーなら自分で解析してディノニクス入手したひな~

Go言語をwindows10でもデバッグしたい

 

2020-12-31 @jumdtw
Go言語を普段使用しているWindows10の環境でデバッグしたかったので検索したのだが、なぜかMac環境での情報しかでない。 なので自分の環境でできるようにした記録を書く。※まぁ、正直Macと変わらな(ry
01vscodeをつかってゆく~
普段使っているエディタがvscodeなので、vscode上でデバッグをしたい。 Go関連の拡張機能としてはGo Team at Googleだけしか入れていない。
02lanch.jsonがわからん
デバッグを試しにしてみたら以下のようになった。
mario
なんかlanch.json作れみたいに言われる
どうやらlanch.jsonとやらが必要らしい。見たことはあるけど、実際よくわからん。
これによるとvscodeデバッグをする際の設定ファイルらしい。まぁ、でしょうねって感じではあるが。
まぁ、とりま必要そうだったので以下のように記述した。というかvscodeが生成してくれた。
mario
vscodeが生成してくれたlanch.json
03試しにfmt.Printをデバッグしていく
元々の目的としてシステムコールを追いたかったのでこの環境構築を始めたので、試しにprint関数を追ってみる。以下のGo言語 tutorialから入手できる コードでprint関数にブレークポイントを設定し、実行を行う。
mario
hello, world的な簡単なコード
mario
fmt.Printlnの先
Println関数がFprintlnに標準出力とPrintlnで渡した引数を渡している。os.Stdoutはio.Writerインターフェイスを満たす 構造体の用だけど、引数のinterface{}ってなんぞや。。。
どうやらGo言語にはinterface{}型って変数の型があるらしい。あらゆる変数の型を格納できるらしい。ヤリマ(ry
まぁ、Println関数には文字列やらint型の変数やらが来るわけだからinterface{}で対応しているのね。ちなみに、その後のinterface{}型の変数をデバッガで追っていったら 以下のようなコードがあった。
mario
switch-case
ここで変数の型をある程度整理していい感じにいい感じしているっぽいですね(適当)。この辺のシステムコールの仕組みとかGo言語だと簡単に調べられるので 低レイヤの勉強には向いてるのかも?

cron で sudo がしたい

 

2020-10-8 @jumdtw
研究用でサーバーに色々なことを定期実行させるために cron を使っていたが sudo をしてくれなかった。 なので visudo をつかって cron でも sudo を使えるようにした。
01cron とは
UNIX 系の OS にはいっていてプログラムを定期実行しれくれるもの。crontab コマンドを使って設定とかをする。以下みたいな設定ファイルを書いて時間を指定する。ここでは、cron 自体の説明はあまりしないので悪しからず。
# 分 時 日 月 曜日 <CMD0>
30 3 * * * <CMD1>
0 5 * * * <CMD2>
ちなみに、指定した時間に PC の電源が切れていた場合は実行されず、その後に電源がついても指定した時間にならなければ実行はされない
02sudo があると実行されない問題
仮に以下のような設定をしたとする。
# 分 時 日 月 曜日 <CMD0>
n h * * * ~/hoge.sh
そんでもって hoge.sh の中身が以下のような感じ。
sudo touch fuga
別に実行するコマンドはなんでもいいのだが、とりあえず一例として touch コマンドとした。なぜかこれがデフォルトでは動かない。
03解決方法(やめたほうがいい)
とにかく解決したかった私はあまりいい方法ではない visudo コマンドを使用して、特定のコマンドを sudo を使用して実行したときにパスワードを必要としないように設定した。
user ALL=NOPASSWD: touch
やばみ。絶対やめたほうがいい。これをやると一部コマンドが root 権限でできてしまうのでとても危険。
03解決方法・改
じゃあどうするのかということだが、sudo を使用するようなものは root に実行させればいい。
root権限に以降
$ sudo su
root権限でcronを使用したことない場合は以下コマンドで設定ファイルを作る
# crontab -u root -e
記述した設定ファイルを読み込ませる
# crontab cron.conf
cron.confと使用するスクリプトは以下のような感じ。root 権限で行うため sudo は必要なくなる。また、先ほどの例では相対パスで書いていたが、root になるとホームディレクトリも変わるので基本的には絶対パスで書いた方がいい。
cron.conf

n h dom mon dow /home/user/test.sh
test.sh

sudo touch fuga
私の環境ではこれで sudo を必要とするスクリプト・コマンドを cron で動かすことができた。ちなみに、 cron はデフォルトではログをはかないので以下のように /etc/rsyslog.d/50-default.conf を書き換えないといけない。
/etc/rsyslog.d/50-default.conf

以下をコメントから除外
cron.* /var/log/cron.log
これで /var/log/cron.log としてログが出る。でも、どうやら cron で設定したスクリプト・コマンドをリダイレクトしてそれぞれのログを生成した方がいいらしい?

ファミコンのわかりづらい仕様について

筆者がweb初心者のくせに調子のって記事書いているので、レイアウトの崩れがあるかもしれませんがお許しください。
スーパーマリオブラザーズを動かせるレベルのファミコンエミュレータもどきをつくったが、やばみがやばみだった。なぜgolangで初めてしまったのか。 終わった後なので笑い話で済むが個人的にグラフィカルなものは golangではおすすめしない。javascriptあたりがやりやすいかな?
作るにあたってファミコンの仕様を色々調べたわけだが、紛らわしいものが多かったのでいくつかをここに書いていく(単純に私が知識不足の人間というのもある)。
それだけだとスーパーマイナー記事になるので、最後に面白いスーパーマリオブラザースの小ネタを載せました。
ファミコンの仕様に興味がない方はそちらをどうぞ。

01Sub/Cmp命令について

sub命令とcmp命令は内部的な計算は同じで、減算を行いその結果をファミコンのステータスレジスタに書き込みます(キャリーだったり、オバーフローだったりの結果を保存するレジスタ)。 sub命令で変換されるフラッグは、キャリー、オバーフロー、ゼロ、ネガティブの四つです。cmp命令ではオーバーフローは更新されません。 sub命令は計算の結果を保存しますが、cmp命令は結果は虚無へ捨てます。まぁ、それだけの大したことないよくある命令です。
問題はキャリーとオバーフローのタイミング
(正直オーバーフローはあまりファミコンでは使われないから気にしなくていいらしいが)
ファミコンではsub,cmpでの減算をおこなう際、
  1. Aレジスタからメモリまたは即値を減算する
  2. キャリーフラッグを反転したものを減算する
といった手順で計算する。
これら手順が終了した際に四つのフラッグを変更するものだと思ってました。
勘違いしていた間違っている仕様
  1. Aレジスタからメモリまたは即値を減算する
  2. キャリーフラッグを反転したものを減算する
  3. 計算結果に応じて4つの各フラッグを書き換える
しかし、実際の仕様は以下でした。
実際の仕様
  1. Aレジスタからメモリまたは即値を減算する
  2. キャリー/オバーフローフラッグを更新する
  3. キャリーフラッグを反転したものを減算する
  4. 計算結果に応じてゼロ/ネガティブフラッグを更新する
まさかのタイミングが違う。これが違うとスーパーマリオブラザースでは正しいタイミングで敵が現れてくれない。
mario
メニュー画面に乱入してしまうクリボー
これに関してはsub命令、cmp命令が間違っていることさえわかってしまえば気づくことができる。ファミコンにはnestestromという便利なものがあり、それで簡単にわかる。 逆に言えばnestestromの存在を知らないとかなりきつい。

02$2007のreadについて

この問題はソフトウェアだから発生する問題な気がする。
ファミコンはメモリマップトioなので、PPUのメモリを読み取る時は$2007番地を利用するのだが、ここにトラップがひそんでいる。 読み込む際、一回目の読み込みはバッファの影響で読み込まれない。なので、$2006番地(PPUの読み込みする番地を指定するメモリ)を更新した後、 一回読み込みをスルーしなければならない。これを知らないとCPUは正しく動作しているのに、うまく動作しないという地獄が始まる。
これはアセンブラを読んでいると気づく問題な気がする。明らかに無駄に$2007を読み込んでいる箇所が見つかるのでそれで気づくことができる。機械語と対話せよ。

03Zero Page IndexX

トラップ。完全なトラップである。ファミコンのアドレス指定には Zero Page Index X なるものがある。これは、オペランドの値とXレジスタの値を足した結果をアドレスとして使うものだ。
で、これの問題が、
addr(8bit) = oprand(8bit) + Xregister(8bit)
という計算になることだ。
つまり、計算であふれた値は切られるということだ。ファミコンのアドレスは16bitなので、これに気づかないと意外とはまる。
でも、冷静に考えるとファミコンは加算減算などの計算を基本8bitでおこなっているということを考えるとすぐに気づける問題でもある。つまりは、みんなもNesDevよく読もう(私もあまり読んでいない)。

04Scroll指定について

完全なト(ry。ファミコンは$2005番地にxとyの値を二回書き込むことで画面をスクロールすることができる。このあたりの詳しい説明は他にゆずるが問題はそのスクロールの基準点である。
メモリの$2000番地を使うと描画の基準点を設定できるのだが、PPUメモリの$2000を基準と設定すると画像の青い点の部分が描画の基準となる。
mario
ファミコンの描画されていない部分を含むゲーム画面
そしてこの基準点に対し例えば、x+10,y+10 と指定すると以下のようになる(画像は正確にx+10,y+10わけではなく大体だと思っていください)。青枠の 部分が描画されていると思ってください。
mario
基準点からxy+10の部分の描画
これはまぁ、わかりやすいと思います。これが基準の位置が変わるとこうなります。
mario
別の基準点からxy+10の部分の描画
ファミコン関連の知識を初見で見ている人は、当り前じゃね?と思うだろうが、この情報どこにも載ってない。 いやまぁ、ファミコンエミュレータを作ってる人はたくさんいるので、知っている人もたくさんいるだろうがこの手の情報はなぜか少ない。 これを知らないとスクロールの基準点は常に左上だと勘違いする。ちなみに、私はこれのせいで二日溶けた。

05まとめ

とてもやりがいがあった。なによりファミコンだとグラフィカルでうまくいくと動きがでて面白かった。まぁただかなり大変ではあった。やろうと思っている人はそれなりに時間がかかると思ったほうがよい。

06小ネタ

ミラー表示

ファミコンはゲームROMの情報量削減のためにスプライトなどをミラーを使って少ない情報量で表現している。具体的には、マリオの体やクリボーだ。
mario
ミラーを実装していないため空中分解するマリオ
mario
ミラーを実装していないため美味しそうに割かれるクリボー
そのため、マリオやクリボーの体は左右対象でデザインされている。これのおかげで少しではあるがROMの容量を空けることができる。

草と雲

スーパーマリオブラザーズをよく見ると実は草と雲は全く同じ形で色が違うだけである。
mario
錬金された草と雲
これによって少ない量の情報量でより多くのものを表現することに成功している。これはどちらかというとデザインが優秀。

ファミコンエミュレータ作り おわり

FC emulator作り with golang

完成までのマラソン記事。がんばってやり切るぞい。

また、注意点としてこの記事はコードを更新しながら作成していっていくので記事で書いてある内容と実際のコードに差が生まれる可能性はあります。

コード本体    

お問い合わせは私のツイッターまでお願いします。@jumdtw

CPUからVRAMへの書き込み


これまでにCPUとPPUを作成したのでそれを連携させてPPUから画面に描画させます。

今回で目標は達成します!!

 CPUとPPUの連携


PPUの画面描画をEbitenというgolangで使えるゲームエンジンで実装しましたが、そのUpdate関数の中でCPUを動作させていきます。

ここでまずお伝えしたいこととしてCPUは直接的にVRAMに書き込みができません。0x2006番地と0x2007番地のメモリマップトioを使用してVRAMに書き込んで行きます。
そのためCPUのstore命令の実装を少し変えます。

// FC_CPU/emuCPU.go
type CpuEmu struct {

    //各割り込みを行うための情報
    Irqaddr uint16
    Nmiaddr uint16
    Resetaddr uint16
    InterruptFlag bool

    // vram へ書き込みを行うための内部情報 
    // vram addr
    VramAddr uint16
    // vram write flag
    VramWriteFlag bool
    // vram write value
    VramWriteValue uint8


    // A X Y S P
    Regi map[string]uint8
    // PC
    RegPc uint16
    // Memory
    Memory [memCap]uint8
}


// FC_CPU/opcmd.go
func (fcEmu *CpuEmu) staAbs() {
    var absposhigh uint16 = uint16(fcEmu.Memory[fcEmu.RegPc+1])
    var absposlow uint16 = uint16(fcEmu.Memory[fcEmu.RegPc])
    var pos uint16 = (absposhigh << 8) + absposlow
    // 0x2006だったらppuへのアクセス
    if pos == 0x2006 {
        fcEmu.VramAddr = fcEmu.VramAddr << 8
        fcEmu.VramAddr += uint16(fcEmu.Regi["A"])
    }
    if pos == 0x2007 {
        fcEmu.VramWriteFlag = true
        fcEmu.VramWriteValue = fcEmu.Regi["A"]
    }else {
        fcEmu.VramWriteFlag = false
    }
    fcEmu.Memory[pos] = fcEmu.Regi["A"]
    fcEmu.RegPc = fcEmu.RegPc + 2
}


まず、CPUの構造体に現在の状態を管理できるフラッグや変数を設置します。そしてstore命令でio制御用にマッピングされている領域に書き込みが行われた場合それ専用の処理を記述しています。今回はVRAMアクセス用のものしか実装していません。

これを実装し、画像データの入ったROMの中身を画面に描画するプログラムをCPUエミュレータに実行させていきます。(使っている画像データがデータだけにそのプログラムの配布ができません。申し訳ありません。)

CPUの実行タイミング


以下は前回作成したUpdate関数を改良したものです。

// FcEmulator.go
func (g *Game) Update(screen *ebiten.Image) error {
    // Generate the noise with random RGB values.
    var numblock int
    var numtile int
    var vv int
    for i :=0; i<30; i++ {
        for k :=0; k < 32; k++ {
            // vv は n枚目のタイルの一番左上のピクセルの最初の配列番号
            // k は一増えるごとに8pixl×ピクセル倍率分増える
            // i は一増えるごとに256×ピクセル倍率に8pixl×ピクセル倍率を掛ける
            // 最後に4を掛けることにより配列数を出す。
            vv = (k*8*Pixlsize+i*256*Pixlsize*8*Pixlsize)*4
            // patternNum がどのブロックにあるか
            // tileは32x30の半分なのでブロックは16x15.ナンバリングは0スタート
            numblock = Numblockreturn(i,k)
            numtile = k+i*32
            DrawTile(g,vv,numblock,numtile)
            // ppu はcpuの3倍のクロックを持っているのでppuの方が動作的には早いが
            // さすがにここで実行すると遅すぎるので将来的には修正が必要
            cpuexecute(g)
        }

    }

    return nil
}

cpuexecute(g)を実行することで一命令実行していきます。ただし、この位置での実行は実際のPPUとCPUの実行速度にかなりの差が出てしまうため将来的には修正が必要担ってきます。今回はこれでも目標に達成できるだけの実行速度には達しているのでこれで妥協します。

以下はcpuexecute(g)のソースコードです。

// FcEmulator.go
func cpuexecute(g *Game){

    // NMI割り込み
    // 多分本来のハードウェア動作だと次にこの動作処理が来るまでにCPU側で処理が終わっている。
    // でもこのエミュレータだと実質同期処理しているような状態なので処理が終わる前に再び
    // nmiaddrで割り込みが入ってしまう。そのため割り込み処理が終わるまでフラッグで判断して処理する。
    nmiflag := g.Cpuemu.Memory[0x2000] & 0b10000000
    if nmiflag == 0b10000000 && g.Cpuemu.InterruptFlag == false{
        // ステータスレジスタの変化
        g.Cpuemu.Regi["P"] = g.Cpuemu.Regi["P"] & 0b11101011
        g.Cpuemu.Regi["P"] = g.Cpuemu.Regi["P"] + 0b00010100

        // 割り込みフラッグ 割り込み中はtrue
        // 割り込み処理に入ったのでtrueにする
        g.Cpuemu.InterruptFlag = true

        // スタックに現在のPCを積む
        var sppos uint16 = 0x1000 + uint16(g.Cpuemu.Regi["S"])
        // 0x4050 だったら 50 40 の順でスタックに積む。個々の動作は等で確認が取れていないためこのエミュレータだけの可能性あり。
        lowaddr := g.Cpuemu.RegPc & 0x00ff
        highaddr := g.Cpuemu.RegPc & 0xff00
        highaddr = highaddr >> 8
        g.Cpuemu.Memory[sppos] = uint8(lowaddr)
        g.Cpuemu.Memory[sppos] = uint8(highaddr)
        g.Cpuemu.Regi["S"] = g.Cpuemu.Regi["S"] - 2

        // 割り込みアドレスの代入
        g.Cpuemu.RegPc = g.Cpuemu.Nmiaddr
    }

    g.Cpuemu.Execute()
    
    if g.Cpuemu.VramWriteFlag {
        g.Ppuemu.Memory[g.Cpuemu.VramAddr] = g.Cpuemu.VramWriteValue
        g.Cpuemu.VramAddr++
        g.Cpuemu.VramWriteFlag = false
    }
}

NMI割り込みとはハードウェアから入る強制的な割り込みです。ファミコンでは一定周期でPPUからCPUへこの割り込みが入ります。(CPU側の設定で遮断可能)
割り込みがあったら割り込み用のアドレスに書き換えます。
もし割り込みがなかったらそのまま命令を実行します。

実行結果


こんな感じになりました。前回と違ってちゃんとCPUから色情報も書き込んでいるので色塗りもいい感じになっています。
f:id:jumdtw:20200612221530j:plain
プログラムの内容自体はROMの中身をただただ描画するだけなのでこれ以上の動きは特にありません。

衝撃の事実


無事目標を達成してファミコンエミュレータの完成です!!!!!!!!!!!!

というのは幻想です

なぜならこのエミュレータは多大な問題を抱えているからです。


・そもそもマッパー0(拡張機能なしの純粋なファミコンのこと。つまりドラクエは無理)しか動かない
・スクロール機能がなくマリオのようなアクション系のゲームが動かない
・そもそもスプライト機能がなくマリオの描画ができない
・コントローラー?知らない子ですね

軽くあげるだけでこれだけありますね。特にスプライトとコントローラーが致命的です。
いやまぁ、実装しようと思えばできるのですが今回の目標は美少女の描画ということにしてしまったので。

やるとしたら番外編ということでまた記事自体は書くかもしれません。

まとめ


今回のエミュレータ作りを通して学べたことは以下です。

golangの標準パッケージのソースが具体的にどのように動いているのか
・並行処理の重要性とその実装
ファミコンのすごさ

個人的には並行処理が学べて楽しかったです。オライリーから書籍も出ているのでぜひ。

参考 

YY-CHR @wiki
電子書籍「ファミコンゲーム製作入門」(おまけ付き)
ギコ猫でもわかるファミコンプログラミング
Memory-Map of NES
ファミコンのグラフィックスの省メモリ化テクニックとは?
Go言語 - 複数ファイルのコンパイル
Golangで自分自身で定義したパッケージをインポートする方法あれこれ
配列と Slice
Go言語でバイナリファイルを読み込む