2007年7月20日金曜日

(X)HTMLコンバータ (2)

命令・コメント・文字列・プリプロセッサ・ラベルのタグ付けに成功しました。と思ったら命令のタグ付けに不具合があったようです。残念。
関数と変数の区別はデータベースを用いる必要がありそうなので、ここでひと段落です。

今回はHTMLタグを2つのモードで生成するモジュールを垂れ流し。
解析スクリプトはあまりにも長すぎるので、もう少し整理してから垂れ流したいと思います。

配色は一時的にle.Antillaのものをお借りしています。#enum global MODE_XHTML = 1
#enum global MODE_HTML

// パラメータ省略用マクロ
#define ctype aloneTag( %1, %2 = "" ) _aloneTag( %1, %2 )
#define ctype startTag( %1, %2 = "" ) _startTag( %1, %2 )

#module
#defcfunc _aloneTag str tagName, str className
    if ( replaceMode@ == MODE_XHTML ) {
        if ( className == "" ) {
            return "<" + tagName + " />"
        } else {
            return "<" + tagName + " class=\"" + className + "\" />"
        }
    } else {
        if ( className == "" ) {
            return "<" + tagName + ">"
        } else {
            return "<" + tagName + " class=\"" + className + "\">"
        }
    }

#defcfunc endTag str tagName
    return "</" + tagName + ">"

#defcfunc _startTag str tagName, str className
    if ( className == "" ) {
        return "<" + tagName + ">"
    } else {
        return "<" + tagName + " class=\"" + className + "\">"
    }
#global
/* モジュールここまで */

*start
    repeat 2
        if ( cnt == 0 ) {
            replaceMode = MODE_HTML
        } else {
            replaceMode = MODE_XHTML
        }
        ; (X)HTMLの出力
        mes startTag( "html" ) + startTag( "head" )
        mes "\t" + startTag( "title" ) + "HTML Sample" + endTag( "title" )
        mes endTag( "head" )
        mes startTag( "body" )
        mes "\t" + startTag( "p""class1" ) + "message test" + aloneTag( "br" ) + "\n\t2nd line" + endTag( "p" )
        mes endTag( "body" ) + endTag( "html" ) + "\n"
    loop
    stop

2007年7月18日水曜日

(X)HTMLコンバータ (1) 簡単なコンバータ

短期集中連載第1弾(?)、HSPスクリプトをXHTMLへコンバートするスクリプト。
HSP開発WikiのTABからスペース変換を参考にさせていただきました。kz3さんに感謝!

今回は動作イメージをつくるためのβ版です。命令や関数の色づけ、HSPスクリプトエディタ用外部ツール化などを目指します。
特にポイントと言える部分はありませんが、2バイト文字を判定するためにIsDBCSLeadByteEx()を利用していたり変わった方法で文字列を挿入していたり珍しいmemexpandを使っていたりとやや異色です。
// HSP to (X)HTML converter 2007.07.18
#uselib "kernel32.dll"
    #cfunc global IsDBCSLeadByteEx "IsDBCSLeadByteEx" sptr, sptr

#const CP_ACP           0
#const TAB_SIZE         4
#const EXPAND_SIZE      1024

*start
    dialog "hsp;*.as;*.hss;*.hsh"16
    fileName = refstr
    on stat goto *notSelected*openFile

// ファイルが選択されなかった → 終了
*notSelected
    end

// ファイルが選択された → ファイルを開く
*openFile
    notesel text
    noteload fileName

    gosub *replaceText

// 前後にタグを挿入(改善の余地あり?)
*addTags
    l = strlentext )
    s = ""
    memExpand text, l + 26
    poke text, l, ""
    poke textstrlen(s), text
    repeat strlen(s)
        poke textcntpeek( s, cnt )
    loop

*showResult
    mesbox textginfo_winxginfo_winy
    title "変換が終了しました。"
    stop

// テキストの置換
*replaceText
    rTextSize = strlen(text)
    sdim rText, rTextSize       // ReplacedText
    i = 0 : x = 0

    repeat
        c = peektextcnt )

        if ( rTextSize <= i + 6 * TAB_SIZE ) {
            rTextSize += EXPAND_SIZE
            memExpand rText, rTextSize
        }

        switch c
        case 0
            break
            swbreak
        case 13
            if ( peektextcnt + 1 ) == 10 ) {
                // 改行
                wpoke rText, i, 2573 : x = 0 : i += 2 // 2573 = ( 10 << 8 ) | 13
                continue cnt + 2
            }
            swbreak
        case '\t'
            repeat TAB_SIZE - ( x \ TAB_SIZE )
                poke rText, i, " " : i += 6 : x++
            loop
            swbreak
        case ' '
            poke rText, i, " " : i += 6 : x++
            swbreak
        case '&'
            poke rText, i, "&"  : i += 5 : x++
            swbreak
        case '<'
            poke rText, i, "<"   : i += 4 : x++
            swbreak
        case '>'
            poke rText, i, ">"   : i += 4 : x++
            swbreak
        default
            if IsDBCSLeadByteEx( CP_ACP, c ) {
                // 2バイト文字の場合
                wpoke rText, i, wpeektextcnt ) : i += 2 : x += 2
                continue cnt + 2
            } else {
                poke rText, i, c : i++ : x++
            }
        swend
