2007年8月24日金曜日

リアルタイムシミュレーションっぽい動き

リアルタイムシミュレーションのように各キャラクタに対して移動指示を出し、移動させるサンプル。
配列を利用すれば、移動目標を複数保持してジグザグに移動させることも可能でしょう。JavaのVectorのような可変長配列が使えれば実装は楽そうですが……。

【操作方法】
キャラクタをクリックしてから他の地点をクリックすると、その地点めがけてキャラクタが移動を開始します。
他のキャラクタをクリックするまでキャラクタの選択状態は変更されません。
#define fillbox%1%2%3 ) boxf %1 - %3 / 2%2 - %3 / 2%1 + %3 / 2%2 + %3 / 2
#define drawbox%1%2%3 ) line %1 - %3 / 2%2 + %3 / 2%1 - %3 / 2%2 - %3 / 2 %line %1 + %3 / 2%2 + %3 / 2 %line %1 + %3 / 2%2 - %3 / 2 %line %1 - %3 / 2%2 - %3 / 2
#const CHARACTER_SIZE 32        // キャラクタの大きさ
#const CHARACTER_NUM   5        // キャラクタの個数
#const CHARACTER_SPEED 5        // キャラクタの速さ
    ddim character_x, CHARACTER_NUM     // キャラクタの位置
    ddim character_y, CHARACTER_NUM     // キャラクタの位置
    ddim target_x,    CHARACTER_NUM     // 移動先地点
    ddim target_y,    CHARACTER_NUM     // 移動先地点
    repeat CHARACTER_NUM
        character_x( cnt ) = doublerndginfo_winx - CHARACTER_SIZE ) )
        character_y( cnt ) = doublerndginfo_winy - CHARACTER_SIZE ) )
        target_x( cnt ) = character_x( cnt )
        target_y( cnt ) = character_y( cnt )
    loop
    command_num = -1            // 指令を出す対象のキャラクタナンバー
    onclick gosub *label_onclick

*main
    gosub *move
    gosub *draw
    wait 5
    goto *main

*move
    repeat CHARACTER_NUM
        theta = atan( character_y( cnt ) - target_y( cnt ), target_x( cnt ) - character_x( cnt ) )

        if absf( target_x( cnt ) - character_x( cnt ) ) > cos( theta ) * CHARACTER_SPEED {
            character_x( cnt ) += cos( theta ) * CHARACTER_SPEED
        } else {
            character_x( cnt ) = target_x( cnt )
        }

        if absf( target_y( cnt ) - character_y( cnt ) ) > sin( theta ) * CHARACTER_SPEED {
            character_y( cnt ) -= sin( theta ) * CHARACTER_SPEED
        } else {
            character_y( cnt ) = target_y( cnt )
        }
    loop
    return

*draw
    redraw 0
    color 255255255 : boxf
    gosub *draw_arrow
    gosub *draw_character
    gosub *draw_frame
    redraw 1
    return

*draw_character
    repeat CHARACTER_NUM
        hsvcolor cnt * 191 / CHARACTER_NUM255255
        fillbox character_x( cnt ), character_y( cnt ), CHARACTER_SIZE
    loop
    return

*draw_arrow
    repeat CHARACTER_NUM
        if ( target_x( cnt ) != character_x( cnt ) ) | ( target_y( cnt ) != character_y( cnt ) ) {
            hsvcolor cnt * 191 / CHARACTER_NUM255255
            line target_x( cnt ), target_y( cnt ), character_x( cnt ), character_y( cnt )
        }
    loop
    return

*draw_frame
    if command_num >= 0 {
        color
        drawbox character_x( command_num ), character_y( command_num ), CHARACTER_SIZE
    }
    return

*label_onclick
    // クリックしたキャラクタを探す
    tmp = -1
    repeat CHARACTER_NUM
        dx = absmousex - character_x( cnt ) )
        dy = absmousey - character_y( cnt ) )
        if ( 0 <= dx ) & ( dx <= CHARACTER_SIZE / 2 ) & ( 0 <= dy ) & ( dy <= CHARACTER_SIZE / 2 ) {
            tmp = cnt
            break
        }
    loop
    if tmp >= 0 : command_num = tmp : return

    // キャラクタ以外をクリックした場合
    if command_num >= 0 {
        target_x( command_num ) = doublemousex )
        target_y( command_num ) = doublemousey )
    }
    return

2007年8月16日木曜日

障害物のあるマップで視野を求める

フリーソフト超激辛ゲームレビュー様でHSPで作られたと思われる素晴らしいゲームが紹介されていました。Elonaというローグライクゲームです。詳しくは製作者様のサイトをご覧ください。

