山水堂
A014.落ちものゲーム基本
落ちものゲームの基本的なプログラムです。
http://dxlib.o.oo7.jp/dxprogram.html#N10
DXライブラリを参考にしました!
-- conf.lua function love.conf(t) t.window.width = 640 t.window.height = 480 end
640x480で設計していますので、コンフィグファイルを設定しておきましょう。
-- main.lua local BLOCKTYPE_NUM = 5 -- ブロックの種類の数 local BLOCK_SIZE = 24 -- ブロックのドットサイズ local WORLD_WIDTH = 8 -- ステージの幅 local WORLD_HEIGHT = 16 -- ステージの高さ local STAGE_X = 210 -- ステージの左上頂点のX座標 local STAGE_Y = 50 -- ステージの左上頂点のY座標 local FALL_INTERVAL = 0.5 -- 黙っててブロックが落ちるまでの時間 local MOVE_INTERVAL = 0.1 -- 左右移動キー長押しの時間 local CHANGE_INTERVAL = 0.2 -- 変換キー長押しの時間 -- ステージデータ local buffer_block = {} -- 一時状態保存用のブロックデータ local block = {} -- 実際のデータ -- アクティブブロックデータ local active_x, active_y local active_block = {} -- ブロックの種類ごとの色データ local block_color = { {128, 128, 128}, {255, 100, 100}, {255, 255, 0}, {255, 0, 255}, {0, 255, 255}, } -- その他変数 local keystate = {} local okey = {} local nkey = {} local move_dt = 0 local fall_dt = 0 local change_dt = 0 local gameover_flag = false function create_new_active_block() -- 新しいブロックの生成 -- アクティブブロックの位置をセット active_x = math.floor(WORLD_WIDTH / 2) + 1 active_y = 3 for i = 1, 3 do active_block[i] = love.math.random(1, BLOCKTYPE_NUM) end end function check_hit_active_block(x, y) -- アクティブブロックが画面上のブロックに当たっていないか調べる local bl = false for i = 1, 3 do if block[y - (i - 1)][x] ~= 0 then bl = true break end end return bl end function count_eliminat_block_line( -- 指定方向の同色をカウント ox, oy, dx, dy ) local count = 0 local color = block[oy][ox] local x, y -- 開始位置 local sx = ox local sy = oy -- 逆方向に移動して、開始位置をセット x = sx + dx * -1 y = sy + dy * -1 while x >= 1 and x <= WORLD_WIDTH and y >= 1 and y <= WORLD_HEIGHT and block[y][x] == color do -- 成功したので保持 sx = x sy = y -- 移動 x = x + dx * -1 y = y + dy * -1 end -- 開始位置から、同色をカウントしてゆく x = sx y = sy while x >= 1 and x <= WORLD_WIDTH and y >= 1 and y <= WORLD_HEIGHT and block[y][x] == color do -- 成功したのでカウント count = count + 1 x = x + dx y = y + dy end return count end function check_eliminat_block_one(ox, oy) -- 特定ブロックが消えるか探索 local num -- 左右をチェック num = count_eliminat_block_line(ox, oy, 1, 0) if num >= 3 then return true end -- 上下をチェック num = count_eliminat_block_line(ox, oy, 0, 1) if num >= 3 then return true end -- 右下をチェック num = count_eliminat_block_line(ox, oy, 1, 1) if num >= 3 then return true end -- 左下をチェック num = count_eliminat_block_line(ox, oy, -1, 1) if num >= 3 then return true end return false end function check_eliminat_block() -- 消えるブロックがあるか調べてあったら消す処理をする local chain_flag local chain = 0 repeat -- 連鎖をカウント chain = chain + 1 -- 連鎖フラグ chain_flag = true -- 消去対象のブロックをセット for y = 1, WORLD_HEIGHT do for x = 1, WORLD_WIDTH do buffer_block[y] = buffer_block[y] or {} buffer_block[y][x] = false -- 空白以外なら、連鎖状況を確認 if block[y][x] ~= 0 then buffer_block[y][x] = check_eliminat_block_one(x, y) end end end -- 消えると判断されたブロックを消す for y = 1, WORLD_HEIGHT do for x = 1, WORLD_WIDTH do if buffer_block[y][x] == true then -- 消去対象なので、空白に block[y][x] = 0 chain_flag = false end end end -- 空きを詰める for x = 1, WORLD_WIDTH do for y = WORLD_HEIGHT, 1, -1 do if block[y][x] == 0 then -- 上を検索 for cy = y - 1, 1, -1 do if block[cy][x] ~= 0 then -- 挿げ替える block[y][x] = block[cy][x] block[cy][x] = 0 break end end end end end until chain_flag end function lock_active_block(x, y) -- アクティブブロックを固定する -- 及び次のブロックを出す -- もし次のブロックを出すスペースがなかったらゲームオーバー for i = 1, 3 do local ypos = y - (i - 1) if ypos >= 1 and ypos <= WORLD_HEIGHT then block[ypos][x] = active_block[i] end end -- 消せるブロックがある場合は消す check_eliminat_block() -- アクティブブロックを生成 create_new_active_block() -- ゲームオーバーをチェック if check_hit_active_block(active_x, active_y) == true then gameover_flag = true end end function move_active_block(mx, my) -- アクティブブロックの移動 local move_x = mx local move_y = my local new_x, new_y -- 新しい位置を算出 new_x = active_x + move_x if move_x ~= 0 and new_x >= 1 and new_x <= WORLD_WIDTH then if check_hit_active_block(new_x, active_y) == true then move_x = 0 end else -- 範囲外 move_x = 0 end new_x = active_x + move_x move_y = 0 for i = 1, my do new_y = active_y + move_y + 1 if new_y >= 1 and new_y <= WORLD_HEIGHT then if check_hit_active_block(new_x, new_y) == true then -- ヒットしたらロック lock_active_block(new_x, active_y + move_y) move_x = 0 move_y = 0 break else move_y = move_y + 1 end else -- 範囲外に来たらロック lock_active_block(new_x, active_y + move_y) move_x = 0 move_y = 0 break end end -- アクティブ位置に反映 active_x = active_x + move_x active_y = active_y + move_y end function love.load() -- アクティブブロックを生成 create_new_active_block() -- マップブロックの初期化 for y = 1, WORLD_HEIGHT do for x = 1, WORLD_WIDTH do block[y] = block[y] or {} block[y][x] = 0 end end end function love.keypressed(key, irepeat) keystate[key] = true if key == "escape" then love.event.quit() end end function love.keyreleased(key) keystate[key] = false end function draw_box( -- DXライブラリ風 left, top, right, bottom, colors, fill_flag ) -- なければ、初期セット colors = colors or {} fill_flag = fill_flag or true -- fillを生成 local fill = "line" if fill_flag then fill = "fill" end -- 色をセット local get_color = { love.graphics.getColor() } love.graphics.setColor(colors[1] or 0, colors[2] or 0, colors[3] or 0, colors[4] or 255) -- 描画! love.graphics.polygon( fill, left, top, right, top, right, bottom, left, bottom ) -- 色を復帰 love.graphics.setColor(unpack(get_color)) end function shallowcopy(orig) -- 浅くコピー local orig_type = type(orig) local copy if orig_type == "table" then copy = {} for orig_key, orig_value in pairs(orig) do copy[orig_key] = orig_value end else -- number, string, boolean, etc copy = orig end return copy end function reset_key() -- キー情報をリセット okey = shallowcopy(nkey) nkey = shallowcopy(keystate) end function is_keyone(key) -- キー入力(初回のみ) return okey[key] ~= true and nkey[key] == true end function is_keypress(key) -- キー入力(押しっぱなし) return nkey[key] end function love.update(dt) -- キー情報をリセット reset_key() -- ゲームオーバー以外 if gameover_flag == false then -- 左右キー入力処理 if is_keyone("left") or is_keyone("right") or move_dt >= MOVE_INTERVAL then if is_keypress("left") then move_active_block(-1, 0) move_dt = 0 end if is_keypress("right") then move_active_block(1, 0) move_dt = 0 end end move_dt = move_dt + dt -- 上キー入力処理 if is_keyone("up") or change_dt >= CHANGE_INTERVAL then if is_keypress("up") then active_block[1], active_block[2], active_block[3] = active_block[3], active_block[1], active_block[2] change_dt = 0 end end change_dt = change_dt + dt -- 落下か、下キー入力処理 if is_keyone("down") or fall_dt >= FALL_INTERVAL then local y = 1 if is_keyone("down") then -- 下キーを押された状態なら、一気に落下 y = WORLD_HEIGHT end move_active_block(0, y) fall_dt = 0 end fall_dt = fall_dt + dt end end function love.draw() -- 枠を描画 do -- 左の枠 draw_box( STAGE_X - 24, STAGE_Y, STAGE_X, STAGE_Y + WORLD_HEIGHT * BLOCK_SIZE, {255, 0, 0}, true ) -- 右の枠 draw_box( STAGE_X + WORLD_WIDTH * BLOCK_SIZE, STAGE_Y, STAGE_X + 24 + WORLD_WIDTH * BLOCK_SIZE, STAGE_Y + WORLD_HEIGHT * BLOCK_SIZE, {255, 0, 0}, true ) -- 下の枠 draw_box( STAGE_X - 24, STAGE_Y + WORLD_HEIGHT * BLOCK_SIZE, STAGE_X + 24 + WORLD_WIDTH * BLOCK_SIZE, STAGE_Y + WORLD_HEIGHT * BLOCK_SIZE + 24, {255, 0, 0}, true ) end -- ブロックを描画 for y = 1, WORLD_HEIGHT do for x = 1, WORLD_WIDTH do if block[y][x] ~= 0 then draw_box( STAGE_X + (x - 1) * BLOCK_SIZE, STAGE_Y + (y - 1) * BLOCK_SIZE, STAGE_X + x * BLOCK_SIZE, STAGE_Y + y * BLOCK_SIZE, block_color[block[y][x]], true ) end end end -- アクティブブロックを描画 for i = 1, #active_block do draw_box( STAGE_X + (active_x - 1) * BLOCK_SIZE, STAGE_Y + ((active_y - 1) - (i - 1)) * BLOCK_SIZE, STAGE_X + (active_x - 1) * BLOCK_SIZE + BLOCK_SIZE, STAGE_Y + ((active_y - 1) - (i - 1)) * BLOCK_SIZE + BLOCK_SIZE, block_color[active_block[i]], true ) end -- ゲームオーバーの場合 if gameover_flag == true then love.graphics.print("GAME OVER", 0, 0) end end
長くなりましたが、これを実行してみると落ちものゲームが始まります!
カーソルの左右で、アクティブブロックの左右の移動。
カーソルの下で、アクティブブロック急速落下。
カーソルの上で、アクティブブロックの中身の変更。
3つ以上の色が隣り合わせになると消えたりと、それなりに動作します。
ただ、消える速度が高速で、何をやっているか分かりにくいですね……まあそこら辺は、ソースをいじって工夫してみてください!