パパコーダー

子どもと一緒にプログラミングを始めよう!2019年3月からCoderDojo溝口という子ども向けのプログラミングサークルを始めました。神奈川県川崎市の近くにお住まいの方はどうぞ遊びに来てください。

FuzeBasicでヘビのゲームを作ろう!

FuzeのサイトにあるWorksheetsの最終回として、ヘビのゲームを作りたいと思います。

こんな感じのゲームです。(意外とハマるかも?)

f:id:oco777:20190511105319j:plain
snake_game

ヘビのゲームを作る

ヘビを表示する

DEFCHARコマンドでヘビのパーツの絵を設定しています。
キーボードの矢印キーで、ヘビを移動することができます。

関数が初めて登場しています。

DEF PROC 関数の名前
~
ENDPROC

関数は、やりたいことをまとめたものになります。
DEF PROC と ENDPROCの間にやりたいことを書いておきます。
そして、PROCコマンドで呼ぶだけで、関数の中に書かれていることを実行します。

PROC 関数の名前

いろいろなところで行いたい処理をまとめて関数にしておくと便利です。

snake_1.fuzeを見てみると、ゲームとしての処理は、最初の部分だけになります。
setup関数で初期化した後は、LOOP内でヘビを動かしているだけです。
このように、ゲームは「初期化処理」を行った後に、「更新処理」をループで回す作り方が一般的になっています。

PROC setup
LOOP
    IF newlevel = TRUE THEN PROC newlevel
    IF SCANKEYBOARD(SCANUP) AND dir <> 3 THEN dir = 1
    IF SCANKEYBOARD(SCANRIGHT) AND dir <> 4 THEN dir = 2
    IF SCANKEYBOARD(SCANDOWN) AND dir <> 1 THEN dir = 3
    IF SCANKEYBOARD(SCANLEFT) AND dir <> 2 THEN dir = 4
    IF TIME - timer > speed THEN PROC snake
    UPDATE
REPEAT
END

snake_1.fuze(クリックすると開きます)

PROC setup
LOOP
    IF newlevel = TRUE THEN PROC newlevel
    IF SCANKEYBOARD(SCANUP) AND dir <> 3 THEN dir = 1
    IF SCANKEYBOARD(SCANRIGHT) AND dir <> 4 THEN dir = 2
    IF SCANKEYBOARD(SCANDOWN) AND dir <> 1 THEN dir = 3
    IF SCANKEYBOARD(SCANLEFT) AND dir <> 2 THEN dir = 4
    IF TIME - timer > speed THEN PROC snake
    UPDATE
REPEAT
END

DEF PROC resetgame
    CLS
    snakelength = 2
    speed = 200
    newlevel = TRUE
ENDPROC

DEF PROC newlevel
    FONTSIZE(zoom)
    screenheight = THEIGHT - 2
    screenwidth = TWIDTH - 1
    DIM screen(screenwidth, screenheight)
    FOR segment = 0 TO snakelength LOOP
        snakex(segment) = INT(TWIDTH / 2)
        snakey(segment) = INT(THEIGHT / 2)
    REPEAT
    dir = 1
    newlevel = FALSE
ENDPROC

DEF PROC setup
    FULLSCREEN = 1
    UPDATEMODE = 0
    zoom = 6
    timer = TIME
    FONTSIZE(zoom)
    DIM snakex(200)
    DIM snakey(200)
    DEFCHAR(1, 60, 126, 219, 255, 255, 255, 153, 199, 126, 60)
    DEFCHAR(2, 0, 60, 126, 255, 255, 255, 255, 126, 60, 0)
    DEFCHAR(3, 0, 0, 60, 126, 126, 126, 126, 60, 0, 0)
    DEFCHAR(4, 170, 85, 170, 85, 170, 85, 170, 85, 170, 85)
    DEFCHAR(5, 28, 126, 123, 255, 255, 255, 255, 126, 126, 24)
    PROC resetgame
ENDPROC

DEF PROC snake
    INK = LIGHTGREEN
    FOR segment = snakelength + 2 TO 1 STEP -1 LOOP
        snakex(segment) = snakex(segment - 1)
        snakey(segment) = snakey(segment - 1)
    REPEAT
    IF dir = 1 THEN snakey(0) = snakey(0) - 1
    IF dir = 2 THEN snakex(0) = snakex(0) + 1
    IF dir = 3 THEN snakey(0) = snakey(0) + 1
    IF dir = 4 THEN snakex(0) = snakex(0) - 1
    PRINTAT(snakex(0),snakey(0));CHR$(1);
    screen(snakex(0),snakey(0)) = 1
    FOR segment = 1 TO snakelength LOOP
        PRINTAT(snakex(segment), snakey(segment));CHR$(2);
        screen(snakex(0),snakey(0)) = 2
    REPEAT
    PRINTAT(snakex(segment),snakey(segment));CHR$(3);
    screen(snakex(segment),snakey(segment)) = 3
    PRINTAT(snakex(segment+1),snakey(segment+1));" ";
    screen(snakex(segment+1),snakey(segment+1)) = 0
    timer = TIME
