ラベル 画像の描画・編集 の投稿を表示しています。 すべての投稿を表示
ラベル 画像の描画・編集 の投稿を表示しています。 すべての投稿を表示

2008年2月12日火曜日

アンドゥや再生ができるペイントツール

GoFのCommandパターンにヒントを得て作成。
描画した履歴を文字列型配列変数に記録しておき、必要に応じて取り出します。
今回は単純な文字列型配列変数ではなく、スタックのモジュールを用意してみました。

bregexp.dll(bregonig.dll)および月影ともさんのbregexp.hspが必要です。
// 文字列用スタック
#module string_stack stack, max
#modinit
    max = 0
    sdim stack, 3210
    return
#deffunc new_sstack array v
    newmod v, string_stack@
    return
#modfunc push str s
    stack(max) = s
    max++
    return
#defcfunc pop modvar string_stack@
    if max == 0 {
        logmes "引数の値が異常です。"
        return ""
    }
    max--
    return stack(max)
#defcfunc get_length modvar string_stack@
    return max
#defcfunc get_last modvar string_stack@
    return stack(max-1)
#defcfunc get_at modvar string_stack@, int index
    if index < 0 || max <= index {
        logmes "引数の値が異常です。"
        return ""
    }
    return stack(index)
#modfunc clear_stack
    max = 0
    return
#global

// 矩形の塗りつぶし
// http://rpen.blogspot.com/2007/11/blog-post.html
#include "gdi32.as"
#module
#const FLOODFILLSURFACE 1
#deffunc fill int x, int y
    current_color = ginfo_rginfo_gginfo_b
    CreateSolidBrush (ginfo_b << 16) | (ginfo_g << 8) | ginfo_r
    if stat {
        hBrush = stat
    } else {
        dialog "ブラシの生成に失敗しました。プログラムを終了します。"1
        end
    }
    SelectObject hDC, hBrush
    pget x, y
    ExtFloodFill hdc, x, y, (ginfo_b << 16) | (ginfo_g << 8) | ginfo_rFLOODFILLSURFACE
    DeleteObject hBrush
    color current_color(0), current_color(1), current_color(2)
    return
#global

// 命令を解析して描画するモジュール
#include "bregexp.hsp"
#module drawer
#define ctype result(%1int(_result(%1))
#deffunc draw str _cmd
    cmd = _cmd
    BSplit _result, cmd, "m/[ ,]+/"
    switch _result(0)
        case "moveTo" : pos result(1), result(2) : swbreak
        case "lineTo" : line result(1), result(2) : swbreak
        case "color"  : color result(1), result(2), result(3) : swbreak
        case "fill"   : fill result(1), result(2) : redraw 1 : swbreak
        default : logmes "未知の命令です" : swbreak
    swend
    return
#deffunc draw_all array cmds, int wait_time
    redraw 0
    color 255255255 : boxf
    color
    repeat get_length(cmds)
        draw get_at(cmds, cnt)
        if wait_time {
            redraw 1
            wait wait_time
            redraw 0
        }
    loop
    redraw 1
    return
#global

#define push_and_do(%1,%2push %1%2 : draw %2

#define WM_MOUSEMOVE    $00000200
#define WM_LBUTTONDOWN  $00000201
#define WM_LBUTTONUP    $00000202
#define WM_RBUTTONDOWN  $00000204

*init
    title "左ドラッグで線を描画 / 右クリックで塗りつぶし"
    oncmd gosub *onLButtonDownWM_LBUTTONDOWN
    oncmd gosub *onRButtonDownWM_RBUTTONDOWN
    oncmd gosub *onLButtonUpWM_LBUTTONUP
    oncmd gosub *onMouseMoveWM_MOUSEMOVE

    objsize 80
    button gosub "color change"*color_change
    button gosub "redraw slowly"*all_draw_slowly
    button gosub "clear"*clear
    button gosub "undo"*undo
    new_sstack cmds
    stop

// 色の変更
*color_change
    hsvcolor rnd(192), 255255
    push_and_do cmds, "color " + ginfo_r + "," + ginfo_g + "," + ginfo_b
    return
// 全消去
*clear
    clear_stack cmds
    draw_all cmds, 0
    return
// アンドゥ
*undo
    tmp = pop(cmds)
    draw_all cmds, 0
    return