ローグ系はやったことも作ったこともあまり無いので処理内容の大半はよく分からないのですが、視野の計算がちょっと気になった&イメージが湧いたのでサンプルスクリプトを作成してみました。
左右非対称だったりと不完全ではありますが、充分実用に耐えうるのではないかと思われます。
#const CHIP_SIZE  16                  // マップチップの大きさ
#const WINX       320                 // ウィンドウの大きさ
#const WINY       320
#const AREA_W     WINX / CHIP_SIZE    // マップの大きさ
#const AREA_H     WINY / CHIP_SIZE
#const OBSTACLE_W 3                   // 障害物の大きさ
#const OBSTACLE_H 2
//
// 数学関数
#define ctype max%1%2 ) ( %1 * ( %1 > %2 ) + %2 * ( %1 <= %2 ) )
#define ctype round%1 ) double(strf("%%0.0f"%1))
//
// マップ描画用マクロ
#define draw_chip%1%2 ) boxf CHIP_SIZE * %1CHIP_SIZE * %2CHIP_SIZE * ( %1 + 1 ) - 2CHIP_SIZE * ( %2 + 1 ) - 2

    screen 0WINXWINY
    randomize
    sdim map, AREA_W * AREA_H
    sdim shadow, AREA_W * AREA_H  // 影を記録する変数。0なら明るい地点、それ以外は影。
    gosub *make_blocks
    onclick gosub *make_blocks
//
// メインループ
*main
    if ( light_x != mousex / CHIP_SIZE ) | ( light_y != mousey / CHIP_SIZE ) {
        renew = 1
    }
    if renew {
        renew = 0
        light_x = mousex / CHIP_SIZE
        light_y = mousey / CHIP_SIZE
        gosub *calc
        gosub *draw
    }
    wait 2
    goto *main
//
// 視野の計算
*calc
    memset shadow, 2AREA_W * AREA_H // 2...未検査
    repeat AREA_H
        y = cnt
        dy = abs( y - light_y )
        repeat AREA_W
            if peek( shadow, y * AREA_W + cnt ) != 2 : continue
            x = cnt
            dx = abs( x - light_x )
            // コメントアウトを解除すると、距離も考慮に入れるようになる
;           if ( dx*dx+dy*dy > 48 ) {
;               poke shadow, y * AREA_W + x, 1    // 1...検査済み(影)
;               continue
;           }
            m = max( dx, dy )
            repeat m
                // 内分点の公式を使い、調査地点と光源の間にあるマス全てを調べる
                _x = int(round(double( light_x * cnt + x * ( m - cnt ) ) / m))
                _y = int(round(double( light_y * cnt + y * ( m - cnt ) ) / m))
                if peek( map, _y * AREA_W + _x ) {
                    // 障害物を見つけた場合、今まで調べた地点はすべて影とする
                    repeat cnt + 1
                        _x = int(round(double( light_x * cnt + x * ( m - cnt ) ) / m))
                        _y = int(round(double( light_y * cnt + y * ( m - cnt ) ) / m))
                        poke shadow, _y * AREA_W + _x, 1    // 1...検査済み(影)
                    loop
                    break
                }
                poke shadow, _y * AREA_W + _x, 0            // 0...検査済み(明るい地点)
            loop
        loop
    loop
    return
//
// 各種描画処理
*draw
    redraw 0
    gosub *draw_map
    gosub *draw_light
    redraw 1
    return
//
// マップを描画
*draw_map
    color : boxf
    repeat AREA_H
        y = cnt
        repeat AREA_W
            if peek( map, y * AREA_W + cnt ) == 0 {
                if peek( shadow, y * AREA_W + cnt ) {
                    color 192192192
                } else {
                    color 255255255
                }
                draw_chip cnt, y
            }
        loop
    loop
    return
//
// 光源を描く
*draw_light
    color 2551280
    draw_chip light_x, light_y
    return
//
// ブロックをランダムに配置
*make_blocks
    memset map, 0AREA_W * AREA_H
    // 10個障害物を作成
    repeat 10
        poke map, rndAREA_W * AREA_H ), 1
    loop
    // 1個大きな障害物(OBSTACLE_WxOBSTACLE_H)を作成
    x = rndAREA_W + 1 - OBSTACLE_W )
    y = rndAREA_H + 1 - OBSTACLE_H )
    repeat OBSTACLE_H
        memset map, 1OBSTACLE_W, ( y + cnt ) * AREA_W + x
    loop
    renew = 1
    return

HHXモジュール 命令・関数から検索

前回はすべての命令・関数を対象としましたが、今回は特定の文字列を含む命令だけを抽出する方法を紹介します。
と言っても前回HHX_select_all()としていたところを、HHX_select_where(p1, p2, p3)とするだけです。メインの検索処理はモジュールが行ってくれます。

検索結果はキーワード適合度の高い順に並びます。検索キーワードが命令概要(C_SUMMARY)に載っている命令の方が説明(C_INST)に載っている命令よりも適合度が高くなります。
キーワード適合度の強さについては、hsp_db.hsp内のコメントをご覧ください。