ENDPROC

f:id:oco777:20190511102220j:plain
snake_1

カベを作る

screen変数で、画面上に何があるかを管理しています。
何もなければ0、ヘビの頭が1、ヘビの胴体が2、ヘビのしっぽが3、そして、カベが4になります。

snake_2.fuze(クリックすると開きます)

DEF PROC newlevel
    ....
    DIM screen(screenwidth, screenheight)
    FOR column = 0 TO screenwidth LOOP
        screen(column,1) = 4
        screen(column,screenheight) = 4
    REPEAT
    FOR row = 2 TO screenheight - 1 LOOP
        screen(0,row) = 4
        screen(screenwidth,row) = 4
    REPEAT
    CLS
    FOR column = 0 TO screenwidth LOOP
        FOR row = 0 TO screenheight LOOP
            content = screen(column,row)
            IF content = 4 THEN INK = BLUE
            IF content <> 0 THEN PRINTAT(column,row);CHR$(content);
        REPEAT
    REPEAT
    FOR segment = 0 TO snakelength LOOP
    ....
ENDPROC

f:id:oco777:20190511103008p:plain
snake_2

ゲームオーバーを作る

ヘビの頭が何かにぶつかったらゲームオーバーにします。
ぶつかったかどうかは、ヘビの頭のある場所にscreen変数で何かあるかどうかを調べて判定しています。

ゲームオーバーになったときに、PLAYMUSICコマンドで音を鳴らしています。
音は %userprofile%\Documents\Fuze\extras\sounds フォルダに入っています。

ゲームオーバーの文字を表示するときに、TWIDTHとTHEIGHTを使っています。
これは、フォント単位の画面の幅と高さになります。
以前、GWIDTHとGHEIGHTを使いましたが、こちらはグラフィック描画で使う単位での画面の幅と高さになります。
文字を書くときは、TWIDTHとTHEIGHTを使うと覚えておいていいと思います。
TはTextを表しています。

snake_3.fuze(クリックすると開きます)

DEF PROC setup
    ....
    DIM snakex(200)
    DIM snakey(200)
    tune1 = LOADMUSIC("snaketune1.wav")
    tune2 = LOADMUSIC("snaketune4.wav")
    tune3 = LOADMUSIC("snaketune3.wav")
    tune4 = LOADMUSIC("snakeintro.mp3")
    gulp = LOADSAMPLE("gulp.wav")
    DEFCHAR(1, 60, 126, 219, 255, 255, 255, 153, 199, 126, 60)
    ....
ENDPROC

DEF PROC snake
    ....
    IF dir = 3 THEN snakey(0) = snakey(0) + 1
    IF dir = 4 THEN snakex(0) = snakex(0) - 1
    IF screen(snakex(0),snakey(0)) <> 0 THEN PROC gameend
    PRINTAT(snakex(0),snakey(0));CHR$(1);
    ....
ENDPROC

DEF PROC gameend
    PLAYMUSIC(tune2,0)
    text1$ = "Ouch!"
    text2$ = "That's Game Over"
    starttime = TIME
    WHILE TIME - starttime < 4000 LOOP
        FOR segment = 1 TO snakelength LOOP
            INK = RND(3) + 7
            PRINTAT(snakex(segment),snakey(segment));CHR$(3);
            PRINTAT(snakex(0),snakey(0));CHR$(1);
            PRINTAT(snakex(segment+1),snakey(segment+1));" ";
        REPEAT
        INK = RND(30)
        PRINTAT(TWIDTH/2-LEN(text1$)/2,THEIGHT/2);text1$
        PRINTAT(TWIDTH/2-LEN(text2$)/2,THEIGHT/2+2);text2$
        UPDATE
    REPEAT
    PROC resetgame
ENDPROC

f:id:oco777:20190511103735j:plain
snake_3

エサを置く

エサを画面上にランダムに置いていきます。
screen変数を調べて、その場所に何もないことを確認しています。

snake_4.fuze(クリックすると開きます)

DEF PROC resetgame
    ....
    newlevel = TRUE
    maxfood = 4
    foodcounter = maxfood
    score = 0
    level = 0
ENDPROC

