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つ以上の色が隣り合わせになると消えたりと、それなりに動作します。

ただ、消える速度が高速で、何をやっているか分かりにくいですね……まあそこら辺は、ソースをいじって工夫してみてください!