#include "../hsphelp/src/hhx_db.hsp"
#module
//
// 文字列の右側を半角スペースで埋める
#defcfunc add_spaces str sTarget, local iLength, local sResult
    iStrlen = strlen( sTarget )
    iLength = 30 - iStrlen
    if iLength < 1 : iLength = 1
    sdim sResult, iLength + iStrlen + 1
    sResult = sTarget
    memset sResult, ' ', iLength, iStrlen
    return sResult
#global

// hsphelpディレクトリの存在を確認
    chdir dir_exe
    dirlist s, "hsphelp"5 
    if stat == 0 : dialog "hsphelpディレクトリが見つかりません。"1 : end
    chdir dir_exe + "/hsphelp"

// HHXのデータベースをロード
    HHX_init_load_db
    if HHX_currentset_sum() ! HHX_diskset_sum() {
        HHX_init_rebuild_db DBR_WRITEDB
    } else {
        HHX_init_extract_db
    }

// 画面上にオブジェクトを配置
    sSerch = "色" : sdim note, 10000 : notesel note
    objsize 12024 : objmode 2 : font msgothic12
    input sSerch, ginfo_winx - 12024
    pos ginfo_winx - 1200
    button gosub "検索"*serch
    pos 024
    mesbox note, ginfo_winxginfo_winy - ginfo_cy

    gosub *serch

    onkey gosub *lbl_onkey
    stop

// 入力ボックスでEnterを押したとき、検索を開始
*lbl_onkey
    objsel -1
    if ( wparam == 13 ) & ( stat == 0 ) : gosub *serch
    return

// ある条件によってデータベースから検索を行い、その結果を列挙する
*serch
    note = ""
    db_num = HHX_select_where( sSerch, -1, -1 )   // 検索対象文字を解説などに含む命令・関数を列挙する
    repeat db_num
        c = HHX_get_next()
        sLine = add_spaces( hhxdata( c, C_NAME ) ) + hhxdata( c, C_SUMMARY )
        noteadd sLine
    loop
    objprm 2, note
    return

2007年8月15日水曜日

2007年8月13日月曜日

HHXモジュール 命令・関数一覧を取得

おそらくあまり利用者のいないHHXモジュールについて、しばらく扱ってみようと思います。
HHXモジュールはHSファイルにアクセスする際、非常に強力な手段として利用することができます。HHXに付属されており、誰でも利用することができます。作者はsprocketさんです。

まずは以前も紹介した、命令・関数一覧の作成から。
データベースのロード手順はどんなスクリプトでも同じです。実際にこのスクリプトがHHX自身のスクリプトとほとんど同じであることを確認していただけると思います。

HHX_select_allを実行しただけでは名称順にソートされた状態ですが、HHX_order_by命令を利用することでグループ名・依存DLL名などによってソートできます。#include "../hsphelp/src/hhx_db.hsp"
#module
//
// 文字列の右側を半角スペースで埋める
#defcfunc add_spaces str sTarget, local iLength, local sResult
    iStrlen = strlen( sTarget )
    iLength = 30 - iStrlen
    if iLength < 1 : iLength = 1
    sdim sResult, iLength + iStrlen + 1
    sResult = sTarget
    memset sResult, ' ', iLength, iStrlen
    return sResult
#global

// hsphelpディレクトリの存在を確認
    chdir dir_exe
    dirlist s, "hsphelp"5 
    if stat == 0 : dialog "hsphelpディレクトリが見つかりません。"1 : end
    chdir dir_exe + "/hsphelp"

// HHXのデータベースをロード
    HHX_init_load_db
    if HHX_currentset_sum() ! HHX_diskset_sum() {
        // HSファイルに何かしらの変更が加わったため、データベースを再構築
        mes "HHXのデータベースをリビルドしています..."
        HHX_init_rebuild_db DBR_WRITEDB // データベースを作成し、配列変数hhxdataxにロード
                                        // より高速化を望むなら DBR_WRITEDB を DBR_READONLY としても良い
    } else {
        HHX_init_extract_db             // 配列変数hhxdataxにメモリ上のデータをロード
    }
    mes "HHXのデータベースをロードしました。"

// データベースから命令・関数をひとつずつ取り出し、その名前を列挙する
    db_num = HHX_select_all()           // すべての命令・関数などを検索対象とする(ABC順)
;   HHX_order_by C_GROUP                // コメントを外すとグループ名でソート(昇順)
    sdim buf, 16000
    notesel buf
    mes "命令一覧を作成します..."
    repeat db_num
        c = HHX_get_next()              // 次の命令・関数などのナンバーを取得
        sLine = add_spaces( hhxdata( c, C_NAME ) ) + hhxdata( c, C_SUMMARY )
        noteadd sLine                   // ナンバー c で表現される命令・関数などの名前をnoteaddする
                                        // C_SUMMARYなら命令・関数の概要を、C_INSTならその説明文を取得することができる(他はhhx_db.hspを参照)
    loop
    mes "命令一覧の作成を完了しました。"
    objmode 2
    font msgothic12
    mesbox buf, ginfo_winxginfo_winy - ginfo_cy
    stop

2007年8月12日日曜日

HSファイルからHTMLヘルプを作成する(2)