DEF PROC newlevel
    ....
        screen(screenwidth,row) = 4
    REPEAT
    FOR counter = 0 TO maxfood LOOP
        empty = TRUE
        WHILE empty = TRUE LOOP
            foodx = RND(screenwidth - 2) + 1
            foody = RND(screenheight - 2) + 2
            IF screen(foodx,foody) = 0 THEN
                screen(foodx,foody) = 5
                empty = FALSE
            ENDIF
        REPEAT
    REPEAT
    CLS
    FOR column = 0 TO screenwidth LOOP
        FOR row = 0 TO screenheight LOOP
            content = screen(column,row)
            IF content = 4 THEN INK = BLUE
            IF content = 5 THEN
                INK = ORANGE
                TONE(1,50,100,.1)
                UPDATE
                WAIT(0.1)
            ENDIF
            IF content <> 0 THEN PRINTAT(column,row);CHR$(content);
        REPEAT
    REPEAT
    FOR segment = 0 TO snakelength LOOP
        snakex(segment) = INT(TWIDTH / 2)
        snakey(segment) = INT(THEIGHT / 2)
    REPEAT
    dir = 1
    newlevel = FALSE
    maxfood = maxfood + 1
    foodcounter = maxfood
ENDPROC

f:id:oco777:20190511104222j:plain
snake_4

エサを食べる

ヘビの頭が、エサのある場所に到達したら、エサを食べます。
エサを食べると、ヘビの胴体が伸びます。
これがゲームを面白くしていますね!
その分、ゲームが難しくなるけど。

snake_5.fuze(クリックすると開きます)

DEF PROC snake
    ....
    IF dir = 4 THEN snakex(0) = snakex(0) - 1
    IF screen(snakex(0),snakey(0)) = 5 THEN PROC checkfood
    IF screen(snakex(0),snakey(0)) <> 0 THEN PROC gameend
    ....
ENDPROC

DEF PROC checkfood
    PROC updatescore(100)
    PLAYSAMPLE(0,gulp,0)
    snakelength = snakelength + 1
    screen(snakex(0),snakey(0)) = 0
    foodcounter = foodcounter - 1
    IF foodcounter <= 0 THEN
        PLAYMUSIC(tune3,0)
        text$ = "Yummy!"
        starttime = TIME
        WHILE TIME - starttime < 3000 LOOP
            FOR segment = 1 TO snakelength LOOP
                INK = RND(3) + 13
                PRINTAT(snakex(segment),snakey(segment));CHR$(3);
                PRINTAT(snakex(0),snakey(0));CHR$(1);
            REPEAT
            INK = RND(30)
            PRINTAT(TWIDTH/2-LEN(text$)/2,THEIGHT/2);text$
            UPDATE
        REPEAT
        newlevel = TRUE
    ENDIF
ENDPROC

DEF PROC updatescore(value)
    score = score + value
    INK = YELLOW
    PRINTAT(0,0);"Score ";score;
    PRINTAT(TWIDTH/2-4,0);"Level ";level;
    UPDATE
ENDPROC

DEF PROC newlevel
    PLAYMUSIC(tune1,0)
    ....
    foodcounter = maxfood
    level = level + 1
    PROC updatescore(0)
    speed = speed - 10
    IF speed <= 50 THEN speed = 50
ENDPROC

f:id:oco777:20190511104734j:plain
snake_5

タイトル画面を作る

ゲームを起動したら、最初に表示されるタイトル画面を作ります。
ゲームオーバーした後も、タイトル画面に戻ります。

snake_6.fuze(クリックすると開きます)

DEF PROC resetgame
    CLS
    text$ = "Play Snake"
    text1$ = "Press SPACE to play"
    text2$ = "Cursor keys control Snake"
    PLAYMUSIC(tune4,0)
    WHILE SCANKEYBOARD(SCANSPACE) = FALSE LOOP
        INK = RND(3) + 13
        FONTSIZE(5)
        PRINTAT(TWIDTH/2-LEN(text$)/2,(THEIGHT/2)-1);text$
        IF TWIDTH < 35 THEN FONTSIZE(3)
        INK = RND(30)
        PRINTAT(TWIDTH/2-LEN(text1$)/2,THEIGHT/2+2);text1$
        PRINTAT(TWIDTH/2-LEN(text2$)/2,THEIGHT/2+3);text2$
        UPDATE
    REPEAT
    PLAYMUSIC(tune1,0)
    snakelength = 2
    ....
ENDPROC

f:id:oco777:20190511105033j:plain
snake_6

まとめ

ヘビのゲームで遊んでみると、意外と面白いですね。
何が面白いのか考えてみましょう。
そして、もっと面白くなるように改良してみましょう!

GitHubにソースをアップしました。
参考にしてください。
ヘビのゲームのソースコード

papa-coder.hatenablog.com

papa-coder.hatenablog.com

papa-coder.hatenablog.com

papa-coder.hatenablog.com

papa-coder.hatenablog.com