; await 1
    loop

    text = rText
    sdim rText, 4
    return

2007年7月16日月曜日

波紋のエフェクト(3次元ver.)

d3moduleを利用した波紋のエフェクトの3次元版。波の高さによって明るさが変わるようにもなっています。
#include "d3m.hsp"
#define  ctype round(%1) double(strf("%%0.0f"%1)) // 四捨五入(hspmathより抜粋)
#const   CONST_A     0.1 // 定数その1。波長を決定付ける。
#const   CONST_B     7   // 定数その2。屈折の程度を決定付ける。

    buffer 1
    onclick goto *start

*start
    onclick 0
    dialog "jpg;*.gif;*.bmp"16
    if stat == 0 : end
    gsel 1 : picload refstr
    sizeX = ginfo_winx
    sizeY = ginfo_winy
    centerX = ginfo_winx / 2
    centerY = ginfo_winy / 2

    screen 0, sizeX, sizeY, SCREEN_NORMAL
    gmode GMODE_GDI, sizeX, sizeY
    gcopy 100
    d3setcam sizeX/2, -300, sizeY/4, sizeX/20, sizeY/2
    redraw 0
    boxf
    gmode GMODE_MEM11
    repeat sizeY : targetY = cnt
        title "変換中…" + str(100 * cnt / sizeY) + "%"
        repeat sizeX : targetX = cnt
            // 中心から見た座標を算出。
            d3getpos vx, vy, targetX, 0, targetY
            dx = vx - centerX
            dy = centerY -vy

            // 中心から見た角度と距離を算出。
            theta = atan(dy, dx)
            dist  = sqrt(dx * dx + dy * dy)

            // ここがポイント。sinで屈折する距離を決めています。
            // ラスタースクロールのスクリプトのほうが分かりやすいかもしれません。
            copyX = centerX + round(cos(theta) * (dist + sin(dist * CONST_A) * CONST_B))
            copyY = centerY + round(sin(theta) * (dist + sin(dist * CONST_A) * CONST_B))

            pos targetX, targetY
            gmode GMODE_ALPHA11absf(sin(dist * CONST_A)) * 64 + 192
            gcopy 1, copyX, copyY
        loop
        await 10
    loop
    redraw 1
    title "変換終了 - クリックで再開"
    onclick 1
    stop

2007年7月15日日曜日

マウスの方向転換を検知する(失敗作)

マウスジェスチャーを実装する第一歩として、マウスの方向転換を検知するスクリプトを試作。残念ながら正確な検知まで到達できませんでしたが、話のタネに晒しておきます。
基本的な流れは

  1. マウスの位置を配列に記録しておく(古いものは削除される)
  2. マウスの進行方向も算出して配列に記録
  3. 過去数フレームと進行方向を比較し、その差が一定の範囲内に収まっていれば方向を転換したとみなす
わざわざ配列を利用したのはある程度ゆっくりでも判定できるようにとの配慮だったのですが、バグの温床ともなってしまったようです。機会があればまた挑戦してみます。#const HISTORY_MAX 3
#const PI          3.1415
#module
#defcfunc dist2d int a, int b
    return sqrt(a * a + b * b)
#global

*init
    dim  historyX, HISTORY_MAX  // X座標の履歴
    dim  historyY, HISTORY_MAX  // Y座標の履歴
    ddim historyA, HISTORY_MAX  // 進行方向の履歴
    gosub *resetCounter

*main
    gosub *calc
    wait 2
    goto *main

*resetCounter
    counter = HISTORY_MAX       // 配列変数history~が一新されるまで判定しない
    return