XHTMLに変わっている点を除き、恐らく機能的には以前同梱されていたヘルプと完全互換。
かなり大きめ(~1.5MB)のテキストデータを扱いますので、スペックに自信がない場合はawaitのコメントアウトをはずすか、他のアプリケーションを終了させた状態で実行することをオススメします。

グループを定義していない命令を想定していなかった問題を修正。
再現性を向上。


現在再現性や利便性の向上を図り、改良中です。満足のいくものができたら公開します。
v1.1を公開しました。
ウェブサイトで新バージョンの配布を行っています。

関連:HSファイルからHTMLヘルプを作成する

// hsファイルからHTMLヘルプを作成
// 機能別索引を除くすべての索引も作成する
#include "../hsphelp/src/hhx_db.hsp"
#module
//
// 関数名・命令名からidを得る
#defcfunc get_id str name, local s
    s = name
    if ( peek( s, 0 ) == '#' ) : s = strmid( s, 1strlen( s ) - 1 )
    return "s_" + s

// 関数名・命令名からファイルの通し番号(独自に定義)を得る
//   help_a.htmlを0、help_b.htmを1、…help_sp.htmを26とする
#defcfunc get_filenum str name, local s, local p
    s = name
    p = peek( s, 0 )
    if ( 'A' <= p ) & ( p <= 'Z' ) : p -= 'A' - 'a'
    if ( 'a' <= p ) & ( p <= 'z' ) {
        return p - 'a'
    } else {
        return 'z' - 'a' + 1
    }
//
// 通し番号からファイル名を得る
#defcfunc get_filename int num
    if ( 0 <= num ) & ( num <= 'z' - 'a' ) {
        return "help_" + strf"%c", num + 'a' ) + ".htm"
    } else {
        return "help_sp.htm"
    }
//
// 半角スペースで埋める
#defcfunc add_spaces str sTarget, local iLength, local sResult
    iStrlen = strlen( sTarget )
    iLength = 30 - iStrlen
    if iLength < 1 : iLength = 1
    sdim sResult, iLength + iStrlen + 1
    sResult = sTarget
    memset sResult, ' ', iLength, iStrlen
    return sResult
#global

    chdir dir_exe + "/hsphelp"
    sdim buf, 10000
    html_header = {"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Frameset//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd\">
<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"ja\" lang=\"ja\">
<head>
\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=Shift_JIS\" />
\t<meta http-equiv=\"Content-Language\" content=\"ja\" />
\t<link rel=\"stylesheet\" type=\"text/css\" href=\"hsphelp.css\" />
\t<title>HSP command help</title>
</head>
"}


    gosub *load_db           // HHXのDBからデータをロードする
    gosub *make_html_files   // HTMLファイルを作成する
    gosub *make_frame_file   // フレーム用HTMLファイルを作成する
    gosub *make_ref_file     // グループ別索引用HTMLを作成
    gosub *make_css_file     // CSSファイルを作成する
    dialog "HTMLファイルの作成が完了しました。\nHTMLファイルを開きますか?"2
    if stat == 6 : exec "indexf.htm"16
    end

*load_db
    HHX_init_load_db
    if HHX_currentset_sum() ! HHX_diskset_sum() {
        mes "HHXのデータベースをリビルドしています..."           // HSファイルに何かしらの変更が加わったため、DBを再構築
        HHX_init_rebuild_db DBR_WRITEDB
    } else {
        HHX_init_extract_db
    }
    mes "HHXのデータベースをロードしました。"
    return

*init_file
    // HTMLのヘッダを書き込む
    sdim file, 20000
    file = html_header + "<body>\n\t<h1>HSP command help</h1>\n"
    file_offset = strlen( file )
    return

*save_file
    // HTMLファイルを保存する
    file += "</body></html>"
    file_offset += strlen"</body></html>" )
    bsave get_filename( file_type ), file, file_offset
    return

*init_index
    // 索引用HTMLのヘッダを書きこむ
    sdim index, 100000  : index  = html_header + "<body>\n\t<h1>HSP command help ABC順索引</h1><ul class=\"index_list\">\n" // 索引
    sdim indexf, 100000 : indexf = html_header + "<body>\n\t<h1>索引</h1>\n\t<ul class=\"indexf_list\">\n"                  // フレーム用ミニ索引
    indexf += "<p><a href=\"hspref.htm\" target=\"main\">グループ別索引</a></p>\n"
    indexf += "<p><a href=\"hsppidx.htm\" target=\"main\">ABC順索引</a></p>\n"
    index_offset  = strlen( index )
    indexf_offset = strlen( indexf )
    return

*save_index
    // 索引用HTMLを保存する
    index  += "\t</ul>\n</body></html>"
    indexf += "\t</ul>\n</body></html>"
    index_offset  += strlen"\t</ul>\n</body></html>" )
    indexf_offset += strlen"\t</ul>\n</body></html>" )
    bsave "hsppidx.htm",  index,  index_offset
    bsave "hsppidxf.htm", indexf, indexf_offset
    return