// すべて描画
*all_draw
    draw_all cmds, 0
    return
// ゆっくりとすべて描画
*all_draw_slowly
    oncmd 0
    gosub *invalidate_buttons
    draw_all cmds, 4
    gosub *validate_buttons
    oncmd 1
    return
// 左ドラッグ開始
*onLButtonDown
    dragging = 1
    push_and_do cmds, "moveTo " + mousex + "," + mousey
    return
// 左ドラッグ終了
*onLButtonUp
    dragging = 0
    return
// 左ドラッグ中
*onMouseMove
    if dragging {
        push_and_do cmds, "lineTo " + mousex + "," + mousey
    }
    return
// 右クリック
*onRButtonDown
    if dragging == 0 {
        push_and_do cmds, "fill " + mousex + "," + mousey
    }
    return

#include "obj.as"
*invalidate_buttons
    repeat 4
        objgray cnt0
    loop
    return
*validate_buttons
    repeat 4
        objgray cnt1
    loop
    return

2008年1月28日月曜日

サイトのサムネイルを表示する

サイトのサムネイルを作成・表示するAPI「ThumbnailAPI」を利用します。
ネットの接続などはスクリプトから明示的には行っていません。imgload命令の内部で使用しているCOMが自動で行ってくれているようです。
#include "mod_img.as"
    imgload "http://img.simpleapi.net/small/http://www.forest.impress.co.jp/"
    stop

2008年1月2日水曜日

RPG風メッセージウィンドウの描画

ツクールなどでよくありそうなデザインのウィンドウを描画します。三角関数とhsvcolorを組み合わせることで、きれいなグラデーションが表現できます。
白と黒の線で囲むことで、どんな背景でもある程度の見易さを確保するよう努めています。
#module
#include "hspmath.as"
#deffunc line_box int x, int y, int _w, int _h, local w, local h
    w = _w - 1 : h = _h - 1
    line x+w, y, x, y : line x+w, y+h
    line x, y+h : line x, y
    return
#deffunc draw_win int x, int y, int _w, int _h, local w, local h
    w = _w - 1 : h = _h - 1
    repeat 10
        angle = M_PI * cnt/20M_PI * (1+cnt)/20
        hsvcolor 130255255-2*cnt*cnt
        boxf x+sin(angle)*w, y, x+sin(angle(1))*w, y+h
    loop
    color : line_box x, y, _w, _h
    color 255255255 : line_box x+1, y+1, _w-2, _h-2
    return
#global
    redraw 0
    draw_win 00240200
    redraw 1
    stop

2007年12月20日木曜日

3D画像ビューア サンプル

Flashなどでありそうな3Dの画像ビューア。のサンプル。
マウスのホイールに連動して回転します。
#include "d3m.hsp"
#include "hspmath.as"
#const TILE_NUM 16
#const SHIFT_TURN 5
#const BUFFER_WIDTH 200
#const BUFFER_HEIGHT 200

    // タイルを表示するための配列変数
    tile_x = 100300300100
    tile_y =   0,   0,   0,   0
    tile_z = 200200,   0,   0

    // タイル用のバッファを作成
    repeat TILE_NUM1
        buffer cntBUFFER_WIDTHBUFFER_HEIGHT
        color , 255 : boxf
        color 255255255
        font msgothic32font_bold
        mes cnt
    loop

    // メイン画面の初期化・カメラ設定
    screen 0240200screen_normal
    title "ホイールで回転"
    d3setcam -250,-300,100250,0,100

    // 各種初期化作業
    gmode GMODE_ALPHA, , , 128
    theta_shift = 0.0                   // タイルの回転量
    theta_add = 0.0                     // タイルの角速度
    moving = 0                          // 回転中フラグ
    need_to_draw = 1                    // 描画フラグ

// ★メインループ
*main
    gosub *move
    gosub *draw
    wait 5
    goto *main

// 移動計算
*move
    mw = mousew
    if moving > 0 {
        // 移動中
        moving--
        theta_shift += theta_add
    } else : if mw != 0 {
        // 静止中かつホイールが動いたとき
        moving = SHIFT_TURN             // SHIFT_TURN回動く
        need_to_draw = 1                // 描画フラグON

        // 角速度を決定
        theta_add = M_PI * 2.0 * sgn(mw) / SHIFT_TURN / TILE_NUM
    }
    return