*calc
    // クリックでデバッグウィンドウ表示
    getkey leftClick, 1 : assert (leftClick == 0)

    // 方向転換後ある程度時間が経っており、マウスが動いているときに限り判定
    if (counter == 0) {
        title ""
        if (0.0 < dist2d(mouseY - historyY(0), mouseX - historyX(0))) {
            ang = atan(mouseY - historyY(0), mouseX - historyX(0))
            repeat HISTORY_MAX
                if (dist2d(mouseY - historyY(cnt), mouseX - historyX(cnt)) < 5.0) {
                    // 距離が短い場合、判定しない
                    continue
                }
                // 角度変化をtに代入
                t = ang - historyA(cnt)
                while (t < -PI) : t += PI * 2 : wend
                while (PI < t)  : t -= PI * 2 : wend

                // 一定の角度内に収まっていれば、方向転換したとみなす
                if ((0.5 < absf(t)) & (absf(t) < 2.5)) {
                    pset mousexmousey
                    if (t < 0.0) : title "turn left"
                    if (0.0 < t) : title "turn right"
                    gosub *resetCounter
                    break
                }
            loop
        }
    }
    if (counter > 0) {
            ang = historyA(0)
            counter--
    }

    // 配列変数history~を1ずつシフトさせる
    repeat HISTORY_MAX - 11
        historyX(HISTORY_MAX - cnt) = historyX(HISTORY_MAX - cnt - 1)
        historyY(HISTORY_MAX - cnt) = historyY(HISTORY_MAX - cnt - 1)
        historyA(HISTORY_MAX - cnt) = historyA(HISTORY_MAX - cnt - 1)
    loop
    historyX(0) = mouseX
    historyY(0) = mouseY
    historyA(0) = ang
    return

2007年7月12日木曜日

波紋のエフェクト


「HSPでSTGを作っていくblog」(kt.さん運営)のラスタースクロールに影響を受け、作成。
kt.さんの載せていらっしゃるスクリプトは動作も軽くループに組み込んでも問題ないのですが、こちらはかなり重いです。awaitを外しても変わらないほど。ひとつのネタとしてお楽しみください。

この記事に限り転載・引用は禁止です。転載・引用可になりました。(2007/7/14)
※細部を修正。(2007/7/14)
#define ctype round(%1) double(strf("%%0.0f"%1)) // 四捨五入(hspmathより抜粋)
#const  CONST_A     0.2 // 定数その1。波長を決定付ける。
#const  CONST_B     7   // 定数その2。屈折の程度を決定付ける。

    buffer 1
    onclick goto *start

*start
    onclick 0
    dialog "jpg;*.gif;*.bmp"16
    if stat == 0 : end
    gsel 1 : picload refstr
    sizeX = ginfo_winx
    sizeY = ginfo_winy
    centerX = ginfo_winx / 2
    centerY = ginfo_winy / 2

    screen 0, sizeX, sizeY, SCREEN_NORMAL
    gcopy 100, sizeX, sizeY
    gmode GMODE_MEM, 11
    title "変換中…"
    redraw 0
    boxf
    repeat sizeY : targetY = cnt
        repeat sizeX : targetX = cnt
            pos targetX, targetY

            // 中心から見た座標を算出。
            dx = targetX - centerX
            dy = targetY - centerY

            // 中心から見た角度と距離を算出。
            theta = atan(dy, dx)
            dist  = sqrt(dx * dx + dy * dy)

            // ここがポイント。sinで屈折する距離を決めています。
            // ラスタースクロールのスクリプトのほうが分かりやすいかもしれません。
            copyX = centerX + round(cos(theta) * (dist + sin(dist * CONST_A) * CONST_B))
            copyY = centerY + round(sin(theta) * (dist + sin(dist * CONST_A) * CONST_B))
            gcopy 1, copyX, copyY
        loop
        await 10
    loop
    redraw 1
    title "変換終了 - クリックで再開"
    onclick 1
    stop

2007年7月9日月曜日

格ゲーのようなコマンド解析

フォーチュンサモナーズ(素晴らしいゲームです)に触発されて作成。
文字列検索用の関数instr()を利用しています。
メジャーな実装方法は知りませんが、間違いなくこれはマイナーな実装方法でしょう……。

※キューに要素を追加することを正しくはエンキュー(enqueue)と呼ぶそうです。#const global COMMAND_LENGTH 16
#module
#deffunc shiftAndPush var p1, str p2, local l
    poke p1, 0strmid(p1, 1COMMAND_LENGTH - 2)
    poke p1, COMMAND_LENGTH - 2, p2
    poke p1, COMMAND_LENGTH - 10
    return
#global

    sdim sCommand, COMMAND_LENGTH
    gosub *clearCommand

// 方向をあらわす文字(テンキー同様、左が4・上が8・右が6……となっている)
    sdim sCom, 417
    sCom(1) = "4" // 左
    sCom(3) = "7" // 左上
    sCom(2) = "8" // 上
    sCom(6) = "9" // 右上
    sCom(4) = "6" // 右
    sCom(12)= "3" // 右下
    sCom(8) = "2" // 下
    sCom(9) = "1" // 左下
    sCom(16)= "A" // スペースキー