*make_html_files
    // HTMLファイルを作成する
    mes "HTMLファイルの作成を開始..."
    db_num = HHX_select_all()           // すべての命令・関数を検索対象とする
    file_type = get_filenum"#" )      // 最初は記号で始まる命令用のファイル(help_sp.htm)からはじめる
    file_name = get_filename( file_type )
    gosub *init_file
    gosub *init_index

    repeat db_num
        c = HHX_get_next()
        db_name  = hhxdata( c, C_NAME ) // 命令・関数名
        id = get_id( db_name )
        if file_type != get_filenum( db_name ) {
            // 次のHTMLファイルへ移行
            gosub *save_file
            gosub *init_file
            file_type = get_filenum( db_name )
            file_name = get_filename( file_type )
        }

        // 索引
        buf = "\t\t<li><a href=\"" + file_name + "#" + id + "\">" + add_spaces( db_name ) + hhxdata( c, C_SUMMARY ) + "</a></li>\n"
        index  += buf : index_offset  += strlen( buf )
        buf = "\t\t<li><a href=\"" + file_name + "#" + id + "\" target=\"main\">" + db_name + "</a></li>\n"
        indexf += buf : indexf_offset += strlen( buf )

        // 見出し(h2タグ)
        buf = "\t<h2 id=\"" + id + "\" class=\"keyword_name\">" + db_name + "</h2>\n"
        // パラメータ
        buf += "\t<p class=\"prm\">" + add_spaces( db_name + " " + hhxdata( c, C_PRM ) ) + "[" + hhxdata( c, C_SUMMARY ) + "]</p>\n"
        // パラメータ詳細
        if hhxdata( c, C_PRM2 ) != "" {
            buf += "\t\t<p class=\"prm_detail\">" + hhxdata( c, C_PRM2 ) + "</p>\n"
        }
        // 説明文
        if hhxdata( c, C_INST ) != "" {
            buf += "\t\t<h3>説明</h3><p class=\"inst\">" + hhxdata( c, C_INST ) + "</p>\n"
        }
        // 備考
        if hhxdata( c, C_NOTE ) != "" {
            buf += "\t\t<h3>備考</h3><p class=\"note\">" + hhxdata( c, C_NOTE ) + "</p>\n"
        }
        // 参照
        if hhxdata( c, C_HREF ) != "" {
            buf += "\t\t<h3>参照</h3><ul class=\"href\">"
            i = 0 : l = strlen( hhxdata( c, C_HREF ) )
            repeat
                getstr s, hhxdata( c, C_HREF ), i, ' '
                i += strsize
                buf += "\t\t\t<li><a href=\"" + get_filenameget_filenum( s ) ) + "#" + get_id( s ) + "\">" + s + "</a></li>\n"
                if l <= i : break
            loop
            buf += "\t\t</ul>"
        }
        // 水平線(次の項目との区切り)
        buf += "\n\t<hr>\n\n"

        // 変数に書き込み
        file += buf
        file_offset += strlen( buf )

;       title str( double( cnt ) / db_num ) + "% finished..."
;       await 1
    loop

    // 最後のファイルをディスクに保存
    gosub *save_file
    gosub *save_index
    return

*make_ref_file
    db_num = HHX_select_all()
    HHX_order_by C_GROUP
    sdim ref, 100000
    ref  = html_header + "<body>\n\t<h1>HSP command help グループ別索引</h1>\n"
    ref += "\t<h2>(グループ未定義)</h2>\n\t\t<ul class=\"ref_list\">\n"
    ref_offset = strlen( ref )
    group_name = ""
    repeat db_num
        c = HHX_get_next()
        buf = ""
        db_name  = hhxdata( c, C_NAME )
        db_group = hhxdata( c, C_GROUP )
        if group_name != db_group {
            buf += "\t\t</ul>\n"
            buf += "\t<h2>" + db_group + "</h2>\n\t\t<ul class=\"ref_list\">\n"
            group_name = db_group
        }
        buf += "\t\t\t<li><a href=\"" + get_filenameget_filenum( db_name ) ) + "#" + get_id( db_name ) + "\">" + add_spaces( db_name ) +  hhxdata( c, C_SUMMARY )  + "</a></li>\n"
        ref += buf
        ref_offset += strlen( buf )
    loop
    ref        += "\t\t</ul>\n</body></html>"
    ref_offset += strlen"\t\t</ul>\n</body></html>" )
    bsave "hspref.htm", ref, ref_offset
    return