// 描画処理
*draw
    if need_to_draw == 0 : return
    if moving == 0 : need_to_draw = 0

    redraw 0
    color : boxf

    vect_x = cos(theta_shift)
    vect_y = sin(theta_shift)
    // 2.0 * M_PI / TILE_NUMだけ角度(位置)を変えながら、タイルをTILE_NUM個描画
    repeat TILE_NUM
        d3rotate vect_x, vect_y, vect_x, vect_y, 2.0 * M_PI / TILE_NUM
        d3setlocal 0,0,0, vect_x,vect_y,0, -vect_y,vect_x,00,0,1
        d3texture tile_x, tile_y, tile_z, cnt + 100BUFFER_WIDTHBUFFER_HEIGHT1
    loop

    redraw 1
    return

2007年11月11日日曜日

閉塞領域の塗りつぶし

ペイントツールお約束の機能も、APIを使えば簡単に実装できます。
昔はSRPGの経路探索を応用したりしてモジュールを組んでたのですが……今思えば結構無茶してますね。

なおHSP2.61用のスクリプトはCrimson Forestさんにあるようです。
// 参考
// http://msdn.microsoft.com/library/ja/default.asp?url=/library/ja/jpgdi/html/_win32_extfloodfill.asp
#include "gdi32.as"
#module
#const FLOODFILLSURFACE 1
// カレントカラーで(x, y)を含む同色領域を塗りつぶす
// 実際の画面に反映させるには redraw 1 を実行すること
#deffunc fill int x, int y
    // カレントカラーを記憶
    current_color = ginfo_rginfo_gginfo_b

    // カレントカラーからブラシを生成
    CreateSolidBrush (ginfo_b << 16) | (ginfo_g << 8) | ginfo_r
    if stat {
        hBrush = stat
    } else {
        dialog "ブラシの生成に失敗しました。プログラムを終了します。"1
        end
    }
    SelectObject hDC, hBrush

    // 塗りつぶす色を取得
    pget x, y

    // 塗りつぶし実行
    ExtFloodFill hdc, x, y, (ginfo_b << 16) | (ginfo_g << 8) | ginfo_rFLOODFILLSURFACE

    // 後始末
    DeleteObject hBrush
    color current_color(0), current_color(1), current_color(2)
    return
#global

    repeat 5
        hsvcolor 191 * cnt / 5255255
        boxf rnd(640), rnd(480), rnd(640), rnd(480)
    loop
    onclick gosub *do_fill
    stop
*do_fill
    hsvcolor rnd(192), 255255
    fill mousexmousey
    redraw 1
    return

2007年10月28日日曜日

角丸四角形を描画

標準命令で角丸四角形を描くモジュール。
半径が大きすぎた場合は自動で調節するおまけつき。

// 角丸四角形を描画する
#module
#define ctype max%1%2 ) ( %1 )*( %1 > %2 ) + ( %2 )*( %1 <= %2 )
#define ctype min%1%2 ) ( %1 )*( %1 < %2 ) + ( %2 )*( %1 >= %2 )
#const DEFAULT_R 20
/*
    rboxf (左上X座標), (左上Y座標), (右下X座標), (右下Y座標), (角の半径)
*/

#deffunc rboxf int _x1, int _y1, int _x2, int _y2, int _r
    x1 = min(_x1, _x2) : x2 = max(_x1, _x2)
    y1 = min(_y1, _y2) : y2 = max(_y1, _y2)
    box_width = x2 - x1 : box_height = y2 - y1

    if ( _r <= 0 ) {
        r = DEFAULT_R
    } else {
        r = _r
    }
    r = min( r, min( box_width / 2, box_height / 2 ) )

    boxf x1, y1+r, x2, y2-r
    boxf x1+r, y1, x2-r, y2
    repeat 4
        x = x1 + ( cnt \ 2 ) * ( box_width - r * 2 + 1)
        y = y1 + ( cnt / 2 ) * ( box_height - r * 2 + 1 )
        circle x, y, x + r * 2, y + r * 2
    loop
    return
#global
// サンプル
    rboxf 505030030050
    rboxf 600400400300100   // 右下→左上の順で指定してもOK
                                    // 半径が大きすぎる場合は自動で調節する

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

2007年8月12日日曜日

ちっちゃなテトリス

結局遊べるところまで完成させてしまいました。短期集中連載第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