*main
    gosub *renewCommands
    gosub *serch
    title sCommand
    wait 2
    goto *main

// キューが更新されるかどうか判定
*renewCommands
    stick keys, 0b01111 : keys &= 0b11111

    if ((keys & 0b0101) == 0b0101) {
        // 上下同時押しは無効
        keys -= 0b0101
    }
    if ((keys & 0b1010) == 0b1010) {
        // 左右同時押しは無効
        keys -= 0b1010
    }
    if (keys != oldKeys) {
        if (keys & 0b11111) != (oldKeys & 0b01111) {
            // 新しく入力をキューに追加
            gosub *insert
        }
        oldKeys = keys
    }

    counter++
    if counter > 10 {
        // 10フレーム入力がないとキューを廃棄
        counter = 0
        gosub *clearCommand
    }
    return

// 入力されたボタンに応じてキューに文字列をプッシュ
*insert
    counter = 0
    if ((keys & 0b01111) != 0) & ((keys & 0b01111) ! (oldKeys & 0b01111)) {
        shiftAndPush sCommand, sCom(keys & 0b01111)
    }
    if (keys & 0b10000) {
        shiftAndPush sCommand, sCom(keys & 0b10000)
    }
    return

// 技の発動条件を検索
*serch
    if instr(sCommand, 0"41236A") > 0 {
        // 左 → 左下 → 下 → 右下 → 右 → スペースキー
        mes "超必殺拳発動!"
        gosub *clearCommand
    }
    if instr(sCommand, 0"236A") > 0 {
        // 下 → 右下 → 右 → スペースキー
        mes "必殺拳発動!"
        gosub *clearCommand
    }
    if instr(sCommand, 0"2146A") > 0 {
        // 下 → 左下 → 左 → 右 → スペースキー
        mes "気合脚発動!"
        gosub *clearCommand
    }
    return

// キューを廃棄
*clearCommand
    memset sCommand, '_'COMMAND_LENGTH - 1
    return

2007年7月1日日曜日

マップのスクロール

本家掲示板に投稿したものに少し手を加えたもの。
説明用なので読みやすくするべく、本来は必要ない変数を多用している。#const WAIT_TIME      3         // ウェイト[10ms]
#const SPEED          5         // 移動スピード
#const CHARACTER_SIZE 5         // キャラクタの描画サイズ
#const BLANK          100       // マップスクロールが始まる位置

    screen 0480240SCREEN_NORMAL
    x = 0 : y = ginfo_winy / 2  // キャラクタの位置
    scrollX = 0 : scrollY = 0   // 画面左上のマップ上での位置(=スクロール量)
    mapWidth = 750              // マップの幅

    // 適当にマップを準備
    buffer 1, mapWidth, ginfo_winySCREEN_NORMAL
    repeat 20
        hsvcolor rnd(192), 100255
        boxf rnd(ginfo_winx), rnd(ginfo_winy), rnd(ginfo_winx), rnd(ginfo_winy)
    loop
    gsel 0

// ★メインループ★
*main
    gosub *moveCharacter    // キャラクタを動かす
    gosub *setScroll        // 描画する位置を決める
    gosub *draw             // いろんな描画処理
    wait WAIT_TIME
    goto *main

// キャラクタを動かす
*moveCharacter
    stick key, 5 : speedX = SPEED * ((key >> 2 & 1) - (key & 1))
    x = limit(x + speedX, 0, mapWidth)
    return

// 描画する位置を決める
*setScroll
    // (x - scrollX)が画面上でのキャラクタの位置になる
    if x - scrollX < BLANK{
        // キャラクタがBLANKより左側に移動した場合、カメラを移動させる
        scrollX = limit(x - BLANK0, mapWidth - ginfo_winx)
    }
    if ginfo_winx - BLANK < x - scrollX {
        // キャラクタが(ginfo_winx - BLANK)より右側に移動した場合、カメラを移動させる
        scrollX = limit(x + BLANK - ginfo_winx0, mapWidth - ginfo_winx)
    }
    return

//いろんな描画処理
*draw
    redraw 0
    gosub *drawMap          // マップを描画する
    gosub *drawCharacter    // キャラクタを描画する
    redraw 1
    return

// マップを描画する
*drawMap
    pos 00 : gcopy 1, scrollX, scrollY, ginfo_winxginfo_winy
    return

// キャラクタを描画する
*drawCharacter
    drawX = x - scrollX : drawY = y
    boxf drawX - CHARACTER_SIZE, y - CHARACTER_SIZE, drawX + CHARACTER_SIZE, drawY + CHARACTER_SIZE
    return