*make_frame_file
    buf = html_header + {"\t<frameset cols=\"80%,*\">
\t\t<frame src=\"hspref.htm\" name=\"main\" />
\t\t<frame src=\"hsppidxf.htm\" name=\"sub\" />
\t</frameset>
\t<noframes>
\t\t<p>これはフレーム対応ブラウザ用のファイルです。<br />
\t\tフレームに対応したブラウザでご覧になるか、<a href=\"hsppidx.htm\">命令一覧</a>をご利用ください。</p>
\t</noframes>
</html>"}

    bsave "indexf.htm", buf, strlen( buf )
    return

*make_css_file
    mes "CSSファイルの作成を開始..."
    buf = {"body {
\tbackground-color : #f0e0d0 ;
}
h2 {
\tcolor : #000080 ;
\tfont-size : large ;
\tfont-weight : bold ;
\twhite-space : pre ;
}
h2.keyword_name {
\tfont-size : x-large ;
\tcolor : #504030 ;
\tfont-weight : bold ;
}
h3 {
\tfont-weight : bold ;
}
p.prm {
\tcolor : #000080 ;
\twhite-space : pre ;
\tmargin-left : 70px ;
\tfont-size : large ;
\tfont-weight : bold ;
\tfont-family : monospace ;
}
p.prm_detail {
\tcolor : #000080 ;
\tfont-size : small ;
\twhite-space : pre ;
\tmargin-left : 70px ;
}
p.inst {
\tmargin-left : 70px ;
\twhite-space : pre ;
}
p.note {
\tmargin-left : 70px ;
}
ul.href {
\tmargin-left : 70px ;
}
ul.index_list {
\tfont-size : large ;
}
ul.index_list li {
\twhite-space : pre ;
\tfont-family : monospace ;
\tmargin-bottom : 1ex ;
}
ul.indexf_list {
}
ul.ref_list {
\tfont-size : large ;
}
ul.ref_list li {
\twhite-space : pre ;
\tfont-family : monospace ;
\tmargin-bottom : 1ex ;
}"}

    bsave "hsphelp.css", buf, strlen( buf )
    return

ちっちゃなテトリス

結局遊べるところまで完成させてしまいました。短期集中連載第2弾とでも呼びましょうか。

これと全く同じものをHSPコンテスト2007へ投稿しました。ので、いつも通り利用・改造・転載など自由ですが、これの改造をHSPコンテスト2007へ投稿することはご遠慮ください。

関連:stick命令をgetkey命令で実装する
fujidigさんによるバグの指摘
#include "hsptv.as"

// 独自stick定義モジュール
#undef stick
#module modStick

// getkeyキーコード
#const GETKEY_LEFT          37
#const GETKEY_UP            38
#const GETKEY_RIGHT         39
#const GETKEY_DOWN          40
#const GETKEY_Z             'Z'
#const GETKEY_X             'X'

#deffunc _initStick
    dim KEY_CODE, 6
    KEY_CODE(0) = GETKEY_XGETKEY_ZGETKEY_DOWNGETKEY_RIGHTGETKEY_UPGETKEY_LEFT
    return

#deffunc _stick var vTarget, int NO_TRIGGER, int CHECK_MODE, local tmp
    vTarget = 0
    repeat 6; = length(KEY_CODE)           // start.ax軽量化のために定数化
        getkey tmp, KEY_CODE(cnt)
        vTarget = vTarget << 1 | tmp
    loop
    if (CHECK_MODE == WIN_ACTIVE_CHECK_ON) & (ginfo_act == -1){
        // HSPウィンドウがアクティブでない
        prev = vTarget
        vTarget = 0
    } else {
        tmp = vTarget
        vTarget &= (-1 ^ prev) | NO_TRIGGER
        prev = tmp
    }
    return
#define global stick(%1%2=0%3=1_stick %1%2%3
#global
    _initStick       // 配列の初期化
// 独自stick定義モジュールここまで

#const global BLOCK_SIZE 32
#const global BLOCK_HSIZE BLOCK_SIZE / 2
#const global BLOCK_COLOR_MAX 8
#const global AREA_WIDTH  10
#const global AREA_HEIGHT 15
#const global AREA_WIDTH2 AREA_WIDTH * BLOCK_SIZE
#const global AREA_HEIGHT2 BLOCK_SIZE * AREA_HEIGHT
#const global AREA_X ( 640 - AREA_WIDTH2 ) / 2
#const global AREA_Y ( 480 - AREA_HEIGHT2 ) / 2
#const HIGHSCORE_MAX 6

#enum STATE_NORMAL = 1
#enum STATE_BLINK
#enum STATE_GAMEOVER

#module
//
// 指定した色でブロックを描く
#deffunc draw_block int x, int y, int c, int mode
    hsvcolor 191 * c / BLOCK_COLOR_MAX255255 - 155 * ( mode != 0 )
    boxf AREA_X + BLOCK_SIZE * x, AREA_Y + BLOCK_SIZE * y, AREA_X + BLOCK_SIZE * ( x + 1 ) - 2AREA_Y + BLOCK_SIZE * ( y + 1 ) - 2
    return
//
// エリアを再描画
#deffunc draw_area var map
    color
    boxf AREA_XAREA_YAREA_X + AREA_WIDTH2 - 1AREA_Y + AREA_HEIGHT2 - 1
    repeat AREA_HEIGHT
        y = cnt
        repeat AREA_WIDTH
            p = peek( map, y * AREA_WIDTH + cnt )
            if p : draw_block cnt, y, p
        loop
    loop
    return
//
// チェックされた行を塗りつぶす
#deffunc blink_sellines var check
    repeat AREA_HEIGHT
        if peek( check, cnt ) {
            boxf AREA_XAREA_Y + BLOCK_SIZE * cntAREA_X + AREA_WIDTH2 - 1AREA_Y + BLOCK_SIZE * ( cnt + 1 ) - 1
        }
    loop
    return
//
// テトリミノを描く
#deffunc draw_tetrimino int block_type, int block_color, int _x, int _y, int mode
    repeat 4
        y = _y + cnt
        if ( AREA_HEIGHT <= y )|( y < 0 ) : continue
        _cnt = cnt
        repeat 4
            x = _x + cnt
            if ( AREA_WIDTH <= x ) : break
            if block_type >> ( _cnt * 4 + cnt ) & 1 {
                draw_block x, y, block_color + 1, mode
            }
        loop
    loop
    return
//
// マップにブロックを固定
#deffunc fix_to_map var map, int block_type, int block_color, int _x, int _y
    repeat 4
        y = _y + cnt
        _cnt = cnt
        repeat 4
            x = _x + cnt
            if block_type >> ( _cnt * 4 + cnt ) & 1 {
                if (x < 0)|(AREA_WIDTH <= x)  : continue
                if (y < 0)|(AREA_HEIGHT <= y) : continue
                poke map, y * AREA_WIDTH + x, block_color + 1
            }
        loop
    loop
    return
//
// 消せる行をチェックし、変数へ結果を返す
#deffunc check_del_lines var map, var check
    ret = 0
    repeat AREA_HEIGHT
        i = 1
        _cnt = cnt
        repeat AREA_WIDTH
            if peek( map, _cnt * AREA_WIDTH + cnt ) == 0 {
                i = 0
                break
            }
        loop
        poke check, cnt, i
        ret += i
    loop
    return ret
//
// nLine行目を消し、上にあるラインを下へ落とす
#deffunc del_line var map, int nLine
;   if ( nLine < 0 ) | ( AREA_HEIGHT <= nLine ) : return -1
    if nLine > 0 {
        memcpy map, map, nLine * AREA_WIDTHAREA_WIDTH0
    }
    memset map, 0AREA_WIDTH0
    return; 0
//
// 指定した行をすべて削除
#deffunc del_sellines var map, var check
    i = AREA_HEIGHT - 1
    repeat AREA_HEIGHT1
        if peek( check, AREA_HEIGHT - cnt ) {
            del_line map, i
        } else {
            i--
        }
    loop
    return; 0
//
// テトリミノが壁やブロックと衝突するか検出
#defcfunc hit_check var map, int block_type, int _x, int _y
    ret = 0
    repeat 4
        y = _y + cnt
        _cnt = cnt
        repeat 4
            x = _x + cnt
            if block_type >> ( _cnt * 4 + cnt ) & 1 {
                if ( x < 0 ) | ( AREA_WIDTH <= x ) | ( AREA_HEIGHT <= y ) {
                    ret = 1
                    break
                }
                if y >= 0 : if peek( map, y * AREA_WIDTH + x ) {
                    ret = 1
                    break
                }
            }
        loop
        if ret : break
    loop
    return ret
#global

    cls 1
    randomize

    hsptv_up -1""

    dim block_type, 47 // Zが時計回り
    block_type( 00 ) = $0660$0660$0660$0660 // ■
    block_type( 01 ) = $2222$00F0$4444$0F00 // |
    block_type( 02 ) = $0270$0232$0072$0262 // ┤
    block_type( 03 ) = $0360$0462$06C0$4620 // s
    block_type( 04 ) = $0630$0264$0C60$2640 // z
    block_type( 05 ) = $2260$0470$0644$0E20 // 「
    block_type( 06 ) = $4460$0740$0622$02E0 // └

    dim  high_score, HIGHSCORE_MAX
    sdim ranker_name, 50HIGHSCORE_MAX
    sdim map, AREA_WIDTH * AREA_HEIGHT
    sdim line_check, AREA_HEIGHT

    sqarea_x = AREA_XAREA_X + AREA_WIDTH2AREA_X + AREA_WIDTH2AREA_X
    sqarea_y = AREA_YAREA_YAREA_Y + AREA_HEIGHT2AREA_Y + AREA_HEIGHT2

    ;pos ginfo_winx : mes "Gameover"
    ;gameover_width = ginfo_mesx : gameover_height = ginfo_mesy
#const gameover_width 64
#const gameover_height 18
#const fall_limit 17
    gmode GMODE_ALPHA, , , 192

*restart
    gosub *reload_highscore
    memset map, 0AREA_WIDTH * AREA_HEIGHT
    next_type = rnd(7)
    state = STATE_NORMAL
    score = 0
    gosub *create_new_tetrimino

*main
    stick keys, 8
    if state == STATE_BLINK {
        state_limit--
        if state_limit == 0 {
            state = STATE_NORMAL
            del_sellines map, line_check
            gosub *create_new_tetrimino
        }
    }
    if state == STATE_GAMEOVER {
        if state_limit : state_limit--
        if state_limit == 1 : hsptv_up score, ""
        if ( state_limit == 0 ) & ( keys >> 4 & 1 ) : goto *restart
    }
    if state == STATE_NORMAL {
        gosub *move_tetrimino
    }
    gosub *draw
    wait 2
    goto *main

*draw
    redraw 0
    draw_area map
    gosub *draw_score
    gosub *draw_next
    if state == STATE_NORMAL {
        gosub *draw_ghost
        draw_tetrimino block_type( moving_rot, moving_type ), moving_type, moving_x, moving_y, 0
    }
    if state == STATE_BLINK {
        c = 255 * ( state_limit / 3 \ 2 )
        color c, c, c
        blink_sellines line_check
    }
    if state == STATE_GAMEOVER {
        color
        gsquare -1, sqarea_x, sqarea_y
        x = AREA_X + ( AREA_WIDTH2 - gameover_width ) / 2
        y = AREA_Y + ( AREA_HEIGHT2 - gameover_height ) / 2
        color 100100100
        pos x+2, y+2
        mes "Gameover"
        color 255255255
        pos x, y
        mes "Gameover"
        if ( state_limit == 0 ) {
            pos x-50, y+40 : mes "push Z key to restart"
        }
    }
    redraw 1
    return

*draw_next
    color
    boxf BLOCK_HSIZEBLOCK_HSIZEBLOCK_HSIZE * 9 - 1BLOCK_HSIZE  * 11 - 1
    pos BLOCK_HSIZEBLOCK_HSIZE
    draw_tetrimino block_type( 0, next_type ), next_type, -410
    return

*draw_score
    color
    boxf BLOCK_HSIZEBLOCK_SIZE * 6BLOCK_HSIZE * 9 - 1BLOCK_HSIZE * 29 - 1
    color 255255255
    pos BLOCK_HSIZE + 6BLOCK_SIZE * 6 + 9
    mes strf"score:%08d", score )
    mes "--------------"
    repeat HIGHSCORE_MAX
        mes "" + ( cnt + 1 ) + ":" + ranker_name( cnt )
        mes strf"      %08d", highscore(cnt) )
    loop
    return

*move_tetrimino
    if state != STATE_NORMAL : return

    // 横方向の移動
    next_moving_x = moving_x + ( keys >> 2 & 1 ) - ( keys & 1 )
    if hit_check( map, block_type( moving_rot, moving_type ), next_moving_x, moving_y ) == 0 {
        moving_x = next_moving_x
    }

    // 縦方向の移動
    fall_count += 1 + 3 * ( keys >> 3 & 1 )
    if fall_count >= fall_limit {
        fall_count = 0
        if hit_check( map, block_type( moving_rot, moving_type ), moving_x, moving_y + 1 ) == 0 {
            moving_y++
        } else {
            // これ以上落下できないので、今の位置に固定
            fix_to_map map, block_type( moving_rot, moving_type ), moving_type, moving_x, moving_y
            gosub *check_delete
        }
    }
    if keys & 2 {
        moving_y = ghost_y
        fall_count = fall_limit
    }

    // 回転処理
    if keys & %110000 {
        next_rot = moving_rot + (keys >> 4 & 1) - (keys >> 5 & 1) & 3
        if hit_check( map, block_type( next_rot, moving_type ), moving_x, moving_y ) == 0 {
            moving_rot = next_rot
//          fall_count--
        }
    }
    return

*check_delete
    check_del_lines map, line_check
    if stat {
        score += stat * stat * 10
        state = STATE_BLINK
        state_limit = 12
    } else {
        gosub *create_new_tetrimino
    }
    return

*draw_ghost
    repeat
        if hit_check( map, block_type( moving_rot, moving_type ), moving_x, moving_y + cnt ) {
            ghost_y = moving_y + cnt - 1
            break
        }
    loop
    if ghost_y > moving_y {
        draw_tetrimino block_type( moving_rot, moving_type ), moving_type, moving_x, ghost_y, 1
    }
    return

*create_new_tetrimino
    moving_x = AREA_WIDTH / 2 - 2// 動かしているテトリミノの位置(左上)
    moving_y = -2
    moving_rot = 0          // 動かしているテトリミノの回転
    moving_type = next_type // 動かしているテトリミノの種類(兼色の種類)
    next_type = rnd(7)
    if hit_check( map, block_type( moving_rot, moving_type ), moving_x, moving_y ) {
        // ゲームオーバー
        state = STATE_GAMEOVER
        state_limit = 20
        gosub *reload_highscore
    }
    return

*reload_highscore
    repeat HIGHSCORE_MAX
        hsptv_getrank highscore(cnt), ranker_name(cnt), s, cnt
    loop
    return